Compare commits
15 Commits
1d1b51d37b
...
master
Author | SHA1 | Date | |
---|---|---|---|
8a7cf24cd6 | |||
669782cd35 | |||
4acd5d5a7f | |||
533ad2551f | |||
3b31f092bd | |||
3c5285a7d2 | |||
b944a609a5 | |||
bb1fdcbad0 | |||
03937baf51 | |||
939f93253e | |||
0846c4c5cc | |||
a63b41c985 | |||
8a551f85b2 | |||
847fbfc1e1 | |||
332dc61fd4 |
22
README.md
22
README.md
@ -11,10 +11,20 @@ Accéder à un message :
|
||||
wget http://localhost:8080/v1/config.session.server.start
|
||||
```
|
||||
|
||||
Démarrer un serveur LemonLDAP de test
|
||||
```
|
||||
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
|
||||
```
|
||||
[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)
|
||||
|
||||
**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
6
Vocabulary.txt
Normal file
@ -0,0 +1,6 @@
|
||||
Message
|
||||
=======
|
||||
|
||||
message: config.session.server.start
|
||||
version: v1
|
||||
uri: v1.config.session.server.start
|
@ -17,10 +17,11 @@ parameters:
|
||||
type: Number
|
||||
ref: Server.ServerId
|
||||
description: |
|
||||
Identifiant de la configuration.
|
||||
deploy:
|
||||
Identifiant du serveur.
|
||||
deployed:
|
||||
type: Boolean
|
||||
description: Configuration de type déployée.
|
||||
default: true
|
||||
|
||||
response:
|
||||
type: ConfigConfiguration
|
||||
|
@ -17,6 +17,6 @@ parameters:
|
||||
type: Number
|
||||
description: |
|
||||
Identifiant du serveur.
|
||||
deploy:
|
||||
deployed:
|
||||
type: Boolean
|
||||
description: Configuration de type déployée.
|
||||
|
@ -13,7 +13,7 @@ public: false
|
||||
domain: server-domain
|
||||
|
||||
parameters:
|
||||
serverid:
|
||||
server_id:
|
||||
type: Number
|
||||
description: |
|
||||
Identifiant du serveur supprimé.
|
@ -10,6 +10,5 @@ public: false
|
||||
domain: servermodel-domain
|
||||
|
||||
parameters:
|
||||
servermodels:
|
||||
type: '[]Servermodel'
|
||||
description: Informations sur les modèles de serveur créés.
|
||||
type: Servermodel
|
||||
description: Informations sur les modèles de serveur créés.
|
@ -10,6 +10,5 @@ public: false
|
||||
domain: servermodel-domain
|
||||
|
||||
parameters:
|
||||
servermodels:
|
||||
type: '[]Servermodel'
|
||||
description: Informations sur les modèles de serveur modifiés.
|
||||
type: 'Servermodel'
|
||||
description: Informations sur les modèles de serveur modifiés.
|
@ -1,17 +1,14 @@
|
||||
---
|
||||
uri: config.session.server.configure
|
||||
uri: session.server.configure
|
||||
|
||||
description: |
|
||||
Configure le server.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.server.configure -s 2
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
session_id:
|
||||
@ -37,7 +34,12 @@ parameters:
|
||||
shortarg: v
|
||||
description: Valeur de la variable.
|
||||
default: null
|
||||
value_multi:
|
||||
type: '[]Any'
|
||||
shortarg: m
|
||||
description: Valeur de la variable de type multi.
|
||||
default: []
|
||||
|
||||
response:
|
||||
type: ConfigStatus
|
||||
type: SessionStatus
|
||||
description: Description de la session.
|
@ -1,17 +1,14 @@
|
||||
---
|
||||
uri: config.session.server.filter
|
||||
uri: session.server.filter
|
||||
|
||||
description: |
|
||||
Filter la configuration a éditer.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.server.filter -s 2
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
session_id:
|
||||
@ -36,5 +33,5 @@ parameters:
|
||||
default: null
|
||||
|
||||
response:
|
||||
type: ConfigSession
|
||||
type: Session
|
||||
description: Description de la session.
|
@ -1,17 +1,14 @@
|
||||
---
|
||||
uri: config.session.server.get
|
||||
uri: session.server.get
|
||||
|
||||
description: |
|
||||
Configure le server.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.server.get -s 2
|
||||
Récupérer la configuration du server.
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
session_id:
|
||||
@ -19,7 +16,12 @@ parameters:
|
||||
ref: Config.SessionId
|
||||
shortarg: s
|
||||
description: Identifiant de la configuration.
|
||||
name:
|
||||
type: String
|
||||
shortarg: n
|
||||
description: Nom de la variable.
|
||||
default: null
|
||||
|
||||
response:
|
||||
type: ConfigSession
|
||||
type: Session
|
||||
description: Description de la session.
|
@ -1,19 +1,16 @@
|
||||
---
|
||||
uri: config.session.server.list
|
||||
uri: session.server.list
|
||||
|
||||
description: |
|
||||
Liste les sessions de configuration des serveurs.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.server.list
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
response:
|
||||
type: '[]ConfigSession'
|
||||
type: '[]Session'
|
||||
description: |
|
||||
Liste des sessions.
|
@ -1,17 +1,14 @@
|
||||
---
|
||||
uri: config.session.server.start
|
||||
uri: session.server.start
|
||||
|
||||
description: |
|
||||
Démarre une session de configuration pour un serveur.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.server.start -c 2
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
id:
|
||||
@ -22,5 +19,5 @@ parameters:
|
||||
Identifiant de la configuration.
|
||||
|
||||
response:
|
||||
type: ConfigSession
|
||||
type: Session
|
||||
description: Description de la session.
|
@ -1,20 +1,17 @@
|
||||
---
|
||||
uri: config.session.server.stop
|
||||
uri: session.server.stop
|
||||
|
||||
description: |
|
||||
Termine une session de configuration d'un serveur.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.server.stop -s xxxxx
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
sessionid:
|
||||
session_id:
|
||||
ref: Config.SessionId
|
||||
type: String
|
||||
shortarg: s
|
||||
@ -26,5 +23,5 @@ parameters:
|
||||
default: false
|
||||
|
||||
response:
|
||||
type: ConfigSession
|
||||
type: Session
|
||||
description: Description de la session.
|
@ -1,17 +1,14 @@
|
||||
---
|
||||
uri: config.session.server.validate
|
||||
uri: session.server.validate
|
||||
|
||||
description: |
|
||||
Valider la configuration d'un serveur.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.server.validate -s xxxxx
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
session_id:
|
||||
@ -21,6 +18,6 @@ parameters:
|
||||
description: Identifiant de la session.
|
||||
|
||||
response:
|
||||
type: ConfigConfigurationStatus
|
||||
type: Session
|
||||
description: Statut de la configuration.
|
||||
|
@ -1,17 +1,14 @@
|
||||
---
|
||||
uri: config.session.servermodel.configure
|
||||
uri: session.servermodel.configure
|
||||
|
||||
description: |
|
||||
Configure le servermodel.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.servermodel.configure -s 2
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
session_id:
|
||||
@ -37,7 +34,12 @@ parameters:
|
||||
shortarg: v
|
||||
description: Valeur de la variable.
|
||||
default: null
|
||||
value_multi:
|
||||
type: '[]Any'
|
||||
shortarg: m
|
||||
description: Valeur de la variable de type multi.
|
||||
default: []
|
||||
|
||||
response:
|
||||
type: ConfigStatus
|
||||
type: SessionStatus
|
||||
description: Description de la session.
|
@ -1,17 +1,14 @@
|
||||
---
|
||||
uri: config.session.servermodel.filter
|
||||
uri: session.servermodel.filter
|
||||
|
||||
description: |
|
||||
Filter la configuration a éditer.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.servermodel.filter -s 2
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
session_id:
|
||||
@ -36,5 +33,5 @@ parameters:
|
||||
default: null
|
||||
|
||||
response:
|
||||
type: ConfigSession
|
||||
type: Session
|
||||
description: Description de la session.
|
@ -1,17 +1,14 @@
|
||||
---
|
||||
uri: config.session.servermodel.get
|
||||
uri: session.servermodel.get
|
||||
|
||||
description: |
|
||||
Configure le servermodel.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.servermodel.get -s 2
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
session_id:
|
||||
@ -19,7 +16,12 @@ parameters:
|
||||
ref: Config.SessionId
|
||||
shortarg: s
|
||||
description: Identifiant de la configuration.
|
||||
name:
|
||||
type: String
|
||||
shortarg: n
|
||||
description: Nom de la variable.
|
||||
default: null
|
||||
|
||||
response:
|
||||
type: ConfigSession
|
||||
type: Session
|
||||
description: Description de la session.
|
@ -1,18 +1,15 @@
|
||||
uri: config.session.servermodel.list
|
||||
uri: session.servermodel.list
|
||||
|
||||
description: |
|
||||
Liste les sessions de configuration des modèles de serveur.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.servermodel.list
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
response:
|
||||
type: '[]ConfigSession'
|
||||
type: '[]Session'
|
||||
description: |
|
||||
Liste des sessions.
|
@ -1,16 +1,13 @@
|
||||
uri: config.session.servermodel.start
|
||||
uri: session.servermodel.start
|
||||
|
||||
description: |
|
||||
Démarre une session de configuration pour un modèle de serveur.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.servermodel.start -c 2
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
id:
|
||||
@ -21,5 +18,5 @@ parameters:
|
||||
Identifiant de la configuration.
|
||||
|
||||
response:
|
||||
type: ConfigSession
|
||||
type: Session
|
||||
description: Description de la session.
|
@ -1,19 +1,16 @@
|
||||
uri: config.session.servermodel.stop
|
||||
uri: session.servermodel.stop
|
||||
|
||||
description: |
|
||||
Termine une session de configuration d'un modèle de serveur.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.servermodel.stop -s xxxxx
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
sessionid:
|
||||
session_id:
|
||||
ref: Config.SessionId
|
||||
type: String
|
||||
shortarg: s
|
||||
@ -25,5 +22,5 @@ parameters:
|
||||
default: false
|
||||
|
||||
response:
|
||||
type: ConfigSession
|
||||
type: Session
|
||||
description: Description de la session.
|
@ -1,17 +1,14 @@
|
||||
---
|
||||
uri: config.session.servermodel.validate
|
||||
uri: session.servermodel.validate
|
||||
|
||||
description: |
|
||||
Valider la configuration d'un modèle serveur.
|
||||
|
||||
sampleuse: |
|
||||
zephir-client config.session.servermodel.validate -s xxxxx
|
||||
|
||||
pattern: rpc
|
||||
|
||||
public: true
|
||||
|
||||
domain: config-domain
|
||||
domain: session-domain
|
||||
|
||||
parameters:
|
||||
session_id:
|
||||
@ -21,6 +18,6 @@ parameters:
|
||||
description: Identifiant de la session.
|
||||
|
||||
response:
|
||||
type: ConfigConfigurationStatus
|
||||
type: Session
|
||||
description: Statut de la configuration.
|
||||
|
26
messages/v1/messages/template.generate.yml
Normal file
26
messages/v1/messages/template.generate.yml
Normal 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.
|
@ -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
|
||||
|
@ -3,8 +3,15 @@ title: ConfigConfiguration
|
||||
type: object
|
||||
description: Description de la configuration.
|
||||
properties:
|
||||
server_id:
|
||||
type: number
|
||||
description: Identifiant du serveur.
|
||||
ref: Server.ServerId
|
||||
deployed:
|
||||
type: boolean
|
||||
description: La configuration est déployée.
|
||||
configuration:
|
||||
type: File
|
||||
type: object
|
||||
description: Détail de la configuration au format JSON.
|
||||
required:
|
||||
- configuration
|
||||
|
@ -3,7 +3,7 @@ title: Server
|
||||
type: object
|
||||
description: Description du serveur.
|
||||
properties:
|
||||
serverid:
|
||||
server_id:
|
||||
type: number
|
||||
description: Identifiant du serveur.
|
||||
ref: Server.ServerId
|
||||
@ -33,7 +33,7 @@ properties:
|
||||
type: string
|
||||
description: Timestamp de la dernière connexion avec le serveur.
|
||||
required:
|
||||
- serverid
|
||||
- server_id
|
||||
- servername
|
||||
- serverdescription
|
||||
- servermodelid
|
||||
|
@ -37,16 +37,16 @@ properties:
|
||||
type: object
|
||||
description: Liste des services applicatifs déclarés pour ce modèle de serveur.
|
||||
schema:
|
||||
type: File
|
||||
type: string
|
||||
description: Contenu du schema.
|
||||
probes:
|
||||
type: File
|
||||
type: string
|
||||
description: Informations sur les sondes.
|
||||
creolefuncs:
|
||||
type: File
|
||||
type: string
|
||||
description: Fonctions Creole.
|
||||
conffiles:
|
||||
type: File
|
||||
type: string
|
||||
description: Fichiers creole au format tar encodé base64
|
||||
required:
|
||||
- servermodelid
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: ConfigStatus
|
||||
title: SessionStatus
|
||||
type: object
|
||||
description: Status de la modification de la configuration.
|
||||
properties:
|
||||
@ -12,9 +12,6 @@ properties:
|
||||
index:
|
||||
type: number
|
||||
description: Index de la variable a modifier.
|
||||
status:
|
||||
type: string
|
||||
description: Status de la modification.
|
||||
message:
|
||||
type: string
|
||||
description: Message d'erreur.
|
@ -1,9 +1,9 @@
|
||||
---
|
||||
title: ConfigSession
|
||||
title: Session
|
||||
type: object
|
||||
description: Description de la session.
|
||||
properties:
|
||||
sessionid:
|
||||
session_id:
|
||||
type: string
|
||||
description: ID de la session.
|
||||
ref: Config.SessionId
|
||||
@ -27,10 +27,10 @@ properties:
|
||||
type: boolean
|
||||
description: La configuration est en mode debug.
|
||||
content:
|
||||
type: file
|
||||
type: object
|
||||
description: Contenu de la configuration.
|
||||
required:
|
||||
- sessionid
|
||||
- session_id
|
||||
- id
|
||||
- username
|
||||
- timestamp
|
14
messages/v1/types/template.yml
Normal file
14
messages/v1/types/template.yml
Normal 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
BIN
schema-tira.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
@ -1,6 +1,12 @@
|
||||
from aiohttp.web import run_app
|
||||
from asyncio import get_event_loop
|
||||
from risotto import get_app
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -1,8 +1,4 @@
|
||||
from .utils import undefined
|
||||
from .dispatcher import register, dispatcher
|
||||
from .http import get_app
|
||||
# just to register every route
|
||||
from . import services as _services
|
||||
|
||||
__ALL__ = ('undefined', 'register', 'dispatcher', 'get_app')
|
||||
__ALL__ = ('get_app',)
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
HTTP_PORT = 8080
|
||||
MESSAGE_ROOT_PATH = 'messages'
|
||||
ROOT_CACHE_DIR = 'cache'
|
||||
DEBUG = False
|
||||
DATABASE_DIR = 'database'
|
||||
INTERNAL_USER = 'internal'
|
||||
CONFIGURATION_DIR = 'configurations'
|
||||
TEMPLATE_DIR = 'templates'
|
||||
TMP_DIR = 'tmp'
|
||||
ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd'
|
||||
|
@ -26,3 +26,7 @@ class Controller:
|
||||
uri,
|
||||
risotto_context,
|
||||
**kwargs)
|
||||
|
||||
async def on_join(self,
|
||||
risotto_context):
|
||||
pass
|
||||
|
@ -1,312 +1,155 @@
|
||||
from tiramisu import Config
|
||||
from inspect import signature
|
||||
from traceback import print_exc
|
||||
from copy import copy
|
||||
from typing import Dict
|
||||
from typing import Dict, Callable
|
||||
|
||||
from .utils import undefined, _
|
||||
from .error import RegistrationError, CallError, NotAllowedError
|
||||
from .message import get_messages
|
||||
from .utils import _
|
||||
from .error import CallError, NotAllowedError
|
||||
from .logger import log
|
||||
from .config import DEBUG
|
||||
from .context import Context
|
||||
from . import register
|
||||
|
||||
|
||||
def register(uri: str,
|
||||
notification: str=undefined):
|
||||
""" Decorator to register function to the dispatcher
|
||||
"""
|
||||
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,
|
||||
class CallDispatcher:
|
||||
def valid_public_function(self,
|
||||
risotto_context: Context,
|
||||
kwargs: Dict,
|
||||
public_only: bool):
|
||||
if public_only and not self.messages[uri]['public']:
|
||||
msg = _(f'the message {version}.{uri} is private')
|
||||
log.error_msg(version, uri, context, kwargs, 'call', msg)
|
||||
if public_only and not self.messages[risotto_context.version][risotto_context.message]['public']:
|
||||
msg = _(f'the message {risotto_context.message} is private')
|
||||
log.error_msg(risotto_context, kwargs, msg)
|
||||
raise NotAllowedError(msg)
|
||||
|
||||
def check_pattern(self,
|
||||
version: str,
|
||||
uri: str,
|
||||
type: str,
|
||||
context: Context,
|
||||
kwargs: Dict):
|
||||
if self.messages[uri]['pattern'] != type:
|
||||
msg = _(f'{version}.{uri} is not a {type} message')
|
||||
log.error_msg(version, uri, context, kwargs, 'call', msg)
|
||||
raise CallError(msg)
|
||||
def valid_call_returns(self,
|
||||
risotto_context: Context,
|
||||
returns: Dict,
|
||||
kwargs: Dict):
|
||||
response = self.messages[risotto_context.version][risotto_context.message]['response']
|
||||
module_name = risotto_context.function.__module__.split('.')[-2]
|
||||
function_name = risotto_context.function.__name__
|
||||
if response.impl_get_information('multi'):
|
||||
if not isinstance(returns, list):
|
||||
err = _(f'function {module_name}.{function_name} has to return a list')
|
||||
log.error_msg(risotto_context, kwargs, err)
|
||||
raise CallError(str(err))
|
||||
else:
|
||||
if not isinstance(returns, dict):
|
||||
err = _(f'function {module_name}.{function_name} has to return a dict')
|
||||
log.error_msg(risotto_context, kwargs, err)
|
||||
raise CallError(str(err))
|
||||
returns = [returns]
|
||||
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,
|
||||
uri: str,
|
||||
kwargs: Dict):
|
||||
""" create a new Config et set values to it
|
||||
"""
|
||||
# create a new config
|
||||
config = Config(self.option)
|
||||
config.property.read_write()
|
||||
# set message option
|
||||
config.option('message').value.set(uri)
|
||||
# store values
|
||||
subconfig = config.option(uri)
|
||||
for key, value in kwargs.items():
|
||||
try:
|
||||
subconfig.option(key).value.set(value)
|
||||
except AttributeError:
|
||||
raise AttributeError(_(f'unknown parameter "{key}"'))
|
||||
# check mandatories options
|
||||
config.property.read_only()
|
||||
mandatories = list(config.value.mandatory())
|
||||
if mandatories:
|
||||
mand = [mand.split('.')[-1] for mand in mandatories]
|
||||
raise ValueError(_(f'missing parameters: {mand}'))
|
||||
# return the config
|
||||
return config
|
||||
|
||||
async def call(self, version, uri, risotto_context, public_only=False, **kwargs):
|
||||
async def call(self,
|
||||
version: str,
|
||||
message: str,
|
||||
old_risotto_context: Context,
|
||||
public_only: bool=False,
|
||||
**kwargs):
|
||||
""" execute the function associate with specified uri
|
||||
arguments are validate before
|
||||
"""
|
||||
new_context = self.new_context(risotto_context,
|
||||
version,
|
||||
uri)
|
||||
self.check_public_function(version,
|
||||
uri,
|
||||
new_context,
|
||||
risotto_context = self.build_new_context(old_risotto_context,
|
||||
version,
|
||||
message,
|
||||
'rpc')
|
||||
self.valid_public_function(risotto_context,
|
||||
kwargs,
|
||||
public_only)
|
||||
self.check_pattern(version,
|
||||
uri,
|
||||
'rpc',
|
||||
new_context,
|
||||
kwargs)
|
||||
self.check_message_type(risotto_context,
|
||||
kwargs)
|
||||
try:
|
||||
config = self.set_config(uri,
|
||||
kwargs)
|
||||
obj = self.uris[version][uri]
|
||||
kw = config.option(uri).value.dict()
|
||||
tiramisu_config = self.load_kwargs_to_config(risotto_context,
|
||||
kwargs)
|
||||
obj = self.messages[version][message]
|
||||
kw = tiramisu_config.option(message).value.dict()
|
||||
risotto_context.function = obj['function']
|
||||
if obj['risotto_context']:
|
||||
kw['risotto_context'] = new_context
|
||||
returns = await obj['function'](self.modules[obj['module']], **kw)
|
||||
kw['risotto_context'] = risotto_context
|
||||
returns = await risotto_context.function(self.injected_self[obj['module']], **kw)
|
||||
except CallError as err:
|
||||
raise err
|
||||
except Exception as err:
|
||||
if DEBUG:
|
||||
print_exc()
|
||||
log.error_msg(version, uri, new_context, kwargs, 'call', err)
|
||||
log.error_msg(risotto_context,
|
||||
kwargs,
|
||||
err)
|
||||
raise CallError(str(err))
|
||||
# FIXME notification
|
||||
# FIXME valider le retour!
|
||||
log.info_msg(version, uri, new_context, kwargs, 'call', _(f'returns {returns}'))
|
||||
# valid returns
|
||||
self.valid_call_returns(risotto_context,
|
||||
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
|
||||
|
||||
async def publish(self, version, uri, risotto_context, public_only=False, **kwargs):
|
||||
new_context = self.new_context(risotto_context,
|
||||
version,
|
||||
uri)
|
||||
self.check_pattern(version,
|
||||
uri,
|
||||
'event',
|
||||
new_context,
|
||||
kwargs)
|
||||
|
||||
class PublishDispatcher:
|
||||
async def publish(self, version, message, old_risotto_context, public_only=False, **kwargs):
|
||||
risotto_context = self.build_new_context(old_risotto_context,
|
||||
version,
|
||||
message,
|
||||
'event')
|
||||
self.check_message_type(risotto_context,
|
||||
kwargs)
|
||||
try:
|
||||
config = self.set_config(uri,
|
||||
kwargs)
|
||||
config_arguments = config.option(uri).value.dict()
|
||||
config = self.load_kwargs_to_config(risotto_context,
|
||||
kwargs)
|
||||
config_arguments = config.option(message).value.dict()
|
||||
except CallError as err:
|
||||
return
|
||||
except Exception as err:
|
||||
# if there is a problem with arguments, just send an error et do nothing
|
||||
if DEBUG:
|
||||
print_exc()
|
||||
log.error_msg(version, uri, new_context, kwargs, 'publish', err)
|
||||
log.error_msg(risotto_context, kwargs, err)
|
||||
return
|
||||
|
||||
# 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']
|
||||
module_name = function.__module__.split('.')[-2]
|
||||
function_name = function.__name__
|
||||
@ -318,18 +161,87 @@ class Dispatcher(RegisterDispatcher):
|
||||
if key in function_obj['arguments']:
|
||||
kw[key] = value
|
||||
if function_obj['risotto_context']:
|
||||
kw['risotto_context'] = new_context
|
||||
kw['risotto_context'] = risotto_context
|
||||
# send event
|
||||
await function(self.modules[function_obj['module']], **kw)
|
||||
returns = await function(self.injected_self[function_obj['module']], **kw)
|
||||
except Exception as err:
|
||||
if DEBUG:
|
||||
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:
|
||||
module_name = function.__module__.split('.')[-2]
|
||||
function_name = function.__name__
|
||||
log.info_msg(version, uri, new_context, kwargs,'publish', info_msg)
|
||||
log.info_msg(risotto_context, kwargs, info_msg)
|
||||
# notification
|
||||
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()
|
||||
|
||||
register.dispatcher = dispatcher
|
||||
|
@ -1,23 +1,73 @@
|
||||
from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound
|
||||
from tiramisu import Config
|
||||
from json import dumps
|
||||
from traceback import print_exc
|
||||
from tiramisu import Config
|
||||
|
||||
|
||||
from .dispatcher import dispatcher
|
||||
from .utils import _
|
||||
from .context import Context
|
||||
from .error import CallError, NotAllowedError
|
||||
from .error import CallError, NotAllowedError, RegistrationError
|
||||
from .message import get_messages
|
||||
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):
|
||||
version, uri = request.match_info.get_info()['path'].rsplit('/', 2)[-2:]
|
||||
context = Context()
|
||||
context.username = request.match_info.get('username', "Anonymous")
|
||||
risotto_context = create_context(request)
|
||||
kwargs = await request.json()
|
||||
try:
|
||||
text = await dispatcher.call(version,
|
||||
uri,
|
||||
context,
|
||||
risotto_context,
|
||||
public_only=True,
|
||||
**kwargs)
|
||||
except NotAllowedError as err:
|
||||
@ -25,48 +75,59 @@ async def handle(request):
|
||||
except CallError as err:
|
||||
raise HTTPBadRequest(reason=str(err))
|
||||
except Exception as err:
|
||||
if DEBUG:
|
||||
print_exc()
|
||||
raise HTTPInternalServerError(reason=str(err))
|
||||
return Response(text=dumps({'response': text}))
|
||||
|
||||
|
||||
async def api(request):
|
||||
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'))
|
||||
async def api(request, risotto_context):
|
||||
global tiramisu
|
||||
if not tiramisu:
|
||||
config = Config(get_messages(load_shortarg=True,
|
||||
only_public=True)[1])
|
||||
config.property.read_write()
|
||||
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
|
||||
"""
|
||||
app = Application()
|
||||
global extra_routes
|
||||
load_services()
|
||||
app = Application(loop=loop)
|
||||
routes = []
|
||||
uris = list(dispatcher.uris.items())
|
||||
uris.sort()
|
||||
for version, uris in dispatcher.uris.items():
|
||||
for version, messages in dispatcher.messages.items():
|
||||
print()
|
||||
print(_('======== Registered messages ========'))
|
||||
for uri in uris:
|
||||
web_uri = f'/api/{version}/{uri}'
|
||||
if dispatcher.messages[uri]['public']:
|
||||
print(f' - {web_uri}')
|
||||
for message in messages:
|
||||
web_message = f'/api/{version}/{message}'
|
||||
if dispatcher.messages[version][message]['public']:
|
||||
print(f' - {web_message}')
|
||||
else:
|
||||
pattern = dispatcher.messages[uri]['pattern']
|
||||
print(f' - {web_uri} (private {pattern})')
|
||||
routes.append(post(web_uri, handle))
|
||||
routes.append(get(f'/api/{version}', api))
|
||||
pattern = dispatcher.messages[version][message]['pattern']
|
||||
print(f' - {web_message} (private {pattern})')
|
||||
routes.append(post(web_message, handle))
|
||||
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)
|
||||
return app
|
||||
await dispatcher.on_join()
|
||||
return await loop.create_server(app.make_handler(), '*', HTTP_PORT)
|
||||
|
||||
|
||||
tiramisu = None
|
||||
|
@ -1,6 +1,7 @@
|
||||
from typing import Dict
|
||||
from .context import Context
|
||||
from .utils import _
|
||||
from .config import DEBUG
|
||||
|
||||
|
||||
class Logger:
|
||||
@ -8,40 +9,39 @@ class Logger:
|
||||
FIXME should add event to a database
|
||||
"""
|
||||
def _get_message_paths(self,
|
||||
risotto_context: Context,
|
||||
type: str):
|
||||
risotto_context: Context):
|
||||
paths = risotto_context.paths
|
||||
if len(paths) == 1:
|
||||
paths_msg = f' messages {type}ed: {paths[0]}'
|
||||
if risotto_context.type:
|
||||
paths_msg = f' {risotto_context.type} '
|
||||
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 += ':'
|
||||
return paths_msg
|
||||
|
||||
def error_msg(self,
|
||||
version: 'str',
|
||||
message: 'str',
|
||||
risotto_context: Context,
|
||||
arguments,
|
||||
type: str,
|
||||
error: str,
|
||||
msg: str=''):
|
||||
""" send message when an error append
|
||||
"""
|
||||
paths_msg = self._get_message_paths(risotto_context, type)
|
||||
print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}" {msg})'))
|
||||
paths_msg = self._get_message_paths(risotto_context)
|
||||
# if DEBUG:
|
||||
print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}": {msg})'))
|
||||
|
||||
def info_msg(self,
|
||||
version: 'str',
|
||||
message: 'str',
|
||||
risotto_context: Context,
|
||||
arguments: Dict,
|
||||
type: str,
|
||||
msg: str=''):
|
||||
""" send message with common information
|
||||
"""
|
||||
if risotto_context.paths:
|
||||
paths_msg = self._get_message_paths(risotto_context, type)
|
||||
paths_msg = self._get_message_paths(risotto_context)
|
||||
else:
|
||||
paths_msg = ''
|
||||
tmsg = _(f'{risotto_context.username}: INFO:{paths_msg}')
|
||||
@ -50,7 +50,8 @@ class Logger:
|
||||
if msg:
|
||||
tmsg += f' {msg}'
|
||||
|
||||
print(tmsg)
|
||||
if DEBUG:
|
||||
print(tmsg)
|
||||
|
||||
|
||||
log = Logger()
|
||||
|
@ -4,7 +4,7 @@ from glob import glob
|
||||
|
||||
from tiramisu import StrOption, IntOption, BoolOption, ChoiceOption, OptionDescription, SymLinkOption, \
|
||||
Config, Calculation, Params, ParamOption, ParamValue, calc_value, calc_value_property_help, \
|
||||
groups
|
||||
groups, Option
|
||||
|
||||
from yaml import load, SafeLoader
|
||||
from os import listdir
|
||||
@ -16,6 +16,25 @@ from ..utils import _
|
||||
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:
|
||||
"""
|
||||
A MessageDefinition is a representation of a message in the Zephir application messaging context
|
||||
@ -130,11 +149,13 @@ class ResponseDefinition:
|
||||
'type',
|
||||
'ref',
|
||||
'parameters',
|
||||
'required')
|
||||
'required',
|
||||
'multi')
|
||||
|
||||
def __init__(self, responses):
|
||||
self.ref = None
|
||||
self.parameters = None
|
||||
self.multi = False
|
||||
self.required = []
|
||||
for key, value in responses.items():
|
||||
if key in ['parameters', 'required']:
|
||||
@ -142,6 +163,7 @@ class ResponseDefinition:
|
||||
elif key == 'type':
|
||||
if value.startswith('[]'):
|
||||
tvalue = value[2:]
|
||||
self.multi = True
|
||||
else:
|
||||
tvalue = value
|
||||
if tvalue in customtypes:
|
||||
@ -414,8 +436,15 @@ def _get_option(name,
|
||||
if hasattr(arg, 'default'):
|
||||
kwargs['default'] = arg.default
|
||||
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)
|
||||
elif type_ == 'Any':
|
||||
return AnyOption(**kwargs)
|
||||
elif 'Number' in type_ or type_ == 'ID' or type_ == 'Integer':
|
||||
return IntOption(**kwargs)
|
||||
elif type_ == 'Boolean':
|
||||
@ -440,7 +469,7 @@ def _parse_args(message_def,
|
||||
for name, arg in new_options.items():
|
||||
current_opt = _get_option(name, arg, file_path, select_option, optiondescription)
|
||||
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))
|
||||
|
||||
|
||||
@ -448,53 +477,46 @@ def _parse_responses(message_def,
|
||||
file_path):
|
||||
"""build option with returns
|
||||
"""
|
||||
responses = OrderedDict()
|
||||
if message_def.response:
|
||||
keys = {'': {'description': '',
|
||||
'columns': {}}}
|
||||
provides = {}
|
||||
to_list = True
|
||||
param_type = message_def.response.type
|
||||
if message_def.response.parameters is None:
|
||||
raise Exception('not implemented yet')
|
||||
#name = 'response'
|
||||
#keys['']['columns'][name] = {'description': message_def.response.description,
|
||||
# 'type': message_def.response.type}
|
||||
#responses = {}
|
||||
#responses['keys'] = keys
|
||||
#return responses
|
||||
|
||||
if param_type.startswith('[]'):
|
||||
to_list = False
|
||||
param_type = param_type[2:]
|
||||
if param_type in ['Dict', 'File']:
|
||||
pass
|
||||
options = []
|
||||
names = []
|
||||
for name, obj in message_def.response.parameters.items():
|
||||
if name in names:
|
||||
raise Exception('multi response with name {} in {}'.format(name, file_path))
|
||||
names.append(name)
|
||||
|
||||
if message_def.response.parameters is not None:
|
||||
for name, obj in message_def.response.parameters.items():
|
||||
if name in responses:
|
||||
raise Exception('multi response with name {} in {}'.format(name, file_path))
|
||||
|
||||
descr = obj.description.strip().rstrip()
|
||||
keys['']['columns'][name] = {'description': obj.description,
|
||||
'type': obj.type}
|
||||
ref = obj.ref
|
||||
if ref:
|
||||
provides[name] = ref
|
||||
else:
|
||||
keys['']['columns'][name] = {'description': descr,
|
||||
'type': obj.type}
|
||||
ref = obj.ref
|
||||
if ref:
|
||||
provides[name] = ref
|
||||
responses['keys'] = keys
|
||||
responses['to_list'] = to_list
|
||||
responses['to_dict'] = False
|
||||
responses['provides'] = provides
|
||||
kwargs = {'name': name,
|
||||
'doc': obj.description.strip().rstrip()}
|
||||
type_ = obj.type
|
||||
if type_.startswith('[]'):
|
||||
kwargs['multi'] = True
|
||||
type_ = type_[2:]
|
||||
option = {'String': StrOption,
|
||||
'Number': IntOption,
|
||||
'Boolean': BoolOption,
|
||||
'Dict': DictOption,
|
||||
# FIXME
|
||||
'File': StrOption}.get(type_)
|
||||
if not option:
|
||||
raise Exception(f'unknown param type {obj.type}')
|
||||
if hasattr(obj, 'default'):
|
||||
kwargs['default'] = obj.default
|
||||
else:
|
||||
name = 'response'
|
||||
keys['']['columns'][name] = {'description': message_def.response.description,
|
||||
'type': message_def.response.type}
|
||||
ref = message_def.response.ref
|
||||
if ref:
|
||||
provides[name] = ref
|
||||
responses['keys'] = keys
|
||||
responses['to_list'] = to_list
|
||||
responses['to_dict'] = True
|
||||
responses['provides'] = provides
|
||||
return responses
|
||||
kwargs['properties'] = ('mandatory',)
|
||||
options.append(option(**kwargs))
|
||||
od = OptionDescription(message_def.uri,
|
||||
message_def.response.description,
|
||||
options)
|
||||
od.impl_set_information('multi', message_def.response.multi)
|
||||
return od
|
||||
|
||||
|
||||
def _getoptions_from_yml(message_def,
|
||||
@ -564,7 +586,6 @@ def get_messages(load_shortarg=False, only_public=False):
|
||||
optiondescriptions = OrderedDict()
|
||||
optiondescriptions_name = []
|
||||
optiondescriptions_info = {}
|
||||
responses = OrderedDict()
|
||||
needs = OrderedDict()
|
||||
messages = list(list_messages())
|
||||
messages.sort()
|
||||
@ -586,9 +607,12 @@ def get_messages(load_shortarg=False, only_public=False):
|
||||
continue
|
||||
optiondescriptions_info[message_def.uri] = {'pattern': message_def.pattern,
|
||||
'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]
|
||||
if message_def.uri in responses:
|
||||
raise Exception('uri {} allready loader'.format(message_def.uri))
|
||||
_getoptions_from_yml(message_def,
|
||||
version,
|
||||
optiondescriptions,
|
||||
@ -596,8 +620,6 @@ def get_messages(load_shortarg=False, only_public=False):
|
||||
needs,
|
||||
select_option,
|
||||
load_shortarg)
|
||||
responses[message_def.uri] = _parse_responses(message_def,
|
||||
message_name)
|
||||
|
||||
root = _get_root_option(select_option, optiondescriptions)
|
||||
try:
|
||||
|
244
src/risotto/register.py
Normal file
244
src/risotto/register.py
Normal 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)
|
@ -4,15 +4,16 @@ from importlib import import_module
|
||||
from ..dispatcher import dispatcher
|
||||
|
||||
|
||||
def list_import():
|
||||
def load_services(modules=None,
|
||||
validate: bool=True):
|
||||
abs_here = dirname(abspath(__file__))
|
||||
here = basename(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)
|
||||
if isdir(absfilename) and isfile(join(absfilename, '__init__.py')):
|
||||
dispatcher.set_module(filename, import_module(f'.{here}.{filename}', module))
|
||||
dispatcher.validate()
|
||||
|
||||
|
||||
list_import()
|
||||
if validate:
|
||||
dispatcher.validate()
|
||||
|
@ -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 ...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):
|
||||
@register('v1.config.configuration.server.updated')
|
||||
async def server_created(self, server_id):
|
||||
print('pouet ' + str(server_id))
|
||||
servermodel = {}
|
||||
server = {}
|
||||
|
||||
@register('v1.config.session.server.start', None)
|
||||
async def get_configuration(self, risotto_context, id):
|
||||
#stop = await self.call('v1.config.session.server.stop', risotto_context, sessionid=str(id))
|
||||
#await self.publish('v1.config.configuration.server.updated', risotto_context, server_id=1, deploy=True)
|
||||
return {'start': id}
|
||||
def __init__(self) -> None:
|
||||
for dirname in [ROOT_CACHE_DIR, DATABASE_DIR]:
|
||||
if not isdir(dirname):
|
||||
raise RegistrationError(_(f'unable to find the cache dir "{dirname}"'))
|
||||
self.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR)
|
||||
super().__init__()
|
||||
|
||||
@register('v1.config.session.server.stop', None)
|
||||
async def get_configuration2(self, sessionid, save):
|
||||
return {'stop': sessionid}
|
||||
async def on_join(self,
|
||||
risotto_context: Context) -> None:
|
||||
""" 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)
|
||||
async def get_configuration3(self, server_id, deploy):
|
||||
return {'get': server_id}
|
||||
async def get_configuration(self,
|
||||
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)
|
||||
async def get_configuration4(self, server_id):
|
||||
return {'deploy': server_id}
|
||||
if deployed:
|
||||
server = self.server[server_id]['server']
|
||||
else:
|
||||
server = self.server[server_id]['server_to_deploy']
|
||||
|
||||
server.property.read_only()
|
||||
try:
|
||||
configuration = server.value.dict(fullpath=True)
|
||||
except:
|
||||
if deployed:
|
||||
msg = _(f'No configuration available for server {server_id}')
|
||||
else:
|
||||
msg = _(f'No undeployed configuration available for server {server_id}')
|
||||
log.error_msg(risotto_context,
|
||||
None,
|
||||
msg)
|
||||
raise CallError(msg)
|
||||
return {'server_id': server_id,
|
||||
'deployed': deployed,
|
||||
'configuration': configuration}
|
||||
|
||||
@register('v1.config.configuration.server.deploy', 'v1.config.configuration.server.updated')
|
||||
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}
|
||||
|
1
src/risotto/services/server/__init__.py
Normal file
1
src/risotto/services/server/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .server import Risotto
|
8
src/risotto/services/server/server.py
Normal file
8
src/risotto/services/server/server.py
Normal 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}]
|
1
src/risotto/services/servermodel/__init__.py
Normal file
1
src/risotto/services/servermodel/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .servermodel import Risotto
|
65
src/risotto/services/servermodel/servermodel.py
Normal file
65
src/risotto/services/servermodel/servermodel.py
Normal 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': ''}
|
1
src/risotto/services/session/__init__.py
Normal file
1
src/risotto/services/session/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .session import Risotto
|
261
src/risotto/services/session/session.py
Normal file
261
src/risotto/services/session/session.py
Normal 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')
|
134
src/risotto/services/session/storage.py
Normal file
134
src/risotto/services/session/storage.py
Normal 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()
|
1
src/risotto/services/template/__init__.py
Normal file
1
src/risotto/services/template/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .template import Risotto
|
49
src/risotto/services/template/template.py
Normal file
49
src/risotto/services/template/template.py
Normal 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
1
templates/1/mailname
Normal file
@ -0,0 +1 @@
|
||||
mode_conteneur_actif: %%mode_conteneur_actif
|
0
tests/fake_services/__init__.py
Normal file
0
tests/fake_services/__init__.py
Normal file
1
tests/fake_services/server/__init__.py
Normal file
1
tests/fake_services/server/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .server import Risotto
|
8
tests/fake_services/server/server.py
Normal file
8
tests/fake_services/server/server.py
Normal 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}]
|
1
tests/fake_services/servermodel/__init__.py
Normal file
1
tests/fake_services/servermodel/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .servermodel import Risotto
|
71
tests/fake_services/servermodel/servermodel.py
Normal file
71
tests/fake_services/servermodel/servermodel.py
Normal 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
334
tests/test_config.py
Normal 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
501
tests/test_session.py
Normal 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)
|
Reference in New Issue
Block a user