Compare commits

..

13 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
a63b41c985 fake servermodel 2019-12-02 10:44:53 +01:00
8a551f85b2 better config integration 2019-12-02 10:29:40 +01:00
63 changed files with 2503 additions and 1132 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:
@ -37,7 +34,12 @@ parameters:
shortarg: v shortarg: v
description: Valeur de la variable. description: Valeur de la variable.
default: null default: null
value_multi:
type: '[]Any'
shortarg: m
description: Valeur de la variable de type multi.
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,20 +1,17 @@
--- ---
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:
sessionid: session_id:
ref: Config.SessionId ref: Config.SessionId
type: String type: String
shortarg: s shortarg: s
@ -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:
@ -37,7 +34,12 @@ parameters:
shortarg: v shortarg: v
description: Valeur de la variable. description: Valeur de la variable.
default: null default: null
value_multi:
type: '[]Any'
shortarg: m
description: Valeur de la variable de type multi.
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,19 +1,16 @@
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:
sessionid: session_id:
ref: Config.SessionId ref: Config.SessionId
type: String type: String
shortarg: s shortarg: s
@ -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:
- sessionid
- 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,9 +1,9 @@
--- ---
title: ConfigSession title: Session
type: object type: object
description: Description de la session. description: Description de la session.
properties: properties:
sessionid: session_id:
type: string type: string
description: ID de la session. description: ID de la session.
ref: Config.SessionId ref: Config.SessionId
@ -27,10 +27,10 @@ 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:
- sessionid - session_id
- id - id
- username - username
- timestamp - timestamp

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,12 @@
from aiohttp.web import run_app from asyncio import get_event_loop
from risotto import get_app from risotto import get_app
if __name__ == '__main__': if __name__ == '__main__':
run_app(get_app()) loop = get_event_loop()
loop.run_until_complete(get_app(loop))
try:
loop.run_forever()
except KeyboardInterrupt:
pass

View File

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

View File

@ -1,5 +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'
CONFIGURATION_DIR = 'configurations'
TEMPLATE_DIR = 'templates'
TMP_DIR = 'tmp'
ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd'

View File

@ -26,3 +26,7 @@ class Controller:
uri, uri,
risotto_context, risotto_context,
**kwargs) **kwargs)
async def on_join(self,
risotto_context):
pass

View File

