Compare commits

...

11 Commits

Author SHA1 Message Date
8a7cf24cd6 correctif doc et ajout dans README sur master 2020-09-03 16:47:29 +02:00
669782cd35 ajout liens pour wiki 2020-09-03 16:22:33 +02:00
4acd5d5a7f ajout schema-tira.jpg 2020-09-03 16:19:55 +02:00
533ad2551f update readme 2019-12-11 16:47:31 +01:00
3b31f092bd add session tests 2019-12-07 16:21:20 +01:00
3c5285a7d2 add test for config's service 2019-12-06 07:14:24 +01:00
b944a609a5 simplify session's code 2019-12-03 21:20:28 +01:00
bb1fdcbad0 save_values 2019-12-03 08:47:12 +01:00
03937baf51 config.session => session 2019-12-03 08:34:11 +01:00
939f93253e add 'template' services 2019-12-02 14:22:40 +01:00
0846c4c5cc Vocabulary 2019-12-02 10:45:07 +01:00
56 changed files with 1713 additions and 695 deletions

View File

@ -11,10 +11,20 @@ Accéder à un message :
wget http://localhost:8080/v1/config.session.server.start wget http://localhost:8080/v1/config.session.server.start
``` ```
Démarrer un serveur LemonLDAP de test [Documentation WIKI cadoles](https://doku.cadoles.com/dokuwiki/doku.php?id=documentation:risotto) n'est qu'un liens vers la doc officiel (pour le moment) [dans la forge](https://forge.cadoles.com/Infra/eole-risotto/wiki)
```
docker pull coudot/lemonldap-ng
echo "127.0.0.1 auth.example.com manager.example.com test1.example.com test2.example.com" >> /etc/hosts
docker run -d --add-host reload.example.com:127.0.0.1 -p 80:80 coudot/lemonldap-ng
```
**branche :**
master
develop
dist/risotto/risotto-2.7.1/develop : pour faire des paquets
dist/risotto/risotto-2.8.0/develop : pour faire des paquets
docker : n'est pas a jour
feature/service_servermodel :
jwt :

6
Vocabulary.txt Normal file
View File

@ -0,0 +1,6 @@
Message
=======
message: config.session.server.start
version: v1
uri: v1.config.session.server.start

View File

@ -17,10 +17,11 @@ parameters:
type: Number type: Number
ref: Server.ServerId ref: Server.ServerId
description: | description: |
Identifiant de la configuration. Identifiant du serveur.
deploy: deployed:
type: Boolean type: Boolean
description: Configuration de type déployée. description: Configuration de type déployée.
default: true
response: response:
type: ConfigConfiguration type: ConfigConfiguration

View File

@ -17,6 +17,6 @@ parameters:
type: Number type: Number
description: | description: |
Identifiant du serveur. Identifiant du serveur.
deploy: deployed:
type: Boolean type: Boolean
description: Configuration de type déployée. description: Configuration de type déployée.

View File

@ -13,7 +13,7 @@ public: false
domain: server-domain domain: server-domain
parameters: parameters:
serverid: server_id:
type: Number type: Number
description: | description: |
Identifiant du serveur supprimé. Identifiant du serveur supprimé.

View File

@ -10,6 +10,5 @@ public: false
domain: servermodel-domain domain: servermodel-domain
parameters: parameters:
servermodels: type: Servermodel
type: '[]Servermodel' description: Informations sur les modèles de serveur créés.
description: Informations sur les modèles de serveur créés.

View File

@ -10,6 +10,5 @@ public: false
domain: servermodel-domain domain: servermodel-domain
parameters: parameters:
servermodels: type: 'Servermodel'
type: '[]Servermodel' description: Informations sur les modèles de serveur modifiés.
description: Informations sur les modèles de serveur modifiés.

View File

@ -1,17 +1,14 @@
--- ---
uri: config.session.server.configure uri: session.server.configure
description: | description: |
Configure le server. Configure le server.
sampleuse: |
zephir-client config.session.server.configure -s 2
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
session_id: session_id:
@ -44,5 +41,5 @@ parameters:
default: [] default: []
response: response:
type: ConfigStatus type: SessionStatus
description: Description de la session. description: Description de la session.

View File

@ -1,17 +1,14 @@
--- ---
uri: config.session.server.filter uri: session.server.filter
description: | description: |
Filter la configuration a éditer. Filter la configuration a éditer.
sampleuse: |
zephir-client config.session.server.filter -s 2
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
session_id: session_id:
@ -36,5 +33,5 @@ parameters:
default: null default: null
response: response:
type: ConfigSession type: Session
description: Description de la session. description: Description de la session.

View File

@ -1,17 +1,14 @@
--- ---
uri: config.session.server.get uri: session.server.get
description: | description: |
Configure le server. Récupérer la configuration du server.
sampleuse: |
zephir-client config.session.server.get -s 2
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
session_id: session_id:
@ -19,7 +16,12 @@ parameters:
ref: Config.SessionId ref: Config.SessionId
shortarg: s shortarg: s
description: Identifiant de la configuration. description: Identifiant de la configuration.
name:
type: String
shortarg: n
description: Nom de la variable.
default: null
response: response:
type: ConfigSession type: Session
description: Description de la session. description: Description de la session.

View File

@ -1,19 +1,16 @@
--- ---
uri: config.session.server.list uri: session.server.list
description: | description: |
Liste les sessions de configuration des serveurs. Liste les sessions de configuration des serveurs.
sampleuse: |
zephir-client config.session.server.list
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
response: response:
type: '[]ConfigSession' type: '[]Session'
description: | description: |
Liste des sessions. Liste des sessions.

View File

@ -1,17 +1,14 @@
--- ---
uri: config.session.server.start uri: session.server.start
description: | description: |
Démarre une session de configuration pour un serveur. Démarre une session de configuration pour un serveur.
sampleuse: |
zephir-client config.session.server.start -c 2
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
id: id:
@ -22,5 +19,5 @@ parameters:
Identifiant de la configuration. Identifiant de la configuration.
response: response:
type: ConfigSession type: Session
description: Description de la session. description: Description de la session.

View File

@ -1,17 +1,14 @@
--- ---
uri: config.session.server.stop uri: session.server.stop
description: | description: |
Termine une session de configuration d'un serveur. Termine une session de configuration d'un serveur.
sampleuse: |
zephir-client config.session.server.stop -s xxxxx
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
session_id: session_id:
@ -26,5 +23,5 @@ parameters:
default: false default: false
response: response:
type: ConfigSession type: Session
description: Description de la session. description: Description de la session.

View File

@ -1,17 +1,14 @@
--- ---
uri: config.session.server.validate uri: session.server.validate
description: | description: |
Valider la configuration d'un serveur. Valider la configuration d'un serveur.
sampleuse: |
zephir-client config.session.server.validate -s xxxxx
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
session_id: session_id:
@ -21,6 +18,6 @@ parameters:
description: Identifiant de la session. description: Identifiant de la session.
response: response:
type: ConfigConfigurationStatus type: Session
description: Statut de la configuration. description: Statut de la configuration.

View File

@ -1,17 +1,14 @@
--- ---
uri: config.session.servermodel.configure uri: session.servermodel.configure
description: | description: |
Configure le servermodel. Configure le servermodel.
sampleuse: |
zephir-client config.session.servermodel.configure -s 2
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
session_id: session_id:
@ -44,5 +41,5 @@ parameters:
default: [] default: []
response: response:
type: ConfigStatus type: SessionStatus
description: Description de la session. description: Description de la session.

View File

@ -1,17 +1,14 @@
--- ---
uri: config.session.servermodel.filter uri: session.servermodel.filter
description: | description: |
Filter la configuration a éditer. Filter la configuration a éditer.
sampleuse: |
zephir-client config.session.servermodel.filter -s 2
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
session_id: session_id:
@ -36,5 +33,5 @@ parameters:
default: null default: null
response: response:
type: ConfigSession type: Session
description: Description de la session. description: Description de la session.

View File

@ -1,17 +1,14 @@
--- ---
uri: config.session.servermodel.get uri: session.servermodel.get
description: | description: |
Configure le servermodel. Configure le servermodel.
sampleuse: |
zephir-client config.session.servermodel.get -s 2
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
session_id: session_id:
@ -19,7 +16,12 @@ parameters:
ref: Config.SessionId ref: Config.SessionId
shortarg: s shortarg: s
description: Identifiant de la configuration. description: Identifiant de la configuration.
name:
type: String
shortarg: n
description: Nom de la variable.
default: null
response: response:
type: ConfigSession type: Session
description: Description de la session. description: Description de la session.

View File

@ -1,18 +1,15 @@
uri: config.session.servermodel.list uri: session.servermodel.list
description: | description: |
Liste les sessions de configuration des modèles de serveur. Liste les sessions de configuration des modèles de serveur.
sampleuse: |
zephir-client config.session.servermodel.list
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
response: response:
type: '[]ConfigSession' type: '[]Session'
description: | description: |
Liste des sessions. Liste des sessions.

View File

@ -1,16 +1,13 @@
uri: config.session.servermodel.start uri: session.servermodel.start
description: | description: |
Démarre une session de configuration pour un modèle de serveur. Démarre une session de configuration pour un modèle de serveur.
sampleuse: |
zephir-client config.session.servermodel.start -c 2
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
id: id:
@ -21,5 +18,5 @@ parameters:
Identifiant de la configuration. Identifiant de la configuration.
response: response:
type: ConfigSession type: Session
description: Description de la session. description: Description de la session.

View File

@ -1,16 +1,13 @@
uri: config.session.servermodel.stop uri: session.servermodel.stop
description: | description: |
Termine une session de configuration d'un modèle de serveur. Termine une session de configuration d'un modèle de serveur.
sampleuse: |
zephir-client config.session.servermodel.stop -s xxxxx
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
session_id: session_id:
@ -25,5 +22,5 @@ parameters:
default: false default: false
response: response:
type: ConfigSession type: Session
description: Description de la session. description: Description de la session.

View File

@ -1,17 +1,14 @@
--- ---
uri: config.session.servermodel.validate uri: session.servermodel.validate
description: | description: |
Valider la configuration d'un modèle serveur. Valider la configuration d'un modèle serveur.
sampleuse: |
zephir-client config.session.servermodel.validate -s xxxxx
pattern: rpc pattern: rpc
public: true public: true
domain: config-domain domain: session-domain
parameters: parameters:
session_id: session_id:
@ -21,6 +18,6 @@ parameters:
description: Identifiant de la session. description: Identifiant de la session.
response: response:
type: ConfigConfigurationStatus type: Session
description: Statut de la configuration. description: Statut de la configuration.

View File

@ -0,0 +1,26 @@
---
uri: template.generate
description: |
Génère et récupère les templates générés.
sampleuse: ~
pattern: rpc
public: true
domain: template-domain
parameters:
server_id:
type: Number
ref: Server.ServerId
shortarg: s
description: |
Identifiant du serveur.
response:
type: Template
description: |
Les fichiers de configuration générés.

View File

@ -1,24 +0,0 @@
---
title: ConfigConfigurationStatus
type: object
description: Statut de la configuration.
properties:
session_id:
type: string
description: ID de la session.
ref: Config.SessionId
status:
type: string
description: Statut de la configuration (peut être ok, error, incomplete)
message:
type: string
description: Message d'erreur si la configuration a le statut error.
mandatories:
type: array
items:
type: string
description: Liste des variables obligatoires non renseignées si la configuration a le statut incomplete.
required:
- session_id
- status

View File

@ -3,8 +3,15 @@ title: ConfigConfiguration
type: object type: object
description: Description de la configuration. description: Description de la configuration.
properties: properties:
server_id:
type: number
description: Identifiant du serveur.
ref: Server.ServerId
deployed:
type: boolean
description: La configuration est déployée.
configuration: configuration:
type: File type: object
description: Détail de la configuration au format JSON. description: Détail de la configuration au format JSON.
required: required:
- configuration - configuration

View File

@ -3,7 +3,7 @@ title: Server
type: object type: object
description: Description du serveur. description: Description du serveur.
properties: properties:
serverid: server_id:
type: number type: number
description: Identifiant du serveur. description: Identifiant du serveur.
ref: Server.ServerId ref: Server.ServerId
@ -33,7 +33,7 @@ properties:
type: string type: string
description: Timestamp de la dernière connexion avec le serveur. description: Timestamp de la dernière connexion avec le serveur.
required: required:
- serverid - server_id
- servername - servername
- serverdescription - serverdescription
- servermodelid - servermodelid

View File

@ -37,16 +37,16 @@ properties:
type: object type: object
description: Liste des services applicatifs déclarés pour ce modèle de serveur. description: Liste des services applicatifs déclarés pour ce modèle de serveur.
schema: schema:
type: File type: string
description: Contenu du schema. description: Contenu du schema.
probes: probes:
type: File type: string
description: Informations sur les sondes. description: Informations sur les sondes.
creolefuncs: creolefuncs:
type: File type: string
description: Fonctions Creole. description: Fonctions Creole.
conffiles: conffiles:
type: File type: string
description: Fichiers creole au format tar encodé base64 description: Fichiers creole au format tar encodé base64
required: required:
- servermodelid - servermodelid

View File

@ -1,5 +1,5 @@
--- ---
title: ConfigStatus title: SessionStatus
type: object type: object
description: Status de la modification de la configuration. description: Status de la modification de la configuration.
properties: properties:
@ -12,9 +12,6 @@ properties:
index: index:
type: number type: number
description: Index de la variable a modifier. description: Index de la variable a modifier.
status:
type: string
description: Status de la modification.
message: message:
type: string type: string
description: Message d'erreur. description: Message d'erreur.

View File

@ -1,5 +1,5 @@
--- ---
title: ConfigSession title: Session
type: object type: object
description: Description de la session. description: Description de la session.
properties: properties:
@ -27,7 +27,7 @@ properties:
type: boolean type: boolean
description: La configuration est en mode debug. description: La configuration est en mode debug.
content: content:
type: file type: object
description: Contenu de la configuration. description: Contenu de la configuration.
required: required:
- session_id - session_id

View File

@ -0,0 +1,14 @@
---
title: Template
type: object
description: Les fichiers de configuration générés.
properties:
server_id:
type: Number
description: Identifiant du serveur.
template_dir:
type: String
description: Nom du répertoire avec les fichiers de configuration générés.
required:
- server_id
- template_dir

BIN
schema-tira.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -1,6 +1,4 @@
from .http import get_app from .http import get_app
# just to register every route
from . import services as _services
__ALL__ = ('get_app',) __ALL__ = ('get_app',)

View File

@ -1,7 +1,10 @@
HTTP_PORT = 8080 HTTP_PORT = 8080
MESSAGE_ROOT_PATH = 'messages' MESSAGE_ROOT_PATH = 'messages'
ROOT_CACHE_DIR = 'cache' ROOT_CACHE_DIR = 'cache'
DEBUG = True DEBUG = False
DATABASE_DIR = 'database' DATABASE_DIR = 'database'
INTERNAL_USER = 'internal' INTERNAL_USER = 'internal'
CONFIGURATION_DIR = 'configurations'
TEMPLATE_DIR = 'templates'
TMP_DIR = 'tmp'
ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd' ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd'

View File

@ -60,7 +60,7 @@ class CallDispatcher:
mandatories = list(config.value.mandatory()) mandatories = list(config.value.mandatory())
if mandatories: if mandatories:
mand = [mand.split('.')[-1] for mand in mandatories] mand = [mand.split('.')[-1] for mand in mandatories]
raise ValueError(_(f'missing parameters in response: {mand}')) raise ValueError(_(f'missing parameters in response: {mand} in message "{risotto_context.message}"'))
try: try:
config.value.dict() config.value.dict()
except Exception as err: except Exception as err:
@ -149,7 +149,7 @@ class PublishDispatcher:
return return
# config is ok, so publish the message # config is ok, so publish the message
for function_obj in self.messages[version][message]['functions']: for function_obj in self.messages[version][message].get('functions', []):
function = function_obj['function'] function = function_obj['function']
module_name = function.__module__.split('.')[-2] module_name = function.__module__.split('.')[-2]
function_name = function.__name__ function_name = function.__name__
@ -163,21 +163,21 @@ class PublishDispatcher:
if function_obj['risotto_context']: if function_obj['risotto_context']:
kw['risotto_context'] = risotto_context kw['risotto_context'] = risotto_context
# send event # send event
await function(self.injected_self[function_obj['module']], **kw) returns = await function(self.injected_self[function_obj['module']], **kw)
except Exception as err: except Exception as err:
if DEBUG: if DEBUG:
print_exc() print_exc()
log.error_msg(risotto_context, kwargs, err, info_msg) log.error_msg(risotto_context, kwargs, err, info_msg)
continue
else: else:
log.info_msg(risotto_context, kwargs, info_msg) log.info_msg(risotto_context, kwargs, info_msg)
# notification
# notification if function_obj.get('notification'):
if obj.get('notification'): notif_version, notif_message = function_obj['notification'].split('.', 1)
notif_version, notif_message = obj['notification'].split('.', 1) await self.publish(notif_version,
await self.publish(notif_version, notif_message,
notif_message, risotto_context,
risotto_context, **returns)
**returns)
class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher): class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher):
@ -238,6 +238,10 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
# return the config # return the config
return config return config
def get_service(self,
name: str):
return self.injected_self[name]
dispatcher = Dispatcher() dispatcher = Dispatcher()
register.dispatcher = dispatcher register.dispatcher = dispatcher

