diff --git a/src/webui/service/main/DescriptorTools.py b/src/webui/service/main/DescriptorTools.py deleted file mode 100644 index 094be2f7d0cfd69ddb5cddc2238e8cec64c75daa..0000000000000000000000000000000000000000 --- a/src/webui/service/main/DescriptorTools.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import copy, json -from typing import Dict, List, Optional, Tuple, Union - -def get_descriptors_add_contexts(contexts : List[Dict]) -> List[Dict]: - contexts_add = copy.deepcopy(contexts) - for context in contexts_add: - context['topology_ids'] = [] - context['service_ids'] = [] - return contexts_add - -def get_descriptors_add_topologies(topologies : List[Dict]) -> List[Dict]: - topologies_add = copy.deepcopy(topologies) - for topology in topologies_add: - topology['device_ids'] = [] - topology['link_ids'] = [] - return topologies_add - -def get_descriptors_add_services(services : List[Dict]) -> List[Dict]: - services_add = [] - for service in services: - service_copy = copy.deepcopy(service) - service_copy['service_endpoint_ids'] = [] - service_copy['service_constraints'] = [] - service_copy['service_config'] = {'config_rules': []} - services_add.append(service_copy) - return services_add - -def get_descriptors_add_slices(slices : List[Dict]) -> List[Dict]: - slices_add = [] - for slice in slices: - slice_copy = copy.deepcopy(slice) - slice_copy['slice_endpoint_ids'] = [] - slice_copy['slice_constraints'] = [] - slice_copy['slice_config'] = {'config_rules': []} - slices_add.append(slice_copy) - return slices_add - -TypeResourceValue = Union[str, int, bool, float, dict, list] -def format_custom_config_rules(config_rules : List[Dict]) -> List[Dict]: - for config_rule in config_rules: - if 'custom' not in config_rule: continue - custom_resource_value : TypeResourceValue = config_rule['custom']['resource_value'] - if isinstance(custom_resource_value, (dict, list)): - custom_resource_value = json.dumps(custom_resource_value, sort_keys=True, indent=0) - config_rule['custom']['resource_value'] = custom_resource_value - return config_rules - -def split_devices_by_rules(devices : List[Dict]) -> Tuple[List[Dict], List[Dict]]: - devices_add = [] - devices_config = [] - for device in devices: - connect_rules = [] - config_rules = [] - for config_rule in device.get('device_config', {}).get('config_rules', []): - custom_resource_key : Optional[str] = config_rule.get('custom', {}).get('resource_key') - if custom_resource_key is not None and custom_resource_key.startswith('_connect/'): - connect_rules.append(config_rule) - else: - config_rules.append(config_rule) - - if len(connect_rules) > 0: - device_add = copy.deepcopy(device) - device_add['device_endpoints'] = [] - device_add['device_config'] = {'config_rules': connect_rules} - devices_add.append(device_add) - - if len(config_rules) > 0: - device['device_config'] = {'config_rules': config_rules} - devices_config.append(device) - - return devices_add, devices_config diff --git a/src/webui/service/main/routes.py b/src/webui/service/main/routes.py index 979d0664bc42221e3559eef498bd53562fe073e7..b161fa845ebbdd76372fbcaf10f6ea8ae68dd513 100644 --- a/src/webui/service/main/routes.py +++ b/src/webui/service/main/routes.py @@ -14,8 +14,8 @@ import json, logging, re from flask import jsonify, redirect, render_template, Blueprint, flash, session, url_for, request -from common.proto.context_pb2 import ( - Connection, Context, Device, Empty, Link, Service, Slice, Topology, ContextIdList, TopologyId, TopologyIdList) +from common.proto.context_pb2 import Empty, ContextIdList, TopologyId, TopologyIdList +from common.tools.descriptor.Loader import DescriptorLoader, compose_notifications from common.tools.grpc.Tools import grpc_message_to_json_string from common.tools.object_factory.Context import json_context_id from common.tools.object_factory.Topology import json_topology_id @@ -23,9 +23,6 @@ from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from service.client.ServiceClient import ServiceClient from slice.client.SliceClient import SliceClient -from webui.service.main.DescriptorTools import ( - format_custom_config_rules, get_descriptors_add_contexts, get_descriptors_add_services, get_descriptors_add_slices, - get_descriptors_add_topologies, split_devices_by_rules) from webui.service.main.forms import ContextTopologyForm, DescriptorForm main = Blueprint('main', __name__) @@ -37,38 +34,6 @@ slice_client = SliceClient() logger = logging.getLogger(__name__) -ENTITY_TO_TEXT = { - # name => singular, plural - 'context' : ('Context', 'Contexts' ), - 'topology' : ('Topology', 'Topologies' ), - 'device' : ('Device', 'Devices' ), - 'link' : ('Link', 'Links' ), - 'service' : ('Service', 'Services' ), - 'slice' : ('Slice', 'Slices' ), - 'connection': ('Connection', 'Connections'), -} - -ACTION_TO_TEXT = { - # action => infinitive, past - 'add' : ('Add', 'Added'), - 'update' : ('Update', 'Updated'), - 'config' : ('Configure', 'Configured'), -} - -def process_descriptor(entity_name, action_name, grpc_method, grpc_class, entities): - entity_name_singluar,entity_name_plural = ENTITY_TO_TEXT[entity_name] - action_infinitive, action_past = ACTION_TO_TEXT[action_name] - num_ok, num_err = 0, 0 - for entity in entities: - try: - grpc_method(grpc_class(**entity)) - num_ok += 1 - except Exception as e: # pylint: disable=broad-except - flash(f'Unable to {action_infinitive} {entity_name_singluar} {str(entity)}: {str(e)}', 'error') - num_err += 1 - if num_ok : flash(f'{str(num_ok)} {entity_name_plural} {action_past}', 'success') - if num_err: flash(f'{str(num_err)} {entity_name_plural} failed', 'danger') - def process_descriptors(descriptors): try: descriptors_file = request.files[descriptors.name] @@ -78,80 +43,11 @@ def process_descriptors(descriptors): flash(f'Unable to load descriptor file: {str(e)}', 'danger') return - dummy_mode = descriptors.get('dummy_mode' , False) - contexts = descriptors.get('contexts' , []) - topologies = descriptors.get('topologies' , []) - devices = descriptors.get('devices' , []) - links = descriptors.get('links' , []) - services = descriptors.get('services' , []) - slices = descriptors.get('slices' , []) - connections = descriptors.get('connections', []) - - # Format CustomConfigRules in Devices, Services and Slices provided in JSON format - for device in devices: - config_rules = device.get('device_config', {}).get('config_rules', []) - config_rules = format_custom_config_rules(config_rules) - device['device_config']['config_rules'] = config_rules - - for service in services: - config_rules = service.get('service_config', {}).get('config_rules', []) - config_rules = format_custom_config_rules(config_rules) - service['service_config']['config_rules'] = config_rules - - for slice in slices: - config_rules = slice.get('slice_config', {}).get('config_rules', []) - config_rules = format_custom_config_rules(config_rules) - slice['slice_config']['config_rules'] = config_rules - - - # Context and Topology require to create the entity first, and add devices, links, services, slices, etc. in a - # second stage. - contexts_add = get_descriptors_add_contexts(contexts) - topologies_add = get_descriptors_add_topologies(topologies) - - if dummy_mode: - # Dummy Mode: used to pre-load databases (WebUI debugging purposes) with no smart or automated tasks. - context_client.connect() - process_descriptor('context', 'add', context_client.SetContext, Context, contexts_add ) - process_descriptor('topology', 'add', context_client.SetTopology, Topology, topologies_add) - process_descriptor('device', 'add', context_client.SetDevice, Device, devices ) - process_descriptor('link', 'add', context_client.SetLink, Link, links ) - process_descriptor('service', 'add', context_client.SetService, Service, services ) - process_descriptor('slice', 'add', context_client.SetSlice, Slice, slices ) - process_descriptor('connection', 'add', context_client.SetConnection, Connection, connections ) - process_descriptor('context', 'update', context_client.SetContext, Context, contexts ) - process_descriptor('topology', 'update', context_client.SetTopology, Topology, topologies ) - context_client.close() - else: - # Normal mode: follows the automated workflows in the different components - assert len(connections) == 0, 'in normal mode, connections should not be set' - - # Device, Service and Slice require to first create the entity and the configure it - devices_add, devices_config = split_devices_by_rules(devices) - services_add = get_descriptors_add_services(services) - slices_add = get_descriptors_add_slices(slices) - - context_client.connect() - device_client.connect() - service_client.connect() - slice_client.connect() - - process_descriptor('context', 'add', context_client.SetContext, Context, contexts_add ) - process_descriptor('topology', 'add', context_client.SetTopology, Topology, topologies_add) - process_descriptor('device', 'add', device_client .AddDevice, Device, devices_add ) - process_descriptor('device', 'config', device_client .ConfigureDevice, Device, devices_config) - process_descriptor('link', 'add', context_client.SetLink, Link, links ) - process_descriptor('service', 'add', service_client.CreateService, Service, services_add ) - process_descriptor('service', 'update', service_client.UpdateService, Service, services ) - process_descriptor('slice', 'add', slice_client .CreateSlice, Slice, slices_add ) - process_descriptor('slice', 'update', slice_client .UpdateSlice, Slice, slices ) - process_descriptor('context', 'update', context_client.SetContext, Context, contexts ) - process_descriptor('topology', 'update', context_client.SetTopology, Topology, topologies ) - - slice_client.close() - service_client.close() - device_client.close() - context_client.close() + descriptor_loader = DescriptorLoader() + descriptor_loader.process_descriptors(descriptors) + results = descriptor_loader.get_results() + for message,level in compose_notifications(results): + flash(message, level) @main.route('/', methods=['GET', 'POST']) def home(): @@ -191,7 +87,7 @@ def home(): if descriptor_form.validate_on_submit(): process_descriptors(descriptor_form.descriptors) return redirect(url_for("main.home")) - except Exception as e: + except Exception as e: # pylint: disable=broad-except logger.exception('Descriptor load failed') flash(f'Descriptor load failed: `{str(e)}`', 'danger') finally: