Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

Commit

Permalink
Manager class for applications using Task API
Browse files Browse the repository at this point in the history
Stores application definitions and state (enabled/disabled). Uses
EnvironmentManager to enforce that the appropriate environment is
available and enabled before enabling an application. Definitions can be
loaded from JSON files.

Signed-off-by: Adam Wierzbicki <awierzbicki@golem.network>
  • Loading branch information
Wiezzel committed Aug 28, 2019
1 parent 69c59eb commit 2c5782a
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 0 deletions.
99 changes: 99 additions & 0 deletions golem/app_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import json
import logging
from pathlib import Path
from typing import Any, Dict, List

from dataclasses import dataclass, field
from dataclasses_json import config, dataclass_json
from marshmallow import fields as mm_fields

from golem.task.envmanager import EnvironmentManager

logger = logging.getLogger(__name__)


@dataclass_json
@dataclass
class AppDefinition:
name: str
requestor_env: str
requestor_prereq: Dict[str, Any] = field(metadata=config(
encoder=json.dumps,
decoder=json.loads,
mm_field=mm_fields.Dict(keys=mm_fields.Str())
))
max_benchmark_score: float
version: str = '0.0'
description: str = ''
author: str = ''
license: str = ''


class AppManager:
""" Manager class for applications using Task API. """

def __init__(self, env_manager: EnvironmentManager) -> None:
self._env_manager = env_manager
self._apps: Dict[str, AppDefinition] = {}
self._state: Dict[str, bool] = {}

def register_app(self, app: AppDefinition) -> None:
""" Register an application in the manager. """
if app.name not in self._apps:
self._apps[app.name] = app
self._state[app.name] = False
logger.info("Application '%s' registered.", app.name)

def register_app_from_json_file(self, json_file: Path) -> None:
""" Parse application definition from the given JSON file and register
the parsed definition in the manager. Raise ValueError if the given
file doesn't contain a valid definition. """
try:
app_json = json_file.read_text(encoding='utf-8')
# pylint: disable=no-member
app = AppDefinition.from_json(app_json) # type: ignore
# pylint: enable=no-member
self.register_app(app)
except (OSError, ValueError, KeyError):
msg = f"Error parsing app definition from file '{json_file}'."
logger.exception(msg)
raise ValueError(msg)

def register_apps_from_dir(self, app_dir: Path) -> None:
""" Read every file in the given directory and attempt to parse it. If
the file contains a valid JSON representation of an app definition
register the parsed definition in the manager. Ignore files which
don't contain valid app definitions. """
for json_file in app_dir.iterdir():
try:
self.register_app_from_json_file(json_file)
except ValueError:
continue

def enabled(self, app_name: str) -> bool:
""" Check if an application with the given name is registered in the
manager and enabled. """
return app_name in self._state and self._state[app_name]

def set_enabled(self, app_name: str, enabled: bool) -> None:
""" Enable or disable an application. Raise an error if the application
is not registered or the environment associated with the application
is not available. """
if app_name not in self._apps:
raise ValueError(f"Application '{app_name}' not registered.")
env_id = self._apps[app_name].requestor_env
if not self._env_manager.enabled(env_id):
raise ValueError(f"Environment '{env_id}' not available.")
self._state[app_name] = enabled
logger.info(
"Application '%s' %s.",
app_name,
'enabled' if enabled else 'disabled')

def apps(self) -> List[AppDefinition]:
""" Get all registered apps. """
return list(self._apps.values())

def app(self, app_name: str) -> AppDefinition:
""" Get an app with given name (assuming it is registered). """
return self._apps[app_name]
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ constantly==15.1.0
crossbar==19.6.2
cryptography==2.7
cytoolz==0.9.0.1
dataclasses-json==0.3.0
dataclasses==0.6
distro==1.3.0
dnspython==1.15.0
Expand Down Expand Up @@ -54,6 +55,7 @@ ipaddress==1.0.19
Jinja2==2.10.1
lmdb==0.94
MarkupSafe==1.0
marshmallow==3.0.0rc6
miniupnpc==2.0.2
mistune==0.8.3
mock==2.0.0
Expand Down
2 changes: 2 additions & 0 deletions requirements_to-freeze.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ cffi==1.12.3
click==7.0
crossbar==19.6.2
dataclasses
dataclasses-json
distro
docker==3.5.0
enforce==0.3.4
Expand All @@ -23,6 +24,7 @@ golem_task_api==0.12.0
html2text==2018.1.9
humanize==0.5.1
incremental==17.5.0
marshmallow
miniupnpc<2.1,>=2.0
ndg-httpsclient
netifaces==0.10.4
Expand Down
91 changes: 91 additions & 0 deletions tests/golem/test_app_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from unittest.mock import Mock

from golem.app_manager import AppDefinition, AppManager
from golem.task.envmanager import EnvironmentManager
from golem.testutils import TempDirFixture


class AppManagerTestBase(TempDirFixture):

def setUp(self):
super().setUp()
self.env_manager = Mock(spec_set=EnvironmentManager)
self.app_manager = AppManager(self.env_manager)

@property
def app_name(self):
return 'test_app'

@property
def app_def(self):
return AppDefinition(
name=self.app_name,
requestor_env='test_env',
requestor_prereq={
'key1': 'value',
'key2': [1, 2, 3]
},
max_benchmark_score=1.0
)


class TestRegisterApp(AppManagerTestBase):

def test_register_app(self):
self.app_manager.register_app(self.app_def)
self.assertEqual(self.app_manager.apps(), [self.app_def])
self.assertEqual(self.app_manager.app(self.app_name), self.app_def)
self.assertFalse(self.app_manager.enabled(self.app_name))

def test_re_register(self):
self.app_manager.register_app(self.app_def)
self.app_manager.register_app(self.app_def)
self.assertEqual(self.app_manager.apps(), [self.app_def])
self.assertEqual(self.app_manager.app(self.app_name), self.app_def)
self.assertFalse(self.app_manager.enabled(self.app_name))


class TestRegisterAppFromJSONFile(AppManagerTestBase):

def test_ok(self):
json_file = self.new_path / 'test_app.json'
json_file.write_text(self.app_def.to_json(), encoding='utf-8') # noqa pylint: disable=no-member
self.app_manager.register_app_from_json_file(json_file)
self.assertEqual(self.app_manager.apps(), [self.app_def])
self.assertEqual(self.app_manager.app(self.app_name), self.app_def)
self.assertFalse(self.app_manager.enabled(self.app_name))

def test_file_missing(self):
json_file = self.new_path / 'test_app.json'
with self.assertRaises(ValueError):
self.app_manager.register_app_from_json_file(json_file)
self.assertEqual(self.app_manager.apps(), [])

def test_invalid_json(self):
json_file = self.new_path / 'test_app.json'
json_file.write_text('(╯°□°)╯︵ ┻━┻', encoding='utf-8')
with self.assertRaises(ValueError):
self.app_manager.register_app_from_json_file(json_file)
self.assertEqual(self.app_manager.apps(), [])


class TestRegisterAppsFromDir(AppManagerTestBase):

def test_register(self):
app_file = self.new_path / 'test_app.json'
bogus_file = self.new_path / 'bogus.json'
app_file.write_text(self.app_def.to_json(), encoding='utf-8') # noqa pylint: disable=no-member
bogus_file.write_text('(╯°□°)╯︵ ┻━┻', encoding='utf-8')
self.app_manager.register_apps_from_dir(self.new_path)
self.assertEqual(self.app_manager.apps(), [self.app_def])


class TestSetEnabled(AppManagerTestBase):

def test_enable_disable(self):
self.app_manager.register_app(self.app_def)
self.assertFalse(self.app_manager.enabled(self.app_name))
self.app_manager.set_enabled(self.app_name, True)
self.assertTrue(self.app_manager.enabled(self.app_name))
self.app_manager.set_enabled(self.app_name, False)
self.assertFalse(self.app_manager.enabled(self.app_name))

0 comments on commit 2c5782a

Please sign in to comment.