risotto/src/risotto/services/config/config.py

442 lines
20 KiB
Python
Raw Normal View History

2019-12-02 10:29:40 +01:00
from lxml.etree import parse
from io import BytesIO
2019-12-07 16:21:20 +01:00
from os import unlink
2019-12-02 10:29:40 +01:00
from os.path import isdir, isfile, join
from traceback import print_exc
2019-12-07 16:21:20 +01:00
from typing import Dict, List
2019-12-02 10:29:40 +01:00
2019-12-03 08:34:11 +01:00
from tiramisu import Storage, delete_session, MetaConfig, MixConfig
2019-12-02 10:29:40 +01:00
from rougail import load as rougail_load
2019-11-28 14:50:53 +01:00
from ...controller import Controller
2019-12-02 10:29:40 +01:00
from ...register import register
2019-12-28 12:29:11 +01:00
from ...config import DATABASE_DIR, ROUGAIL_DTD_PATH, get_config
2019-11-29 16:38:33 +01:00
from ...context import Context
2019-12-02 10:29:40 +01:00
from ...utils import _
2019-12-03 21:20:28 +01:00
from ...error import CallError, RegistrationError
2019-12-02 10:29:40 +01:00
from ...logger import log
2019-11-29 16:38:33 +01:00
2019-11-28 14:50:53 +01:00
class Risotto(Controller):
2019-11-29 16:38:33 +01:00
2019-12-26 15:33:51 +01:00
def __init__(self,
test) -> None:
global conf_storage
2019-12-19 15:00:24 +01:00
self.cache_root_path = join(get_config().get('cache').get('root_path'), 'servermodel')
for dirname in [self.cache_root_path, DATABASE_DIR]:
2019-12-03 21:20:28 +01:00
if not isdir(dirname):
raise RegistrationError(_(f'unable to find the cache dir "{dirname}"'))
2019-12-26 15:33:51 +01:00
if not test:
self.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR)
2019-12-13 16:42:10 +01:00
self.servermodel = {}
self.server = {}
2019-12-26 15:33:51 +01:00
super().__init__(test)
2019-11-29 16:38:33 +01:00
2019-12-02 10:29:40 +01:00
async def on_join(self,
risotto_context: Context) -> None:
""" pre-load servermodel and server
"""
await self.load_servermodels(risotto_context)
2019-12-02 14:22:40 +01:00
await self.load_servers(risotto_context)
2019-11-29 16:38:33 +01:00
2019-12-02 10:29:40 +01:00
async def load_servermodels(self,
risotto_context: Context) -> None:
""" load all available servermodels
"""
2019-12-28 12:29:11 +01:00
await log.info_msg(risotto_context,
None,
'Load servermodels')
2019-12-02 10:29:40 +01:00
servermodels = await self.call('v1.servermodel.list',
risotto_context)
# load each servermodels
2019-11-29 16:38:33 +01:00
for servermodel in servermodels:
try:
2019-12-02 10:29:40 +01:00
await self.load_servermodel(risotto_context,
2019-12-13 16:42:10 +01:00
servermodel['servermodel_id'],
servermodel['servermodel_name'])
2019-12-02 10:29:40 +01:00
except CallError as err:
pass
# do link to this servermodel
2019-11-29 16:38:33 +01:00
for servermodel in servermodels:
2019-12-13 16:42:10 +01:00
if 'servermodel_parents_id' in servermodel:
for servermodelparentid in servermodel['servermodel_parents_id']:
2019-12-26 11:38:31 +01:00
await self.servermodel_legacy(risotto_context,
servermodel['servermodel_name'],
servermodel['servermodel_id'],
servermodelparentid)
2019-11-29 16:38:33 +01:00
2019-12-02 14:22:40 +01:00
def get_funcs_filename(self,
2019-12-13 16:42:10 +01:00
servermodel_id: int):
2019-12-20 10:58:12 +01:00
return join(self.cache_root_path, str(servermodel_id), "funcs.py")
2019-11-29 16:38:33 +01:00
2019-12-02 10:29:40 +01:00
async def load_servermodel(self,
risotto_context: Context,
2019-12-13 16:42:10 +01:00
servermodel_id: int,
servermodel_name: str) -> None:
2019-12-02 10:29:40 +01:00
""" Loads a servermodel
"""
2019-12-20 10:58:12 +01:00
cache_file = join(self.cache_root_path, str(servermodel_id), "dictionaries.xml")
2019-12-13 16:42:10 +01:00
funcs_file = self.get_funcs_filename(servermodel_id)
2019-12-28 12:29:11 +01:00
await log.info_msg(risotto_context,
None,
f'Load servermodel {servermodel_name} ({servermodel_id})')
2019-11-29 16:38:33 +01:00
2019-12-26 15:33:51 +01:00
# use file in cache
2019-12-18 17:11:42 +01:00
with open(cache_file) as fileio:
xmlroot = parse(fileio).getroot()
2020-01-13 19:53:09 +01:00
try:
self.servermodel[servermodel_id] = await self.build_metaconfig(servermodel_id,
servermodel_name,
xmlroot,
funcs_file)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
msg = _(f'unable to load {servermodel_name}: {err}')
await log.error_msg(risotto_context,
None,
msg)
2019-12-26 11:38:31 +01:00
async def build_metaconfig(self,
servermodel_id: int,
servermodel_name: str,
xmlroot: str,
funcs_file: str) -> MetaConfig:
2019-12-02 10:29:40 +01:00
""" Build metaconfig for a servermodel
"""
# build tiramisu's session ID
2019-12-13 16:42:10 +01:00
session_id = f'v_{servermodel_id}'
2019-12-02 10:29:40 +01:00
optiondescription = rougail_load(xmlroot,
ROUGAIL_DTD_PATH,
funcs_file)
# build servermodel metaconfig (v_xxx.m_v_xxx)
2019-12-26 11:38:31 +01:00
metaconfig = await MetaConfig([],
optiondescription=optiondescription,
persistent=True,
session_id=session_id,
storage=self.save_storage)
mixconfig = await MixConfig(children=[],
optiondescription=optiondescription,
persistent=True,
session_id='m_' + session_id,
storage=self.save_storage)
await metaconfig.config.add(mixconfig)
2019-12-02 10:29:40 +01:00
# change default rights
2019-12-26 11:38:31 +01:00
ro_origin = await metaconfig.property.getdefault('read_only', 'append')
2019-12-02 10:29:40 +01:00
ro_append = frozenset(ro_origin - {'force_store_value'})
2019-12-26 11:38:31 +01:00
rw_origin = await metaconfig.property.getdefault('read_write', 'append')
2019-12-02 10:29:40 +01:00
rw_append = frozenset(rw_origin - {'force_store_value'})
2019-12-26 11:38:31 +01:00
await metaconfig.property.setdefault(ro_append, 'read_only', 'append')
await metaconfig.property.setdefault(rw_append, 'read_write', 'append')
2019-12-02 10:29:40 +01:00
2019-12-26 11:38:31 +01:00
await metaconfig.property.read_only()
await metaconfig.permissive.add('basic')
await metaconfig.permissive.add('normal')
await metaconfig.permissive.add('expert')
2019-12-02 10:29:40 +01:00
# set informtion and owner
2019-12-26 11:38:31 +01:00
await metaconfig.owner.set('v_{}'.format(servermodel_name))
await metaconfig.information.set('servermodel_id', servermodel_id)
await metaconfig.information.set('servermodel_name', servermodel_name)
2019-12-02 10:29:40 +01:00
# return configuration
return metaconfig
2019-12-26 11:38:31 +01:00
async def servermodel_legacy(self,
risotto_context: Context,
servermodel_name: str,
servermodel_id: int,
servermodel_parent_id: int) -> None:
2019-12-02 10:29:40 +01:00
""" Make link between parent and children
"""
2019-11-29 16:38:33 +01:00
if servermodel_parent_id is None:
return
if not self.servermodel.get(servermodel_parent_id):
2019-12-28 12:29:11 +01:00
msg = _(f'Servermodel with id {servermodel_parent_id} not loaded, skipping legacy for servermodel {servermodel_name} ({servermodel_id})')
await log.error_msg(risotto_context,
None,
msg)
2019-11-29 16:38:33 +01:00
return
servermodel_parent = self.servermodel[servermodel_parent_id]
2019-12-26 11:38:31 +01:00
servermodel_parent_name = await servermodel_parent.information.get('servermodel_name')
2019-12-28 12:29:11 +01:00
msg = _(f'Create legacy of servermodel {servermodel_name} ({servermodel_id}) with parent {servermodel_parent_name} ({servermodel_parent_id})')
await log.info_msg(risotto_context,
None,
msg)
2019-12-02 10:29:40 +01:00
# do link
2019-12-26 11:38:31 +01:00
mix = await servermodel_parent.config.get('m_v_' + str(servermodel_parent_id))
2019-11-29 16:38:33 +01:00
try:
2019-12-26 11:38:31 +01:00
await mix.config.add(self.servermodel[servermodel_id])
2019-11-29 16:38:33 +01:00
except Exception as err:
2019-12-28 12:29:11 +01:00
await log.error_msg(risotto_context,
None,
str(err))
2019-11-29 16:38:33 +01:00
2019-12-02 10:29:40 +01:00
async def load_servers(self,
risotto_context: Context) -> None:
""" load all available servers
"""
2019-12-28 12:29:11 +01:00
await log.info_msg(risotto_context,
None,
f'Load servers')
2019-12-02 10:29:40 +01:00
# get all servers
servers = await self.call('v1.server.list',
risotto_context)
# loads servers
2019-11-29 16:38:33 +01:00
for server in servers:
try:
2019-12-26 11:38:31 +01:00
await self.load_server(risotto_context,
server['server_id'],
server['server_name'],
server['server_servermodel_id'])
2019-11-29 16:38:33 +01:00
except Exception as err:
2020-01-13 19:53:09 +01:00
if get_config().get('global').get('debug'):
2019-12-02 14:22:40 +01:00
print_exc()
2019-12-19 17:24:20 +01:00
server_name = server['server_name']
2019-12-02 14:22:40 +01:00
server_id = server['server_id']
2019-12-19 17:24:20 +01:00
msg = _(f'unable to load server {server_name} ({server_id}): {err}')
2019-12-28 12:29:11 +01:00
await log.error_msg(risotto_context,
None,
msg)
2019-12-02 10:29:40 +01:00
2019-12-26 11:38:31 +01:00
async def load_server(self,
risotto_context: Context,
server_id: int,
server_name: str,
server_servermodel_id: int) -> None:
2019-12-02 10:29:40 +01:00
""" Loads a server
"""
2019-12-02 14:22:40 +01:00
if server_id in self.server:
2019-11-29 16:38:33 +01:00
return
2019-12-28 12:29:11 +01:00
await log.info_msg(risotto_context,
None,
f'Load server {server_name} ({server_id})')
2019-12-19 17:24:20 +01:00
if not server_servermodel_id in self.servermodel:
msg = f'unable to find servermodel with id {server_servermodel_id}'
2019-12-28 12:29:11 +01:00
await log.error_msg(risotto_context,
None,
msg)
2019-12-02 10:29:40 +01:00
raise CallError(msg)
2019-12-13 16:42:10 +01:00
2019-12-02 10:29:40 +01:00
# check if server was already created
2019-12-02 14:22:40 +01:00
session_id = f's_{server_id}'
2019-12-02 10:29:40 +01:00
# get the servermodel's metaconfig
2019-12-19 17:24:20 +01:00
metaconfig = self.servermodel[server_servermodel_id]
2019-12-02 10:29:40 +01:00
# create server configuration and server 'to deploy' configuration and store it
2019-12-26 11:38:31 +01:00
self.server[server_id] = {'server': await self.build_config(session_id,
server_id,
server_name,
metaconfig),
'server_to_deploy': await self.build_config(f'std_{server_id}',
server_id,
server_name,
metaconfig),
2019-12-19 17:24:20 +01:00
'funcs_file': self.get_funcs_filename(server_servermodel_id)}
2019-12-02 10:29:40 +01:00
2019-12-26 11:38:31 +01:00
async def build_config(self,
session_id: str,
server_id: int,
server_name: str,
metaconfig: MetaConfig) -> None:
2019-12-02 10:29:40 +01:00
""" build server's config
"""
2019-12-26 11:38:31 +01:00
config = await metaconfig.config.new(session_id,
storage=self.save_storage,
persistent=True)
await config.information.set('server_id', server_id)
await config.information.set('server_name', server_name)
await config.owner.set(server_name)
await config.property.read_only()
2019-12-02 14:22:40 +01:00
return config
2019-12-02 10:29:40 +01:00
@register('v1.server.created')
async def server_created(self,
2019-12-02 14:22:40 +01:00
risotto_context: Context,
server_id: int,
2019-12-19 17:24:20 +01:00
server_name: str,
server_servermodel_id: int) -> None:
2019-12-02 10:29:40 +01:00
""" Loads server's configuration when a new server is created
"""
2019-12-26 11:38:31 +01:00
await self.load_server(risotto_context,
server_id,
server_name,
server_servermodel_id)
2019-12-02 10:29:40 +01:00
@register('v1.server.deleted')
async def server_deleted(self,
2019-12-02 14:22:40 +01:00
server_id: int) -> None:
2019-12-02 10:29:40 +01:00
# delete config to it's parents
2019-12-07 16:21:20 +01:00
for server_type in ['server', 'server_to_deploy']:
config = self.server[server_id]['server']
2019-12-26 11:38:31 +01:00
for parent in await config.config.parents():
2019-12-26 15:33:51 +01:00
await parent.config.pop(await config.config.name())
2019-12-07 16:21:20 +01:00
delete_session(storage=self.save_storage,
2019-12-26 11:38:31 +01:00
session_id=await config.config.name())
2019-12-02 10:29:40 +01:00
# delete metaconfig
2019-12-02 14:22:40 +01:00
del self.server[server_id]
2019-12-02 10:29:40 +01:00
@register('v1.servermodel.created')
async def servermodel_created(self,
2019-12-07 16:21:20 +01:00
risotto_context: Context,
2019-12-13 16:42:10 +01:00
servermodel_id: int,
servermodel_name: str,
servermodel_parents_id: List[int]) -> None:
2019-12-02 10:29:40 +01:00
""" when servermodels are created, load it and do link
"""
2019-12-07 16:21:20 +01:00
await self.load_and_link_servermodel(risotto_context,
2019-12-13 16:42:10 +01:00
servermodel_id,
servermodel_name,
servermodel_parents_id)
2019-12-07 16:21:20 +01:00
async def load_and_link_servermodel(self,
risotto_context: Context,
2019-12-13 16:42:10 +01:00
servermodel_id: int,
servermodel_name: str,
servermodel_parents_id: List[int]) -> None:
2019-12-07 16:21:20 +01:00
await self.load_servermodel(risotto_context,
2019-12-13 16:42:10 +01:00
servermodel_id,
servermodel_name)
if servermodel_parents_id is not None:
for servermodelparentid in servermodel_parents_id:
2019-12-26 11:38:31 +01:00
await self.servermodel_legacy(risotto_context,
servermodel_name,
servermodel_id,
servermodelparentid)
2019-12-07 16:21:20 +01:00
2019-12-26 11:38:31 +01:00
async def servermodel_delete(self,
servermodel_id: int) -> List[MetaConfig]:
2019-12-13 16:42:10 +01:00
metaconfig = self.servermodel.pop(servermodel_id)
2019-12-26 11:38:31 +01:00
mixconfig = await metaconfig.config.list()[0]
2019-12-07 16:21:20 +01:00
children = []
2019-12-26 11:38:31 +01:00
for child in await mixconfig.config.list():
2019-12-07 16:21:20 +01:00
children.append(child)
2019-12-26 11:38:31 +01:00
await mixconfig.config.pop(await child.config.name())
await metaconfig.config.pop(await mixconfig.config.name())
2019-12-07 16:21:20 +01:00
delete_session(storage=self.save_storage,
2019-12-26 11:38:31 +01:00
session_id=await mixconfig.config.name())
2019-12-07 16:21:20 +01:00
del mixconfig
2019-12-26 11:38:31 +01:00
for parent in await metaconfig.config.parents():
await parent.config.pop(await metaconfig.config.name())
2019-12-07 16:21:20 +01:00
delete_session(storage=self.save_storage,
2019-12-26 11:38:31 +01:00
session_id=await metaconfig.config.name())
2019-12-07 16:21:20 +01:00
return children
2019-12-26 15:33:51 +01:00
#
# @register('v1.servermodel.updated')
# async def servermodel_updated(self,
# risotto_context: Context,
# servermodel_id: int,
# servermodel_name: str,
# servermodel_parents_id: List[int]) -> None:
# log.info_msg(risotto_context,
# None,
# f'Reload servermodel {servermodel_name} ({servermodel_id})')
# # unlink cache to force download new aggregated file
# cache_file = join(self.cache_root_path, str(servermodel_id)+".xml")
# if isfile(cache_file):
# unlink(cache_file)
#
# # store all informations
# if servermodel_id in self.servermodel:
# old_values = await self.servermodel[servermodel_id].value.exportation()
# old_permissives = await self.servermodel[servermodel_id].permissive.exportation()
# old_properties = await self.servermodel[servermodel_id].property.exportation()
# children = await self.servermodel_delete(servermodel_id)
# else:
# old_values = None
#
# # create new one
# await self.load_and_link_servermodel(risotto_context,
# servermodel_id,
# servermodel_name,
# servermodel_parents_id)
#
# # migrates informations
# if old_values is not None:
# await self.servermodel[servermodel_id].value.importation(old_values)
# await self.servermodel[servermodel_id].permissive.importation(old_permissives)
# await self.servermodel[servermodel_id].property.importation(old_properties)
# for child in children:
# await self.servermodel_legacy(risotto_context,
# await child.information.get('servermodel_name'),
# await child.information.get('servermodel_id'),
# servermodel_id)
2019-11-29 16:38:33 +01:00
2020-01-14 14:11:41 +01:00
@register('v1.config.configuration.server.get')
2019-12-02 10:29:40 +01:00
async def get_configuration(self,
2020-01-13 19:53:09 +01:00
risotto_context: Context,
server_name: str,
2019-12-07 16:21:20 +01:00
deployed: bool) -> bytes:
2020-01-13 19:53:09 +01:00
server = await self.call('v1.server.describe',
risotto_context,
server_name=server_name)
server_id = server['server_id']
2019-12-02 10:29:40 +01:00
if server_id not in self.server:
msg = _(f'cannot find server with id {server_id}')
2019-12-28 12:29:11 +01:00
await log.error_msg(risotto_context,
None,
msg)
2019-12-02 10:29:40 +01:00
raise CallError(msg)
2019-12-07 16:21:20 +01:00
if deployed:
2019-12-02 10:29:40 +01:00
server = self.server[server_id]['server']
else:
server = self.server[server_id]['server_to_deploy']
2019-12-26 11:38:31 +01:00
await server.property.read_only()
2019-12-02 10:29:40 +01:00
try:
2020-01-13 19:53:09 +01:00
configuration = await server.value.dict(fullpath=True,
leader_to_list=True)
2019-12-02 10:29:40 +01:00
except:
2019-12-07 16:21:20 +01:00
if deployed:
2019-12-02 10:29:40 +01:00
msg = _(f'No configuration available for server {server_id}')
else:
msg = _(f'No undeployed configuration available for server {server_id}')
2019-12-28 12:29:11 +01:00
await log.error_msg(risotto_context,
None,
msg)
2019-12-02 10:29:40 +01:00
raise CallError(msg)
2020-01-13 19:53:09 +01:00
return {'server_name': server_name,
2019-12-07 16:21:20 +01:00
'deployed': deployed,
'configuration': configuration}
2019-11-29 16:38:33 +01:00
@register('v1.config.configuration.server.deploy', 'v1.config.configuration.server.updated')
2019-12-02 10:29:40 +01:00
async def deploy_configuration(self,
risotto_context: Context,
server_name: str) -> Dict:
2019-11-29 16:38:33 +01:00
"""Copy values, permissions, permissives from config 'to deploy' to active config
"""
server = await self.call('v1.server.describe',
risotto_context,
server_name=server_name)
server_id = server['server_id']
# FIXME is server_to_deploy working?
2019-12-02 10:29:40 +01:00
config = self.server[server_id]['server']
config_std = self.server[server_id]['server_to_deploy']
2019-11-29 16:38:33 +01:00
2019-12-02 10:29:40 +01:00
# when deploy, calculate force_store_value
2019-12-26 11:38:31 +01:00
ro = await config_std.property.getdefault('read_only', 'append')
2019-11-29 16:38:33 +01:00
if 'force_store_value' not in ro:
ro = frozenset(list(ro) + ['force_store_value'])
2019-12-26 11:38:31 +01:00
await config_std.property.setdefault(ro, 'read_only', 'append')
rw = await config_std.property.getdefault('read_write', 'append')
2019-11-29 16:38:33 +01:00
rw = frozenset(list(rw) + ['force_store_value'])
2019-12-26 11:38:31 +01:00
await config_std.property.setdefault(rw, 'read_write', 'append')
await config_std.property.add('force_store_value')
2019-11-29 16:38:33 +01:00
2019-12-02 10:29:40 +01:00
# copy informations from server 'to deploy' configuration to server configuration
2019-12-26 11:38:31 +01:00
await config.value.importation(await config_std.value.exportation())
await config.permissive.importation(await config_std.permissive.exportation())
await config.property.importation(await config_std.property.exportation())
2019-11-29 16:38:33 +01:00
2019-12-02 10:29:40 +01:00
return {'server_id': server_id,
'server_name': server_name,
2019-12-07 16:21:20 +01:00
'deployed': True}