442 lines
20 KiB
Python
442 lines
20 KiB
Python
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 ...register import register
|
|
from ...config import DATABASE_DIR, ROUGAIL_DTD_PATH, get_config
|
|
from ...context import Context
|
|
from ...utils import _
|
|
from ...error import CallError, RegistrationError
|
|
from ...logger import log
|
|
|
|
|
|
class Risotto(Controller):
|
|
|
|
def __init__(self,
|
|
test) -> None:
|
|
global conf_storage
|
|
self.cache_root_path = join(get_config().get('cache').get('root_path'), 'servermodel')
|
|
for dirname in [self.cache_root_path, DATABASE_DIR]:
|
|
if not isdir(dirname):
|
|
raise RegistrationError(_(f'unable to find the cache dir "{dirname}"'))
|
|
if not test:
|
|
self.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR)
|
|
self.servermodel = {}
|
|
self.server = {}
|
|
super().__init__(test)
|
|
|
|
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
|
|
"""
|
|
await 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['servermodel_id'],
|
|
servermodel['servermodel_name'])
|
|
except CallError as err:
|
|
pass
|
|
|
|
# do link to this servermodel
|
|
for servermodel in servermodels:
|
|
if 'servermodel_parents_id' in servermodel:
|
|
for servermodelparentid in servermodel['servermodel_parents_id']:
|
|
await self.servermodel_legacy(risotto_context,
|
|
servermodel['servermodel_name'],
|
|
servermodel['servermodel_id'],
|
|
servermodelparentid)
|
|
|
|
def get_funcs_filename(self,
|
|
servermodel_id: int):
|
|
return join(self.cache_root_path, str(servermodel_id), "funcs.py")
|
|
|
|
async def load_servermodel(self,
|
|
risotto_context: Context,
|
|
servermodel_id: int,
|
|
servermodel_name: str) -> None:
|
|
""" Loads a servermodel
|
|
"""
|
|
cache_file = join(self.cache_root_path, str(servermodel_id), "dictionaries.xml")
|
|
funcs_file = self.get_funcs_filename(servermodel_id)
|
|
await log.info_msg(risotto_context,
|
|
None,
|
|
f'Load servermodel {servermodel_name} ({servermodel_id})')
|
|
|
|
# use file in cache
|
|
with open(cache_file) as fileio:
|
|
xmlroot = parse(fileio).getroot()
|
|
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)
|
|
|
|
async def build_metaconfig(self,
|
|
servermodel_id: int,
|
|
servermodel_name: str,
|
|
xmlroot: str,
|
|
funcs_file: str) -> MetaConfig:
|
|
""" Build metaconfig for a servermodel
|
|
"""
|
|
# build tiramisu's session ID
|
|
session_id = f'v_{servermodel_id}'
|
|
optiondescription = rougail_load(xmlroot,
|
|
ROUGAIL_DTD_PATH,
|
|
funcs_file)
|
|
|
|
# build servermodel metaconfig (v_xxx.m_v_xxx)
|
|
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)
|
|
|
|
# change default rights
|
|
ro_origin = await metaconfig.property.getdefault('read_only', 'append')
|
|
ro_append = frozenset(ro_origin - {'force_store_value'})
|
|
rw_origin = await metaconfig.property.getdefault('read_write', 'append')
|
|
rw_append = frozenset(rw_origin - {'force_store_value'})
|
|
await metaconfig.property.setdefault(ro_append, 'read_only', 'append')
|
|
await metaconfig.property.setdefault(rw_append, 'read_write', 'append')
|
|
|
|
await metaconfig.property.read_only()
|
|
await metaconfig.permissive.add('basic')
|
|
await metaconfig.permissive.add('normal')
|
|
await metaconfig.permissive.add('expert')
|
|
|
|
# set informtion and owner
|
|
await metaconfig.owner.set('v_{}'.format(servermodel_name))
|
|
await metaconfig.information.set('servermodel_id', servermodel_id)
|
|
await metaconfig.information.set('servermodel_name', servermodel_name)
|
|
|
|
# return configuration
|
|
return metaconfig
|
|
|
|
async 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):
|
|
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)
|
|
return
|
|
servermodel_parent = self.servermodel[servermodel_parent_id]
|
|
servermodel_parent_name = await servermodel_parent.information.get('servermodel_name')
|
|
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)
|
|
|
|
# do link
|
|
mix = await servermodel_parent.config.get('m_v_' + str(servermodel_parent_id))
|
|
try:
|
|
await mix.config.add(self.servermodel[servermodel_id])
|
|
except Exception as err:
|
|
await log.error_msg(risotto_context,
|
|
None,
|
|
str(err))
|
|
|
|
async def load_servers(self,
|
|
risotto_context: Context) -> None:
|
|
""" load all available servers
|
|
"""
|
|
await 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:
|
|
await self.load_server(risotto_context,
|
|
server['server_id'],
|
|
server['server_name'],
|
|
server['server_servermodel_id'])
|
|
except Exception as err:
|
|
if get_config().get('global').get('debug'):
|
|
print_exc()
|
|
server_name = server['server_name']
|
|
server_id = server['server_id']
|
|
msg = _(f'unable to load server {server_name} ({server_id}): {err}')
|
|
await log.error_msg(risotto_context,
|
|
None,
|
|
msg)
|
|
|
|
async def load_server(self,
|
|
risotto_context: Context,
|
|
server_id: int,
|
|
server_name: str,
|
|
server_servermodel_id: int) -> None:
|
|
""" Loads a server
|
|
"""
|
|
if server_id in self.server:
|
|
return
|
|
await log.info_msg(risotto_context,
|
|
None,
|
|
f'Load server {server_name} ({server_id})')
|
|
if not server_servermodel_id in self.servermodel:
|
|
msg = f'unable to find servermodel with id {server_servermodel_id}'
|
|
await 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[server_servermodel_id]
|
|
|
|
# create server configuration and server 'to deploy' configuration and store it
|
|
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),
|
|
'funcs_file': self.get_funcs_filename(server_servermodel_id)}
|
|
|
|
async def build_config(self,
|
|
session_id: str,
|
|
server_id: int,
|
|
server_name: str,
|
|
metaconfig: MetaConfig) -> None:
|
|
""" build server's config
|
|
"""
|
|
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()
|
|
return config
|
|
|
|
@register('v1.server.created')
|
|
async def server_created(self,
|
|
risotto_context: Context,
|
|
server_id: int,
|
|
server_name: str,
|
|
server_servermodel_id: int) -> None:
|
|
""" Loads server's configuration when a new server is created
|
|
"""
|
|
await self.load_server(risotto_context,
|
|
server_id,
|
|
server_name,
|
|
server_servermodel_id)
|
|
|
|
@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 await config.config.parents():
|
|
await parent.config.pop(await config.config.name())
|
|
delete_session(storage=self.save_storage,
|
|
session_id=await config.config.name())
|
|
# delete metaconfig
|
|
del self.server[server_id]
|
|
|
|
@register('v1.servermodel.created')
|
|
async def servermodel_created(self,
|
|
risotto_context: Context,
|
|
servermodel_id: int,
|
|
servermodel_name: str,
|
|
servermodel_parents_id: List[int]) -> None:
|
|
""" when servermodels are created, load it and do link
|
|
"""
|
|
await self.load_and_link_servermodel(risotto_context,
|
|
servermodel_id,
|
|
servermodel_name,
|
|
servermodel_parents_id)
|
|
|
|
|
|
async def load_and_link_servermodel(self,
|
|
risotto_context: Context,
|
|
servermodel_id: int,
|
|
servermodel_name: str,
|
|
servermodel_parents_id: List[int]) -> None:
|
|
await self.load_servermodel(risotto_context,
|
|
servermodel_id,
|
|
servermodel_name)
|
|
if servermodel_parents_id is not None:
|
|
for servermodelparentid in servermodel_parents_id:
|
|
await self.servermodel_legacy(risotto_context,
|
|
servermodel_name,
|
|
servermodel_id,
|
|
servermodelparentid)
|
|
|
|
async def servermodel_delete(self,
|
|
servermodel_id: int) -> List[MetaConfig]:
|
|
metaconfig = self.servermodel.pop(servermodel_id)
|
|
mixconfig = await metaconfig.config.list()[0]
|
|
children = []
|
|
for child in await mixconfig.config.list():
|
|
children.append(child)
|
|
await mixconfig.config.pop(await child.config.name())
|
|
await metaconfig.config.pop(await mixconfig.config.name())
|
|
delete_session(storage=self.save_storage,
|
|
session_id=await mixconfig.config.name())
|
|
del mixconfig
|
|
for parent in await metaconfig.config.parents():
|
|
await parent.config.pop(await metaconfig.config.name())
|
|
delete_session(storage=self.save_storage,
|
|
session_id=await metaconfig.config.name())
|
|
return children
|
|
#
|
|
# @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)
|
|
|
|
@register('v1.config.configuration.server.get')
|
|
async def get_configuration(self,
|
|
risotto_context: Context,
|
|
server_name: str,
|
|
deployed: bool) -> bytes:
|
|
server = await self.call('v1.server.describe',
|
|
risotto_context,
|
|
server_name=server_name)
|
|
server_id = server['server_id']
|
|
if server_id not in self.server:
|
|
msg = _(f'cannot find server with id {server_id}')
|
|
await log.error_msg(risotto_context,
|
|
None,
|
|
msg)
|
|
raise CallError(msg)
|
|
|
|
if deployed:
|
|
server = self.server[server_id]['server']
|
|
else:
|
|
server = self.server[server_id]['server_to_deploy']
|
|
|
|
await server.property.read_only()
|
|
try:
|
|
configuration = await server.value.dict(fullpath=True,
|
|
leader_to_list=True)
|
|
except:
|
|
if deployed:
|
|
msg = _(f'No configuration available for server {server_id}')
|
|
else:
|
|
msg = _(f'No undeployed configuration available for server {server_id}')
|
|
await log.error_msg(risotto_context,
|
|
None,
|
|
msg)
|
|
raise CallError(msg)
|
|
return {'server_name': server_name,
|
|
'deployed': deployed,
|
|
'configuration': configuration}
|
|
|
|
@register('v1.config.configuration.server.deploy', 'v1.config.configuration.server.updated')
|
|
async def deploy_configuration(self,
|
|
risotto_context: Context,
|
|
server_name: str) -> Dict:
|
|
"""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?
|
|
config = self.server[server_id]['server']
|
|
config_std = self.server[server_id]['server_to_deploy']
|
|
|
|
# when deploy, calculate force_store_value
|
|
ro = await config_std.property.getdefault('read_only', 'append')
|
|
if 'force_store_value' not in ro:
|
|
ro = frozenset(list(ro) + ['force_store_value'])
|
|
await config_std.property.setdefault(ro, 'read_only', 'append')
|
|
rw = await config_std.property.getdefault('read_write', 'append')
|
|
rw = frozenset(list(rw) + ['force_store_value'])
|
|
await config_std.property.setdefault(rw, 'read_write', 'append')
|
|
await config_std.property.add('force_store_value')
|
|
|
|
# copy informations from server 'to deploy' configuration to server configuration
|
|
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())
|
|
|
|
return {'server_id': server_id,
|
|
'server_name': server_name,
|
|
'deployed': True}
|