From 03937baf51ed77b04af097f9c90c0526bb82ad0d Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 3 Dec 2019 08:34:11 +0100 Subject: [PATCH] config.session => session --- ...igure.yml => session.server.configure.yml} | 9 +- ...r.filter.yml => session.server.filter.yml} | 9 +- ....server.get.yml => session.server.get.yml} | 9 +- ...erver.list.yml => session.server.list.yml} | 9 +- ...ver.start.yml => session.server.start.yml} | 9 +- ...erver.stop.yml => session.server.stop.yml} | 9 +- ...lidate.yml => session.server.validate.yml} | 9 +- ....yml => session.servermodel.configure.yml} | 9 +- ...ter.yml => session.servermodel.filter.yml} | 9 +- ...el.get.yml => session.servermodel.get.yml} | 9 +- ....list.yml => session.servermodel.list.yml} | 9 +- ...tart.yml => session.servermodel.start.yml} | 9 +- ....stop.yml => session.servermodel.stop.yml} | 9 +- ...e.yml => session.servermodel.validate.yml} | 9 +- ...s.yml => session.configuration.status.yml} | 2 +- .../{config.status.yml => session.status.yml} | 2 +- .../types/{config.session.yml => session.yml} | 2 +- src/risotto/services/config/config.py | 278 +----------------- src/risotto/services/config/storage.py | 138 --------- src/risotto/services/session/__init__.py | 1 + src/risotto/services/session/session.py | 275 +++++++++++++++++ src/risotto/services/session/storage.py | 148 ++++++++++ src/risotto/services/template/template.py | 1 + 23 files changed, 472 insertions(+), 501 deletions(-) rename messages/v1/messages/{config.session.server.configure.yml => session.server.configure.yml} (84%) rename messages/v1/messages/{config.session.server.filter.yml => session.server.filter.yml} (81%) rename messages/v1/messages/{config.session.server.get.yml => session.server.get.yml} (65%) rename messages/v1/messages/{config.session.server.list.yml => session.server.list.yml} (52%) rename messages/v1/messages/{config.session.server.start.yml => session.server.start.yml} (67%) rename messages/v1/messages/{config.session.server.stop.yml => session.server.stop.yml} (74%) rename messages/v1/messages/{config.session.server.validate.yml => session.server.validate.yml} (62%) rename messages/v1/messages/{config.session.servermodel.configure.yml => session.servermodel.configure.yml} (83%) rename messages/v1/messages/{config.session.servermodel.filter.yml => session.servermodel.filter.yml} (80%) rename messages/v1/messages/{config.session.servermodel.get.yml => session.servermodel.get.yml} (64%) rename messages/v1/messages/{config.session.servermodel.list.yml => session.servermodel.list.yml} (52%) rename messages/v1/messages/{config.session.servermodel.start.yml => session.servermodel.start.yml} (67%) rename messages/v1/messages/{config.session.servermodel.stop.yml => session.servermodel.stop.yml} (73%) rename messages/v1/messages/{config.session.servermodel.validate.yml => session.servermodel.validate.yml} (62%) rename messages/v1/types/{config.configuration.status.yml => session.configuration.status.yml} (94%) rename messages/v1/types/{config.status.yml => session.status.yml} (95%) rename messages/v1/types/{config.session.yml => session.yml} (97%) delete mode 100644 src/risotto/services/config/storage.py create mode 100644 src/risotto/services/session/__init__.py create mode 100644 src/risotto/services/session/session.py create mode 100644 src/risotto/services/session/storage.py diff --git a/messages/v1/messages/config.session.server.configure.yml b/messages/v1/messages/session.server.configure.yml similarity index 84% rename from messages/v1/messages/config.session.server.configure.yml rename to messages/v1/messages/session.server.configure.yml index fde302d..d80141d 100644 --- a/messages/v1/messages/config.session.server.configure.yml +++ b/messages/v1/messages/session.server.configure.yml @@ -1,17 +1,14 @@ --- -uri: config.session.server.configure +uri: session.server.configure description: | Configure le server. -sampleuse: | - zephir-client config.session.server.configure -s 2 - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: session_id: @@ -44,5 +41,5 @@ parameters: default: [] response: - type: ConfigStatus + type: SessionStatus description: Description de la session. diff --git a/messages/v1/messages/config.session.server.filter.yml b/messages/v1/messages/session.server.filter.yml similarity index 81% rename from messages/v1/messages/config.session.server.filter.yml rename to messages/v1/messages/session.server.filter.yml index e2c4530..770ac2d 100644 --- a/messages/v1/messages/config.session.server.filter.yml +++ b/messages/v1/messages/session.server.filter.yml @@ -1,17 +1,14 @@ --- -uri: config.session.server.filter +uri: session.server.filter description: | Filter la configuration a éditer. -sampleuse: | - zephir-client config.session.server.filter -s 2 - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: session_id: @@ -36,5 +33,5 @@ parameters: default: null response: - type: ConfigSession + type: Session description: Description de la session. diff --git a/messages/v1/messages/config.session.server.get.yml b/messages/v1/messages/session.server.get.yml similarity index 65% rename from messages/v1/messages/config.session.server.get.yml rename to messages/v1/messages/session.server.get.yml index a91b1f9..3484914 100644 --- a/messages/v1/messages/config.session.server.get.yml +++ b/messages/v1/messages/session.server.get.yml @@ -1,17 +1,14 @@ --- -uri: config.session.server.get +uri: session.server.get description: | Configure le server. -sampleuse: | - zephir-client config.session.server.get -s 2 - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: session_id: @@ -21,5 +18,5 @@ parameters: description: Identifiant de la configuration. response: - type: ConfigSession + type: Session description: Description de la session. diff --git a/messages/v1/messages/config.session.server.list.yml b/messages/v1/messages/session.server.list.yml similarity index 52% rename from messages/v1/messages/config.session.server.list.yml rename to messages/v1/messages/session.server.list.yml index 3e9877c..80dce42 100644 --- a/messages/v1/messages/config.session.server.list.yml +++ b/messages/v1/messages/session.server.list.yml @@ -1,19 +1,16 @@ --- -uri: config.session.server.list +uri: session.server.list description: | Liste les sessions de configuration des serveurs. -sampleuse: | - zephir-client config.session.server.list - pattern: rpc public: true -domain: config-domain +domain: session-domain response: - type: '[]ConfigSession' + type: '[]Session' description: | Liste des sessions. diff --git a/messages/v1/messages/config.session.server.start.yml b/messages/v1/messages/session.server.start.yml similarity index 67% rename from messages/v1/messages/config.session.server.start.yml rename to messages/v1/messages/session.server.start.yml index da585a0..c88ed98 100644 --- a/messages/v1/messages/config.session.server.start.yml +++ b/messages/v1/messages/session.server.start.yml @@ -1,17 +1,14 @@ --- -uri: config.session.server.start +uri: session.server.start description: | Démarre une session de configuration pour un serveur. -sampleuse: | - zephir-client config.session.server.start -c 2 - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: id: @@ -22,5 +19,5 @@ parameters: Identifiant de la configuration. response: - type: ConfigSession + type: Session description: Description de la session. diff --git a/messages/v1/messages/config.session.server.stop.yml b/messages/v1/messages/session.server.stop.yml similarity index 74% rename from messages/v1/messages/config.session.server.stop.yml rename to messages/v1/messages/session.server.stop.yml index 4c8ad76..72876a3 100644 --- a/messages/v1/messages/config.session.server.stop.yml +++ b/messages/v1/messages/session.server.stop.yml @@ -1,17 +1,14 @@ --- -uri: config.session.server.stop +uri: session.server.stop description: | Termine une session de configuration d'un serveur. -sampleuse: | - zephir-client config.session.server.stop -s xxxxx - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: session_id: @@ -26,5 +23,5 @@ parameters: default: false response: - type: ConfigSession + type: Session description: Description de la session. diff --git a/messages/v1/messages/config.session.server.validate.yml b/messages/v1/messages/session.server.validate.yml similarity index 62% rename from messages/v1/messages/config.session.server.validate.yml rename to messages/v1/messages/session.server.validate.yml index 9112336..81134c6 100644 --- a/messages/v1/messages/config.session.server.validate.yml +++ b/messages/v1/messages/session.server.validate.yml @@ -1,17 +1,14 @@ --- -uri: config.session.server.validate +uri: session.server.validate description: | Valider la configuration d'un serveur. -sampleuse: | - zephir-client config.session.server.validate -s xxxxx - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: session_id: @@ -21,6 +18,6 @@ parameters: description: Identifiant de la session. response: - type: ConfigConfigurationStatus + type: SessionConfigurationStatus description: Statut de la configuration. diff --git a/messages/v1/messages/config.session.servermodel.configure.yml b/messages/v1/messages/session.servermodel.configure.yml similarity index 83% rename from messages/v1/messages/config.session.servermodel.configure.yml rename to messages/v1/messages/session.servermodel.configure.yml index f35057d..4cb0c9b 100644 --- a/messages/v1/messages/config.session.servermodel.configure.yml +++ b/messages/v1/messages/session.servermodel.configure.yml @@ -1,17 +1,14 @@ --- -uri: config.session.servermodel.configure +uri: session.servermodel.configure description: | Configure le servermodel. -sampleuse: | - zephir-client config.session.servermodel.configure -s 2 - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: session_id: @@ -44,5 +41,5 @@ parameters: default: [] response: - type: ConfigStatus + type: SessionStatus description: Description de la session. diff --git a/messages/v1/messages/config.session.servermodel.filter.yml b/messages/v1/messages/session.servermodel.filter.yml similarity index 80% rename from messages/v1/messages/config.session.servermodel.filter.yml rename to messages/v1/messages/session.servermodel.filter.yml index e1cbd73..c14b075 100644 --- a/messages/v1/messages/config.session.servermodel.filter.yml +++ b/messages/v1/messages/session.servermodel.filter.yml @@ -1,17 +1,14 @@ --- -uri: config.session.servermodel.filter +uri: session.servermodel.filter description: | Filter la configuration a éditer. -sampleuse: | - zephir-client config.session.servermodel.filter -s 2 - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: session_id: @@ -36,5 +33,5 @@ parameters: default: null response: - type: ConfigSession + type: Session description: Description de la session. diff --git a/messages/v1/messages/config.session.servermodel.get.yml b/messages/v1/messages/session.servermodel.get.yml similarity index 64% rename from messages/v1/messages/config.session.servermodel.get.yml rename to messages/v1/messages/session.servermodel.get.yml index 21287c8..c22cd9b 100644 --- a/messages/v1/messages/config.session.servermodel.get.yml +++ b/messages/v1/messages/session.servermodel.get.yml @@ -1,17 +1,14 @@ --- -uri: config.session.servermodel.get +uri: session.servermodel.get description: | Configure le servermodel. -sampleuse: | - zephir-client config.session.servermodel.get -s 2 - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: session_id: @@ -21,5 +18,5 @@ parameters: description: Identifiant de la configuration. response: - type: ConfigSession + type: Session description: Description de la session. diff --git a/messages/v1/messages/config.session.servermodel.list.yml b/messages/v1/messages/session.servermodel.list.yml similarity index 52% rename from messages/v1/messages/config.session.servermodel.list.yml rename to messages/v1/messages/session.servermodel.list.yml index aa211ea..574585d 100644 --- a/messages/v1/messages/config.session.servermodel.list.yml +++ b/messages/v1/messages/session.servermodel.list.yml @@ -1,18 +1,15 @@ -uri: config.session.servermodel.list +uri: session.servermodel.list description: | Liste les sessions de configuration des modèles de serveur. -sampleuse: | - zephir-client config.session.servermodel.list - pattern: rpc public: true -domain: config-domain +domain: session-domain response: - type: '[]ConfigSession' + type: '[]Session' description: | Liste des sessions. diff --git a/messages/v1/messages/config.session.servermodel.start.yml b/messages/v1/messages/session.servermodel.start.yml similarity index 67% rename from messages/v1/messages/config.session.servermodel.start.yml rename to messages/v1/messages/session.servermodel.start.yml index b54299a..960ae01 100644 --- a/messages/v1/messages/config.session.servermodel.start.yml +++ b/messages/v1/messages/session.servermodel.start.yml @@ -1,16 +1,13 @@ -uri: config.session.servermodel.start +uri: session.servermodel.start description: | Démarre une session de configuration pour un modèle de serveur. -sampleuse: | - zephir-client config.session.servermodel.start -c 2 - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: id: @@ -21,5 +18,5 @@ parameters: Identifiant de la configuration. response: - type: ConfigSession + type: Session description: Description de la session. diff --git a/messages/v1/messages/config.session.servermodel.stop.yml b/messages/v1/messages/session.servermodel.stop.yml similarity index 73% rename from messages/v1/messages/config.session.servermodel.stop.yml rename to messages/v1/messages/session.servermodel.stop.yml index e228886..d41b084 100644 --- a/messages/v1/messages/config.session.servermodel.stop.yml +++ b/messages/v1/messages/session.servermodel.stop.yml @@ -1,16 +1,13 @@ -uri: config.session.servermodel.stop +uri: session.servermodel.stop description: | Termine une session de configuration d'un modèle de serveur. -sampleuse: | - zephir-client config.session.servermodel.stop -s xxxxx - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: session_id: @@ -25,5 +22,5 @@ parameters: default: false response: - type: ConfigSession + type: Session description: Description de la session. diff --git a/messages/v1/messages/config.session.servermodel.validate.yml b/messages/v1/messages/session.servermodel.validate.yml similarity index 62% rename from messages/v1/messages/config.session.servermodel.validate.yml rename to messages/v1/messages/session.servermodel.validate.yml index a3c237c..7d3e25d 100644 --- a/messages/v1/messages/config.session.servermodel.validate.yml +++ b/messages/v1/messages/session.servermodel.validate.yml @@ -1,17 +1,14 @@ --- -uri: config.session.servermodel.validate +uri: session.servermodel.validate description: | Valider la configuration d'un modèle serveur. -sampleuse: | - zephir-client config.session.servermodel.validate -s xxxxx - pattern: rpc public: true -domain: config-domain +domain: session-domain parameters: session_id: @@ -21,6 +18,6 @@ parameters: description: Identifiant de la session. response: - type: ConfigConfigurationStatus + type: SessionConfigurationStatus description: Statut de la configuration. diff --git a/messages/v1/types/config.configuration.status.yml b/messages/v1/types/session.configuration.status.yml similarity index 94% rename from messages/v1/types/config.configuration.status.yml rename to messages/v1/types/session.configuration.status.yml index c516315..d84c3e2 100644 --- a/messages/v1/types/config.configuration.status.yml +++ b/messages/v1/types/session.configuration.status.yml @@ -1,5 +1,5 @@ --- -title: ConfigConfigurationStatus +title: SessionConfigurationStatus type: object description: Statut de la configuration. properties: diff --git a/messages/v1/types/config.status.yml b/messages/v1/types/session.status.yml similarity index 95% rename from messages/v1/types/config.status.yml rename to messages/v1/types/session.status.yml index 10f5a24..cdad2ad 100644 --- a/messages/v1/types/config.status.yml +++ b/messages/v1/types/session.status.yml @@ -1,5 +1,5 @@ --- -title: ConfigStatus +title: SessionStatus type: object description: Status de la modification de la configuration. properties: diff --git a/messages/v1/types/config.session.yml b/messages/v1/types/session.yml similarity index 97% rename from messages/v1/types/config.session.yml rename to messages/v1/types/session.yml index e2c570a..0641515 100644 --- a/messages/v1/types/config.session.yml +++ b/messages/v1/types/session.yml @@ -1,5 +1,5 @@ --- -title: ConfigSession +title: Session type: object description: Description de la session. properties: diff --git a/src/risotto/services/config/config.py b/src/risotto/services/config/config.py index 56a508d..11d381a 100644 --- a/src/risotto/services/config/config.py +++ b/src/risotto/services/config/config.py @@ -1,24 +1,20 @@ from lxml.etree import parse from io import BytesIO -from os import urandom # , unlink from os.path import isdir, isfile, join -from binascii import hexlify from traceback import print_exc from json import dumps -from typing import Dict, List, Optional, Any +from typing import Dict -from tiramisu import Storage, list_sessions, delete_session, Config, MetaConfig, MixConfig +from tiramisu import Storage, delete_session, MetaConfig, MixConfig from rougail import load as rougail_load from ...controller import Controller from ...register import register -from ...http import register as register_http from ...config import ROOT_CACHE_DIR, DATABASE_DIR, DEBUG, ROUGAIL_DTD_PATH from ...context import Context from ...utils import _ from ...error import CallError, NotAllowedError, RegistrationError from ...logger import log -from .storage import storage_server, storage_servermodel if not isdir(ROOT_CACHE_DIR): @@ -31,23 +27,8 @@ class Risotto(Controller): def __init__(self) -> None: self.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR) - self.modify_storage = Storage(engine='dictionary') super().__init__() - def valid_user(self, - session_id: str, - risotto_context: Context, - type: str) -> None: - """ check if current user is the session owner - """ - if type == 'server': - storage = storage_server - else: - storage = storage_servermodel - username = risotto_context.username - if username != storage.get_session(session_id)['username']: - raise NotAllowedError() - async def on_join(self, risotto_context: Context) -> None: """ pre-load servermodel and server @@ -449,258 +430,3 @@ class Risotto(Controller): return {'server_id': server_id, 'deploy': True} - - def get_session(self, - session_id: str, - type: str) -> Dict: - """ Get session information from storage - """ - if type == 'server': - return storage_server.get_session(session_id) - return storage_servermodel.get_session(session_id) - - def get_session_informations(self, - session_id: str, - type: str) -> Dict: - """ format session with a session ID name - """ - session = self.get_session(session_id, - type) - return self.format_session(session_id, - session) - - def format_session(self, - session_name: str, - session: Dict) -> Dict: - """ format session - """ - return {'session_id': session_name, - 'id': session['id'], - 'username': session['username'], - 'timestamp': session['timestamp'], - 'namespace': session['namespace'], - 'mode': session['mode'], - 'debug': session['debug']} - - def list_sessions(self, - type: str) -> List: - ret = [] - if type == 'server': - storage = storage_server - else: - storage = storage_servermodel - for session in storage.list_sessions(): - ret.append(self.format_session(session['session_id'], session)) - return ret - - def load_dict(self, - session: Dict) -> Dict: - if not session['option']: - session['option'] = session['config'].option(session['namespace']) - return session['option'].dict(remotable='all') - - @register(['v1.config.session.server.start', 'v1.config.session.servermodel.start'], None) - async def start_session(self, - risotto_context: Context, - id: int) -> Dict: - """ start a new config session for a server or a servermodel - """ - type = risotto_context.message.rsplit('.', 2)[-2] - server_list = getattr(self, type) - - if id not in server_list: - raise Exception(_(f'cannot find {type} with id {id}')) - - # check if a session already exists, in this case returns it - session_list = self.list_sessions(type) - for sess in session_list: - if sess['id'] == id and sess['username'] == risotto_context.username: - session_id = sess['session_id'] - session = self.get_session(session_id, type) - return self.format_session(session_id, session) - - # create a new session - if type == 'server': - storage = storage_server - else: - storage = storage_servermodel - while True: - session_id = 'z' + hexlify(urandom(23)).decode() - if not storage.has_session(session_id): - break - else: - print('session {} already exists'.format(session_id)) - username = risotto_context.username - storage.add_session(session_id, - server_list[id], - id, - username, - self.modify_storage) - return self.get_session_informations(session_id, - type) - - @register(['v1.config.session.server.list', 'v1.config.session.servermodel.list'], None) - async def list_session_server(self, - risotto_context: Context): - type = risotto_context.message.rsplit('.', 2)[-2] - return self.list_sessions(type) - - @register(['v1.config.session.server.filter', 'v1.config.session.servermodel.filter'], None) - async def filter_session(self, - risotto_context: Context, - session_id: str, - namespace: str, - mode: str, - debug: Optional[bool]): - type = risotto_context.message.rsplit('.', 2)[-2] - session = self.get_session(session_id, - type) - if namespace is not None: - session['option'] = None - session['namespace'] = namespace - if type == 'server': - storage = storage_server - else: - storage = storage_servermodel - if mode is not None: - if mode not in ('basic', 'normal', 'expert'): - raise CallError(f'unknown mode {mode}') - storage.set_config_mode(session_id, - mode) - if debug is not None: - storage.set_config_debug(session_id, - debug) - return self.get_session_informations(session_id, - type) - - @register(['v1.config.session.server.configure', 'v1.config.session.servermodel.configure'], None) - async def configure_session(self, - risotto_context: Context, - session_id: str, - action: str, - name: str, - index: int, - value: Any, - value_multi: Optional[List]) -> Dict: - type = risotto_context.message.rsplit('.', 2)[-2] - session = self.get_session(session_id, - type) - ret = {'session_id': session_id, - 'name': name} - if index is not None: - ret['index'] = index - option = session['config'].option(name).option - if option.ismulti() and not option.isfollower(): - value = value_multi - try: - update = {'name': name, - 'action': action, - 'value': value} - if index is not None: - update['index'] = index - if not session['option']: - session['option'] = session['config'].option(session['namespace']) - self.load_dict(session) - updates = {'updates': [update]} - session['option'].updates(updates) - ret['status'] = 'ok' - except Exception as err: - if DEBUG: - print_exc() - ret['message'] = str(err) - ret['status'] = 'error' - return ret - - @register(['v1.config.session.server.validate', 'v1.config.session.servermodel.validate'], None) - async def validate_session(self, - risotto_context: Context, - session_id: str) -> Dict: - type = risotto_context.message.rsplit('.', 2)[-2] - session = self.get_session(session_id, type) - ret = {} - try: - session['config'].forcepermissive.option(session['namespace']).value.dict() - except Exception as err: - ret['status'] = 'error' - ret['message'] = str(err) - else: - if type == 'server': - mandatories = list(session['config'].forcepermissive.value.mandatory()) - if mandatories: - ret['status'] = 'incomplete' - ret['mandatories'] = mandatories - else: - ret['status'] = 'ok' - else: - ret['status'] = 'ok' - return ret - - @register(['v1.config.session.server.get', 'v1.config.session.servermodel.get'], None) - async def get_session_(self, - risotto_context: Context, - session_id: str) -> Dict: - type = risotto_context.message.rsplit('.', 2)[-2] - info = self.get_session_informations(session_id, - type) - info['content'] = session_id - session = self.get_session(session_id, - type) - if not session['option']: - session['option'] = session['config'].option(session['namespace']) - info['content'] = dumps(session['option'].value.dict(fullpath=True)) - - return info - - @register(['v1.config.session.server.stop', 'v1.config.session.servermodel.stop'], None) - async def stop_session(self, - risotto_context: Context, - session_id: str, - save: bool) -> Dict: - type = risotto_context.message.rsplit('.', 2)[-2] - self.valid_user(session_id, - risotto_context, - type) - session = self.get_session(session_id, - type) - id_ = session['id'] - if type == 'server': - storage = storage_server - if save: - storage.save_values(session_id) - if self.server[id_].option('creole.general.available_probes').value.get() == "oui": - self.publish('v1.config.configuration.server.updated', server_id=id_, deploy=False) - else: - storage = storage_servermodel - if save: - storage.save_values(session_id) - for probe in self.servermodel[id_].config.list(): - # FIXME should use config.information.get('server_id') - name = probe.config.name() - if name.startswith('p_'): - server_id = int(name.rsplit('_', 1)[-1]) - if self.server[server_id].option('creole.general.available_probes').value.get() == "oui": - self.publish('v1.config.configuration.server.updated', server_id=server_id) - storage.del_session(session_id) - return self.format_session(session_id, session) - - @register_http('v1', '/config/server/{session_id}') - async def get_server_api(self, - request, - risotto_context: Context, - session_id: str) -> Dict: - self.valid_user(session_id, - risotto_context, - 'server') - session = storage_server.get_session(session_id) - return self.load_dict(session) - - @register_http('v1', '/config/servermodel/{session_id}') - async def get_servermodel_api(self, - request, - risotto_context: Context, - session_id: str) -> Dict: - self.valid_user(session_id, - risotto_context, - 'servermodel') - session = storage_servermodel.get_session(session_id) - return self.load_dict(session) diff --git a/src/risotto/services/config/storage.py b/src/risotto/services/config/storage.py deleted file mode 100644 index dfe7006..0000000 --- a/src/risotto/services/config/storage.py +++ /dev/null @@ -1,138 +0,0 @@ -import time -from rougail import modes - - -class StorageError(Exception): - pass - - -class Storage(object): - __slots__ = ('sessions',) - - def __init__(self): - self.sessions = {} - - def has_session(self, id_): - return id_ in self.sessions - - def config_exists(self, id_): - return self.sessions[id_]['config_exists'] - - def add_session(self, session_id, orig_config, server_id, username, storage): - for session in self.sessions.values(): - if session['id'] == server_id: - raise Storage(_(f'{username} already edits this configuration')) - prefix_id = "{}_".format(session_id) - config_server, orig_config = self.transform_orig_config(orig_config, server_id) - config_id = "{}{}".format(prefix_id, config_server) - meta = orig_config.config.deepcopy(session_id=config_id, storage=storage, metaconfig_prefix=prefix_id) - config = meta - while True: - try: - children = list(config.config.list()) - except: - break - if children: - config = children[0] - else: - break - config.property.read_write() - self.set_owner(config, username) - orig_values = config.value.exportation() - config.information.set('orig_values', orig_values) - config_exists = False - for owner in orig_values[3]: - if isinstance(owner, list): - if set(owner) != {'forced'}: - config_exists = True - break - elif owner != 'forced': - config_exists = True - break - self.sessions[session_id] = {'config': config, - # do not delete meta, so keep it! - 'meta': meta, - 'orig_config': orig_config, - 'id': server_id, - 'timestamp': int(time.time()), - 'username': username, - 'option': None, - 'namespace': 'creole', - 'config_exists': config_exists} - self.set_config_mode(session_id, 'normal') - self.set_config_debug(session_id, False) - - def list_sessions(self): - for session_id, session in self.sessions.items(): - yield {'session_id': session_id, - 'id': session['id'], - 'timestamp': session['timestamp'], - 'username': session['username'], - 'namespace': session['namespace'], - 'mode': session['mode'], - 'debug': session['debug']} - - def del_session(self, id_): - del self.sessions[id_] - - def get_session(self, id_): - if id_ not in self.sessions: - raise Exception('please start a session before') - return self.sessions[id_] - - def save_values(self, id_): - config = self.sessions[id_]['config'] - server_id = self.sessions[id_]['id'] - orig_config = self.sessions[id_]['orig_config'] - values = config.value.exportation() - orig_config.value.importation(values) - orig_config.permissive.importation(config.permissive.exportation()) - # current values become old values in diff_config - config.information.set('orig_values', values) - - def get_username(self, id_): - return self.get_session(id_)['username'] - - def set_config_mode(self, id_, mode): - """ Define which edition mode to select - """ - config = self.get_session(id_)['config'] - for mode_level in modes.values(): - if modes[mode] < mode_level: - config.property.add(mode_level.name) - else: - config.property.pop(mode_level.name) - self.sessions[id_]['mode'] = mode - - def set_config_debug(self, id_, is_debug): - """ Enable/Disable debug mode - """ - config = self.get_session(id_)['config'] - if is_debug: - config.property.pop('hidden') - else: - config.property.add('hidden') - self.sessions[id_]['debug'] = is_debug - - -class StorageServer(Storage): - def transform_orig_config(self, orig_config, server_id): - config_server = "std_{}".format(server_id) - orig_config = orig_config.config(config_server) - return config_server, orig_config - - def set_owner(self, config, username): - config.owner.set(username) - - -class StorageServermodel(Storage): - def transform_orig_config(self, orig_config, server_id): - config_server = "v_{}".format(server_id) - return config_server, orig_config - - def set_owner(self, config, username): - config.owner.set('servermodel_' + username) - - -storage_server = StorageServer() -storage_servermodel = StorageServermodel() diff --git a/src/risotto/services/session/__init__.py b/src/risotto/services/session/__init__.py new file mode 100644 index 0000000..c9d4b3d --- /dev/null +++ b/src/risotto/services/session/__init__.py @@ -0,0 +1 @@ +from .session import Risotto diff --git a/src/risotto/services/session/session.py b/src/risotto/services/session/session.py new file mode 100644 index 0000000..221d951 --- /dev/null +++ b/src/risotto/services/session/session.py @@ -0,0 +1,275 @@ +from os import urandom # , unlink +from binascii import hexlify +from traceback import print_exc +from json import dumps +from typing import Dict, List, Optional, Any +from tiramisu import Storage + + +from ...http import register as register_http +from ...config import DEBUG +from ...context import Context +from ...utils import _ +from ...error import CallError +from .storage import storage_server, storage_servermodel +from ...controller import Controller +from ...register import register +from ...dispatcher import dispatcher + + +class Risotto(Controller): + def __init__(self): + self.modify_storage = Storage(engine='dictionary') + + def valid_user(self, + session_id: str, + risotto_context: Context, + type: str) -> None: + """ check if current user is the session owner + """ + if type == 'server': + storage = storage_server + else: + storage = storage_servermodel + username = risotto_context.username + if username != storage.get_session(session_id)['username']: + raise NotAllowedError() + + @register(['v1.session.server.start', 'v1.session.servermodel.start'], None) + async def start_session(self, + risotto_context: Context, + id: int) -> Dict: + """ start a new config session for a server or a servermodel + """ + type = risotto_context.message.rsplit('.', 2)[-2] + config_module = dispatcher.get_service('config') + if type == 'server': + if id not in config_module.server: + raise Exception(_(f'cannot find {type} with id {id}')) + server = config_module.server[id] + config = server['server'] + storage = storage_server + else: + if id not in config_module.servermodel: + raise Exception(_(f'cannot find {type} with id {id}')) + config = config_module.servermodel[id] + storage = storage_servermodel + + # check if a session already exists, in this case returns it + session_list = self.list_sessions(type) + for sess in session_list: + if sess['id'] == id and sess['username'] == risotto_context.username: + session_id = sess['session_id'] + session = self.get_session(session_id, type) + return self.format_session(session_id, session) + + # create a new session + while True: + session_id = 'z' + hexlify(urandom(23)).decode() + if not storage.has_session(session_id): + break + else: + print('session {} already exists'.format(session_id)) + username = risotto_context.username + storage.add_session(session_id, + config, + id, + username, + self.modify_storage) + return self.get_session_informations(session_id, + type) + + @register(['v1.session.server.list', 'v1.session.servermodel.list'], None) + async def list_session_server(self, + risotto_context: Context): + type = risotto_context.message.rsplit('.', 2)[-2] + return self.list_sessions(type) + + @register(['v1.session.server.filter', 'v1.session.servermodel.filter'], None) + async def filter_session(self, + risotto_context: Context, + session_id: str, + namespace: str, + mode: str, + debug: Optional[bool]): + type = risotto_context.message.rsplit('.', 2)[-2] + if type == 'server': + storage = storage_server + else: + storage = storage_servermodel + if namespace is not None: + storage.set_namespace(session_id, + namespace) + if mode is not None: + if mode not in ('basic', 'normal', 'expert'): + raise CallError(f'unknown mode {mode}') + storage.set_config_mode(session_id, + mode) + if debug is not None: + storage.set_config_debug(session_id, + debug) + return self.get_session_informations(session_id, + type) + + @register(['v1.session.server.configure', 'v1.session.servermodel.configure'], None) + async def configure_session(self, + risotto_context: Context, + session_id: str, + action: str, + name: str, + index: int, + value: Any, + value_multi: Optional[List]) -> Dict: + type = risotto_context.message.rsplit('.', 2)[-2] + session = self.get_session(session_id, + type) + ret = {'session_id': session_id, + 'name': name} + if index is not None: + ret['index'] = index + option = session['config'].option(name).option + if option.ismulti() and not option.isfollower(): + value = value_multi + try: + update = {'name': name, + 'action': action, + 'value': value} + if index is not None: + update['index'] = index + updates = {'updates': [update]} + session['option'].updates(updates) + ret['status'] = 'ok' + except Exception as err: + if DEBUG: + print_exc() + ret['message'] = str(err) + ret['status'] = 'error' + return ret + + @register(['v1.session.server.validate', 'v1.session.servermodel.validate'], None) + async def validate_session(self, + risotto_context: Context, + session_id: str) -> Dict: + type = risotto_context.message.rsplit('.', 2)[-2] + session = self.get_session(session_id, type) + ret = {} + try: + session['config'].forcepermissive.option(session['namespace']).value.dict() + except Exception as err: + ret['status'] = 'error' + ret['message'] = str(err) + else: + if type == 'server': + mandatories = list(session['config'].forcepermissive.value.mandatory()) + if mandatories: + ret['status'] = 'incomplete' + ret['mandatories'] = mandatories + else: + ret['status'] = 'ok' + else: + ret['status'] = 'ok' + return ret + + @register(['v1.session.server.get', 'v1.session.servermodel.get'], None) + async def get_session_(self, + risotto_context: Context, + session_id: str) -> Dict: + type = risotto_context.message.rsplit('.', 2)[-2] + info = self.get_session_informations(session_id, + type) + info['content'] = session_id + session = self.get_session(session_id, + type) + info['content'] = dumps(session['option'].value.dict(fullpath=True)) + + return info + + @register(['v1.session.server.stop', 'v1.session.servermodel.stop'], None) + async def stop_session(self, + risotto_context: Context, + session_id: str, + save: bool) -> Dict: + type = risotto_context.message.rsplit('.', 2)[-2] + self.valid_user(session_id, + risotto_context, + type) + session = self.get_session(session_id, + type) + id_ = session['id'] + if type == 'server': + storage = storage_server + else: + storage = storage_servermodel + if save: + storage.save_values(session_id) + storage.del_session(session_id) + return self.format_session(session_id, session) + + @register_http('v1', '/config/server/{session_id}') + async def get_server_api(self, + request, + risotto_context: Context, + session_id: str) -> Dict: + self.valid_user(session_id, + risotto_context, + 'server') + session = storage_server.get_session(session_id) + return self.load_dict(session) + + @register_http('v1', '/config/servermodel/{session_id}') + async def get_servermodel_api(self, + request, + risotto_context: Context, + session_id: str) -> Dict: + self.valid_user(session_id, + risotto_context, + 'servermodel') + session = storage_servermodel.get_session(session_id) + return self.load_dict(session) + + def get_session(self, + session_id: str, + type: str) -> Dict: + """ Get session information from storage + """ + if type == 'server': + return storage_server.get_session(session_id) + return storage_servermodel.get_session(session_id) + + def get_session_informations(self, + session_id: str, + type: str) -> Dict: + """ format session with a session ID name + """ + session = self.get_session(session_id, + type) + return self.format_session(session_id, + session) + + def format_session(self, + session_name: str, + session: Dict) -> Dict: + """ format session + """ + return {'session_id': session_name, + 'id': session['id'], + 'username': session['username'], + 'timestamp': session['timestamp'], + 'namespace': session['namespace'], + 'mode': session['mode'], + 'debug': session['debug']} + + def list_sessions(self, + type: str) -> List: + ret = [] + if type == 'server': + storage = storage_server + else: + storage = storage_servermodel + for session_id, session in storage.get_sessions().items(): + ret.append(self.format_session(session_id, session)) + return ret + + def load_dict(self, + session: Dict) -> Dict: + return session['option'].dict(remotable='all') diff --git a/src/risotto/services/session/storage.py b/src/risotto/services/session/storage.py new file mode 100644 index 0000000..432fee2 --- /dev/null +++ b/src/risotto/services/session/storage.py @@ -0,0 +1,148 @@ +import time +from tiramisu import Config +from rougail import modes + + +class StorageError(Exception): + pass + + +class Storage(object): + __slots__ = ('sessions',) + + def __init__(self): + self.sessions = {} + + def has_session(self, + id: int): + return id in self.sessions + + def add_session(self, + session_id: int, + orig_config: Config, + server_id: int, + username: str, + config_storage): + for session in self.sessions.values(): + if session['id'] == server_id: + raise Storage(_(f'{username} already edits this configuration')) + prefix_id = f'{session_id}_' + config_name = self.get_config_name(server_id) + config_id = f'{prefix_id}{config_name}' + print(config_id) + + # copy Config and all it's parents + meta = orig_config.config.deepcopy(session_id=config_id, + storage=config_storage, + metaconfig_prefix=prefix_id) + + # retrieve the copied config (not metaconfig) + config = meta + while True: + try: + children = list(config.config.list()) + except: + break + if children: + config = children[0] + else: + break + config.property.read_write() + # set the default owner + self.set_owner(config, + username) + + # store it + self.sessions[session_id] = {'config': config, + # do not delete meta, so keep it! + 'meta': meta, + 'id': server_id, + 'timestamp': int(time.time()), + 'username': username} + self.set_config_mode(session_id, + 'normal') + self.set_config_debug(session_id, + False) + self.set_namespace(session_id, + 'creole') + + def set_namespace(self, + session_id: int, + namespace: str): + self.sessions[session_id]['option'] = self.sessions[session_id]['config'].option(namespace) + self.sessions[session_id]['namespace'] = namespace + + def get_sessions(self): + return self.sessions; + + def del_session(self, + id: int): + del self.sessions[id] + + def get_session(self, + id: int): + if id not in self.sessions: + raise Exception(f'the session {id} not exists') + return self.sessions[id] + + def save_values(self, id_): + # FIXME + config = self.sessions[id_]['config'] + server_id = self.sessions[id_]['id'] + orig_config = self.sessions[id_]['orig_config'] + values = config.value.exportation() + orig_config.value.importation(values) + orig_config.permissive.importation(config.permissive.exportation()) + + def get_username(self, + id: int): + return self.get_session(id)['username'] + + def set_config_mode(self, + id: int, + mode: str): + """ Define which edition mode to select + """ + config = self.get_session(id)['config'] + for mode_level in modes.values(): + if modes[mode] < mode_level: + config.property.add(mode_level.name) + else: + config.property.pop(mode_level.name) + self.sessions[id]['mode'] = mode + + def set_config_debug(self, id_, is_debug): + """ Enable/Disable debug mode + """ + config = self.get_session(id_)['config'] + if is_debug: + config.property.pop('hidden') + else: + config.property.add('hidden') + self.sessions[id_]['debug'] = is_debug + + +class StorageServer(Storage): + def get_config_name(self, + server_id: int): + return f'std_{server_id}' + + def set_owner(self, + config: Config, + username: str): + config.owner.set(username) + + +class StorageServermodel(Storage): + def get_config_name(self, + server_id: int): + return f'v_{server_id}' + + def set_owner(self, + config: Config, + username: str): + config.owner.set('servermodel_' + username) + + +storage_server = StorageServer() +storage_servermodel = StorageServermodel() diff --git a/src/risotto/services/template/template.py b/src/risotto/services/template/template.py index 23e3703..02aadda 100644 --- a/src/risotto/services/template/template.py +++ b/src/risotto/services/template/template.py @@ -8,6 +8,7 @@ from ...controller import Controller from ...register import register from ...dispatcher import dispatcher + class Risotto(Controller): def __init__(self): self.storage = Storage(engine='dictionary')