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

537 lines
24 KiB
Python

#!/usr/bin/env python3
#import logging
#from lxml.etree import parse
#from io import StringIO
#from autobahn.wamp.exception import ApplicationError
#import asyncio
from tiramisu import Storage, MixConfig, delete_session
#from tiramisu.error import PropertiesOptionError
#
#from os import urandom, unlink
#from os.path import isfile, join
#from binascii import hexlify
#from json import dumps, loads
#from aiohttp.web import HTTPForbidden
#from creole.loader import PopulateTiramisuObjects
#from zephir.controller import ZephirCommonController, run
#from zephir.http import register as register_http
#from zephir.wamp import register as register_wamp
#from zephir.config import DEBUG
##from eolegenconfig import webapi
#from eolegenconfig.lib import storage
#from eolegenconfig import lib
#from zephir.i18n import _
from ...controller import Controller
from ...dispatcher import register
from ...config import ROOT_CACHE_DIR, DATABASE_DIR
from ...context import Context
from .storage import storage_server, storage_servermodel
class Risotto(Controller):
servermodel = {}
# FIXME : should be renamed to probe
server = {}
def __init__(self, *args, **kwargs):
# add root and statics
# FIXME
#default_storage.setting(engine='sqlite3', dir_database='/srv/database')
self.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR)
self.modify_storage = Storage(engine='dictionary')
super().__init__(*args, **kwargs)
def valid_user(self, sessionid, risotto_context):
username = risotto_context.username
if username != storage.get_username(sessionid):
raise HTTPForbidden()
async def onJoin(self, *args, **kwargs):
await super().onJoin(*args, **kwargs)
await asyncio.sleep(1)
await self.load_servermodels()
await self.load_servers()
async def load_servermodels(self):
print('Load servermodels')
try:
servermodels = await self.call('v1.servermodel.list')
except ApplicationError as err:
print(_('cannot load servermodel list: {}').format(str(err)))
return
for servermodel in servermodels:
try:
await self.load_servermodel(servermodel['servermodelid'], servermodel['servermodelname'])
except ApplicationError as err:
if DEBUG:
print('Error, cannot load servermodel {}: {}'.format(servermodel['servermodelname'], err))
for servermodel in servermodels:
if 'servermodelparentsid' in servermodel:
for servermodelparentid in servermodel['servermodelparentsid']:
self.servermodel_legacy(servermodel['servermodelname'], servermodel['servermodelid'], servermodelparentid)
async def load_servermodel(self, servermodelid, servermodelname):
logging.getLogger().setLevel(logging.INFO)
cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml")
creolefunc_file = join(ROOT_CACHE_DIR, str(servermodelid)+".creolefuncs")
print('Load servermodel {} ({})'.format(servermodelname, servermodelid))
if isfile(cache_file):
fileio = open(cache_file)
else:
servermodel = await self.call('v1.servermodel.describe',
servermodelid=servermodelid,
inheritance=False,
resolvdepends=False,
schema=True,
creolefuncs=True)
fileio = StringIO()
fileio.write(servermodel['schema'])
fileio.seek(0)
with open(cache_file, 'w') as cache:
cache.write(servermodel['schema'])
with open(creolefunc_file, 'w') as cache:
cache.write(servermodel['creolefuncs'])
del servermodel
xmlroot = parse(fileio).getroot()
tiramisu_objects = PopulateTiramisuObjects()
tiramisu_objects.parse_dtd('/srv/src/creole/data/creole.dtd')
tiramisu_objects.make_tiramisu_objects(xmlroot, creolefunc_file)
config = tiramisu_objects.build(persistent=True,
session_id='v_{}'.format(servermodelid),
meta_config=True)
config.owner.set('v_{}'.format(servermodelname))
config.information.set('servermodel_id', servermodelid)
config.information.set('servermodel_name', servermodelname)
self.servermodel[servermodelid] = config
def servermodel_legacy(self, servermodel_name, servermodel_id, servermodel_parent_id):
if servermodel_parent_id is None:
return
if not self.servermodel.get(servermodel_parent_id):
if DEBUG:
print(f'Servermodel with id {servermodel_parent_id} not loaded, skipping legacy for servermodel {servermodel_name} ({servermodel_id})')
return
servermodel_parent = self.servermodel[servermodel_parent_id]
servermodel_parent_name = servermodel_parent.information.get('servermodel_name')
if DEBUG:
print(f'Create legacy of servermodel {servermodel_name} ({servermodel_id}) with parent {servermodel_parent_name} ({servermodel_parent_id})')
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:
print(str(err))
async def load_servers(self):
print('Load servers')
try:
risotto_context = Context()
risotto_context.username = 'root'
servers = await self.call('v1.server.list', risotto_context)
except ApplicationError as err:
print(_('cannot load server list: {}').format(str(err)))
return
for server in servers:
try:
self.load_server(server['serverid'], server['servername'], server['servermodelid'])
await self._load_env(server['serverid'])
except Exception as err:
print('Unable to load server {} ({}): {}'.format(server['servername'], server['serverid'], err))
def load_server(self, serverid, servername, servermodelid):
if serverid in self.server:
return
print('Load server {} ({})'.format(servername, serverid))
if not servermodelid in self.servermodel:
raise ValueError(f'unable to find servermodel with id {servermodelid}')
metaconfig = self.servermodel[servermodelid].config.new('p_{}'.format(serverid),
persistent=True,
type='metaconfig')
metaconfig.information.set('server_id', serverid)
metaconfig.information.set('server_name', servername)
metaconfig.owner.set('probe')
config = metaconfig.config.new('s_{}'.format(serverid),
persistent=True)
config.owner.set(servername)
config = metaconfig.config.new('std_{}'.format(serverid),
persistent=True)
config.owner.set(servername)
if 'disabled' not in config.property.get():
# has to be read_only
ro = list(config.property.getdefault('read_only', 'append'))
if 'force_store_value' in ro:
# force_store_value is not allowed for new server (wait when configuration is deploy)
ro.remove('force_store_value')
config.property.setdefault(frozenset(ro), 'read_only', 'append')
rw = list(config.property.getdefault('read_write', 'append'))
rw.remove('force_store_value')
config.property.setdefault(frozenset(rw), 'read_write', 'append')
config.property.read_only()
self.server[serverid] = metaconfig
async def _load_env(self, server_id):
metaconfig = self.server[server_id]
old_informations = {}
for old_information in metaconfig.information.list():
old_informations[old_information] = metaconfig.information.get(old_information)
metaconfig.config.reset()
for old_information, old_value in old_informations.items():
metaconfig.information.set(old_information, old_value)
risotto_context = Context()
risotto_context.username = 'root'
server = await self.call('v1.server.describe', risotto_context=risotto_context, serverid=server_id, environment=True)
for key, value in server['serverenvironment'].items():
metaconfig.unrestraint.option(key).value.set(value)
if server['serverenvironment']:
metaconfig.unrestraint.option('creole.general.available_probes').value.set("oui")
else:
metaconfig.unrestraint.option('creole.general.available_probes').value.set("non")
# @register('v1.server.created', None)
# async def server_created(self, serverid, servername, servermodelid):
# self.load_server(serverid, servername, servermodelid)
#
# @register('v1.server.deleted', None)
# async def server_deleted(self, serverid):
# metaconfig = self.server[serverid]
# # remove config inside metaconfig
# for config in metaconfig.config.list():
# metaconfig.config.pop(config.config.name())
# delete_session(config.config.name())
# del config
# # delete config to parents
# for parent in metaconfig.config.parents():
# parent.config.pop(metaconfig.config.name())
# # delete metaconfig
# delete_session(metaconfig.config.name())
# del self.server[serverid]
# del metaconfig
# @register('v1.server.environment.updated', "v1.config.configuration.server.updated")
# async def env_updated(self, server_id):
# await self._load_env(server_id)
# self.publish('v1.config.configuration.server.updated', server_id=server_id, deploy=False)
# return {'server_id': server_id, 'deploy': True}
# @register('v1.servermodel.created', None)
# async def servermodel_created(self, servermodels):
# for servermodel in servermodels:
# await self.load_servermodel(servermodel['servermodelid'], servermodel['servermodelname'])
# for servermodel in servermodels:
# if 'servermodelparentsid' in servermodel:
# for servermodelparentid in servermodel['servermodelparentsid']:
# self.servermodel_legacy(servermodel['servermodelname'], servermodel['servermodelid'], servermodelparentid)
#
# @register('v1.servermodel.updated', None)
# async def servermodel_updated(self, servermodels):
# for servermodel in servermodels:
# servermodelid = servermodel['servermodelid']
# servermodelname = servermodel['servermodelname']
# servermodelparentsid = servermodel.get('servermodelparentsid')
# print('Reload servermodel {} ({})'.format(servermodelname, servermodelid))
# # unlink cache to force download new aggregated file
# cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml")
# if isfile(cache_file):
# unlink(cache_file)
# # get current servermodel
# old_servermodel = self.servermodel[servermodelid]
# # create new one
# await self.load_servermodel(servermodelid, servermodelname)
# # migrate all informations
# self.servermodel[servermodelid].value.importation(old_servermodel.value.exportation())
# self.servermodel[servermodelid].permissive.importation(old_servermodel.permissive.exportation())
# self.servermodel[servermodelid].property.importation(old_servermodel.property.exportation())
# # remove link to legacy
# if servermodelparentsid:
# for servermodelparentid in servermodelparentsid:
# mix = self.servermodel[servermodelparentid].config.get('m_v_' + str(servermodelparentid))
# try:
# mix.config.pop(old_servermodel.config.name())
# except:
# # if mix config is reloaded too
# pass
# # add new link
# self.servermodel_legacy(servermodelname, servermodelid, servermodelparentid)
# # load servers in servermodel
# for subconfig in old_servermodel.config.list():
# if not isinstance(subconfig, MixConfig):
# name = subconfig.config.name()
# try:
# old_servermodel.config.pop(name)
# except:
# pass
# server_id = subconfig.information.get('server_id')
# server_name = subconfig.information.get('server_name')
# del self.server[server_id]
# self.load_server(server_id, server_name, servermodelid)
# else:
# for subsubconfig in subconfig.config.list():
# name = subsubconfig.config.name()
# try:
# subconfig.config.pop(name)
# except:
# pass
# self.servermodel_legacy(subsubconfig.information.get('servermodel_name'), subsubconfig.information.get('servermodel_id'), servermodelid)
@register('v1.config.configuration.server.get', None)
async def get_configuration(self, server_id, deploy):
return {'configuration': (server_id, deploy)}
@register('v1.config.configuration.server.deploy', 'v1.config.configuration.server.updated')
async def deploy_configuration(self, server_id):
"""Copy values, permissions, permissives from config 'to deploy' to active config
"""
metaconfig = self.server[server_id]
config_std = metaconfig.config("std_{}".format(server_id))
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')
config = metaconfig.config("s_{}".format(server_id))
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, 'deploy': True}
# SESSION
#__________________________________________________________________
def get_session(self, session_id, type):
if type == 'server':
return storage_server.get_session(session_id)
return storage_servermodel.get_session(session_id)
def get_session_informations(self, session_id, type):
session = self.get_session(session_id, type)
return self.format_session(session_id, session)
def format_session(self, session_name, session):
return {'sessionid': session_name,
'id': session['id'],
'username': session['username'],
'timestamp': session['timestamp'],
'namespace': session['namespace'],
'mode': session['mode'],
'debug': session['debug']}
def list_sessions(self, type):
ret = []
if type == 'server':
storage = storage_server
else:
storage = storage_servermodel
for session in storage.list_sessions():
ret.append(self.format_session(session['sessionid'], session))
return ret
def load_dict(self, session):
if not session['option']:
session['option'] = session['config'].option(session['namespace'])
return session['option'].dict(remotable='all')
# start
async def start_session(self, risotto_context, id, type, server_list):
if id not in server_list:
raise Exception(_(f'cannot find {type} with id {id}'))
session_id = ''
session_list = self.list_sessions(type)
for sess in session_list:
if sess['id'] == id and sess['username'] == risotto_context.username:
session_id = sess['sessionid']
session = self.get_session(session_id, type)
return self.format_session(session_id, session)
else:
session_id = ''
if session_id == '':
if type == 'server':
storage = storage_server
else:
storage = storage_servermodel
while True:
session_id = 'z' + hexlify(urandom(23)).decode()
if not storage.has_session(session_id):
break
else:
print('session {} already exists'.format(session_id))
username = risotto_context.username
storage.add_session(session_id, server_list[id], type, id, username, self.modify_storage)
return self.get_session_informations(session_id, type)
@register('v1.config.session.server.start', None)
async def start_session_server(self, risotto_context, id):
return await self.start_session(risotto_context, id, 'server', self.server)
@register('v1.config.session.servermodel.start', None)
async def start_session_servermodel(self, risotto_context, id):
return await self.start_session(risotto_context, id, 'servermodel', self.servermodel)
# list
@register('v1.config.session.server.list', None)
async def list_session_server(self):
return self.list_sessions('server')
@register('v1.config.session.servermodel.list', None)
async def list_session_servermodel(self):
return self.list_sessions('servermodel')
# filter
async def filter_session(self, session_id, type, namespace, mode, debug):
session = self.get_session(session_id, type)
if namespace is not None:
session['option'] = None
session['namespace'] = namespace
if type == 'server':
storage = storage_server
else:
storage = storage_servermodel
if mode is not None:
if mode not in ('basic', 'normal', 'expert'):
raise Exception(f'unknown mode {mode}')
storage.set_config_mode(session_id, mode)
if debug is not None:
storage.set_config_debug(session_id, debug)
return self.get_session_informations(session_id, type)
@register('v1.config.session.server.filter', None)
async def filter_session_server(self, session_id, namespace, mode, debug):
return await self.filter_session(session_id, 'server', namespace, mode, debug)
@register('v1.config.session.servermodel.filter', None)
async def filter_session_servermodel(self, session_id, namespace, mode, debug):
return await self.filter_session(session_id, 'servermodel', namespace, mode, debug)
# configure
async def configure_session(self, session_id, type, action, name, index, value):
session = self.get_session(session_id, type)
ret = {'session_id': session_id,
'name': name}
if index is not None:
ret['index'] = index
try:
update = {'name': name,
'action': action,
'value': value}
if index is not None:
update['index'] = index
if not session['option']:
session['option'] = session['config'].option(session['namespace'])
self.load_dict(session)
updates = {'updates': [update]}
session['option'].updates(updates)
ret['status'] = 'ok'
except Exception as err:
import traceback
traceback.print_exc()
ret['message'] = str(err)
ret['status'] = 'error'
return ret
@register('v1.config.session.server.configure', None)
async def configure_session_server(self, session_id, action, name, index, value):
return await self.configure_session(session_id, 'server', action, name, index, value)
@register('v1.config.session.servermodel.configure', None)
async def configure_session_servermodel(self, session_id, action, name, index, value):
return await self.configure_session(session_id, 'servermodel', action, name, index, value)
# validate
async def validate_session(self, session_id, type):
session = self.get_session(session_id, type)
ret = {}
try:
session['config'].forcepermissive.option(session['namespace']).value.dict()
except Exception as err:
ret['status'] = 'error'
ret['message'] = str(err)
else:
if type == 'server':
mandatories = list(session['config'].forcepermissive.value.mandatory())
if mandatories:
ret['status'] = 'incomplete'
ret['mandatories'] = mandatories
else:
ret['status'] = 'ok'
else:
ret['status'] = 'ok'
return ret
@register('v1.config.session.server.validate', None)
async def validate_session_server(self, session_id):
return await self.validate_session(session_id, 'server')
@register('v1.config.session.servermodel.validate', None)
async def validate_session_servermodel(self, session_id):
return await self.validate_session(session_id, 'servermodel')
# get
async def get_session_(self, session_id, type):
info = self.get_session_informations(session_id, type)
info['content'] = session_id
return info
@register('v1.config.session.server.get', None)
async def get_session_server(self, session_id):
return await self.get_session_(session_id, 'server')
@register('v1.config.session.servermodel.get', None)
async def get_session_servermodel(self, session_id):
return await self.get_session_(session_id, 'servermodel')
# stop
async def stop_session(self, risotto_context, session_id, type, save):
session = self.get_session(session_id, type)
if save:
await self._post_save_config(risotto_context, None, session_id)
if type == 'server':
storage = storage_server
else:
storage = storage_servermodel
storage.del_session(session_id, type)
return self.format_session(session_id, session)
@register('v1.config.session.server.stop', None)
async def stop_session_server(self, risotto_context, sessionid, save):
return await self.stop_session(sessionid, 'server', save)
@register('v1.config.session.servermodel.stop', None)
async def stop_session_servermodel(self, risotto_context, sessionid, save):
return await self.stop_session(risotto_context, sessionid, 'servermodel', save)
# GEN_CONFIG
#__________________________________________________________________
async def _post_save_config(self, risotto_context, request, sessionid):
self.valid_user(sessionid, risotto_context)
lib.save_values(sessionid, 'save')
id_ = storage.get_id(sessionid)
if storage.get_type(sessionid) == 'server':
if self.server[id_].option('creole.general.available_probes').value.get() == "oui":
self.publish('v1.config.configuration.server.updated', server_id=id_, deploy=False)
else:
for probe in self.servermodel[id_].config.list():
# FIXME should use config.information.get('server_id')
name = probe.config.name()
if name.startswith('p_'):
server_id = int(name.rsplit('_', 1)[-1])
if self.server[server_id].option('creole.general.available_probes').value.get() == "oui":
self.publish('v1.config.configuration.server.updated', server_id=server_id)
return {}