def switch_object(obj: Union[Host, ClusterObject], new_prototype: Prototype) -> None: """Upgrade object""" log.info('upgrade switch from %s to %s', proto_ref(obj.prototype), proto_ref(new_prototype)) old_prototype = obj.prototype obj.prototype = new_prototype obj.save() switch_config(obj, new_prototype, old_prototype)
def check_versions(proto, conf, label): ref = proto_ref(proto) msg = '{} has no mandatory \"versions\" key ({})' if not conf: err('INVALID_VERSION_DEFINITION', msg.format(label, ref)) if 'versions' not in conf: err('INVALID_VERSION_DEFINITION', msg.format(label, ref)) if not isinstance(conf['versions'], dict): err('INVALID_VERSION_DEFINITION', msg.format(label, ref)) check_extra_keys( conf['versions'], ('min', 'max', 'min_strict', 'max_strict'), '{} versions of {}'.format(label, proto_ref(proto)) ) if 'min' in conf['versions'] and 'min_strict' in conf['versions']: msg = 'min and min_strict can not be used simultaneously in versions of {} ({})' err('INVALID_VERSION_DEFINITION', msg.format(label, ref)) if 'min' not in conf['versions'] and 'min_strict' not in conf['versions']: msg = 'min or min_strict should be present in versions of {} ({})' err('INVALID_VERSION_DEFINITION', msg.format(label, ref)) if 'max' in conf['versions'] and 'max_strict' in conf['versions']: msg = 'max and max_strict can not be used simultaneously in versions of {} ({})' err('INVALID_VERSION_DEFINITION', msg.format(label, ref)) if 'max' not in conf['versions'] and 'max_strict' not in conf['versions']: msg = 'max and max_strict should be present in versions of {} ({})' err('INVALID_VERSION_DEFINITION', msg.format(label, ref)) for name in ('min', 'min_strict', 'max', 'max_strict'): if name in conf['versions'] and not conf['versions'][name]: msg = '{} versions of {} should be not null ({})' err('INVALID_VERSION_DEFINITION', msg.format(name, label, ref))
def check_multi_bind(actual_import, cluster, service, export_cluster, export_service, cb_list=None): if actual_import.multibind: return if cb_list is None: cb_list = ClusterBind.objects.filter(cluster=cluster, service=service) for cb in cb_list: if cb.source_service: source_proto = cb.source_service.prototype else: source_proto = cb.source_cluster.prototype if export_service: if source_proto == export_service.prototype: msg = 'can not multi bind {} to {}' err('BIND_ERROR', msg.format(proto_ref(source_proto), obj_ref(cluster))) else: if source_proto == export_cluster.prototype: msg = 'can not multi bind {} to {}' err('BIND_ERROR', msg.format(proto_ref(source_proto), obj_ref(cluster)))
def check_upgrade_import(obj, upgrade): # pylint: disable=too-many-branches def get_export(cbind): if cbind.source_service: return cbind.source_service else: return cbind.source_cluster def get_import(cbind): # pylint: disable=redefined-outer-name if cbind.service: return cbind.service else: return cbind.cluster if obj.prototype.type != 'cluster': return True, '' for cbind in ClusterBind.objects.filter(cluster=obj): export = get_export(cbind) impr_obj = get_import(cbind) try: proto = Prototype.objects.get(bundle=upgrade.bundle, name=impr_obj.prototype.name, type=impr_obj.prototype.type) except Prototype.DoesNotExist: msg = 'Upgrade does not have new version of {} required for import' return False, msg.format(proto_ref(impr_obj.prototype)) try: pi = PrototypeImport.objects.get(prototype=proto, name=export.prototype.name) if not version_in(export.prototype.version, pi): msg = 'Import "{}" of {} versions ({}, {}) does not match export version: {} ({})' return (False, msg.format(export.prototype.name, proto_ref(proto), pi.min_version, pi.max_version, export.prototype.version, obj_ref(export))) except PrototypeImport.DoesNotExist: # msg = 'New version of {} does not have import "{}"' # ADCM-1507 # return False, msg.format(proto_ref(proto), export.prototype.name) cbind.delete() for cbind in ClusterBind.objects.filter(source_cluster=obj): export = get_export(cbind) try: proto = Prototype.objects.get(bundle=upgrade.bundle, name=export.prototype.name, type=export.prototype.type) except Prototype.DoesNotExist: msg = 'Upgrade does not have new version of {} required for export' return False, msg.format(proto_ref(export.prototype)) import_obj = get_import(cbind) pi = PrototypeImport.objects.get(prototype=import_obj.prototype, name=export.prototype.name) if not version_in(proto.version, pi): msg = 'Export of {} does not match import versions: ({}, {}) ({})' return (False, msg.format(proto_ref(proto), pi.min_version, pi.max_version, obj_ref(import_obj))) return True, ''
def add_service_to_cluster(cluster, proto): check_proto_type(proto, 'service') check_license(proto.bundle) if not proto.shared: if cluster.prototype.bundle != proto.bundle: msg = '{} does not belong to bundle "{}" {}' err( 'SERVICE_CONFLICT', msg.format(proto_ref(proto), cluster.prototype.bundle.name, cluster.prototype.version), ) with transaction.atomic(): cs = ClusterObject.objects.create(cluster=cluster, prototype=proto) obj_conf = init_object_config(proto, cs) cs.config = obj_conf cs.save() add_components_to_service(cluster, cs) cm.issue.update_hierarchy_issues(cs) rbac.models.re_apply_object_policy(cluster) cm.status_api.post_event('add', 'service', cs.id, 'cluster', str(cluster.id)) load_service_map() log.info( f'service #{cs.id} {cs.prototype.name} is added to cluster #{cluster.id} {cluster.name}' ) return cs
def save_components(proto, conf, bundle_hash): ref = proto_ref(proto) if not in_dict(conf, 'components'): return for comp_name in conf['components']: cc = conf['components'][comp_name] validate_name(comp_name, f'Component name "{comp_name}" of {ref}') component = StagePrototype( type='component', parent=proto, path=proto.path, name=comp_name, version=proto.version, adcm_min_version=proto.adcm_min_version, ) dict_to_obj(cc, 'description', component) dict_to_obj(cc, 'display_name', component) dict_to_obj(cc, 'monitoring', component) fix_display_name(cc, component) check_component_constraint(proto, comp_name, cc) dict_to_obj(cc, 'params', component) dict_to_obj(cc, 'constraint', component) dict_to_obj(cc, 'requires', component) dict_to_obj(cc, 'venv', component) dict_to_obj(cc, 'bound_to', component) dict_to_obj(cc, 'config_group_customization', component) component.save() save_actions(component, cc, bundle_hash) save_prototype_config(component, cc, bundle_hash)
def check_component_requires(comp): if not comp.requires: return ref = 'in requires of component "{}" of {}'.format( comp.name, proto_ref(comp.prototype)) req_list = comp.requires for i, item in enumerate(req_list): if 'service' in item: try: service = StagePrototype.objects.get(name=item['service'], type='service') except StagePrototype.DoesNotExist: msg = 'Unknown service "{}" {}' err('COMPONENT_CONSTRAINT_ERROR', msg.format(item['service'], ref)) else: service = comp.prototype req_list[i]['service'] = comp.prototype.name try: req_comp = StagePrototype.objects.get(name=item['component'], type='component', parent=service) except StagePrototype.DoesNotExist: msg = 'Unknown component "{}" {}' err('COMPONENT_CONSTRAINT_ERROR', msg.format(item['component'], ref)) if comp == req_comp: msg = 'Component can not require themself {}' err('COMPONENT_CONSTRAINT_ERROR', msg.format(ref)) comp.requires = req_list comp.save()
def check_component_constraint_definition(proto, name, conf): if not isinstance(conf, dict): return if 'constraint' not in conf: return const = conf['constraint'] ref = proto_ref(proto) def check_item(item): if isinstance(item, int): return elif item == '+': return elif item == 'odd': return else: msg = 'constraint item of component "{}" in {} should be only digit or "+" or "odd"' err('INVALID_COMPONENT_DEFINITION', msg.format(name, ref)) if not isinstance(const, list): msg = 'constraint of component "{}" in {} should be array' err('INVALID_COMPONENT_DEFINITION', msg.format(name, ref)) if len(const) > 2: msg = 'constraint of component "{}" in {} should have only 1 or 2 elements' err('INVALID_COMPONENT_DEFINITION', msg.format(name, ref)) check_item(const[0]) if len(const) > 1: check_item(const[1])
def save_upgrade(proto, conf): ref = proto_ref(proto) if not in_dict(conf, 'upgrade'): return if not isinstance(conf['upgrade'], list): msg = 'Upgrade definition of {} should be an array' err('INVALID_UPGRADE_DEFINITION', msg.format(ref)) for item in conf['upgrade']: allow = ('versions', 'from_edition', 'states', 'name', 'description') check_extra_keys(item, allow, 'upgrade of {}'.format(ref)) check_upgrade(proto, item) upg = StageUpgrade(name=item['name']) set_version(upg, item) dict_to_obj(item, 'description', upg) if 'states' in item: check_upgrade_states(proto, item) dict_to_obj(item['states'], 'available', upg) if 'available' in item['states']: upg.state_available = item['states']['available'] if 'on_success' in item['states']: upg.state_on_success = item['states']['on_success'] check_upgrade_edition(proto, item) if in_dict(item, 'from_edition'): upg.from_edition = item['from_edition'] upg.save()
def check_upgrade_edition(proto, conf): if 'from_edition' not in conf: return if isinstance(conf['from_edition'], str) and conf['from_edition'] == 'any': return if not isinstance(conf['from_edition'], list): msg = 'from_edition upgrade filed of {} should be array, not string' err('INVALID_UPGRADE_DEFINITION', msg.format(proto_ref(proto)))
def check_sub_action(proto, sub_config, action): label = 'sub action of action' ref = '{} "{}" in {}'.format(label, action.name, proto_ref(proto)) check_key(proto.type, proto.name, label, action.name, 'name', sub_config) check_key(proto.type, proto.name, label, action.name, 'script', sub_config) check_key(proto.type, proto.name, label, action.name, 'script_type', sub_config) allow = ('name', 'display_name', 'script', 'script_type', 'on_fail', 'params') check_extra_keys(sub_config, allow, ref)
def check_component_constraint(proto, name, conf): if not conf: return if 'constraint' not in conf: return if len(conf['constraint']) > 2: msg = 'constraint of component "{}" in {} should have only 1 or 2 elements' err('INVALID_COMPONENT_DEFINITION', msg.format(name, proto_ref(proto)))
def check_default_import(proto, conf): ref = proto_ref(proto) if 'default' not in conf: return groups = get_config_groups(proto) for key in conf['default']: if key not in groups: msg = 'No import default group "{}" in config ({})' err('INVALID_OBJECT_DEFINITION', msg.format(key, ref))
def get_license_hash(proto, conf, bundle_hash): if 'license' not in conf: return None if not isinstance(conf['license'], str): err('INVALID_OBJECT_DEFINITION', 'license should be a string ({})'.format(proto_ref(proto))) msg = 'license file' body = read_bundle_file(proto, conf['license'], bundle_hash, msg) sha1 = hashlib.sha256() sha1.update(body.encode('utf-8')) return sha1.hexdigest()
def check_component_requires(proto, name, conf): if not isinstance(conf, dict): return if 'requires' not in conf: return req = conf['requires'] ref = proto_ref(proto) if not isinstance(req, list): msg = 'requires of component "{}" in {} should be array' err('INVALID_COMPONENT_DEFINITION', msg.format(name, ref)) for item in req: check_extra_keys(item, ('service', 'component'), f'requires of component "{name}" of {ref}')
def check_default_import(proto, conf): ref = proto_ref(proto) if 'default' not in conf: return if not isinstance(conf['default'], list): msg = 'Import deafult section should be an array ({})' err('INVALID_OBJECT_DEFINITION', msg.format(ref)) groups = get_config_groups(proto) for key in conf['default']: if key not in groups: msg = 'No import deafult group "{}" in config ({})' err('INVALID_OBJECT_DEFINITION', msg.format(key, ref))
def save_export(proto, conf): ref = proto_ref(proto) if not in_dict(conf, 'export'): return if isinstance(conf['export'], str): export = [conf['export']] elif isinstance(conf['export'], list): export = conf['export'] msg = '{} does not has "{}" config group' for key in export: if not StagePrototypeConfig.objects.filter(prototype=proto, name=key): err('INVALID_OBJECT_DEFINITION', msg.format(ref, key)) se = StagePrototypeExport(prototype=proto, name=key) se.save()
def check_bound_component(proto, name, conf): if not isinstance(conf, dict): return if 'bound_to' not in conf: return bind = conf['bound_to'] ref = proto_ref(proto) if not isinstance(bind, dict): msg = 'bound_to of component "{}" in {} should be a map' err('INVALID_COMPONENT_DEFINITION', msg.format(name, ref)) check_extra_keys(bind, ('service', 'component'), f'bound_to of component "{name}" of {ref}') msg = 'Component "{}" has no mandatory "{}" key in bound_to statment ({})' for item in ('service', 'component'): if item not in bind: err('INVALID_COMPONENT_DEFINITION', msg.format(name, item, ref))
def re_check_config(): for c in StagePrototypeConfig.objects.filter(type='variant'): ref = proto_ref(c.prototype) lim = c.limits if lim['source']['type'] == 'list': keys = lim['source']['name'].split('/') name = keys[0] subname = '' if len(keys) > 1: subname = keys[1] try: s = StagePrototypeConfig.objects.get(prototype=c.prototype, name=name, subname=subname) except StagePrototypeConfig.DoesNotExist: msg = f'Unknown config source name "{{}}" for {ref} config "{c.name}/{c.subname}"' err('INVALID_CONFIG_DEFINITION', msg.format(lim['source']['name'])) if s == c: msg = f'Config parameter "{c.name}/{c.subname}" can not refer to itself ({ref})' err('INVALID_CONFIG_DEFINITION', msg) elif lim['source']['type'] == 'builtin': if not lim['source']['args']: continue if lim['source']['name'] == 'host': msg = f'in source:args of {ref} config "{c.name}/{c.subname}"' check_variant_host(lim['source']['args'], msg) if 'service' in lim['source']['args']: service = lim['source']['args']['service'] try: sp_service = StagePrototype.objects.get(type='service', name=service) except StagePrototype.DoesNotExist: msg = 'Service "{}" in source:args of {} config "{}/{}" does not exists' err('INVALID_CONFIG_DEFINITION', msg.format(service, ref, c.name, c.subname)) if 'component' in lim['source']['args']: comp = lim['source']['args']['component'] try: StagePrototype.objects.get(type='component', name=comp, parent=sp_service) except StagePrototype.DoesNotExist: msg = 'Component "{}" in source:args of {} config "{}/{}" does not exists' err('INVALID_CONFIG_DEFINITION', msg.format(comp, ref, c.name, c.subname))
def save_import(proto, conf): ref = proto_ref(proto) if not in_dict(conf, 'import'): return for key in conf['import']: if 'default' in conf['import'][key] and 'required' in conf['import'][ key]: msg = 'Import can\'t have default and be required in the same time ({})' err('INVALID_OBJECT_DEFINITION', msg.format(ref)) check_default_import(proto, conf['import'][key]) si = StagePrototypeImport(prototype=proto, name=key) if 'versions' in conf['import'][key]: check_versions(proto, conf['import'][key], f'import "{key}"') set_version(si, conf['import'][key]) dict_to_obj(conf['import'][key], 'required', si) dict_to_obj(conf['import'][key], 'multibind', si) dict_to_obj(conf['import'][key], 'default', si) si.save()
def re_check_actions(): for act in StageAction.objects.all(): if not act.hostcomponentmap: continue hc = act.hostcomponentmap ref = 'in hc_acl of action "{}" of {}'.format(act.name, proto_ref(act.prototype)) for item in hc: sp = StagePrototype.objects.filter(type='service', name=item['service']) if not sp: msg = 'Unknown service "{}" {}' err('INVALID_ACTION_DEFINITION', msg.format(item['service'], ref)) if not StagePrototype.objects.filter( parent=sp[0], type='component', name=item['component']): msg = 'Unknown component "{}" of service "{}" {}' err('INVALID_ACTION_DEFINITION', msg.format(item['component'], sp[0].name, ref))
def check_upgrade_states(proto, ac): if 'states' not in ac: return False ref = 'upgrade states of {}'.format(proto_ref(proto)) check_extra_keys(ac['states'], ('available', 'on_success'), ref) if 'on_success' in ac['states']: if not isinstance(ac['states']['on_success'], str): msg = 'states:on_success of {} should be string' err('INVALID_ACTION_DEFINITION', msg.format(ref)) if 'available' not in ac['states']: return True if isinstance(ac['states']['available'], str) and ac['states']['available'] == 'any': return True if not isinstance(ac['states']['available'], list): msg = 'states:available of upgrade in {} "{}" should be array, not string' err('INVALID_UPGRADE_DEFINITION', msg.format(proto.type, proto.name)) return True
def set_service_state(cluster_id, service_name, state): try: cluster = Cluster.objects.get(id=cluster_id) except Cluster.DoesNotExist: msg = 'Cluster # {} does not exist' err('CLUSTER_NOT_FOUND', msg.format(cluster_id)) try: proto = Prototype.objects.get(type='service', name=service_name, bundle=cluster.prototype.bundle) except Prototype.DoesNotExist: msg = 'Service "{}" does not exist' err('SERVICE_NOT_FOUND', msg.format(service_name)) try: obj = ClusterObject.objects.get(cluster=cluster, prototype=proto) except ClusterObject.DoesNotExist: msg = '{} does not exist in cluster # {}' err('OBJECT_NOT_FOUND', msg.format(proto_ref(proto), cluster.id)) return push_obj(obj, state)
def save_components(proto, conf, bundle_hash): ref = proto_ref(proto) if not in_dict(conf, 'components'): return if proto.type != 'service': log.warning('%s has unexpected "components" key', ref) return if not isinstance(conf['components'], dict): msg = 'Components definition should be a map ({})' err('INVALID_COMPONENT_DEFINITION', msg.format(ref)) for comp_name in conf['components']: cc = conf['components'][comp_name] err_msg = 'Component name "{}" of {}'.format(comp_name, ref) validate_name(comp_name, err_msg) allow = ( 'display_name', 'description', 'params', 'constraint', 'requires', 'monitoring', 'bound_to', 'actions', 'config', ) check_extra_keys(cc, allow, 'component "{}" of {}'.format(comp_name, ref)) component = StagePrototype( type='component', parent=proto, path=proto.path, name=comp_name, version=proto.version, adcm_min_version=proto.adcm_min_version, ) dict_to_obj(cc, 'description', component) dict_to_obj(cc, 'display_name', component) dict_to_obj(cc, 'monitoring', component) fix_display_name(cc, component) check_component_constraint_definition(proto, comp_name, cc) check_component_requires(proto, comp_name, cc) check_bound_component(proto, comp_name, cc) dict_to_obj(cc, 'params', component) dict_to_obj(cc, 'constraint', component) dict_to_obj(cc, 'requires', component) dict_to_obj(cc, 'bound_to', component) component.save() save_actions(component, cc, bundle_hash) save_prototype_config(component, cc, bundle_hash)
def save_sub_actions(proto, conf, action): ref = proto_ref(proto) if action.type != 'task': return if not isinstance(conf['scripts'], list): msg = 'scripts entry of action "{}" in {} should be a list' err('INVALID_ACTION_DEFINITION', msg.format(action.name, ref)) for sub in conf['scripts']: check_sub_action(proto, sub, action) sub_action = StageSubAction( action=action, script=sub['script'], script_type=sub['script_type'], name=sub['name'] ) sub_action.display_name = sub['name'] if 'display_name' in sub: sub_action.display_name = sub['display_name'] dict_to_obj(sub, 'params', sub_action) if 'on_fail' in sub: sub_action.state_on_fail = sub['on_fail'] sub_action.save()
def save_import(proto, conf): ref = proto_ref(proto) if not in_dict(conf, 'import'): return if proto.type not in ('cluster', 'service'): err('INVALID_OBJECT_DEFINITION', 'Only cluster or service can has an import section') if not isinstance(conf['import'], dict): err('INVALID_OBJECT_DEFINITION', '{} import should be object type'.format(ref)) allowed_keys = ('versions', 'default', 'required', 'multibind') for key in conf['import']: check_extra_keys(conf['import'][key], allowed_keys, ref + ' import') check_versions(proto, conf['import'][key], f'import "{key}"') if 'default' in conf['import'][key] and 'required' in conf['import'][key]: msg = 'Import can\'t have default and be required in the same time ({})' err('INVALID_OBJECT_DEFINITION', msg.format(ref)) check_default_import(proto, conf['import'][key]) si = StagePrototypeImport(prototype=proto, name=key) set_version(si, conf['import'][key]) dict_to_obj(conf['import'][key], 'required', si) dict_to_obj(conf['import'][key], 'multibind', si) dict_to_obj(conf['import'][key], 'default', si) si.save()
def add_service_to_cluster(cluster, proto): check_proto_type(proto, 'service') check_license(proto.bundle) if not proto.shared: if cluster.prototype.bundle != proto.bundle: msg = '{} does not belong to bundle "{}" {}' err( 'SERVICE_CONFLICT', msg.format(proto_ref(proto), cluster.prototype.bundle.name, cluster.prototype.version)) spec, _, conf, attr = get_prototype_config(proto) with transaction.atomic(): obj_conf = init_object_config(spec, conf, attr) cs = ClusterObject(cluster=cluster, prototype=proto, config=obj_conf) cs.save() add_components_to_service(cluster, cs) process_file_type(cs, spec, conf) cm.issue.save_issue(cs) cm.issue.save_issue(cluster) cm.status_api.post_event('add', 'service', cs.id, 'cluster', str(cluster.id)) cm.status_api.load_service_map() return cs
def check_action_states(proto, action, ac): if 'states' not in ac: return False ref = 'action "{}" of {}'.format(action, proto_ref(proto)) check_extra_keys(ac['states'], ('available', 'on_success', 'on_fail'), ref) check_key(proto.type, proto, 'states of action', action, 'available', ac['states']) if 'on_success' in ac['states']: if not isinstance(ac['states']['on_success'], str): msg = 'states:on_success of {} should be string' err('INVALID_ACTION_DEFINITION', msg.format(ref)) if 'on_fail' in ac['states']: if not isinstance(ac['states']['on_fail'], str): msg = 'states:on_fail of {} should be string' err('INVALID_ACTION_DEFINITION', msg.format(ref)) if isinstance(ac['states']['available'], str) and ac['states']['available'] == 'any': return True if not isinstance(ac['states']['available'], list): msg = 'states:available of {} should be array, not string' err('INVALID_ACTION_DEFINITION', msg.format(ref)) return True
def save_export(proto, conf): ref = proto_ref(proto) if not in_dict(conf, 'export'): return if proto.type not in ('cluster', 'service'): msg = 'Only cluster or service can have export section ({})' err('INVALID_OBJECT_DEFINITION', msg.format(ref)) if isinstance(conf['export'], str): export = [conf['export']] elif isinstance(conf['export'], list): export = conf['export'] else: err('INVALID_OBJECT_DEFINITION', '{} export should be string or array type'.format(ref)) msg = '{} does not has "{}" config group' for key in export: try: if not StagePrototypeConfig.objects.filter(prototype=proto, name=key): err('INVALID_OBJECT_DEFINITION', msg.format(ref, key)) except StagePrototypeConfig.DoesNotExist: err('INVALID_OBJECT_DEFINITION', msg.format(ref, key)) se = StagePrototypeExport(prototype=proto, name=key) se.save()
def check_action_hc(proto, conf, name): if 'hc_acl' not in conf: return ref = proto_ref(proto) if not isinstance(conf['hc_acl'], list): msg = 'hc_acl of action "{}" in {} should be array' err('INVALID_ACTION_DEFINITION', msg.format(name, ref)) allow = ('service', 'component', 'action') for idx, item in enumerate(conf['hc_acl']): if not isinstance(item, dict): msg = 'hc_acl entry of action "{}" in {} should be a map' err('INVALID_ACTION_DEFINITION', msg.format(name, ref)) check_extra_keys(item, allow, 'hc_acl of action "{}" in {}'.format(name, ref)) if 'service' not in item: if proto.type == 'service': item['service'] = proto.name conf['hc_acl'][idx]['service'] = proto.name for key in allow: if key not in item: msg = 'hc_acl of action "{}" in {} doesn\'t has required key "{}"' err('INVALID_ACTION_DEFINITION', msg.format(name, ref, key)) if item['action'] not in ('add', 'remove'): msg = 'hc_acl of action "{}" in {} action value "{}" is not "add" or "remove"' err('INVALID_ACTION_DEFINITION', msg.format(name, ref, item['action']))