@ -1,284 +1,44 @@
from tiramisu import Config from tiramisu import Config
from inspect import signature
from traceback import print_exc from traceback import print_exc
from copy import copy from copy import copy
from typing import Dict, Callable from typing import Dict, Callable
from .utils import undefined, _ from .utils import _
from .error import RegistrationError, CallError, NotAllowedError from .error import CallError, NotAllowedError
from .message import get_messages
from .logger import log from .logger import log
from .config import DEBUG from .config import DEBUG
from .context import Context from .context import Context
from . import register
def register(uri: str, class CallDispatcher:
notification: str=undefined): def valid_public_function(self,
""" Decorator to register function to the dispatcher risotto_context: Context,
"""
version, uri = uri.split('.', 1)
def decorator(function):
dispatcher.set_function(version,
uri,
notification,
function)
return decorator
class RegisterDispatcher:
def get_function_args(self, function):
# remove self
first_argument_index = 1
return [param.name for param in list(signature(function).parameters.values())[first_argument_index:]]
def _valid_rpc_params(self, version, uri, function, module_name):
""" parameters function must have strictly all arguments with the correct name
"""
def get_message_args():
# load config
config = Config(self.option)
config.property.read_write()
# set message to the uri name
config.option('message').value.set(uri)
# get message argument
subconfig = config.option(uri)
return set(config.option(uri).value.dict().keys())
def get_function_args():
function_args = self.get_function_args(function)
# risotto_context is a special argument, remove it
if function_args and function_args[0] == 'risotto_context':
function_args = function_args[1:]
return set(function_args)
# get message arguments
message_args = get_message_args()
# get function arguments
function_args = get_function_args()
# compare message arguments with function parameter
# it must not have more or less arguments
if message_args != function_args:
# raise if arguments are not equal
msg = []
missing_function_args = message_args - function_args
if missing_function_args:
msg.append(_(f'missing arguments: {missing_function_args}'))
extra_function_args = function_args - message_args
if extra_function_args:
msg.append(_(f'extra arguments: {extra_function_args}'))
function_name = function.__name__
msg = _(' and ').join(msg)
raise RegistrationError(_(f'error with {module_name}.{function_name} arguments: {msg}'))
def _valid_event_params(self, version, uri, function, module_name):
""" parameters function validation for event messages
"""
def get_message_args():
# load config
config = Config(self.option)
config.property.read_write()
# set message to the uri name
config.option('message').value.set(uri)
# get message argument
subconfig = config.option(uri)
return set(config.option(uri).value.dict().keys())
def get_function_args():
function_args = self.get_function_args(function)
# risotto_context is a special argument, remove it
if function_args[0] == 'risotto_context':
function_args = function_args[1:]
return set(function_args)
# get message arguments
message_args = get_message_args()
# get function arguments
function_args = get_function_args()
# compare message arguments with function parameter
# it can have less arguments but not more
extra_function_args = function_args - message_args
if extra_function_args:
# raise if too many arguments
function_name = function.__name__
msg = _(f'extra arguments: {extra_function_args}')
raise RegistrationError(_(f'error with {module_name}.{function_name} arguments: {msg}'))
def set_function(self,
version: str,
uri: str,
notification: str,
function: Callable):
""" register a function to an URI
URI is a message
"""
# xxx module can only be register with v1.xxxx..... message
module_name = function.__module__.split('.')[-2]
uri_namespace = uri.split('.', 1)[0]
if uri_namespace != module_name:
raise RegistrationError(_(f'cannot registered to {uri} message in module {module_name}'))
# check if message exists
try:
if not Config(self.option).option(uri).option.type() == 'message':
raise RegistrationError(_(f'{uri} is not a valid message'))
except AttributeError:
raise RegistrationError(_(f'the message {uri} not exists'))
# create an uris' version if needed
if version not in self.uris:
self.uris[version] = {}
self.function_names[version] = {}
# valid function is unique per module
if module_name not in self.function_names[version]:
self.function_names[version][module_name] = []
function_name = function.__name__
if function_name in self.function_names[version][module_name]:
raise RegistrationError(_(f'multiple registration of {module_name}.{function_name} function'))
self.function_names[version][module_name].append(function_name)
# True if first argument is the risotto_context
function_args = self.get_function_args(function)
if function_args and function_args[0] == 'risotto_context':
inject_risotto_context = True
function_args.pop(0)
else:
inject_risotto_context = False
if self.messages[uri]['pattern'] == 'rpc':
# check if a RPC function is already register for this uri
if uri in self.uris[version]:
raise RegistrationError(_(f'uri {uri} already registered'))
# valid function's arguments
self._valid_rpc_params(version, uri, function, module_name)
# register this function
dico = {'module': module_name,
'function': function,
'risotto_context': inject_risotto_context}
if notification is undefined:
raise RegistrationError(_('notification is mandatory when registered {uri} with {module_name}.{function_name} even if you set None'))
if notification:
dico['notification'] = notification
self.uris[version][uri] = dico
else:
# if event
# valid function's arguments
self._valid_event_params(version, uri, function, module_name)
# register this function
if uri not in self.uris[version]:
self.uris[version][uri] = []
dico = {'module': module_name,
'function': function,
'arguments': function_args,
'risotto_context': inject_risotto_context}
if notification and notification is not undefined:
dico['notification'] = notification
self.uris[version][uri].append(dico)
def set_module(self, module_name, module):
""" register and instanciate a new module
"""
try:
self.injected_self[module_name] = module.Risotto()
except AttributeError as err:
raise RegistrationError(_(f'unable to register the module {module_name}, this module must have Risotto class'))
def validate(self):
""" check if all messages have a function
"""
# FIXME only v1 supported
missing_messages = set(self.messages.keys()) - set(self.uris['v1'].keys())
if missing_messages:
raise RegistrationError(_(f'missing uri {missing_messages}'))
class Dispatcher(RegisterDispatcher):
""" Manage message (call or publish)
so launch a function when a message is called
"""
def __init__(self):
# reference to instanciate module (to inject self in method): {"module_name": instance_of_module}
self.injected_self = {}
# list of uris with informations: {"v1": {"module_name.xxxxx": yyyyyy}}
self.uris = {}
# all function for a module, to avoid conflict name {"v1": {"module_name": ["function_name"]}}
self.function_names = {}
self.messages, self.option = get_messages()
def new_context(self,
context: Context,
version: str,
uri: str):
new_context = Context()
new_context.paths = copy(context.paths)
new_context.paths.append(version + '.' + uri)
new_context.username = context.username
return new_context
def check_public_function(self,
version: str,
uri: str,
context: Context,
kwargs: Dict, kwargs: Dict,
public_only: bool): public_only: bool):
if public_only and not self.messages[uri]['public']: if public_only and not self.messages[risotto_context.version][risotto_context.message]['public']:
msg = _(f'the message {version}.{uri} is private') msg = _(f'the message {risotto_context.message} is private')
log.error_msg(version, uri, context, kwargs, 'call', msg) log.error_msg(risotto_context, kwargs, msg)
raise NotAllowedError(msg) raise NotAllowedError(msg)
def check_pattern(self,
version: str,
uri: str,
type: str,
context: Context,
kwargs: Dict):
if self.messages[uri]['pattern'] != type:
msg = _(f'{version}.{uri} is not a {type} message')
log.error_msg(version, uri, context, kwargs, 'call', msg)
raise CallError(msg)
def set_config(self,
uri: str,
kwargs: Dict):
""" create a new Config et set values to it
"""
# create a new config
config = Config(self.option)
config.property.read_write()
# set message option
config.option('message').value.set(uri)
# store values
subconfig = config.option(uri)
for key, value in kwargs.items():
try:
subconfig.option(key).value.set(value)
except AttributeError:
raise AttributeError(_(f'unknown parameter "{key}"'))
# check mandatories options
config.property.read_only()
mandatories = list(config.value.mandatory())
if mandatories:
mand = [mand.split('.')[-1] for mand in mandatories]
raise ValueError(_(f'missing parameters: {mand}'))
# return the config
return config
def valid_call_returns(self, def valid_call_returns(self,
function: Callable, risotto_context: Context,
returns: Dict, returns: Dict,
version: str,
uri:str,
context: Context,
kwargs: Dict): kwargs: Dict):
if isinstance(returns, dict): response = self.messages[risotto_context.version][risotto_context.message]['response']
module_name = risotto_context.function.__module__.split('.')[-2]
function_name = risotto_context.function.__name__
if response.impl_get_information('multi'):
if not isinstance(returns, list):
err = _(f'function {module_name}.{function_name} has to return a list')
log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
else:
if not isinstance(returns, dict):
err = _(f'function {module_name}.{function_name} has to return a dict')
log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
returns = [returns] returns = [returns]
if not isinstance(returns, list):
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
err = _(f'function {module_name}.{function_name} has to return a dict or a list')
log.error_msg(version, uri, context, kwargs, 'call', err)
raise CallError(str(err))
response = self.messages[uri]['response']
if response is None: if response is None:
raise Exception('hu?') raise Exception('hu?')
else: else:
@ -289,69 +49,69 @@ class Dispatcher(RegisterDispatcher):
for key, value in ret.items(): for key, value in ret.items():
config.option(key).value.set(value) config.option(key).value.set(value)
except AttributeError: except AttributeError:
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
err = _(f'function {module_name}.{function_name} return the unknown parameter "{key}"') err = _(f'function {module_name}.{function_name} return the unknown parameter "{key}"')
log.error_msg(version, uri, context, kwargs, 'call', err) log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err)) raise CallError(str(err))
except ValueError: except ValueError:
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
err = _(f'function {module_name}.{function_name} return the parameter "{key}" with an unvalid value "{value}"') err = _(f'function {module_name}.{function_name} return the parameter "{key}" with an unvalid value "{value}"')
log.error_msg(version, uri, context, kwargs, 'call', err) log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err)) raise CallError(str(err))
config.property.read_only() config.property.read_only()
mandatories = list(config.value.mandatory())
if mandatories:
mand = [mand.split('.')[-1] for mand in mandatories]
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:
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
err = _(f'function {module_name}.{function_name} return an invalid response {err}') err = _(f'function {module_name}.{function_name} return an invalid response {err}')
log.error_msg(version, uri, context, kwargs, 'call', err) log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err)) raise CallError(str(err))
async def call(self,
async def call(self, version, uri, risotto_context, public_only=False, **kwargs): version: str,
message: str,
old_risotto_context: Context,
public_only: bool=False,
**kwargs):
""" execute the function associate with specified uri """ execute the function associate with specified uri
arguments are validate before arguments are validate before
""" """
new_context = self.new_context(risotto_context, risotto_context = self.build_new_context(old_risotto_context,
version, version,
uri) message,
self.check_public_function(version, 'rpc')
uri, self.valid_public_function(risotto_context,
new_context,
kwargs, kwargs,
public_only) public_only)
self.check_pattern(version, self.check_message_type(risotto_context,
uri, kwargs)
'rpc',
new_context,
kwargs)
try: try:
config = self.set_config(uri, tiramisu_config = self.load_kwargs_to_config(risotto_context,
kwargs) kwargs)
obj = self.uris[version][uri] obj = self.messages[version][message]
kw = config.option(uri).value.dict() kw = tiramisu_config.option(message).value.dict()
risotto_context.function = obj['function']
if obj['risotto_context']: if obj['risotto_context']:
kw['risotto_context'] = new_context kw['risotto_context'] = risotto_context
returns = await obj['function'](self.injected_self[obj['module']], **kw) returns = await risotto_context.function(self.injected_self[obj['module']], **kw)
except CallError as err: except CallError as err:
raise err raise err
except Exception as err: except Exception as err:
if DEBUG: if DEBUG:
print_exc() print_exc()
log.error_msg(version, uri, new_context, kwargs, 'call', err) log.error_msg(risotto_context,
kwargs,
err)
raise CallError(str(err)) raise CallError(str(err))
# valid returns # valid returns
self.valid_call_returns(obj['function'], self.valid_call_returns(risotto_context,
returns, returns,
version,
uri,
new_context,
kwargs) kwargs)
# log the success # log the success
log.info_msg(version, uri, new_context, kwargs, 'call', _(f'returns {returns}')) log.info_msg(risotto_context,
kwargs,
_(f'returns {returns}'))
# notification # notification
if obj.get('notification'): if obj.get('notification'):
notif_version, notif_message = obj['notification'].split('.', 1) notif_version, notif_message = obj['notification'].split('.', 1)
@ -362,34 +122,34 @@ class Dispatcher(RegisterDispatcher):
for ret in send_returns: for ret in send_returns:
await self.publish(notif_version, await self.publish(notif_version,
notif_message, notif_message,
new_context, risotto_context,
**ret) **ret)
return returns return returns
async def publish(self, version, uri, risotto_context, public_only=False, **kwargs):
new_context = self.new_context(risotto_context, class PublishDispatcher:
version, async def publish(self, version, message, old_risotto_context, public_only=False, **kwargs):
uri) risotto_context = self.build_new_context(old_risotto_context,
self.check_pattern(version, version,
uri, message,
'event', 'event')
new_context, self.check_message_type(risotto_context,
kwargs) kwargs)
try: try:
config = self.set_config(uri, config = self.load_kwargs_to_config(risotto_context,
kwargs) kwargs)
config_arguments = config.option(uri).value.dict() config_arguments = config.option(message).value.dict()
except CallError as err: except CallError as err:
return return
except Exception as err: except Exception as err:
# if there is a problem with arguments, just send an error et do nothing # if there is a problem with arguments, just send an error et do nothing
if DEBUG: if DEBUG:
print_exc() print_exc()
log.error_msg(version, uri, new_context, kwargs, 'publish', err) log.error_msg(risotto_context, kwargs, err)
return return
# config is ok, so publish the message # config is ok, so publish the message
for function_obj in self.uris[version][uri]: 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__
@ -401,26 +161,87 @@ class Dispatcher(RegisterDispatcher):
if key in function_obj['arguments']: if key in function_obj['arguments']:
kw[key] = value kw[key] = value
if function_obj['risotto_context']: if function_obj['risotto_context']:
kw['risotto_context'] = new_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(version, uri, new_context, kwargs, 'publish', err, info_msg) log.error_msg(risotto_context, kwargs, err, info_msg)
continue
else: else:
module_name = function.__module__.split('.')[-2] log.info_msg(risotto_context, kwargs, info_msg)
function_name = function.__name__ # notification
log.info_msg(version, uri, new_context, kwargs,'publish', info_msg) if function_obj.get('notification'):
notif_version, notif_message = function_obj['notification'].split('.', 1)
await self.publish(notif_version,
notif_message,
risotto_context,
**returns)
# notification
if obj.get('notification'): class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher):
notif_version, notif_message = obj['notification'].split('.', 1) """ Manage message (call or publish)
await self.publish(notif_version, so launch a function when a message is called
notif_message, """
new_context, def build_new_context(self,
**returns) old_risotto_context: Context,
version: str,
message: str,
type: str):
""" This is a new call or a new publish, so create a new context
"""
uri = version + '.' + message
risotto_context = Context()
risotto_context.username = old_risotto_context.username
risotto_context.paths = copy(old_risotto_context.paths)
risotto_context.paths.append(uri)
risotto_context.uri = uri
risotto_context.type = type
risotto_context.message = message
risotto_context.version = version
return risotto_context
def check_message_type(self,
risotto_context: Context,
kwargs: Dict):
if self.messages[risotto_context.version][risotto_context.message]['pattern'] != risotto_context.type:
msg = _(f'{risotto_context.uri} is not a {risotto_context.type} message')
log.error_msg(risotto_context, kwargs, msg)
raise CallError(msg)
def load_kwargs_to_config(self,
risotto_context: Context,
kwargs: Dict):
""" create a new Config et set values to it
"""
# create a new config
config = Config(self.option)
config.property.read_write()
# set message's option
config.option('message').value.set(risotto_context.message)
# store values
subconfig = config.option(risotto_context.message)
for key, value in kwargs.items():
try:
subconfig.option(key).value.set(value)
except AttributeError:
if DEBUG:
print_exc()
raise AttributeError(_(f'unknown parameter "{key}"'))
# check mandatories options
config.property.read_only()
mandatories = list(config.value.mandatory())
if mandatories:
mand = [mand.split('.')[-1] for mand in mandatories]
raise ValueError(_(f'missing parameters: {mand}'))
# return the config
return config
def get_service(self,
name: str):
return self.injected_self[name]
dispatcher = Dispatcher() dispatcher = Dispatcher()
register.dispatcher = dispatcher

