Compare commits

..

15 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
847fbfc1e1 import config-manager controller 2019-11-29 16:38:33 +01:00
332dc61fd4 add notification and valid returns 2019-11-29 14:20:16 +01:00
62 changed files with 2665 additions and 545 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,3 +1,10 @@
HTTP_PORT = 8080 HTTP_PORT = 8080
MESSAGE_ROOT_PATH = 'messages' MESSAGE_ROOT_PATH = 'messages'
ROOT_CACHE_DIR = 'cache'
DEBUG = False DEBUG = False
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,312 +1,155 @@
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 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, 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[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, uri, function):
""" 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'{uri} is not a valid message'))
# 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[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
self.uris[version][uri] = {'module': module_name,
'function': function,
'risotto_context': inject_risotto_context}
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] = []
self.uris[version][uri].append({'module': module_name,
'function': function,
'arguments': function_args,
'risotto_context': inject_risotto_context})
def set_module(self, module_name, module):
""" register and instanciate a new module
"""
try:
self.modules[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):
self.modules = {}
self.uris = {}
self.function_names = {}
self.messages, self.option = get_messages()
config = Config(self.option)
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, def valid_call_returns(self,
version: str, risotto_context: Context,
uri: str, returns: Dict,
type: str, kwargs: Dict):
context: Context, response = self.messages[risotto_context.version][risotto_context.message]['response']
kwargs: Dict): module_name = risotto_context.function.__module__.split('.')[-2]
if self.messages[uri]['pattern'] != type: function_name = risotto_context.function.__name__
msg = _(f'{version}.{uri} is not a {type} message') if response.impl_get_information('multi'):
log.error_msg(version, uri, context, kwargs, 'call', msg) if not isinstance(returns, list):
raise CallError(msg) 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]
if response is None:
raise Exception('hu?')
else:
for ret in returns:
config = Config(response, display_name=lambda self, dyn_name: self.impl_getname())
config.property.read_write()
try:
for key, value in ret.items():
config.option(key).value.set(value)
except AttributeError:
err = _(f'function {module_name}.{function_name} return the unknown parameter "{key}"')
log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
except ValueError:
err = _(f'function {module_name}.{function_name} return the parameter "{key}" with an unvalid value "{value}"')
log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
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:
config.value.dict()
except Exception as err:
err = _(f'function {module_name}.{function_name} return an invalid response {err}')
log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
def set_config(self, async def call(self,
uri: str, version: str,
kwargs: Dict): message: str,
""" create a new Config et set values to it old_risotto_context: Context,
""" public_only: bool=False,
# create a new config **kwargs):
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
async def call(self, version, uri, risotto_context, public_only=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.modules[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))
# FIXME notification # valid returns
# FIXME valider le retour! self.valid_call_returns(risotto_context,
log.info_msg(version, uri, new_context, kwargs, 'call', _(f'returns {returns}')) returns,
kwargs)
# log the success
log.info_msg(risotto_context,
kwargs,
_(f'returns {returns}'))
# notification
if obj.get('notification'):
notif_version, notif_message = obj['notification'].split('.', 1)
if not isinstance(returns, list):
send_returns = [returns]
else:
send_returns = returns
for ret in send_returns:
await self.publish(notif_version,
notif_message,
risotto_context,
**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__
@ -318,18 +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.modules[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)
class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher):
""" Manage message (call or publish)
so launch a function when a message is called
"""
def build_new_context(self,
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,23 +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, HTTP_PORT
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:
@ -25,48 +75,59 @@ async def handle(request):
except CallError as err: except CallError as err:
raise HTTPBadRequest(reason=str(err)) raise HTTPBadRequest(reason=str(err))
except Exception as err: except Exception as err:
if DEBUG:
print_exc()
raise HTTPInternalServerError(reason=str(err)) raise HTTPInternalServerError(reason=str(err))
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()
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))
@ -448,53 +477,46 @@ def _parse_responses(message_def,
file_path): file_path):
"""build option with returns """build option with returns
""" """
responses = OrderedDict() if message_def.response.parameters is None:
if message_def.response: raise Exception('not implemented yet')
keys = {'': {'description': '', #name = 'response'
'columns': {}}} #keys['']['columns'][name] = {'description': message_def.response.description,
provides = {} # 'type': message_def.response.type}
to_list = True #responses = {}
param_type = message_def.response.type #responses['keys'] = keys
#return responses
if param_type.startswith('[]'): options = []
to_list = False names = []
param_type = param_type[2:] for name, obj in message_def.response.parameters.items():
if param_type in ['Dict', 'File']: if name in names:
pass raise Exception('multi response with name {} in {}'.format(name, file_path))
names.append(name)
if message_def.response.parameters is not None: kwargs = {'name': name,
for name, obj in message_def.response.parameters.items(): 'doc': obj.description.strip().rstrip()}
if name in responses: type_ = obj.type
raise Exception('multi response with name {} in {}'.format(name, file_path)) if type_.startswith('[]'):
kwargs['multi'] = True
descr = obj.description.strip().rstrip() type_ = type_[2:]
keys['']['columns'][name] = {'description': obj.description, option = {'String': StrOption,
'type': obj.type} 'Number': IntOption,
ref = obj.ref 'Boolean': BoolOption,
if ref: 'Dict': DictOption,
provides[name] = ref # FIXME
else: 'File': StrOption}.get(type_)
keys['']['columns'][name] = {'description': descr, if not option:
'type': obj.type} raise Exception(f'unknown param type {obj.type}')
ref = obj.ref if hasattr(obj, 'default'):
if ref: kwargs['default'] = obj.default
provides[name] = ref
responses['keys'] = keys
responses['to_list'] = to_list
responses['to_dict'] = False
responses['provides'] = provides
else: else:
name = 'response' kwargs['properties'] = ('mandatory',)
keys['']['columns'][name] = {'description': message_def.response.description, options.append(option(**kwargs))
'type': message_def.response.type} od = OptionDescription(message_def.uri,
ref = message_def.response.ref message_def.response.description,
if ref: options)
provides[name] = ref od.impl_set_information('multi', message_def.response.multi)
responses['keys'] = keys return od
responses['to_list'] = to_list
responses['to_dict'] = True
responses['provides'] = provides
return responses
def _getoptions_from_yml(message_def, def _getoptions_from_yml(message_def,
@ -564,7 +586,6 @@ def get_messages(load_shortarg=False, only_public=False):
optiondescriptions = OrderedDict() optiondescriptions = OrderedDict()
optiondescriptions_name = [] optiondescriptions_name = []
optiondescriptions_info = {} optiondescriptions_info = {}
responses = OrderedDict()
needs = OrderedDict() needs = OrderedDict()
messages = list(list_messages()) messages = list(list_messages())
messages.sort() messages.sort()
@ -586,9 +607,12 @@ def get_messages(load_shortarg=False, only_public=False):
continue continue
optiondescriptions_info[message_def.uri] = {'pattern': message_def.pattern, optiondescriptions_info[message_def.uri] = {'pattern': message_def.pattern,
'public': message_def.public} 'public': message_def.public}
if message_def.pattern == 'rpc':
optiondescriptions_info[message_def.uri]['response'] = _parse_responses(message_def,
message_name)
elif message_def.response:
raise Exception(f'response not allowed for {message_def.uri}')
version = message_name.split('.')[0] version = message_name.split('.')[0]
if message_def.uri in responses:
raise Exception('uri {} allready loader'.format(message_def.uri))
_getoptions_from_yml(message_def, _getoptions_from_yml(message_def,
version, version,
optiondescriptions, optiondescriptions,
@ -596,8 +620,6 @@ def get_messages(load_shortarg=False, only_public=False):
needs, needs,
select_option, select_option,
load_shortarg) load_shortarg)
responses[message_def.uri] = _parse_responses(message_def,
message_name)
root = _get_root_option(select_option, optiondescriptions) root = _get_root_option(select_option, optiondescriptions)
try: try:

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,26 +1,441 @@
from lxml.etree import parse
from io import BytesIO
from os import unlink
from os.path import isdir, isfile, join
from traceback import print_exc
from typing import Dict, List
from tiramisu import Storage, delete_session, MetaConfig, MixConfig
from rougail import load as rougail_load
from ...controller import Controller from ...controller import Controller
from ...dispatcher import register from ...register import register
from ...config import ROOT_CACHE_DIR, DATABASE_DIR, DEBUG, ROUGAIL_DTD_PATH
from ...context import Context
from ...utils import _
from ...error import CallError, RegistrationError
from ...logger import log
class Risotto(Controller): class Risotto(Controller):
@register('v1.config.configuration.server.updated') servermodel = {}
async def server_created(self, server_id): server = {}
print('pouet ' + str(server_id))
@register('v1.config.session.server.start', None) def __init__(self) -> None:
async def get_configuration(self, risotto_context, id): for dirname in [ROOT_CACHE_DIR, DATABASE_DIR]:
#stop = await self.call('v1.config.session.server.stop', risotto_context, sessionid=str(id)) if not isdir(dirname):
#await self.publish('v1.config.configuration.server.updated', risotto_context, server_id=1, deploy=True) raise RegistrationError(_(f'unable to find the cache dir "{dirname}"'))
return {'start': id} self.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR)
super().__init__()
@register('v1.config.session.server.stop', None) async def on_join(self,
async def get_configuration2(self, sessionid, save): risotto_context: Context) -> None:
return {'stop': sessionid} """ pre-load servermodel and server
"""
await self.load_servermodels(risotto_context)
await self.load_servers(risotto_context)
async def load_servermodels(self,
risotto_context: Context) -> None:
""" load all available servermodels
"""
log.info_msg(risotto_context,
None,
'Load servermodels')
servermodels = await self.call('v1.servermodel.list',
risotto_context)
# load each servermodels
for servermodel in servermodels:
try:
await self.load_servermodel(risotto_context,
servermodel['servermodelid'],
servermodel['servermodelname'])
except CallError as err:
pass
# do link to this servermodel
for servermodel in servermodels:
if 'servermodelparentsid' in servermodel:
for servermodelparentid in servermodel['servermodelparentsid']:
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,
risotto_context: Context,
servermodelid: int,
servermodelname: str) -> None:
""" Loads a servermodel
"""
cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml")
funcs_file = self.get_funcs_filename(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):
fileio = open(cache_file)
else:
servermodel = await self.call('v1.servermodel.describe',
risotto_context,
servermodelid=servermodelid,
inheritance=False,
resolvdepends=False,
schema=True,
creolefuncs=True)
fileio = BytesIO()
fileio.write(servermodel['schema'].encode())
fileio.seek(0)
with open(cache_file, 'w') as cache:
cache.write(servermodel['schema'])
with open(funcs_file, 'w') as cache:
cache.write(servermodel['creolefuncs'])
del servermodel
# loads tiramisu config and store it
xmlroot = parse(fileio).getroot()
self.servermodel[servermodelid] = self.build_metaconfig(servermodelid,
servermodelname,
xmlroot,
funcs_file)
def build_metaconfig(self,
servermodelid: int,
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)
# 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)
# 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:
return
if not self.servermodel.get(servermodel_parent_id):
if DEBUG:
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
servermodel_parent = self.servermodel[servermodel_parent_id]
servermodel_parent_name = servermodel_parent.information.get('servermodel_name')
if DEBUG:
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))
try:
mix.config.add(self.servermodel[servermodel_id])
except Exception as err:
if DEBUG:
log.error_msg(risotto_context,
None,
str(err))
async def load_servers(self,
risotto_context: Context) -> None:
""" load all available servers
"""
log.info_msg(risotto_context,
None,
f'Load servers')
# get all servers
servers = await self.call('v1.server.list',
risotto_context)
# loads servers
for server in servers:
try:
self.load_server(risotto_context,
server['server_id'],
server['servername'],
server['servermodelid'])
except Exception as 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,
risotto_context: Context,
server_id: int,
servername: str,
servermodelid: int) -> None:
""" Loads a server
"""
if server_id in self.server:
return
log.info_msg(risotto_context,
None,
f'Load server {servername} ({server_id})')
if not servermodelid in self.servermodel:
msg = f'unable to find servermodel with id {servermodelid}'
log.error_msg(risotto_context,
None,
msg)
raise CallError(msg)
# check if server was already created
session_id = f's_{server_id}'
# get the servermodel's metaconfig
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)
config.information.set('server_id', server_id)
config.information.set('server_name', servername)
config.owner.set(servername)
config.property.read_only()
return config
@register('v1.server.created')
async def server_created(self,
risotto_context: Context,
server_id: int,
servername: str,
servermodelid: int) -> None:
""" Loads server's configuration when a new server is created
"""
self.load_server(risotto_context,
server_id,
servername,
servermodelid)
@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]
@register('v1.servermodel.created')
async def servermodel_created(self,
risotto_context: Context,
servermodelid: int,
servermodelname: str,
servermodelparentsid: List[int]) -> None:
""" when servermodels are created, load it and do link
"""
await self.load_and_link_servermodel(risotto_context,
servermodelid,
servermodelname,
servermodelparentsid)
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:
old_values = None
# create new one
await self.load_and_link_servermodel(risotto_context,
servermodelid,
servermodelname,
servermodelparentsid)
# migrates informations
if old_values is not None:
self.servermodel[servermodelid].value.importation(old_values)
self.servermodel[servermodelid].permissive.importation(old_permissives)
self.servermodel[servermodelid].property.importation(old_properties)
for child in children:
self.servermodel_legacy(risotto_context,
child.information.get('servermodel_name'),
child.information.get('servermodel_id'),
servermodelid)
@register('v1.config.configuration.server.get', None) @register('v1.config.configuration.server.get', None)
async def get_configuration3(self, server_id, deploy): async def get_configuration(self,
return {'get': server_id} 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)
@register('v1.config.configuration.server.deploy', None) if deployed:
async def get_configuration4(self, server_id): server = self.server[server_id]['server']
return {'deploy': server_id} 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')
async def deploy_configuration(self,
server_id: int) -> Dict:
"""Copy values, permissions, permissives from config 'to deploy' to active config
"""
config = self.server[server_id]['server']
config_std = self.server[server_id]['server_to_deploy']
# when deploy, calculate force_store_value
ro = config_std.property.getdefault('read_only', 'append')
if 'force_store_value' not in ro:
ro = frozenset(list(ro) + ['force_store_value'])
config_std.property.setdefault(ro, 'read_only', 'append')
rw = config_std.property.getdefault('read_write', 'append')
rw = frozenset(list(rw) + ['force_store_value'])
config_std.property.setdefault(rw, 'read_write', 'append')
config_std.property.add('force_store_value')
# copy informations from server 'to deploy' configuration to server configuration
config.value.importation(config_std.value.exportation())
config.permissive.importation(config_std.permissive.exportation())
config.property.importation(config_std.property.exportation())
return {'server_id': server_id,
'deployed': True}

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)