View File

@ -1,6 +1,9 @@
from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound
from tiramisu import Config
from json import dumps from json import dumps
from traceback import print_exc
from tiramisu import Config
from .dispatcher import dispatcher from .dispatcher import dispatcher
from .utils import _ from .utils import _
from .context import Context from .context import Context
@ -8,7 +11,7 @@ from .error import CallError, NotAllowedError, RegistrationError
from .message import get_messages from .message import get_messages
from .logger import log from .logger import log
from .config import DEBUG, HTTP_PORT from .config import DEBUG, HTTP_PORT
from traceback import print_exc from .services import load_services
def create_context(request): def create_context(request):
@ -96,6 +99,7 @@ async def get_app(loop):
""" build all routes """ build all routes
""" """
global extra_routes global extra_routes
load_services()
app = Application(loop=loop) app = Application(loop=loop)
routes = [] routes = []
for version, messages in dispatcher.messages.items(): for version, messages in dispatcher.messages.items():

View File

@ -1,6 +1,7 @@
from typing import Dict from typing import Dict
from .context import Context from .context import Context
from .utils import _ from .utils import _
from .config import DEBUG
class Logger: class Logger:
@ -30,6 +31,7 @@ class Logger:
""" send message when an error append """ send message when an error append
""" """
paths_msg = self._get_message_paths(risotto_context) paths_msg = self._get_message_paths(risotto_context)
# if DEBUG:
print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}": {msg})')) print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}": {msg})'))
def info_msg(self, def info_msg(self,
@ -48,7 +50,8 @@ class Logger:
if msg: if msg:
tmsg += f' {msg}' tmsg += f' {msg}'
print(tmsg) if DEBUG:
print(tmsg)
log = Logger() log = Logger()

View File

@ -159,7 +159,8 @@ class RegisterDispatcher:
# valid function's arguments # valid function's arguments
if self.messages[version][message]['pattern'] == 'rpc': if self.messages[version][message]['pattern'] == 'rpc':
if notification is undefined: if notification is undefined:
raise RegistrationError(_('notification is mandatory when registered {message} with {module_name}.{function_name} even if you set None')) function_name = function.__name__
raise RegistrationError(_(f'notification is mandatory when registered "{message}" with "{module_name}.{function_name}" even if you set None'))
valid_params = self.valid_rpc_params valid_params = self.valid_rpc_params
else: else:
valid_params = self.valid_event_params valid_params = self.valid_event_params
@ -208,10 +209,10 @@ class RegisterDispatcher:
self.messages[version][message]['functions'] = [] self.messages[version][message]['functions'] = []
dico = {'module': module_name, dico = {'module': module_name,
'functions': function, 'function': function,
'arguments': function_args, 'arguments': function_args,
'risotto_context': inject_risotto_context} 'risotto_context': inject_risotto_context}
if notification: if notification and notification is not undefined:
dico['notification'] = notification dico['notification'] = notification
self.messages[version][message]['functions'].append(dico) self.messages[version][message]['functions'].append(dico)

View File

@ -4,15 +4,16 @@ from importlib import import_module
from ..dispatcher import dispatcher from ..dispatcher import dispatcher
def list_import(): def load_services(modules=None,
validate: bool=True):
abs_here = dirname(abspath(__file__)) abs_here = dirname(abspath(__file__))
here = basename(abs_here) here = basename(abs_here)
module = basename(dirname(abs_here)) module = basename(dirname(abs_here))
for filename in listdir(abs_here): if not modules:
modules = listdir(abs_here)
for filename in modules:
absfilename = join(abs_here, filename) absfilename = join(abs_here, filename)
if isdir(absfilename) and isfile(join(absfilename, '__init__.py')): if isdir(absfilename) and isfile(join(absfilename, '__init__.py')):
dispatcher.set_module(filename, import_module(f'.{here}.{filename}', module)) dispatcher.set_module(filename, import_module(f'.{here}.{filename}', module))
dispatcher.validate() if validate:
dispatcher.validate()
list_import()

View File

@ -1,28 +1,20 @@
from lxml.etree import parse from lxml.etree import parse
from io import BytesIO from io import BytesIO
from os import urandom # , unlink from os import unlink
from os.path import isdir, isfile, join from os.path import isdir, isfile, join
from binascii import hexlify
from traceback import print_exc from traceback import print_exc
from json import dumps from typing import Dict, List
from typing import Dict, List, Optional, Any
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 rougail import load as rougail_load
from ...controller import Controller from ...controller import Controller
from ...register import register from ...register import register
from ...http import register as register_http
from ...config import ROOT_CACHE_DIR, DATABASE_DIR, DEBUG, ROUGAIL_DTD_PATH from ...config import ROOT_CACHE_DIR, DATABASE_DIR, DEBUG, ROUGAIL_DTD_PATH
from ...context import Context from ...context import Context
from ...utils import _ from ...utils import _
from ...error import CallError, NotAllowedError, RegistrationError from ...error import CallError, RegistrationError
from ...logger import log from ...logger import log
from .storage import storage_server, storage_servermodel
if not isdir(ROOT_CACHE_DIR):
raise RegistrationError(_(f'unable to find the cache dir "{ROOT_CACHE_DIR}"'))
class Risotto(Controller): class Risotto(Controller):
@ -30,30 +22,18 @@ class Risotto(Controller):
server = {} server = {}
def __init__(self) -> None: def __init__(self) -> None:
for dirname in [ROOT_CACHE_DIR, DATABASE_DIR]:
if not isdir(dirname):
raise RegistrationError(_(f'unable to find the cache dir "{dirname}"'))
self.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR) self.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR)
self.modify_storage = Storage(engine='dictionary')
super().__init__() 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, async def on_join(self,
risotto_context: Context) -> None: risotto_context: Context) -> None:
""" pre-load servermodel and server """ pre-load servermodel and server
""" """
await self.load_servermodels(risotto_context) await self.load_servermodels(risotto_context)
# FIXME await self.load_servers(risotto_context) await self.load_servers(risotto_context)
async def load_servermodels(self, async def load_servermodels(self,
risotto_context: Context) -> None: risotto_context: Context) -> None:
@ -78,10 +58,16 @@ class Risotto(Controller):
for servermodel in servermodels: for servermodel in servermodels:
if 'servermodelparentsid' in servermodel: if 'servermodelparentsid' in servermodel:
for servermodelparentid in servermodel['servermodelparentsid']: for servermodelparentid in servermodel['servermodelparentsid']:
self.servermodel_legacy(servermodel['servermodelname'], self.servermodel_legacy(risotto_context,
servermodel['servermodelname'],
servermodel['servermodelid'], servermodel['servermodelid'],
servermodelparentid) servermodelparentid)
def get_funcs_filename(self,
servermodelid: int):
return join(ROOT_CACHE_DIR, str(servermodelid)+".creolefuncs")
async def load_servermodel(self, async def load_servermodel(self,
risotto_context: Context, risotto_context: Context,
@ -90,7 +76,7 @@ class Risotto(Controller):
""" Loads a servermodel """ Loads a servermodel
""" """
cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml") cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml")
funcs_file = join(ROOT_CACHE_DIR, str(servermodelid)+".creolefuncs") funcs_file = self.get_funcs_filename(servermodelid)
log.info_msg(risotto_context, log.info_msg(risotto_context,
None, None,
f'Load servermodel {servermodelname} ({servermodelid})') f'Load servermodel {servermodelname} ({servermodelid})')
@ -171,6 +157,7 @@ class Risotto(Controller):
return metaconfig return metaconfig
def servermodel_legacy(self, def servermodel_legacy(self,
risotto_context: Context,
servermodel_name: str, servermodel_name: str,
servermodel_id: int, servermodel_id: int,
servermodel_parent_id: int) -> None: servermodel_parent_id: int) -> None:
@ -216,29 +203,32 @@ class Risotto(Controller):
# loads servers # loads servers
for server in servers: for server in servers:
try: try:
self.load_server(server['serverid'], self.load_server(risotto_context,
server['server_id'],
server['servername'], server['servername'],
server['servermodelid']) server['servermodelid'])
except Exception as err: except Exception as err:
if DEBUG:
print_exc()
servername = server['servername'] servername = server['servername']
serverid = server['serverid'] server_id = server['server_id']
msg = _(f'Unable to load server {servername} ({serverid}): {err}') msg = _(f'unable to load server {servername} ({server_id}): {err}')
log.error_msg(risotto_context, log.error_msg(risotto_context,
None, None,
msg) msg)
def load_server(self, def load_server(self,
risotto_context: Context, risotto_context: Context,
serverid: int, server_id: int,
servername: str, servername: str,
servermodelid: int) -> None: servermodelid: int) -> None:
""" Loads a server """ Loads a server
""" """
if serverid in self.server: if server_id in self.server:
return return
log.info_msg(risotto_context, log.info_msg(risotto_context,
None, None,
f'Load server {servername} ({serverid})') f'Load server {servername} ({server_id})')
if not servermodelid in self.servermodel: if not servermodelid in self.servermodel:
msg = f'unable to find servermodel with id {servermodelid}' msg = f'unable to find servermodel with id {servermodelid}'
log.error_msg(risotto_context, log.error_msg(risotto_context,
@ -247,147 +237,155 @@ class Risotto(Controller):
raise CallError(msg) raise CallError(msg)
# check if server was already created # check if server was already created
session_id = f's_{serverid}' session_id = f's_{server_id}'
is_new_config = session_id not in list_sessions()
# get the servermodel's metaconfig # get the servermodel's metaconfig
metaconfig = self.servermodel[servermodelid] metaconfig = self.servermodel[servermodelid]
# create server configuration and server 'to deploy' configuration and store it # create server configuration and server 'to deploy' configuration and store it
self.server[serverid] = {'server': self.build_config(session_id, self.server[server_id] = {'server': self.build_config(session_id,
is_new_config), server_id,
'server_to_deploy': self.build_config(f'std_{serverid}', servername,
is_new_config)} metaconfig),
'server_to_deploy': self.build_config(f'std_{server_id}',
server_id,
servername,
metaconfig),
'funcs_file': self.get_funcs_filename(servermodelid)}
def build_config(self, def build_config(self,
session_id: str, session_id: str,
is_new_config: bool) -> None: server_id: int,
servername: str,
metaconfig: MetaConfig) -> None:
""" build server's config """ build server's config
""" """
config = metaconfig.config.new(session_id, config = metaconfig.config.new(session_id,
storage=self.save_storage,
persistent=True) persistent=True)
config.information.set('server_id', serverid) config.information.set('server_id', server_id)
config.information.set('server_name', servername) config.information.set('server_name', servername)
config.owner.set(servername) config.owner.set(servername)
# if new config, remove force_store_value before switchint to read-only mode
# force_store_value is not allowed for new server (wait when configuration is deploy)
if is_new_config:
ro = list(config.property.getdefault('read_only', 'append'))
ro.remove('force_store_value')
config.property.setdefault(frozenset(ro), 'read_only', 'append')
rw = list(config.property.getdefault('read_write', 'append'))
rw.remove('force_store_value')
config.property.setdefault(frozenset(rw), 'read_write', 'append')
config.property.read_only() config.property.read_only()
return config
@register('v1.server.created') @register('v1.server.created')
async def server_created(self, async def server_created(self,
serverid: int, risotto_context: Context,
server_id: int,
servername: str, servername: str,
servermodelid: int) -> None: servermodelid: int) -> None:
""" Loads server's configuration when a new server is created """ Loads server's configuration when a new server is created
""" """
self.load_server(serverid, self.load_server(risotto_context,
server_id,
servername, servername,
servermodelid) servermodelid)
@register('v1.server.deleted') @register('v1.server.deleted')
async def server_deleted(self, async def server_deleted(self,
serverid: int) -> None: server_id: int) -> None:
# delete config to it's parents # delete config to it's parents
for config in self.server[serverid].values(): for server_type in ['server', 'server_to_deploy']:
config = self.server[server_id]['server']
for parent in config.config.parents(): for parent in config.config.parents():
parent.config.pop(config.config.name()) parent.config.pop(config.config.name())
delete_session(config.config.name()) delete_session(storage=self.save_storage,
session_id=config.config.name())
# delete metaconfig # delete metaconfig
del self.server[serverid] del self.server[server_id]
@register('v1.servermodel.created') @register('v1.servermodel.created')
async def servermodel_created(self, async def servermodel_created(self,
servermodels) -> None: risotto_context: Context,
servermodelid: int,
servermodelname: str,
servermodelparentsid: List[int]) -> None:
""" when servermodels are created, load it and do link """ when servermodels are created, load it and do link
""" """
for servermodel in servermodels: await self.load_and_link_servermodel(risotto_context,
await self.load_servermodel(servermodel['servermodelid'], servermodel['servermodelname']) servermodelid,
for servermodel in servermodels: servermodelname,
if 'servermodelparentsid' in servermodel: servermodelparentsid)
for servermodelparentid in servermodel['servermodelparentsid']:
self.servermodel_legacy(servermodel['servermodelname'], servermodel['servermodelid'], servermodelparentid)
async def load_and_link_servermodel(self,
risotto_context: Context,
servermodelid: int,
servermodelname: str,
servermodelparentsid: List[int]) -> None:
await self.load_servermodel(risotto_context,
servermodelid,
servermodelname)
if servermodelparentsid is not None:
for servermodelparentid in servermodelparentsid:
self.servermodel_legacy(risotto_context,
servermodelname,
servermodelid,
servermodelparentid)
def servermodel_delete(self,
servermodelid: int) -> List[MetaConfig]:
metaconfig = self.servermodel.pop(servermodelid)
mixconfig = next(metaconfig.config.list())
children = []
for child in mixconfig.config.list():
children.append(child)
mixconfig.config.pop(child.config.name())
metaconfig.config.pop(mixconfig.config.name())
delete_session(storage=self.save_storage,
session_id=mixconfig.config.name())
del mixconfig
for parent in metaconfig.config.parents():
parent.config.pop(metaconfig.config.name())
delete_session(storage=self.save_storage,
session_id=metaconfig.config.name())
return children
@register('v1.servermodel.updated') @register('v1.servermodel.updated')
async def servermodel_updated(self, async def servermodel_updated(self,
risotto_context: Context, risotto_context: Context,
servermodels) -> None: servermodelid: int,
for servermodel in servermodels: servermodelname: str,
servermodelid = servermodel['servermodelid'] servermodelparentsid: List[int]) -> None:
servermodelname = servermodel['servermodelname'] log.info_msg(risotto_context,
servermodelparentsid = servermodel.get('servermodelparentsid') None,
log.info_msg(risotto_context, f'Reload servermodel {servermodelname} ({servermodelid})')
None, # unlink cache to force download new aggregated file
f'Reload servermodel {servermodelname} ({servermodelid})') cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml")
# unlink cache to force download new aggregated file if isfile(cache_file):
cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml") unlink(cache_file)
if isfile(cache_file):
unlink(cache_file)
# get current servermodel # store all informations
old_servermodel = self.servermodel[servermodelid] if servermodelid in self.servermodel:
old_values = self.servermodel[servermodelid].value.exportation()
old_permissives = self.servermodel[servermodelid].permissive.exportation()
old_properties = self.servermodel[servermodelid].property.exportation()
children = self.servermodel_delete(servermodelid)
else:
old_values = None
# create new one # create new one
await self.load_servermodel(servermodelid, servermodelname) await self.load_and_link_servermodel(risotto_context,
servermodelid,
servermodelname,
servermodelparentsid)
# migrate all informations # migrates informations
self.servermodel[servermodelid].value.importation(old_servermodel.value.exportation()) if old_values is not None:
self.servermodel[servermodelid].permissive.importation(old_servermodel.permissive.exportation()) self.servermodel[servermodelid].value.importation(old_values)
self.servermodel[servermodelid].property.importation(old_servermodel.property.exportation()) self.servermodel[servermodelid].permissive.importation(old_permissives)
self.servermodel[servermodelid].property.importation(old_properties)
# remove link to legacy for child in children:
if servermodelparentsid: self.servermodel_legacy(risotto_context,
for servermodelparentid in servermodelparentsid: child.information.get('servermodel_name'),
mix = self.servermodel[servermodelparentid].config.get('m_v_' + str(servermodelparentid)) child.information.get('servermodel_id'),
try: servermodelid)
mix.config.pop(old_servermodel.config.name())
except:
# if mix config is reloaded too
pass
# add new link
self.servermodel_legacy(servermodelname, servermodelid, servermodelparentid)
# reload servers or servermodels in servermodel
for subconfig in old_servermodel.config.list():
if not isinstance(subconfig, MixConfig):
# a server
name = subconfig.config.name()
if name.startswith('str_'):
continue
server_id = subconfig.information.get('server_id')
server_name = subconfig.information.get('server_name')
try:
old_servermodel.config.pop(name)
old_servermodel.config.pop(f'std_{server_id}')
except:
pass
del self.server[server_id]
self.load_server(server_id,
server_name,
servermodelid)
else:
# a servermodel
for subsubconfig in subconfig.config.list():
name = subsubconfig.config.name()
try:
subconfig.config.pop(name)
except:
pass
self.servermodel_legacy(subsubconfig.information.get('servermodel_name'),
subsubconfig.information.get('servermodel_id'),
servermodelid)
@register('v1.config.configuration.server.get', None) @register('v1.config.configuration.server.get', None)
async def get_configuration(self, async def get_configuration(self,
server_id: int, server_id: int,
deploy: bool) -> bytes: deployed: bool) -> bytes:
if server_id not in self.server: if server_id not in self.server:
msg = _(f'cannot find server with id {server_id}') msg = _(f'cannot find server with id {server_id}')
log.error_msg(risotto_context, log.error_msg(risotto_context,
@ -395,16 +393,16 @@ class Risotto(Controller):
msg) msg)
raise CallError(msg) raise CallError(msg)
if deploy: if deployed:
server = self.server[server_id]['server'] server = self.server[server_id]['server']
else: else:
server = self.server[server_id]['server_to_deploy'] server = self.server[server_id]['server_to_deploy']
server.property.read_only() server.property.read_only()
try: try:
dico = server.value.dict(fullpath=True) configuration = server.value.dict(fullpath=True)
except: except:
if deploy: if deployed:
msg = _(f'No configuration available for server {server_id}') msg = _(f'No configuration available for server {server_id}')
else: else:
msg = _(f'No undeployed configuration available for server {server_id}') msg = _(f'No undeployed configuration available for server {server_id}')
@ -412,8 +410,9 @@ class Risotto(Controller):
None, None,
msg) msg)
raise CallError(msg) raise CallError(msg)
return dumps(dico).encode() return {'server_id': server_id,
'deployed': deployed,
'configuration': configuration}
@register('v1.config.configuration.server.deploy', 'v1.config.configuration.server.updated') @register('v1.config.configuration.server.deploy', 'v1.config.configuration.server.updated')
async def deploy_configuration(self, async def deploy_configuration(self,
@ -439,259 +438,4 @@ class Risotto(Controller):
config.property.importation(config_std.property.exportation()) config.property.importation(config_std.property.exportation())
return {'server_id': server_id, return {'server_id': server_id,
'deploy': True} 'deployed': 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)

View File

@ -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()

View File

@ -0,0 +1 @@
from .server import Risotto

View File

@ -0,0 +1,8 @@
from ...controller import Controller
from ...register import register
class Risotto(Controller):
@register('v1.server.list', None)
async def server_list(self):
return [{'server_id': 1, 'servername': 'one', 'serverdescription': 'the first', 'servermodelid': 1}]

View File

@ -14,6 +14,30 @@ class Risotto(Controller):
async def servermodel_describe(self, inheritance, creolefuncs, servermodelid, schema, conffiles, resolvdepends, probes): async def servermodel_describe(self, inheritance, creolefuncs, servermodelid, schema, conffiles, resolvdepends, probes):
schema = """<?xml version='1.0' encoding='UTF-8'?> schema = """<?xml version='1.0' encoding='UTF-8'?>
<creole> <creole>
<family name="containers">
<family name="container0" doc="test">
<family doc="files" name="files">
<family doc="file0" name="file0">
<variable doc="" multi="False" name="mkdir" type="boolean">
<value>False</value>
</variable>
<variable doc="" multi="False" name="name" type="string">
<value>/etc/mailname</value>
</variable>
<variable doc="" multi="False" name="rm" type="boolean">
<value>False</value>
</variable>
<variable doc="" multi="False" name="source" type="string">
<value>mailname</value>
</variable>
<variable doc="" multi="False" name="activate" type="boolean">
<value>True</value>
</variable>
</family>
</family>
<property>basic</property>
</family>
</family>
<family doc="" name="creole"> <family doc="" name="creole">
<family doc="general" name="general"> <family doc="general" name="general">
<property>normal</property> <property>normal</property>
@ -38,5 +62,4 @@ class Risotto(Controller):
<separators/> <separators/>
</family> </family>
</creole>""" </creole>"""
print('pouet') return {'servermodelid': 1, 'servermodelname': 'name', 'servermodeldescription': 'description', 'subreleasename': 'name', 'sourceid': 1, 'schema': schema, 'creolefuncs': ''}
return [{'servermodelid': 1, 'servermodelname': 'name', 'servermodeldescription': 'description', 'subreleasename': 'name', 'sourceid': 1, 'schema': schema, 'creolefuncs': ''}]

View File

@ -0,0 +1 @@
from .session import Risotto

View File

@ -0,0 +1,261 @@
from os import urandom # , unlink
from binascii import hexlify
from traceback import print_exc
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 get_storage(self,
type: str):
if type == 'server':
return storage_server
return storage_servermodel
def get_session(self,
risotto_context: Context,
session_id: str,
type: str) -> Dict:
""" Get session information from storage
"""
if type == 'server':
storage = storage_server
else:
storage = storage_servermodel
return storage.get_session(session_id,
risotto_context.username)
def get_session_informations(self,
risotto_context: Context,
session_id: str,
type: str) -> Dict:
""" format session with a session ID name
"""
session = self.get_session(risotto_context,
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']}
@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}'))
config = config_module.server[id]['server']
else:
if id not in config_module.servermodel:
raise Exception(_(f'cannot find {type} with id {id}'))
config = config_module.servermodel[id]
storage = self.get_storage(type)
# check if a session already exists
sessions = storage.get_sessions()
for session in sessions.values():
if sess['id'] == id:
if sess['username'] == risotto_context.username:
# same user so returns it
return self.format_session(session['session_id'], session)
else:
raise CallError(_(f'{username} already edits this configuration'))
# create a new session
while True:
session_id = 'z' + hexlify(urandom(23)).decode()
if not session_id in sessions:
break
storage.add_session(session_id,
config,
id,
risotto_context.username,
self.modify_storage)
# return session's information
return self.get_session_informations(risotto_context,
session_id,
type)
@register(['v1.session.server.list', 'v1.session.servermodel.list'], None)
async def list_session_server(self,
risotto_context: Context) -> Dict:
type = risotto_context.message.rsplit('.', 2)[-2]
storage = self.get_storage(type)
return [self.format_session(session_id, session) for session_id, session in storage.get_sessions().items()]
@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]
storage = self.get_storage(type)
# to validate the session right
storage.get_session(session_id,
risotto_context.username)
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(risotto_context,
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(risotto_context,
session_id,
type)
# if multi and not follower the value is in fact in value_multi
option = session['option'].option(name).option
if option.ismulti() and not option.isfollower():
value = value_multi
namespace = session['namespace']
update = {'name': f'{namespace}.{name}',
'action': action,
'value': value}
if index is not None:
update['index'] = index
updates = {'updates': [update]}
ret = session['option'].updates(updates)
if update['name'] in ret:
for val in ret[update['name']][index]:
if isinstance(val, ValueError):
raise CallError(val)
ret = {'session_id': session_id,
'name': name}
if index is not None:
ret['index'] = index
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(risotto_context,
session_id,
type)
try:
session['config'].forcepermissive.option(session['namespace']).value.dict()
except Exception as err:
raise CallError(str(err))
if type == 'server':
mandatories = list(session['config'].forcepermissive.value.mandatory())
if mandatories:
if len(mandatories) == 1:
mandatories = mandatories[0]
msg = _('the parameter "--{mandatories}" is mandatory')
else:
mandatories = '", "--'.join(mandatories)
msg = _('parameters "{mandatories}" are mandatories')
raise CallError(msg)
return self.format_session(session_id,
session)
@register(['v1.session.server.get', 'v1.session.servermodel.get'], None)
async def get_session_server(self,
risotto_context: Context,
session_id: str,
name: Optional[str]) -> Dict:
type = risotto_context.message.rsplit('.', 2)[-2]
session = self.get_session(risotto_context,
session_id,
type)
info = self.format_session(session_id, session)
if name is not None:
info['content'] = {name: session['option'].option(name).value.get()}
else:
info['content'] = session['option'].value.dict()
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]
storage = self.get_storage(type)
session = storage.get_session(session_id,
risotto_context.username)
id_ = session['id']
config_module = dispatcher.get_service('config')
if type == 'server':
config = config_module.server[id_]['server']
else:
config = config_module.servermodel[id_]
if save:
modif_config = session['config']
config.value.importation(modif_config.value.exportation())
config.permissive.importation(modif_config.permissive.exportation())
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:
session = storage_server.get_session(session_id,
risotto_context.username)
return session['option'].dict(remotable='all')
@register_http('v1', '/config/servermodel/{session_id}')
async def get_servermodel_api(self,
request,
risotto_context: Context,
session_id: str) -> Dict:
session = storage_servermodel.get_session(session_id,
risotto_context.username)
return session['option'].dict(remotable='all')

View File

@ -0,0 +1,134 @@
import time
from typing import Dict
from tiramisu import Config
from rougail import modes
from ...error import CallError, NotAllowedError
class StorageError(Exception):
pass
class Storage(object):
__slots__ = ('sessions',)
def __init__(self):
self.sessions = {}
def add_session(self,
session_id: int,
orig_config: Config,
server_id: int,
username: str,
config_storage):
prefix_id = f'{session_id}_'
config_name = self.get_config_name(server_id)
config_id = f'{prefix_id}{config_name}'
# 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())
if not children:
# it's an empty metaconfig
break
config = children[0]
except:
# it's a config, so no "list" method
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_config_mode(self,
id: int,
mode: str):
""" Define which edition mode to select
"""
config = self.sessions[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.sessions[id_]['config']
if is_debug:
config.property.pop('hidden')
else:
config.property.add('hidden')
self.sessions[id_]['debug'] = is_debug
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 get_session(self,
session_id: int,
username: str) -> Dict:
if session_id not in self.sessions:
raise Exception(f'the session {id} not exists')
session = self.sessions[session_id]
if username != session['username']:
raise NotAllowedError()
return session
def del_session(self,
id: int):
del self.sessions[id]
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()

View File

@ -0,0 +1 @@
from .template import Risotto

View File

@ -0,0 +1,49 @@
from os import mkdir
from os.path import isdir, join
from shutil import rmtree
from rougail.template import generate
from tiramisu import Storage
from ...config import ROOT_CACHE_DIR, CONFIGURATION_DIR, TEMPLATE_DIR, TMP_DIR
from ...controller import Controller
from ...register import register
from ...dispatcher import dispatcher
class Risotto(Controller):
def __init__(self):
self.storage = Storage(engine='dictionary')
@register('v1.template.generate', None)
async def template_get(self,
server_id: int):
config_module = dispatcher.get_service('config')
server = config_module.server[server_id]
config = meta = server['server'].config.deepcopy(storage=self.storage)
while True:
try:
children = list(config.config.list())
except:
break
if children:
config = children[0]
else:
break
print(config.value.dict())
configurations_dir = join(CONFIGURATION_DIR,
str(server_id))
if isdir(configurations_dir):
rmtree(configurations_dir)
mkdir(configurations_dir)
tmp_dir = join(TMP_DIR, str(server_id))
if isdir(tmp_dir):
rmtree(tmp_dir)
mkdir(tmp_dir)
templates_dir = join(TEMPLATE_DIR, str(server_id))
generate(config,
server['funcs_file'],
templates_dir,
tmp_dir,
configurations_dir)
return {'server_id': server_id,
'template_dir': configurations_dir}

1
templates/1/mailname Normal file
View File

@ -0,0 +1 @@
mode_conteneur_actif: %%mode_conteneur_actif

View File

View File

@ -0,0 +1 @@
from .server import Risotto

View File

@ -0,0 +1,8 @@
from risotto.controller import Controller
from risotto.register import register
class Risotto(Controller):
@register('v1.server.list', None)
async def server_list(self):
return [{'server_id': 3, 'servername': 'one', 'serverdescription': 'the first', 'servermodelid': 1}]

View File

@ -0,0 +1 @@
from .servermodel import Risotto

View File

@ -0,0 +1,71 @@
from risotto.controller import Controller
from risotto.register import register
class Risotto(Controller):
@register('v1.servermodel.list', None)
async def servermodel_list(self, sourceid):
return [{'servermodelid': 1,
'servermodelname': 'name1',
'subreleasename': 'name1',
'sourceid': 1,
'servermodeldescription': 'description1'},
{'servermodelid': 2,
'servermodelname': 'name2',
'subreleasename': 'name2',
'sourceid': 2,
'servermodeldescription': 'description2',
'servermodelparentsid': [1]}]
@register('v1.servermodel.describe', None)
async def servermodel_describe(self, inheritance, creolefuncs, servermodelid, schema, conffiles, resolvdepends, probes):
schema = """<?xml version='1.0' encoding='UTF-8'?>
<creole>
<family name="containers">
<family name="container0" doc="test">
<family doc="files" name="files">
<family doc="file0" name="file0">
<variable doc="" multi="False" name="mkdir" type="boolean">
<value>False</value>
</variable>
<variable doc="" multi="False" name="name" type="string">
<value>/etc/mailname</value>
</variable>
<variable doc="" multi="False" name="rm" type="boolean">
<value>False</value>
</variable>
<variable doc="" multi="False" name="source" type="string">
<value>mailname</value>
</variable>
<variable doc="" multi="False" name="activate" type="boolean">
<value>True</value>
</variable>
</family>
</family>
<property>basic</property>
</family>
</family>
<family doc="" name="creole">
<family doc="general" name="general">
<property>normal</property>
<variable doc="No change" multi="False" name="mode_conteneur_actif" type="choice">
<choice type="string">oui</choice>
<choice type="string">non</choice>
<property>mandatory</property>
<property>normal</property>
<value type="string">non</value>
</variable>
<leader doc="master" name="master">
<property>normal</property>
<variable doc="master" multi="True" name="master" type="string"/>
<variable doc="slave1" multi="True" name="slave1" type="string">
<property>normal</property>
</variable>
<variable doc="slave2" multi="True" name="slave2" type="string">
<property>normal</property>
</variable>
</leader>
</family>
<separators/>
</family>
</creole>"""
return {'servermodelid': 1, 'servermodelname': 'name', 'servermodeldescription': 'description', 'subreleasename': 'name', 'sourceid': 1, 'schema': schema, 'creolefuncs': ''}

334
tests/test_config.py Normal file
View File

@ -0,0 +1,334 @@
from importlib import import_module
import pytest
from tiramisu import Storage, list_sessions, delete_session
from risotto.context import Context
from risotto.services import load_services
from risotto.dispatcher import dispatcher
from risotto.config import DATABASE_DIR
def setup_module(module):
load_services(['config'],
validate=False)
config_module = dispatcher.get_service('config')
config_module.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR, name='test')
dispatcher.set_module('server', import_module(f'.server', 'fake_services'))
dispatcher.set_module('servermodel', import_module(f'.servermodel', 'fake_services'))
def setup_function(function):
config_module = dispatcher.get_service('config')
config_module.server = {}
config_module.servermodel = {}
def teardown_function(function):
# delete all sessions
config_module = dispatcher.get_service('config')
for session in list_sessions(storage=config_module.save_storage):
delete_session(storage=config_module.save_storage, session_id=session)
def get_fake_context(module_name):
risotto_context = Context()
risotto_context.username = 'test'
risotto_context.paths.append(f'{module_name}.on_join')
risotto_context.type = None
return risotto_context
@pytest.mark.asyncio
async def test_on_join():
config_module = dispatcher.get_service('config')
assert config_module.servermodel == {}
assert config_module.server == {}
#
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
assert list(config_module.servermodel.keys()) == [1, 2]
assert list(config_module.server) == [3]
assert set(config_module.server[3]) == {'server', 'server_to_deploy', 'funcs_file'}
assert config_module.server[3]['funcs_file'] == 'cache/1.creolefuncs'
@pytest.mark.asyncio
async def test_server_created():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
#
assert list(config_module.server) == [3]
await dispatcher.publish('v1',
'server.created',
fake_context,
server_id=4,
servername='name3',
serverdescription='description3',
servermodelid=2)
assert list(config_module.server) == [3, 4]
assert set(config_module.server[4]) == {'server', 'server_to_deploy', 'funcs_file'}
assert config_module.server[4]['funcs_file'] == 'cache/2.creolefuncs'
@pytest.mark.asyncio
async def test_server_deleted():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
#
assert list(config_module.server) == [3]
await dispatcher.publish('v1',
'server.created',
fake_context,
server_id=4,
servername='name4',
serverdescription='description4',
servermodelid=2)
assert list(config_module.server) == [3, 4]
await dispatcher.publish('v1',
'server.deleted',
fake_context,
server_id=4)
assert list(config_module.server) == [3]
@pytest.mark.asyncio
async def test_servermodel_created():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
#
assert list(config_module.servermodel) == [1, 2]
servermodel = {'servermodeid': 3,
'servermodelname': 'name3'}
await dispatcher.publish('v1',
'servermodel.created',
fake_context,
servermodelid=3,
servermodeldescription='name3',
subreleasename='2.7.0',
sourceid=1,
servermodelname='name3')
assert list(config_module.servermodel) == [1, 2, 3]
assert not list(config_module.servermodel[3].config.parents())
@pytest.mark.asyncio
async def test_servermodel_herited_created():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
#
assert list(config_module.servermodel) == [1, 2]
await dispatcher.publish('v1',
'servermodel.created',
fake_context,
servermodelid=3,
servermodelname='name3',
subreleasename='2.7.0',
sourceid=1,
servermodeldescription='name3',
servermodelparentsid=[1])
assert list(config_module.servermodel) == [1, 2, 3]
assert len(list(config_module.servermodel[3].config.parents())) == 1
@pytest.mark.asyncio
async def test_servermodel_multi_herited_created():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
#
assert list(config_module.servermodel) == [1, 2]
await dispatcher.publish('v1',
'servermodel.created',
fake_context,
servermodelid=3,
servermodelname='name3',
subreleasename='2.7.0',
sourceid=1,
servermodeldescription='name3',
servermodelparentsid=[1, 2])
assert list(config_module.servermodel) == [1, 2, 3]
assert len(list(config_module.servermodel[3].config.parents())) == 2
@pytest.mark.asyncio
async def test_servermodel_updated_not_exists():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
#
assert list(config_module.servermodel) == [1, 2]
await dispatcher.publish('v1',
'servermodel.updated',
fake_context,
servermodelid=3,
servermodelname='name3',
subreleasename='2.7.0',
sourceid=1,
servermodeldescription='name3',
servermodelparentsid=[1, 2])
assert list(config_module.servermodel) == [1, 2, 3]
assert len(list(config_module.servermodel[3].config.parents())) == 2
@pytest.mark.asyncio
async def test_servermodel_updated1():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
#
assert list(config_module.servermodel) == [1, 2]
metaconfig1 = config_module.servermodel[1]
metaconfig2 = config_module.servermodel[2]
mixconfig1 = next(metaconfig1.config.list())
mixconfig2 = next(metaconfig2.config.list())
assert len(list(metaconfig1.config.parents())) == 0
assert len(list(metaconfig2.config.parents())) == 1
assert len(list(mixconfig1.config.list())) == 1
assert len(list(mixconfig2.config.list())) == 0
#
await dispatcher.publish('v1',
'servermodel.updated',
fake_context,
servermodelid=1,
servermodelname='name1-1',
subreleasename='2.7.0',
sourceid=1,
servermodeldescription='name1-1')
assert set(config_module.servermodel) == {1, 2}
assert config_module.servermodel[1].information.get('servermodel_name') == 'name1-1'
assert metaconfig1 != config_module.servermodel[1]
assert metaconfig2 == config_module.servermodel[2]
metaconfig1 = config_module.servermodel[1]
assert mixconfig1 != next(metaconfig1.config.list())
mixconfig1 = next(metaconfig1.config.list())
#
assert len(list(metaconfig1.config.parents())) == 0
assert len(list(metaconfig2.config.parents())) == 1
assert len(list(mixconfig1.config.list())) == 1
assert len(list(mixconfig2.config.list())) == 0
@pytest.mark.asyncio
async def test_servermodel_updated2():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
# create a new servermodel
assert list(config_module.servermodel) == [1, 2]
mixconfig1 = next(config_module.servermodel[1].config.list())
mixconfig2 = next(config_module.servermodel[2].config.list())
assert len(list(mixconfig1.config.list())) == 1
assert len(list(mixconfig2.config.list())) == 0
await dispatcher.publish('v1',
'servermodel.created',
fake_context,
servermodelid=3,
servermodelname='name3',
subreleasename='2.7.0',
sourceid=1,
servermodeldescription='name3',
servermodelparentsid=[1])
assert list(config_module.servermodel) == [1, 2, 3]
assert len(list(config_module.servermodel[3].config.parents())) == 1
assert config_module.servermodel[3].information.get('servermodel_name') == 'name3'
assert len(list(mixconfig1.config.list())) == 2
assert len(list(mixconfig2.config.list())) == 0
#
await dispatcher.publish('v1',
'servermodel.updated',
fake_context,
servermodelid=3,
servermodelname='name3-1',
subreleasename='2.7.0',
sourceid=1,
servermodeldescription='name3-1',
servermodelparentsid=[1, 2])
assert list(config_module.servermodel) == [1, 2, 3]
assert config_module.servermodel[3].information.get('servermodel_name') == 'name3-1'
assert len(list(mixconfig1.config.list())) == 2
assert len(list(mixconfig2.config.list())) == 1
@pytest.mark.asyncio
async def test_servermodel_updated_config():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
#
config_module.servermodel[1].property.read_write()
assert config_module.servermodel[1].option('creole.general.mode_conteneur_actif').value.get() == 'non'
config_module.servermodel[1].option('creole.general.mode_conteneur_actif').value.set('oui')
assert config_module.servermodel[1].option('creole.general.mode_conteneur_actif').value.get() == 'oui'
#
await dispatcher.publish('v1',
'servermodel.updated',
fake_context,
servermodelid=1,
servermodelname='name1-1',
subreleasename='2.7.0',
sourceid=1,
servermodeldescription='name1-1')
assert config_module.servermodel[1].option('creole.general.mode_conteneur_actif').value.get() == 'oui'
@pytest.mark.asyncio
async def test_server_configuration_get():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
#
config_module.server[3]['server_to_deploy'].property.read_write()
assert config_module.server[3]['server_to_deploy'].option('creole.general.mode_conteneur_actif').value.get() == 'non'
config_module.server[3]['server_to_deploy'].option('creole.general.mode_conteneur_actif').value.set('oui')
assert config_module.server[3]['server_to_deploy'].option('creole.general.mode_conteneur_actif').value.get() == 'oui'
assert config_module.server[3]['server'].option('creole.general.mode_conteneur_actif').value.get() == 'non'
#
values = await dispatcher.call('v1',
'config.configuration.server.get',
fake_context,
server_id=3)
configuration = {'configuration':
{'creole.general.mode_conteneur_actif': 'non',
'creole.general.master.master': [],
'creole.general.master.slave1': [],
'creole.general.master.slave2': [],
'containers.container0.files.file0.mkdir': False,
'containers.container0.files.file0.name': '/etc/mailname',
'containers.container0.files.file0.rm': False,
'containers.container0.files.file0.source': 'mailname',
'containers.container0.files.file0.activate': True},
'server_id': 3,
'deployed': True}
assert values == configuration
#
values = await dispatcher.call('v1',
'config.configuration.server.get',
fake_context,
server_id=3,
deployed=False)
configuration['configuration']['creole.general.mode_conteneur_actif'] = 'oui'
configuration['deployed'] = False
assert values == configuration
@pytest.mark.asyncio
async def test_config_deployed():
config_module = dispatcher.get_service('config')
fake_context = get_fake_context('config')
await config_module.on_join(fake_context)
#
config_module.server[3]['server_to_deploy'].property.read_write()
assert config_module.server[3]['server_to_deploy'].option('creole.general.mode_conteneur_actif').value.get() == 'non'
config_module.server[3]['server_to_deploy'].option('creole.general.mode_conteneur_actif').value.set('oui')
assert config_module.server[3]['server_to_deploy'].option('creole.general.mode_conteneur_actif').value.get() == 'oui'
assert config_module.server[3]['server'].option('creole.general.mode_conteneur_actif').value.get() == 'non'
values = await dispatcher.publish('v1',
'config.configuration.server.deploy',
fake_context,
server_id=3)
assert config_module.server[3]['server_to_deploy'].option('creole.general.mode_conteneur_actif').value.get() == 'oui'
assert config_module.server[3]['server'].option('creole.general.mode_conteneur_actif').value.get() == 'oui'

501
tests/test_session.py Normal file
View File

@ -0,0 +1,501 @@
from importlib import import_module
import pytest
from tiramisu import Storage
from risotto.context import Context
from risotto.services import load_services
from risotto.dispatcher import dispatcher
from risotto.config import DATABASE_DIR
from risotto.services.session.storage import storage_server, storage_servermodel
def get_fake_context(module_name):
risotto_context = Context()
risotto_context.username = 'test'
risotto_context.paths.append(f'{module_name}.on_join')
risotto_context.type = None
return risotto_context
def setup_module(module):
load_services(['config', 'session'],
validate=False)
config_module = dispatcher.get_service('config')
config_module.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR, name='test')
dispatcher.set_module('server', import_module(f'.server', 'fake_services'))
dispatcher.set_module('servermodel', import_module(f'.servermodel', 'fake_services'))
def teardown_function(function):
config_module = dispatcher.get_service('session')
storage_server.sessions = {}
storage_servermodel.sessions = {}
@pytest.mark.asyncio
async def test_server_start():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.server:
await config_module.on_join(fake_context)
await dispatcher.call('v1',
'session.server.start',
fake_context,
id=3)
@pytest.mark.asyncio
async def test_server_list():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.server:
await config_module.on_join(fake_context)
assert not await dispatcher.call('v1',
'session.server.list',
fake_context)
await dispatcher.call('v1',
'session.server.start',
fake_context,
id=3)
assert await dispatcher.call('v1',
'session.server.list',
fake_context)
@pytest.mark.asyncio
async def test_server_filter_namespace():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.server:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.server.start',
fake_context,
id=3)
session_id = session['session_id']
namespace = 'containers'
await dispatcher.call('v1',
'session.server.filter',
fake_context,
session_id=session_id,
namespace=namespace)
list_result = await dispatcher.call('v1',
'session.server.list',
fake_context)
assert list_result[0]['namespace'] == namespace
@pytest.mark.asyncio
async def test_server_filter_mode():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.server:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.server.start',
fake_context,
id=3)
session_id = session['session_id']
assert session['mode'] == 'normal'
mode = 'expert'
await dispatcher.call('v1',
'session.server.filter',
fake_context,
session_id=session_id,
mode=mode)
list_result = await dispatcher.call('v1',
'session.server.list',
fake_context)
assert list_result[0]['mode'] == mode
@pytest.mark.asyncio
async def test_server_filter_debug():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.server:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.server.start',
fake_context,
id=3)
session_id = session['session_id']
assert session['debug'] == False
debug = True
await dispatcher.call('v1',
'session.server.filter',
fake_context,
session_id=session_id,
debug=debug)
list_result = await dispatcher.call('v1',
'session.server.list',
fake_context)
assert list_result[0]['debug'] == debug
#FIXME
@pytest.mark.asyncio
async def test_server_filter_get():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.server:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.server.start',
fake_context,
id=3)
session_id = session['session_id']
values = await dispatcher.call('v1',
'session.server.get',
fake_context,
session_id=session_id)
assert values == {'content': {"general.mode_conteneur_actif": "non",
"general.master.master": [],
"general.master.slave1": [],
"general.master.slave2": []},
'debug': False,
'id': 3,
'mode': 'normal',
'namespace': 'creole',
'session_id': session_id,
'timestamp': values['timestamp'],
'username': 'test'}
@pytest.mark.asyncio
async def test_server_filter_get_one_value():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.server:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.server.start',
fake_context,
id=3)
session_id = session['session_id']
values = await dispatcher.call('v1',
'session.server.get',
fake_context,
session_id=session_id,
name="general.mode_conteneur_actif")
assert values == {'content': {"general.mode_conteneur_actif": "non"},
'debug': False,
'id': 3,
'mode': 'normal',
'namespace': 'creole',
'session_id': session_id,
'timestamp': values['timestamp'],
'username': 'test'}
@pytest.mark.asyncio
async def test_server_filter_configure():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.server:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.server.start',
fake_context,
id=3)
session_id = session['session_id']
await dispatcher.call('v1',
'session.server.configure',
fake_context,
session_id=session_id,
action='modify',
name='general.mode_conteneur_actif',
value='oui')
list_result = await dispatcher.call('v1',
'session.server.list',
fake_context)
values = await dispatcher.call('v1',
'session.server.get',
fake_context,
session_id=session_id,
name="general.mode_conteneur_actif")
assert values == {'content': {"general.mode_conteneur_actif": "oui"},
'debug': False,
'id': 3,
'mode': 'normal',
'namespace': 'creole',
'session_id': session_id,
'timestamp': values['timestamp'],
'username': 'test'}
@pytest.mark.asyncio
async def test_server_filter_validate():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.server:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.server.start',
fake_context,
id=3)
session_id = session['session_id']
await dispatcher.call('v1',
'session.server.validate',
fake_context,
session_id=session_id)
@pytest.mark.asyncio
async def test_server_stop():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.server:
await config_module.on_join(fake_context)
assert not await dispatcher.call('v1',
'session.server.list',
fake_context)
start = await dispatcher.call('v1',
'session.server.start',
fake_context,
id=3)
session_id = start['session_id']
assert await dispatcher.call('v1',
'session.server.list',
fake_context)
await dispatcher.call('v1',
'session.server.stop',
fake_context,
session_id=session_id)
assert not await dispatcher.call('v1',
'session.server.list',
fake_context)
# servermodel
@pytest.mark.asyncio
async def test_servermodel_start():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.servermodel:
await config_module.on_join(fake_context)
await dispatcher.call('v1',
'session.servermodel.start',
fake_context,
id=1)
@pytest.mark.asyncio
async def test_servermodel_list():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.servermodel:
await config_module.on_join(fake_context)
assert not await dispatcher.call('v1',
'session.servermodel.list',
fake_context)
await dispatcher.call('v1',
'session.servermodel.start',
fake_context,
id=1)
assert await dispatcher.call('v1',
'session.servermodel.list',
fake_context)
@pytest.mark.asyncio
async def test_servermodel_filter_namespace():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.servermodel:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.servermodel.start',
fake_context,
id=1)
session_id = session['session_id']
namespace = 'containers'
await dispatcher.call('v1',
'session.servermodel.filter',
fake_context,
session_id=session_id,
namespace=namespace)
list_result = await dispatcher.call('v1',
'session.servermodel.list',
fake_context)
assert list_result[0]['namespace'] == namespace
@pytest.mark.asyncio
async def test_servermodel_filter_mode():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.servermodel:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.servermodel.start',
fake_context,
id=1)
session_id = session['session_id']
assert session['mode'] == 'normal'
mode = 'expert'
await dispatcher.call('v1',
'session.servermodel.filter',
fake_context,
session_id=session_id,
mode=mode)
list_result = await dispatcher.call('v1',
'session.servermodel.list',
fake_context)
assert list_result[0]['mode'] == mode
@pytest.mark.asyncio
async def test_servermodel_filter_debug():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.servermodel:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.servermodel.start',
fake_context,
id=1)
session_id = session['session_id']
assert session['debug'] == False
debug = True
await dispatcher.call('v1',
'session.servermodel.filter',
fake_context,
session_id=session_id,
debug=debug)
list_result = await dispatcher.call('v1',
'session.servermodel.list',
fake_context)
assert list_result[0]['debug'] == debug
@pytest.mark.asyncio
async def test_servermodel_filter_get():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.servermodel:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.servermodel.start',
fake_context,
id=1)
session_id = session['session_id']
values = await dispatcher.call('v1',
'session.servermodel.get',
fake_context,
session_id=session_id)
assert values == {'content': {"general.mode_conteneur_actif": "non",
"general.master.master": [],
"general.master.slave1": [],
"general.master.slave2": []},
'debug': False,
'id': 1,
'mode': 'normal',
'namespace': 'creole',
'session_id': session_id,
'timestamp': values['timestamp'],
'username': 'test'}
@pytest.mark.asyncio
async def test_servermodel_filter_get_one_value():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.servermodel:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.servermodel.start',
fake_context,
id=1)
session_id = session['session_id']
values = await dispatcher.call('v1',
'session.servermodel.get',
fake_context,
session_id=session_id,
name="general.mode_conteneur_actif")
assert values == {'content': {"general.mode_conteneur_actif": "non"},
'debug': False,
'id': 1,
'mode': 'normal',
'namespace': 'creole',
'session_id': session_id,
'timestamp': values['timestamp'],
'username': 'test'}
@pytest.mark.asyncio
async def test_servermodel_filter_configure():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.servermodel:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.servermodel.start',
fake_context,
id=1)
session_id = session['session_id']
await dispatcher.call('v1',
'session.servermodel.configure',
fake_context,
session_id=session_id,
action='modify',
name='general.mode_conteneur_actif',
value='oui')
list_result = await dispatcher.call('v1',
'session.servermodel.list',
fake_context)
values = await dispatcher.call('v1',
'session.servermodel.get',
fake_context,
session_id=session_id,
name="general.mode_conteneur_actif")
assert values == {'content': {"general.mode_conteneur_actif": "oui"},
'debug': False,
'id': 1,
'mode': 'normal',
'namespace': 'creole',
'session_id': session_id,
'timestamp': values['timestamp'],
'username': 'test'}
@pytest.mark.asyncio
async def test_servermodel_filter_validate():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.servermodel:
await config_module.on_join(fake_context)
session = await dispatcher.call('v1',
'session.servermodel.start',
fake_context,
id=1)
session_id = session['session_id']
await dispatcher.call('v1',
'session.servermodel.validate',
fake_context,
session_id=session_id)
@pytest.mark.asyncio
async def test_servermodel_stop():
fake_context = get_fake_context('session')
config_module = dispatcher.get_service('config')
if not config_module.servermodel:
await config_module.on_join(fake_context)
assert not await dispatcher.call('v1',
'session.servermodel.list',
fake_context)
start = await dispatcher.call('v1',
'session.servermodel.start',
fake_context,
id=1)
session_id = start['session_id']
assert await dispatcher.call('v1',
'session.servermodel.list',
fake_context)
await dispatcher.call('v1',
'session.servermodel.stop',
fake_context,
session_id=session_id)
assert not await dispatcher.call('v1',
'session.servermodel.list',
fake_context)