View File

@ -1,25 +1,73 @@
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
from .error import CallError, NotAllowedError 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 from .config import DEBUG, HTTP_PORT
from traceback import print_exc from .services import load_services
def create_context(request):
risotto_context = Context()
risotto_context.username = request.match_info.get('username', "Anonymous")
return risotto_context
def register(version: str,
path: str):
""" Decorator to register function to the http route
"""
def decorator(function):
if path in extra_routes:
raise RegistrationError(f'the route {path} is already registered')
extra_routes[path] = {'function': function,
'version': version}
return decorator
class extra_route_handler:
async def __new__(cls, request):
kwargs = dict(request.match_info)
kwargs['request'] = request
kwargs['risotto_context'] = create_context(request)
kwargs['risotto_context'].version = cls.version
kwargs['risotto_context'].paths.append(cls.path)
kwargs['risotto_context'].type = 'http_get'
function_name = cls.function.__module__
# if not 'api' function
if function_name != 'risotto.http':
module_name = function_name.split('.')[-2]
kwargs['self'] = dispatcher.injected_self[module_name]
try:
returns = await cls.function(**kwargs)
except NotAllowedError as err:
raise HTTPNotFound(reason=str(err))
except CallError as err:
raise HTTPBadRequest(reason=str(err))
except Exception as err:
if DEBUG:
print_exc()
raise HTTPInternalServerError(reason=str(err))
log.info_msg(kwargs['risotto_context'],
dict(request.match_info))
return Response(text=dumps(returns))
async def handle(request): async def handle(request):
version, uri = request.match_info.get_info()['path'].rsplit('/', 2)[-2:] version, uri = request.match_info.get_info()['path'].rsplit('/', 2)[-2:]
context = Context() risotto_context = create_context(request)
context.username = request.match_info.get('username', "Anonymous")
kwargs = await request.json() kwargs = await request.json()
try: try:
text = await dispatcher.call(version, text = await dispatcher.call(version,
uri, uri,
context, risotto_context,
public_only=True, public_only=True,
**kwargs) **kwargs)
except NotAllowedError as err: except NotAllowedError as err:
@ -33,45 +81,53 @@ async def handle(request):
return Response(text=dumps({'response': text})) return Response(text=dumps({'response': text}))
async def api(request): async def api(request, risotto_context):
context = Context()
context.username = request.match_info.get('username', "Anonymous")
path = request.match_info.get_info()['path']
if path.endswith('/'):
path = path[:-1]
version = path.rsplit('/', 1)[-1]
log.info_msg(version, None, context, {}, None, _(f'get {version} API'))
global tiramisu global tiramisu
if not tiramisu: if not tiramisu:
config = Config(get_messages(load_shortarg=True, config = Config(get_messages(load_shortarg=True,
only_public=True)[1]) only_public=True)[1])
config.property.read_write() config.property.read_write()
tiramisu = config.option.dict(remotable='none') tiramisu = config.option.dict(remotable='none')
return Response(text=dumps(tiramisu)) return tiramisu
def get_app(): extra_routes = {'': {'function': api,
'version': 'v1'}}
async def get_app(loop):
""" build all routes """ build all routes
""" """
app = Application() global extra_routes
load_services()
app = Application(loop=loop)
routes = [] routes = []
uris = list(dispatcher.uris.items()) for version, messages in dispatcher.messages.items():
uris.sort()
for version, uris in dispatcher.uris.items():
print() print()
print(_('======== Registered messages ========')) print(_('======== Registered messages ========'))
for uri in uris: for message in messages:
web_uri = f'/api/{version}/{uri}' web_message = f'/api/{version}/{message}'
if dispatcher.messages[uri]['public']: if dispatcher.messages[version][message]['public']:
print(f' - {web_uri}') print(f' - {web_message}')
else: else:
pattern = dispatcher.messages[uri]['pattern'] pattern = dispatcher.messages[version][message]['pattern']
print(f' - {web_uri} (private {pattern})') print(f' - {web_message} (private {pattern})')
routes.append(post(web_uri, handle)) routes.append(post(web_message, handle))
routes.append(get(f'/api/{version}', api))
print() print()
print(_('======== Registered extra routes ========'))
for path, extra in extra_routes.items():
version = extra['version']
path = f'/api/{version}{path}'
extra['path'] = path
extra_handler = type(path, (extra_route_handler,), extra)
routes.append(get(path, extra_handler))
print(f' - {path} (http_get)')
# routes.append(get(f'/api/{version}', api))
print()
del extra_routes
app.add_routes(routes) app.add_routes(routes)
return app await dispatcher.on_join()
return await loop.create_server(app.make_handler(), '*', HTTP_PORT)
tiramisu = None tiramisu = None

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:
@ -8,40 +9,39 @@ class Logger:
FIXME should add event to a database FIXME should add event to a database
""" """
def _get_message_paths(self, def _get_message_paths(self,
risotto_context: Context, risotto_context: Context):
type: str):
paths = risotto_context.paths paths = risotto_context.paths
if len(paths) == 1: if risotto_context.type:
paths_msg = f' messages {type}ed: {paths[0]}' paths_msg = f' {risotto_context.type} '
else: else:
paths_msg = f' sub-messages {type}ed: ' paths_msg = ' '
if len(paths) == 1:
paths_msg += f'message: {paths[0]}'
else:
paths_msg += f'sub-messages: '
paths_msg += ' > '.join(paths) paths_msg += ' > '.join(paths)
paths_msg += ':'
return paths_msg return paths_msg
def error_msg(self, def error_msg(self,
version: 'str',
message: 'str',
risotto_context: Context, risotto_context: Context,
arguments, arguments,
type: str,
error: str, error: str,
msg: str=''): msg: str=''):
""" send message when an error append """ send message when an error append
""" """
paths_msg = self._get_message_paths(risotto_context, type) paths_msg = self._get_message_paths(risotto_context)
print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}" {msg})')) # if DEBUG:
print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}": {msg})'))
def info_msg(self, def info_msg(self,
version: 'str',
message: 'str',
risotto_context: Context, risotto_context: Context,
arguments: Dict, arguments: Dict,
type: str,
msg: str=''): msg: str=''):
""" send message with common information """ send message with common information
""" """
if risotto_context.paths: if risotto_context.paths:
paths_msg = self._get_message_paths(risotto_context, type) paths_msg = self._get_message_paths(risotto_context)
else: else:
paths_msg = '' paths_msg = ''
tmsg = _(f'{risotto_context.username}: INFO:{paths_msg}') tmsg = _(f'{risotto_context.username}: INFO:{paths_msg}')
@ -50,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

@ -4,7 +4,7 @@ from glob import glob
from tiramisu import StrOption, IntOption, BoolOption, ChoiceOption, OptionDescription, SymLinkOption, \ from tiramisu import StrOption, IntOption, BoolOption, ChoiceOption, OptionDescription, SymLinkOption, \
Config, Calculation, Params, ParamOption, ParamValue, calc_value, calc_value_property_help, \ Config, Calculation, Params, ParamOption, ParamValue, calc_value, calc_value_property_help, \
groups groups, Option
from yaml import load, SafeLoader from yaml import load, SafeLoader
from os import listdir from os import listdir
@ -16,6 +16,25 @@ from ..utils import _
groups.addgroup('message') groups.addgroup('message')
class DictOption(Option):
__slots__ = tuple()
_type = 'dict'
_display_name = _('dict')
def validate(self, value):
if not isinstance(value, dict):
raise ValueError()
class AnyOption(Option):
__slots__ = tuple()
_type = 'any value'
_display_name = _('any')
def validate(self, value):
pass
class MessageDefinition: class MessageDefinition:
""" """
A MessageDefinition is a representation of a message in the Zephir application messaging context A MessageDefinition is a representation of a message in the Zephir application messaging context
@ -130,11 +149,13 @@ class ResponseDefinition:
'type', 'type',
'ref', 'ref',
'parameters', 'parameters',
'required') 'required',
'multi')
def __init__(self, responses): def __init__(self, responses):
self.ref = None self.ref = None
self.parameters = None self.parameters = None
self.multi = False
self.required = [] self.required = []
for key, value in responses.items(): for key, value in responses.items():
if key in ['parameters', 'required']: if key in ['parameters', 'required']:
@ -142,6 +163,7 @@ class ResponseDefinition:
elif key == 'type': elif key == 'type':
if value.startswith('[]'): if value.startswith('[]'):
tvalue = value[2:] tvalue = value[2:]
self.multi = True
else: else:
tvalue = value tvalue = value
if tvalue in customtypes: if tvalue in customtypes:
@ -414,8 +436,15 @@ def _get_option(name,
if hasattr(arg, 'default'): if hasattr(arg, 'default'):
kwargs['default'] = arg.default kwargs['default'] = arg.default
type_ = arg.type type_ = arg.type
if type_ == 'Dict' or 'String' in type_ or 'Any' in type_: if type_.startswith('[]'):
kwargs['multi'] = True
type_ = type_[2:]
if type_ == 'Dict':
return DictOption(**kwargs)
elif type_ == 'String':
return StrOption(**kwargs) return StrOption(**kwargs)
elif type_ == 'Any':
return AnyOption(**kwargs)
elif 'Number' in type_ or type_ == 'ID' or type_ == 'Integer': elif 'Number' in type_ or type_ == 'ID' or type_ == 'Integer':
return IntOption(**kwargs) return IntOption(**kwargs)
elif type_ == 'Boolean': elif type_ == 'Boolean':
@ -440,7 +469,7 @@ def _parse_args(message_def,
for name, arg in new_options.items(): for name, arg in new_options.items():
current_opt = _get_option(name, arg, file_path, select_option, optiondescription) current_opt = _get_option(name, arg, file_path, select_option, optiondescription)
options.append(current_opt) options.append(current_opt)
if arg.shortarg and load_shortarg: if hasattr(arg, 'shortarg') and arg.shortarg and load_shortarg:
options.append(SymLinkOption(arg.shortarg, current_opt)) options.append(SymLinkOption(arg.shortarg, current_opt))
@ -473,6 +502,7 @@ def _parse_responses(message_def,
option = {'String': StrOption, option = {'String': StrOption,
'Number': IntOption, 'Number': IntOption,
'Boolean': BoolOption, 'Boolean': BoolOption,
'Dict': DictOption,
# FIXME # FIXME
'File': StrOption}.get(type_) 'File': StrOption}.get(type_)
if not option: if not option:
@ -482,9 +512,11 @@ def _parse_responses(message_def,
else: else:
kwargs['properties'] = ('mandatory',) kwargs['properties'] = ('mandatory',)
options.append(option(**kwargs)) options.append(option(**kwargs))
return OptionDescription(message_def.uri, od = OptionDescription(message_def.uri,
message_def.response.description, message_def.response.description,
options) options)
od.impl_set_information('multi', message_def.response.multi)
return od
def _getoptions_from_yml(message_def, def _getoptions_from_yml(message_def,

244
src/risotto/register.py Normal file
View File

@ -0,0 +1,244 @@
from tiramisu import Config
from inspect import signature
from typing import Callable, Optional
from .utils import undefined, _
from .error import RegistrationError
from .message import get_messages
from .context import Context
from .config import INTERNAL_USER
def register(uris: str,
notification: str=undefined):
""" Decorator to register function to the dispatcher
"""
if not isinstance(uris, list):
uris = [uris]
def decorator(function):
for uri in uris:
version, message = uri.split('.', 1)
dispatcher.set_function(version,
message,
notification,
function)
return decorator
class RegisterDispatcher:
def __init__(self):
# reference to instanciate module (to inject self in method): {"module_name": instance_of_module}
self.injected_self = {}
# list of uris with informations: {"v1": {"module_name.xxxxx": yyyyyy}}
self.messages = {}
# load tiramisu objects
messages, self.option = get_messages()
#FIXME
version = 'v1'
self.messages[version] = {}
for tiramisu_message, obj in messages.items():
self.messages[version][tiramisu_message] = obj
def get_function_args(self,
function: Callable):
# remove self
first_argument_index = 1
return [param.name for param in list(signature(function).parameters.values())[first_argument_index:]]
def valid_rpc_params(self,
version: str,
message: str,
function: Callable,
module_name: str):
""" parameters function must have strictly all arguments with the correct name
"""
def get_message_args():
# load config
config = Config(self.option)
config.property.read_write()
# set message to the uri name
config.option('message').value.set(message)
# get message argument
subconfig = config.option(message)
return set(config.option(message).value.dict().keys())
def get_function_args():
function_args = self.get_function_args(function)
# risotto_context is a special argument, remove it
if function_args and function_args[0] == 'risotto_context':
function_args = function_args[1:]
return set(function_args)
# get message arguments
message_args = get_message_args()
# get function arguments
function_args = get_function_args()
# compare message arguments with function parameter
# it must not have more or less arguments
if message_args != function_args:
# raise if arguments are not equal
msg = []
missing_function_args = message_args - function_args
if missing_function_args:
msg.append(_(f'missing arguments: {missing_function_args}'))
extra_function_args = function_args - message_args
if extra_function_args:
msg.append(_(f'extra arguments: {extra_function_args}'))
function_name = function.__name__
msg = _(' and ').join(msg)
raise RegistrationError(_(f'error with {module_name}.{function_name} arguments: {msg}'))
def valid_event_params(self,
version: str,
message: str,
function: Callable,
module_name: str):
""" parameters function validation for event messages
"""
def get_message_args():
# load config
config = Config(self.option)
config.property.read_write()
# set message to the message name
config.option('message').value.set(message)
# get message argument
subconfig = config.option(message)
return set(config.option(message).value.dict().keys())
def get_function_args():
function_args = self.get_function_args(function)
# risotto_context is a special argument, remove it
if function_args[0] == 'risotto_context':
function_args = function_args[1:]
return set(function_args)
# get message arguments
message_args = get_message_args()
# get function arguments
function_args = get_function_args()
# compare message arguments with function parameter
# it can have less arguments but not more
extra_function_args = function_args - message_args
if extra_function_args:
# raise if too many arguments
function_name = function.__name__
msg = _(f'extra arguments: {extra_function_args}')
raise RegistrationError(_(f'error with {module_name}.{function_name} arguments: {msg}'))
def set_function(self,
version: str,
message: str,
notification: str,
function: Callable):
""" register a function to an URI
URI is a message
"""
# check if message exists
if message not in self.messages[version]:
raise RegistrationError(_(f'the message {message} not exists'))
# xxx module can only be register with v1.xxxx..... message
module_name = function.__module__.split('.')[-2]
message_namespace = message.split('.', 1)[0]
if self.messages[version][message]['pattern'] == 'rpc' and message_namespace != module_name:
raise RegistrationError(_(f'cannot registered the "{message}" message in module "{module_name}"'))
# True if first argument is the risotto_context
function_args = self.get_function_args(function)
if function_args and function_args[0] == 'risotto_context':
inject_risotto_context = True
function_args.pop(0)
else:
inject_risotto_context = False
# check if already register
if 'function' in self.messages[version][message]:
raise RegistrationError(_(f'uri {version}.{message} already registered'))
# valid function's arguments
if self.messages[version][message]['pattern'] == 'rpc':
if notification is undefined:
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
else:
valid_params = self.valid_event_params
valid_params(version,
message,
function,
module_name)
# register
if self.messages[version][message]['pattern'] == 'rpc':
register = self.register_rpc
else:
register = self.register_event
register(version,
message,
module_name,
function,
function_args,
inject_risotto_context,
notification)
def register_rpc(self,
version: str,
message: str,
module_name: str,
function: Callable,
function_args: list,
inject_risotto_context: bool,
notification: Optional[str]):
self.messages[version][message]['module'] = module_name
self.messages[version][message]['function'] = function
self.messages[version][message]['arguments'] = function_args
self.messages[version][message]['risotto_context'] = inject_risotto_context
if notification:
self.messages[version][message]['notification'] = notification
def register_event(self,
version: str,
message: str,
module_name: str,
function: Callable,
function_args: list,
inject_risotto_context: bool,
notification: Optional[str]):
if 'functions' not in self.messages[version][message]:
self.messages[version][message]['functions'] = []
dico = {'module': module_name,
'function': function,
'arguments': function_args,
'risotto_context': inject_risotto_context}
if notification and notification is not undefined:
dico['notification'] = notification
self.messages[version][message]['functions'].append(dico)
def set_module(self, module_name, module):
""" register and instanciate a new module
"""
try:
self.injected_self[module_name] = module.Risotto()
except AttributeError as err:
raise RegistrationError(_(f'unable to register the module {module_name}, this module must have Risotto class'))
def validate(self):
""" check if all messages have a function
"""
missing_messages = []
for version, messages in self.messages.items():
for message, message_obj in messages.items():
if not 'functions' in message_obj and not 'function' in message_obj:
missing_messages.append(message)
if missing_messages:
raise RegistrationError(_(f'missing uri {missing_messages}'))
async def on_join(self):
for module_name, module in self.injected_self.items():
risotto_context = Context()
risotto_context.username = INTERNAL_USER
risotto_context.paths.append(f'{module_name}.on_join')
risotto_context.type = None
await module.on_join(risotto_context)

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,300 +1,428 @@
#!/usr/bin/env python3 from lxml.etree import parse
#import logging from io import BytesIO
#from lxml.etree import parse from os import unlink
#from io import StringIO from os.path import isdir, isfile, join
#from autobahn.wamp.exception import ApplicationError from traceback import print_exc
#import asyncio from typing import Dict, List
from tiramisu import Storage, MixConfig, delete_session
#from tiramisu.error import PropertiesOptionError from tiramisu import Storage, delete_session, MetaConfig, MixConfig
# from rougail import load as rougail_load
#from os import urandom, unlink
#from os.path import isfile, join
#from binascii import hexlify
#from json import dumps, loads
#from aiohttp.web import HTTPForbidden
#from creole.loader import PopulateTiramisuObjects
#from zephir.controller import ZephirCommonController, run
#from zephir.http import register as register_http
#from zephir.wamp import register as register_wamp
#from zephir.config import DEBUG
##from eolegenconfig import webapi
#from eolegenconfig.lib import storage
#from eolegenconfig import lib
#from zephir.i18n import _
from ...controller import Controller from ...controller import Controller
from ...dispatcher import register from ...register import register
from ...config import ROOT_CACHE_DIR, DATABASE_DIR from ...config import ROOT_CACHE_DIR, DATABASE_DIR, DEBUG, ROUGAIL_DTD_PATH
from ...context import Context from ...context import Context
from .storage import storage_server, storage_servermodel from ...utils import _
from ...error import CallError, RegistrationError
from ...logger import log
class Risotto(Controller): class Risotto(Controller):
servermodel = {} servermodel = {}
# FIXME : should be renamed to probe
server = {} server = {}
def __init__(self, *args, **kwargs): def __init__(self) -> None:
# add root and statics for dirname in [ROOT_CACHE_DIR, DATABASE_DIR]:
# FIXME if not isdir(dirname):
#default_storage.setting(engine='sqlite3', dir_database='/srv/database') 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__(*args, **kwargs)
def valid_user(self, sessionid, risotto_context): async def on_join(self,
username = risotto_context.username risotto_context: Context) -> None:
if username != storage.get_username(sessionid): """ pre-load servermodel and server
raise HTTPForbidden() """
await self.load_servermodels(risotto_context)
await self.load_servers(risotto_context)
async def onJoin(self, *args, **kwargs): async def load_servermodels(self,
await super().onJoin(*args, **kwargs) risotto_context: Context) -> None:
await asyncio.sleep(1) """ load all available servermodels
await self.load_servermodels() """
await self.load_servers() log.info_msg(risotto_context,
None,
'Load servermodels')
servermodels = await self.call('v1.servermodel.list',
risotto_context)
async def load_servermodels(self): # load each servermodels
print('Load servermodels')
try:
servermodels = await self.call('v1.servermodel.list')
except ApplicationError as err:
print(_('cannot load servermodel list: {}').format(str(err)))
return
for servermodel in servermodels: for servermodel in servermodels:
try: try:
await self.load_servermodel(servermodel['servermodelid'], servermodel['servermodelname']) await self.load_servermodel(risotto_context,
except ApplicationError as err: servermodel['servermodelid'],
if DEBUG: servermodel['servermodelname'])
print('Error, cannot load servermodel {}: {}'.format(servermodel['servermodelname'], err)) except CallError as err:
pass
# do link to this servermodel
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'], servermodel['servermodelid'], servermodelparentid) self.servermodel_legacy(risotto_context,
servermodel['servermodelname'],
servermodel['servermodelid'],
servermodelparentid)
def get_funcs_filename(self,
servermodelid: int):
return join(ROOT_CACHE_DIR, str(servermodelid)+".creolefuncs")
async def load_servermodel(self, servermodelid, servermodelname):
logging.getLogger().setLevel(logging.INFO) async def load_servermodel(self,
risotto_context: Context,
servermodelid: int,
servermodelname: str) -> None:
""" Loads a servermodel
"""
cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml") cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml")
creolefunc_file = join(ROOT_CACHE_DIR, str(servermodelid)+".creolefuncs") funcs_file = self.get_funcs_filename(servermodelid)
print('Load servermodel {} ({})'.format(servermodelname, servermodelid)) log.info_msg(risotto_context,
None,
f'Load servermodel {servermodelname} ({servermodelid})')
# use file in cache if found, otherwise retrieve it in servermodel context
if isfile(cache_file): if isfile(cache_file):
fileio = open(cache_file) fileio = open(cache_file)
else: else:
servermodel = await self.call('v1.servermodel.describe', servermodel = await self.call('v1.servermodel.describe',
risotto_context,
servermodelid=servermodelid, servermodelid=servermodelid,
inheritance=False, inheritance=False,
resolvdepends=False, resolvdepends=False,
schema=True, schema=True,
creolefuncs=True) creolefuncs=True)
fileio = StringIO() fileio = BytesIO()
fileio.write(servermodel['schema']) fileio.write(servermodel['schema'].encode())
fileio.seek(0) fileio.seek(0)
with open(cache_file, 'w') as cache: with open(cache_file, 'w') as cache:
cache.write(servermodel['schema']) cache.write(servermodel['schema'])
with open(creolefunc_file, 'w') as cache: with open(funcs_file, 'w') as cache:
cache.write(servermodel['creolefuncs']) cache.write(servermodel['creolefuncs'])
del servermodel del servermodel
# loads tiramisu config and store it
xmlroot = parse(fileio).getroot() xmlroot = parse(fileio).getroot()
tiramisu_objects = PopulateTiramisuObjects() self.servermodel[servermodelid] = self.build_metaconfig(servermodelid,
tiramisu_objects.parse_dtd('/srv/src/creole/data/creole.dtd') servermodelname,
tiramisu_objects.make_tiramisu_objects(xmlroot, creolefunc_file) xmlroot,
config = tiramisu_objects.build(persistent=True, funcs_file)
session_id='v_{}'.format(servermodelid),
meta_config=True)
config.owner.set('v_{}'.format(servermodelname)) def build_metaconfig(self,
config.information.set('servermodel_id', servermodelid) servermodelid: int,
config.information.set('servermodel_name', servermodelname) servermodelname: str,
xmlroot: str,
funcs_file: str) -> MetaConfig:
""" Build metaconfig for a servermodel
"""
# build tiramisu's session ID
session_id = f'v_{servermodelid}'
optiondescription = rougail_load(xmlroot,
ROUGAIL_DTD_PATH,
funcs_file)
self.servermodel[servermodelid] = config # build servermodel metaconfig (v_xxx.m_v_xxx)
metaconfig = MetaConfig([],
optiondescription=optiondescription,
persistent=True,
session_id=session_id,
storage=self.save_storage)
mixconfig = MixConfig(children=[],
optiondescription=optiondescription,
persistent=True,
session_id='m_' + session_id,
storage=self.save_storage)
metaconfig.config.add(mixconfig)
def servermodel_legacy(self, servermodel_name, servermodel_id, servermodel_parent_id): # change default rights
ro_origin = metaconfig.property.getdefault('read_only', 'append')
ro_append = frozenset(ro_origin - {'force_store_value'})
rw_origin = metaconfig.property.getdefault('read_write', 'append')
rw_append = frozenset(rw_origin - {'force_store_value'})
metaconfig.property.setdefault(ro_append, 'read_only', 'append')
metaconfig.property.setdefault(rw_append, 'read_write', 'append')
metaconfig.property.read_only()
metaconfig.permissive.add('basic')
metaconfig.permissive.add('normal')
metaconfig.permissive.add('expert')
# set informtion and owner
metaconfig.owner.set('v_{}'.format(servermodelname))
metaconfig.information.set('servermodel_id', servermodelid)
metaconfig.information.set('servermodel_name', servermodelname)
# return configuration
return metaconfig
def servermodel_legacy(self,
risotto_context: Context,
servermodel_name: str,
servermodel_id: int,
servermodel_parent_id: int) -> None:
""" Make link between parent and children
"""
if servermodel_parent_id is None: if servermodel_parent_id is None:
return return
if not self.servermodel.get(servermodel_parent_id): if not self.servermodel.get(servermodel_parent_id):
if DEBUG: if DEBUG:
print(f'Servermodel with id {servermodel_parent_id} not loaded, skipping legacy for servermodel {servermodel_name} ({servermodel_id})') msg = _(f'Servermodel with id {servermodel_parent_id} not loaded, skipping legacy for servermodel {servermodel_name} ({servermodel_id})')
log.error_msg(risotto_context,
None,
msg)
return return
servermodel_parent = self.servermodel[servermodel_parent_id] servermodel_parent = self.servermodel[servermodel_parent_id]
servermodel_parent_name = servermodel_parent.information.get('servermodel_name') servermodel_parent_name = servermodel_parent.information.get('servermodel_name')
if DEBUG: if DEBUG:
print(f'Create legacy of servermodel {servermodel_name} ({servermodel_id}) with parent {servermodel_parent_name} ({servermodel_parent_id})') msg = _(f'Create legacy of servermodel {servermodel_name} ({servermodel_id}) with parent {servermodel_parent_name} ({servermodel_parent_id})')
log.info_msg(risotto_context,
None,
msg)
# do link
mix = servermodel_parent.config.get('m_v_' + str(servermodel_parent_id)) mix = servermodel_parent.config.get('m_v_' + str(servermodel_parent_id))
try: try:
mix.config.add(self.servermodel[servermodel_id]) mix.config.add(self.servermodel[servermodel_id])
except Exception as err: except Exception as err:
if DEBUG: if DEBUG:
print(str(err)) log.error_msg(risotto_context,
None,
str(err))
async def load_servers(self,
async def load_servers(self): risotto_context: Context) -> None:
print('Load servers') """ load all available servers
try: """
risotto_context = Context() log.info_msg(risotto_context,
risotto_context.username = 'root' None,
servers = await self.call('v1.server.list', risotto_context) f'Load servers')
except ApplicationError as err: # get all servers
print(_('cannot load server list: {}').format(str(err))) servers = await self.call('v1.server.list',
return risotto_context)
# loads servers
for server in servers: for server in servers:
try: try:
self.load_server(server['serverid'], server['servername'], server['servermodelid']) self.load_server(risotto_context,
await self._load_env(server['serverid']) server['server_id'],
server['servername'],
server['servermodelid'])
except Exception as err: except Exception as err:
print('Unable to load server {} ({}): {}'.format(server['servername'], server['serverid'], err)) if DEBUG:
print_exc()
servername = server['servername']
server_id = server['server_id']
msg = _(f'unable to load server {servername} ({server_id}): {err}')
log.error_msg(risotto_context,
None,
msg)
def load_server(self, serverid, servername, servermodelid): def load_server(self,
if serverid in self.server: risotto_context: Context,
server_id: int,
servername: str,
servermodelid: int) -> None:
""" Loads a server
"""
if server_id in self.server:
return return
print('Load server {} ({})'.format(servername, serverid)) log.info_msg(risotto_context,
None,
f'Load server {servername} ({server_id})')
if not servermodelid in self.servermodel: if not servermodelid in self.servermodel:
raise ValueError(f'unable to find servermodel with id {servermodelid}') msg = f'unable to find servermodel with id {servermodelid}'
metaconfig = self.servermodel[servermodelid].config.new('p_{}'.format(serverid), log.error_msg(risotto_context,
persistent=True, None,
type='metaconfig') msg)
metaconfig.information.set('server_id', serverid) raise CallError(msg)
metaconfig.information.set('server_name', servername)
metaconfig.owner.set('probe') # check if server was already created
config = metaconfig.config.new('s_{}'.format(serverid), session_id = f's_{server_id}'
persistent=True)
config.owner.set(servername) # get the servermodel's metaconfig
config = metaconfig.config.new('std_{}'.format(serverid), metaconfig = self.servermodel[servermodelid]
# create server configuration and server 'to deploy' configuration and store it
self.server[server_id] = {'server': self.build_config(session_id,
server_id,
servername,
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,
session_id: str,
server_id: int,
servername: str,
metaconfig: MetaConfig) -> None:
""" build server's config
"""
config = metaconfig.config.new(session_id,
storage=self.save_storage,
persistent=True) persistent=True)
config.information.set('server_id', server_id)
config.information.set('server_name', servername)
config.owner.set(servername) config.owner.set(servername)
config.property.read_only()
return config
if 'disabled' not in config.property.get(): @register('v1.server.created')
# has to be read_only async def server_created(self,
ro = list(config.property.getdefault('read_only', 'append')) risotto_context: Context,
if 'force_store_value' in ro: server_id: int,
# force_store_value is not allowed for new server (wait when configuration is deploy) servername: str,
ro.remove('force_store_value') servermodelid: int) -> None:
config.property.setdefault(frozenset(ro), 'read_only', 'append') """ Loads server's configuration when a new server is created
rw = list(config.property.getdefault('read_write', 'append')) """
rw.remove('force_store_value') self.load_server(risotto_context,
config.property.setdefault(frozenset(rw), 'read_write', 'append') server_id,
config.property.read_only() servername,
servermodelid)
self.server[serverid] = metaconfig @register('v1.server.deleted')
async def server_deleted(self,
server_id: int) -> None:
# delete config to it's parents
for server_type in ['server', 'server_to_deploy']:
config = self.server[server_id]['server']
for parent in config.config.parents():
parent.config.pop(config.config.name())
delete_session(storage=self.save_storage,
session_id=config.config.name())
# delete metaconfig
del self.server[server_id]
async def _load_env(self, server_id): @register('v1.servermodel.created')
metaconfig = self.server[server_id] async def servermodel_created(self,
old_informations = {} risotto_context: Context,
for old_information in metaconfig.information.list(): servermodelid: int,
old_informations[old_information] = metaconfig.information.get(old_information) servermodelname: str,
metaconfig.config.reset() servermodelparentsid: List[int]) -> None:
for old_information, old_value in old_informations.items(): """ when servermodels are created, load it and do link
metaconfig.information.set(old_information, old_value) """
risotto_context = Context() await self.load_and_link_servermodel(risotto_context,
risotto_context.username = 'root' servermodelid,
server = await self.call('v1.server.describe', risotto_context=risotto_context, serverid=server_id, environment=True) servermodelname,
for key, value in server['serverenvironment'].items(): servermodelparentsid)
metaconfig.unrestraint.option(key).value.set(value)
if server['serverenvironment']:
metaconfig.unrestraint.option('creole.general.available_probes').value.set("oui") 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')
async def servermodel_updated(self,
risotto_context: Context,
servermodelid: int,
servermodelname: str,
servermodelparentsid: List[int]) -> None:
log.info_msg(risotto_context,
None,
f'Reload servermodel {servermodelname} ({servermodelid})')
# unlink cache to force download new aggregated file
cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml")
if isfile(cache_file):
unlink(cache_file)
# store all informations
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: else:
metaconfig.unrestraint.option('creole.general.available_probes').value.set("non") old_values = None
# @register('v1.server.created', None) # create new one
# async def server_created(self, serverid, servername, servermodelid): await self.load_and_link_servermodel(risotto_context,
# self.load_server(serverid, servername, servermodelid) servermodelid,
# servermodelname,
# @register('v1.server.deleted', None) servermodelparentsid)
# async def server_deleted(self, serverid):
# metaconfig = self.server[serverid]
# # remove config inside metaconfig
# for config in metaconfig.config.list():
# metaconfig.config.pop(config.config.name())
# delete_session(config.config.name())
# del config
# # delete config to parents
# for parent in metaconfig.config.parents():
# parent.config.pop(metaconfig.config.name())
# # delete metaconfig
# delete_session(metaconfig.config.name())
# del self.server[serverid]
# del metaconfig
# @register('v1.server.environment.updated', "v1.config.configuration.server.updated") # migrates informations
# async def env_updated(self, server_id): if old_values is not None:
# await self._load_env(server_id) self.servermodel[servermodelid].value.importation(old_values)
# self.publish('v1.config.configuration.server.updated', server_id=server_id, deploy=False) self.servermodel[servermodelid].permissive.importation(old_permissives)
# return {'server_id': server_id, 'deploy': True} self.servermodel[servermodelid].property.importation(old_properties)
for child in children:
# @register('v1.servermodel.created', None) self.servermodel_legacy(risotto_context,
# async def servermodel_created(self, servermodels): child.information.get('servermodel_name'),
# for servermodel in servermodels: child.information.get('servermodel_id'),
# await self.load_servermodel(servermodel['servermodelid'], servermodel['servermodelname']) servermodelid)
# for servermodel in servermodels:
# if 'servermodelparentsid' in servermodel:
# for servermodelparentid in servermodel['servermodelparentsid']:
# self.servermodel_legacy(servermodel['servermodelname'], servermodel['servermodelid'], servermodelparentid)
#
# @register('v1.servermodel.updated', None)
# async def servermodel_updated(self, servermodels):
# for servermodel in servermodels:
# servermodelid = servermodel['servermodelid']
# servermodelname = servermodel['servermodelname']
# servermodelparentsid = servermodel.get('servermodelparentsid')
# print('Reload servermodel {} ({})'.format(servermodelname, servermodelid))
# # unlink cache to force download new aggregated file
# cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml")
# if isfile(cache_file):
# unlink(cache_file)
# # get current servermodel
# old_servermodel = self.servermodel[servermodelid]
# # create new one
# await self.load_servermodel(servermodelid, servermodelname)
# # migrate all informations
# self.servermodel[servermodelid].value.importation(old_servermodel.value.exportation())
# self.servermodel[servermodelid].permissive.importation(old_servermodel.permissive.exportation())
# self.servermodel[servermodelid].property.importation(old_servermodel.property.exportation())
# # remove link to legacy
# if servermodelparentsid:
# for servermodelparentid in servermodelparentsid:
# mix = self.servermodel[servermodelparentid].config.get('m_v_' + str(servermodelparentid))
# try:
# mix.config.pop(old_servermodel.config.name())
# except:
# # if mix config is reloaded too
# pass
# # add new link
# self.servermodel_legacy(servermodelname, servermodelid, servermodelparentid)
# # load servers in servermodel
# for subconfig in old_servermodel.config.list():
# if not isinstance(subconfig, MixConfig):
# name = subconfig.config.name()
# try:
# old_servermodel.config.pop(name)
# except:
# pass
# server_id = subconfig.information.get('server_id')
# server_name = subconfig.information.get('server_name')
# del self.server[server_id]
# self.load_server(server_id, server_name, servermodelid)
# else:
# 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, server_id, deploy): async def get_configuration(self,
return {'configuration': (server_id, deploy)} server_id: int,
deployed: bool) -> bytes:
if server_id not in self.server:
msg = _(f'cannot find server with id {server_id}')
log.error_msg(risotto_context,
None,
msg)
raise CallError(msg)
if deployed:
server = self.server[server_id]['server']
else:
server = self.server[server_id]['server_to_deploy']
server.property.read_only()
try:
configuration = server.value.dict(fullpath=True)
except:
if deployed:
msg = _(f'No configuration available for server {server_id}')
else:
msg = _(f'No undeployed configuration available for server {server_id}')
log.error_msg(risotto_context,
None,
msg)
raise CallError(msg)
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, server_id): async def deploy_configuration(self,
server_id: int) -> Dict:
"""Copy values, permissions, permissives from config 'to deploy' to active config """Copy values, permissions, permissives from config 'to deploy' to active config
""" """
metaconfig = self.server[server_id] config = self.server[server_id]['server']
config_std = metaconfig.config("std_{}".format(server_id)) config_std = self.server[server_id]['server_to_deploy']
# when deploy, calculate force_store_value
ro = config_std.property.getdefault('read_only', 'append') ro = config_std.property.getdefault('read_only', 'append')
if 'force_store_value' not in ro: if 'force_store_value' not in ro:
ro = frozenset(list(ro) + ['force_store_value']) ro = frozenset(list(ro) + ['force_store_value'])
@ -304,233 +432,10 @@ class Risotto(Controller):
config_std.property.setdefault(rw, 'read_write', 'append') config_std.property.setdefault(rw, 'read_write', 'append')
config_std.property.add('force_store_value') config_std.property.add('force_store_value')
config = metaconfig.config("s_{}".format(server_id)) # copy informations from server 'to deploy' configuration to server configuration
config.value.importation(config_std.value.exportation()) config.value.importation(config_std.value.exportation())
config.permissive.importation(config_std.permissive.exportation()) config.permissive.importation(config_std.permissive.exportation())
config.property.importation(config_std.property.exportation()) config.property.importation(config_std.property.exportation())
return {'server_id': server_id, 'deploy': True}
# SESSION return {'server_id': server_id,
#__________________________________________________________________ 'deployed': True}
def get_session(self, session_id, type):
if type == 'server':
return storage_server.get_session(session_id)
return storage_servermodel.get_session(session_id)
def get_session_informations(self, session_id, type):
session = self.get_session(session_id, type)
return self.format_session(session_id, session)
def format_session(self, session_name, session):
return {'sessionid': 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):
ret = []
if type == 'server':
storage = storage_server
else:
storage = storage_servermodel
for session in storage.list_sessions():
ret.append(self.format_session(session['sessionid'], session))
return ret
def load_dict(self, session):
if not session['option']:
session['option'] = session['config'].option(session['namespace'])
return session['option'].dict(remotable='all')
# start
async def start_session(self, risotto_context, id, type, server_list):
if id not in server_list:
raise Exception(_(f'cannot find {type} with id {id}'))
session_id = ''
session_list = self.list_sessions(type)
for sess in session_list:
if sess['id'] == id and sess['username'] == risotto_context.username:
session_id = sess['sessionid']
session = self.get_session(session_id, type)
return self.format_session(session_id, session)
else:
session_id = ''
if session_id == '':
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], type, id, username, self.modify_storage)
return self.get_session_informations(session_id, type)
@register('v1.config.session.server.start', None)
async def start_session_server(self, risotto_context, id):
return await self.start_session(risotto_context, id, 'server', self.server)
@register('v1.config.session.servermodel.start', None)
async def start_session_servermodel(self, risotto_context, id):
return await self.start_session(risotto_context, id, 'servermodel', self.servermodel)
# list
@register('v1.config.session.server.list', None)
async def list_session_server(self):
return self.list_sessions('server')
@register('v1.config.session.servermodel.list', None)
async def list_session_servermodel(self):
return self.list_sessions('servermodel')
# filter
async def filter_session(self, session_id, type, namespace, mode, debug):
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 Exception(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.filter', None)
async def filter_session_server(self, session_id, namespace, mode, debug):
return await self.filter_session(session_id, 'server', namespace, mode, debug)
@register('v1.config.session.servermodel.filter', None)
async def filter_session_servermodel(self, session_id, namespace, mode, debug):
return await self.filter_session(session_id, 'servermodel', namespace, mode, debug)
# configure
async def configure_session(self, session_id, type, action, name, index, value):
session = self.get_session(session_id, type)
ret = {'session_id': session_id,
'name': name}
if index is not None:
ret['index'] = index
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:
import traceback
traceback.print_exc()
ret['message'] = str(err)
ret['status'] = 'error'
return ret
@register('v1.config.session.server.configure', None)
async def configure_session_server(self, session_id, action, name, index, value):
return await self.configure_session(session_id, 'server', action, name, index, value)
@register('v1.config.session.servermodel.configure', None)
async def configure_session_servermodel(self, session_id, action, name, index, value):
return await self.configure_session(session_id, 'servermodel', action, name, index, value)
# validate
async def validate_session(self, session_id, type):
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.validate', None)
async def validate_session_server(self, session_id):
return await self.validate_session(session_id, 'server')
@register('v1.config.session.servermodel.validate', None)
async def validate_session_servermodel(self, session_id):
return await self.validate_session(session_id, 'servermodel')
# get
async def get_session_(self, session_id, type):
info = self.get_session_informations(session_id, type)
info['content'] = session_id
return info
@register('v1.config.session.server.get', None)
async def get_session_server(self, session_id):
return await self.get_session_(session_id, 'server')
@register('v1.config.session.servermodel.get', None)
async def get_session_servermodel(self, session_id):
return await self.get_session_(session_id, 'servermodel')
# stop
async def stop_session(self, risotto_context, session_id, type, save):
session = self.get_session(session_id, type)
if save:
await self._post_save_config(risotto_context, None, session_id)
if type == 'server':
storage = storage_server
else:
storage = storage_servermodel
storage.del_session(session_id, type)
return self.format_session(session_id, session)
@register('v1.config.session.server.stop', None)
async def stop_session_server(self, risotto_context, sessionid, save):
return await self.stop_session(sessionid, 'server', save)
@register('v1.config.session.servermodel.stop', None)
async def stop_session_servermodel(self, risotto_context, sessionid, save):
return await self.stop_session(risotto_context, sessionid, 'servermodel', save)
# GEN_CONFIG
#__________________________________________________________________
async def _post_save_config(self, risotto_context, request, sessionid):
self.valid_user(sessionid, risotto_context)
lib.save_values(sessionid, 'save')
id_ = storage.get_id(sessionid)
if storage.get_type(sessionid) == 'server':
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:
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)
return {}

View File

@ -1,151 +0,0 @@
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, sessionid, 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(sessionid)
config_server, orig_config = self.transform_orig_config(orig_config)
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(self, config)
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[sessionid] = {'config': config,
# do not delete meta, so keep it!
'meta': meta,
'orig_config': orig_config,
'id': server_id,
'timestamp': time.time(),
'username': username,
'option': None,
'namespace': 'creole',
'config_exists': config_exists}
self.set_config_mode(sessionid, 'normal')
self.set_config_debug(sessionid, False)
def list_sessions(self):
for sessionid, session in self.sessions.items():
yield {'sessionid': sessionid,
'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 GenConfigError('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 get_id(self, id_):
return self.get_session(id_)['id']
def set_config_mode(self, id_, mode):
""" Define which edition mode to select
:param id_: session id
:type id_: `str`
:param mode: possible values = ['basic', 'normal', 'expert']
:type mode: `str`
:returns: session mode value
:type :`bool`
"""
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)
# store mode in session in case config object gets reloader
self.sessions[id_]['mode'] = mode
def set_config_debug(self, id_, is_debug):
""" Enable/Disable debug mode
:param id_: session id
:type id_: `str`
:param is_debug: True to enable debug mode
:type is_debug: `bool`
:returns: session debug value
:type :`bool`
"""
config = self.get_session(id_)['config']
if is_debug:
config.property.pop('hidden')
else:
config.property.add('hidden')
self.sessions[id_]['debug'] = is_debug
return is_debug
class StorageServer(Storage):
def transform_orig_config(self, orig_config):
config_server = "std_{}".format(server_id)
orig_config = orig_config.config(config_server)
return config_server, orig_config
def set_owner(self, config):
config.owner.set(username)
class StorageServermodel(Storage):
def transform_orig_config(self, orig_config):
config_server = "v_{}".format(server_id)
return config_server, orig_config
def set_owner(self, config):
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

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

View File

@ -0,0 +1,65 @@
from ...controller import Controller
from ...register import register
class Risotto(Controller):
@register('v1.servermodel.list', None)
async def servermodel_list(self, sourceid):
return [{'servermodelid': 1,
'servermodelname': 'name',
'subreleasename': 'name',
'sourceid': 1,
'servermodeldescription': 'description'}]
@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': ''}

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)