Loading src/tests/ofc22/descriptors_emulated_xr.json +12 −0 Original line number Original line Diff line number Diff line Loading @@ -96,6 +96,18 @@ "device_operational_status": 1, "device_operational_status": 1, "device_drivers": [6], "device_drivers": [6], "device_endpoints": [] "device_endpoints": [] }, { "device_id": {"device_uuid": {"uuid": "X2-XR-CONSTELLATION"}}, "device_type": "xr-constellation", "device_config": {"config_rules": [ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.19.219.44"}}, {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "443"}}, {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"username\": \"xr-user-1\", \"password\": \"xr-user-1\", \"hub_module_name\": \"XR HUB 2\", \"consistency-mode\": \"lifecycle\"}"}} ]}, "device_operational_status": 1, "device_drivers": [6], "device_endpoints": [] } } ], ], "links": [ "links": [ Loading src/webui/service/service/routes.py +179 −3 Original line number Original line Diff line number Diff line Loading @@ -12,27 +12,56 @@ # See the License for the specific language governing permissions and # See the License for the specific language governing permissions and # limitations under the License. # limitations under the License. from contextlib import contextmanager import json import grpc import grpc from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for from collections import defaultdict from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for, request from common.proto.context_pb2 import ( from common.proto.context_pb2 import ( IsolationLevelEnum, Service, ServiceId, ServiceTypeEnum, ServiceStatusEnum, Connection) IsolationLevelEnum, Service, ServiceId, ServiceTypeEnum, ServiceStatusEnum, Connection, Empty, DeviceDriverEnum, ConfigActionEnum, Device, DeviceList) from common.tools.context_queries.Context import get_context from common.tools.context_queries.Context import get_context from common.tools.context_queries.Topology import get_topology from common.tools.context_queries.EndPoint import get_endpoint_names from common.tools.context_queries.EndPoint import get_endpoint_names from common.tools.context_queries.Service import get_service from common.tools.context_queries.Service import get_service from context.client.ContextClient import ContextClient from context.client.ContextClient import ContextClient from service.client.ServiceClient import ServiceClient from service.client.ServiceClient import ServiceClient from typing import Optional, Set from common.tools.object_factory.Topology import json_topology_id from common.tools.object_factory.ConfigRule import json_config_rule_set from common.tools.object_factory.Context import json_context_id service = Blueprint('service', __name__, url_prefix='/service') service = Blueprint('service', __name__, url_prefix='/service') context_client = ContextClient() context_client = ContextClient() service_client = ServiceClient() service_client = ServiceClient() @contextmanager def connected_client(c): try: c.connect() yield c finally: c.close() # Context client must be in connected state when calling this function def get_device_drivers_in_use(topology_uuid: str, context_uuid: str) -> Set[str]: active_drivers = set() grpc_topology = get_topology(context_client, topology_uuid, context_uuid=context_uuid, rw_copy=False) topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids} grpc_devices: DeviceList = context_client.ListDevices(Empty()) for device in grpc_devices.devices: if device.device_id.device_uuid.uuid in topo_device_uuids: for driver in device.device_drivers: active_drivers.add(DeviceDriverEnum.Name(driver)) return active_drivers @service.get('/') @service.get('/') def home(): def home(): if 'context_uuid' not in session or 'topology_uuid' not in session: if 'context_uuid' not in session or 'topology_uuid' not in session: flash("Please select a context!", "warning") flash("Please select a context!", "warning") return redirect(url_for("main.home")) return redirect(url_for("main.home")) context_uuid = session['context_uuid'] context_uuid = session['context_uuid'] topology_uuid = session['topology_uuid'] context_client.connect() context_client.connect() Loading @@ -44,10 +73,12 @@ def home(): try: try: services = context_client.ListServices(context_obj.context_id) services = context_client.ListServices(context_obj.context_id) services = services.services services = services.services active_drivers = get_device_drivers_in_use(topology_uuid, context_uuid) except grpc.RpcError as e: except grpc.RpcError as e: if e.code() != grpc.StatusCode.NOT_FOUND: raise if e.code() != grpc.StatusCode.NOT_FOUND: raise if e.details() != 'Context({:s}) not found'.format(context_uuid): raise if e.details() != 'Context({:s}) not found'.format(context_uuid): raise services, device_names, endpoints_data = list(), dict(), dict() services, device_names, endpoints_data = list(), dict(), dict() active_drivers = set() else: else: endpoint_ids = list() endpoint_ids = list() for service_ in services: for service_ in services: Loading @@ -57,7 +88,7 @@ def home(): context_client.close() context_client.close() return render_template( return render_template( 'service/home.html', services=services, device_names=device_names, endpoints_data=endpoints_data, 'service/home.html', services=services, device_names=device_names, endpoints_data=endpoints_data, ste=ServiceTypeEnum, sse=ServiceStatusEnum) ste=ServiceTypeEnum, sse=ServiceStatusEnum, active_drivers=active_drivers) @service.route('add', methods=['GET', 'POST']) @service.route('add', methods=['GET', 'POST']) Loading @@ -67,6 +98,151 @@ def add(): #return render_template('service/home.html') #return render_template('service/home.html') def get_hub_module_name(dev: Device) -> Optional[str]: for cr in dev.device_config.config_rules: if cr.action == ConfigActionEnum.CONFIGACTION_SET and cr.custom and cr.custom.resource_key == "_connect/settings": try: cr_dict = json.loads(cr.custom.resource_value) if "hub_module_name" in cr_dict: return cr_dict["hub_module_name"] except json.JSONDecodeError: pass return None @service.route('add-xr', methods=['GET', 'POST']) def add_xr(): ### FIXME: copypaste if 'context_uuid' not in session or 'topology_uuid' not in session: flash("Please select a context!", "warning") return redirect(url_for("main.home")) context_uuid = session['context_uuid'] topology_uuid = session['topology_uuid'] context_client.connect() grpc_topology = get_topology(context_client, topology_uuid, context_uuid=context_uuid, rw_copy=False) if grpc_topology is None: flash('Context({:s})/Topology({:s}) not found'.format(str(context_uuid), str(topology_uuid)), 'danger') return redirect(url_for("main.home")) else: topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids} grpc_devices= context_client.ListDevices(Empty()) devices = [ device for device in grpc_devices.devices if device.device_id.device_uuid.uuid in topo_device_uuids and DeviceDriverEnum.DEVICEDRIVER_XR in device.device_drivers ] devices.sort(key=lambda dev: dev.name) hub_interfaces_by_device = defaultdict(list) leaf_interfaces_by_device = defaultdict(list) constellation_name_to_uuid = {} dev_ep_to_uuid = {} ep_uuid_to_name = {} for d in devices: constellation_name_to_uuid[d.name] = d.device_id.device_uuid.uuid hm_name = get_hub_module_name(d) if hm_name is not None: hm_if_prefix= hm_name + "|" for ep in d.device_endpoints: dev_ep_to_uuid[(d.name, ep.name)] = ep.endpoint_id.endpoint_uuid.uuid if ep.name.startswith(hm_if_prefix): hub_interfaces_by_device[d.name].append(ep.name) else: leaf_interfaces_by_device[d.name].append(ep.name) ep_uuid_to_name[ep.endpoint_id.endpoint_uuid.uuid] = (d.name, ep.name) hub_interfaces_by_device[d.name].sort() leaf_interfaces_by_device[d.name].sort() # Find out what endpoints are already used so that they can be disabled # in the create screen context_obj = get_context(context_client, context_uuid, rw_copy=False) if context_obj is None: flash('Context({:s}) not found'.format(str(context_uuid)), 'danger') return redirect(request.url) services = context_client.ListServices(context_obj.context_id) ep_used_by={} for service in services.services: if service.service_type == ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE: for ep in service.service_endpoint_ids: ep_uuid = ep.endpoint_uuid.uuid if ep_uuid in ep_uuid_to_name: dev_name, ep_name = ep_uuid_to_name[ep_uuid] ep_used_by[f"{ep_name}@{dev_name}"] = service.name context_client.close() if request.method != 'POST': return render_template('service/add-xr.html', devices=devices, hub_if=hub_interfaces_by_device, leaf_if=leaf_interfaces_by_device, ep_used_by=ep_used_by) else: service_name = request.form["service_name"] if service_name == "": flash(f"Service name must be specified", 'danger') constellation = request.form["constellation"] constellation_uuid = constellation_name_to_uuid.get(constellation, None) if constellation_uuid is None: flash(f"Invalid constellation \"{constellation}\"", 'danger') hub_if = request.form["hubif"] hub_if_uuid = dev_ep_to_uuid.get((constellation, hub_if), None) if hub_if_uuid is None: flash(f"Invalid hub interface \"{hub_if}\"", 'danger') leaf_if = request.form["leafif"] leaf_if_uuid = dev_ep_to_uuid.get((constellation, leaf_if), None) if leaf_if_uuid is None: flash(f"Invalid leaf interface \"{leaf_if}\"", 'danger') if service_name == "" or constellation_uuid is None or hub_if_uuid is None or leaf_if_uuid is None: return redirect(request.url) json_context_uuid=json_context_id(context_uuid) sr = { "name": service_name, "service_id": { "context_id": {"context_uuid": {"uuid": context_uuid}}, "service_uuid": {"uuid": service_name} }, 'service_type' : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, "service_endpoint_ids": [ {'device_id': {'device_uuid': {'uuid': constellation_uuid}}, 'endpoint_uuid': {'uuid': hub_if_uuid}, 'topology_id': json_topology_id("admin", context_id=json_context_uuid)}, {'device_id': {'device_uuid': {'uuid': constellation_uuid}}, 'endpoint_uuid': {'uuid': leaf_if_uuid}, 'topology_id': json_topology_id("admin", context_id=json_context_uuid)} ], 'service_status' : {'service_status': ServiceStatusEnum.SERVICESTATUS_PLANNED}, 'service_constraints' : [], } json_tapi_settings = { 'capacity_value' : 50.0, 'capacity_unit' : 'GHz', 'layer_proto_name': 'PHOTONIC_MEDIA', 'layer_proto_qual': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC', 'direction' : 'UNIDIRECTIONAL', } config_rule = json_config_rule_set('/settings', json_tapi_settings) with connected_client(service_client) as sc: endpoints, sr['service_endpoint_ids'] = sr['service_endpoint_ids'], [] try: create_response = sc.CreateService(Service(**sr)) except Exception as e: flash(f'Failure to update service name {service_name} with endpoints and configuration, exception {str(e)}', 'danger') return redirect(request.url) sr['service_endpoint_ids'] = endpoints sr['service_config'] = {'config_rules': [config_rule]} try: update_response = sc.UpdateService(Service(**sr)) flash(f'Created service {update_response.service_uuid.uuid}', 'success') except Exception as e: flash(f'Failure to update service {create_response.service_uuid.uuid} with endpoints and configuration, exception {str(e)}', 'danger') return redirect(request.url) return redirect(url_for('service.home')) @service.get('<path:service_uuid>/detail') @service.get('<path:service_uuid>/detail') def detail(service_uuid: str): def detail(service_uuid: str): if 'context_uuid' not in session or 'topology_uuid' not in session: if 'context_uuid' not in session or 'topology_uuid' not in session: Loading src/webui/service/templates/service/add-xr.html 0 → 100644 +105 −0 Original line number Original line Diff line number Diff line <!-- Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) 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. --> {% extends 'base.html' %} {% block content %} <script> var js_hub_if = JSON.parse('{{hub_if | tojson | safe}}'); var js_leaf_if = JSON.parse('{{leaf_if | tojson | safe}}'); var js_ep_used_by = JSON.parse('{{ep_used_by | tojson | safe}}'); function clear_select_except_first(s) { while (s.options.length > 1) { s.remove(1); } } function add_ep_to_select(sel, dev_name, ep_name) { used_by = js_ep_used_by[ep_name + "@" + dev_name]; var o; if (used_by === undefined) { o = new Option(ep_name, ep_name) } else { o = new Option(ep_name + " (used by " + used_by + ")", ep_name) o.disabled=true } sel.add(o); } function constellationSelected() { const constellation_select = document.getElementById('constellation'); const hubif_select = document.getElementById('hubif'); const leafif_select = document.getElementById('leafif'); clear_select_except_first(hubif_select) clear_select_except_first(leafif_select) if (constellation_select.value) { const hub_ifs=js_hub_if[constellation_select.value] for (const hi of hub_ifs) { add_ep_to_select(hubif_select, constellation_select.value, hi); } const leaf_ifs=js_leaf_if[constellation_select.value] for (const li of leaf_ifs) { add_ep_to_select(leafif_select, constellation_select.value, li); } } } </script> <h1>Add XR Service</h1> <form action="#" method="post"> <fieldset class="form-group row mb-3"> <label for="service_name" class="col-sm-3 col-form-label">Service name:</label> <div class="col-sm-9"> <input type="text" id="service_name" name="service_name" class="form-control"> </div> </fieldset> <fieldset class="form-group row mb-3"> <label for="constellation" class="col-sm-3 col-form-label">Constellation:</label> <div class="col-sm-9"> <select name="constellation" id="constellation" onchange="constellationSelected()" class="form-select"> <option value="">(choose constellation)</option> {% for dev in devices %} <option value="{{dev.name}}">{{dev.name}}</option> {% endfor %} </select> </div> </fieldset> <fieldset class="form-group row mb-3"> <label for="hubif" class="col-sm-3 col-form-label">Hub Endpoint:</label> <div class="col-sm-9"> <select name="hubif" id="hubif" class="col-sm-8 form-select"> <option value="">(choose hub endpoint)</option> </select> </div> </fieldset> <fieldset class="form-group row mb-3"> <label for="leafif" class="col-sm-3 col-form-label">Leaf Endpoint:</label> <div class="col-sm-9"> <select name="leafif" id="leafif" class="col-sm-8 form-select"> <option value="">(choose leaf endpoint)</option> </select> </div> </fieldset> <input type="submit" class="btn btn-primary" value="Create"> </form> {% endblock %} src/webui/service/templates/service/home.html +12 −0 Original line number Original line Diff line number Diff line Loading @@ -26,6 +26,18 @@ Add New Service Add New Service </a> </a> </div> --> </div> --> <!-- Only display XR service addition button if there are XR constellations. Otherwise it might confuse user, as other service types do not have GUI to add service yet. --> {% if "DEVICEDRIVER_XR" in active_drivers %} <div class="col"> <a href="{{ url_for('service.add_xr') }}" class="btn btn-primary" style="margin-bottom: 10px;"> <i class="bi bi-plus"></i> Add New XR Service </a> </div> {% endif %} <div class="col"> <div class="col"> {{ services | length }} services found in context <i>{{ session['context_uuid'] }}</i> {{ services | length }} services found in context <i>{{ session['context_uuid'] }}</i> </div> </div> Loading Loading
src/tests/ofc22/descriptors_emulated_xr.json +12 −0 Original line number Original line Diff line number Diff line Loading @@ -96,6 +96,18 @@ "device_operational_status": 1, "device_operational_status": 1, "device_drivers": [6], "device_drivers": [6], "device_endpoints": [] "device_endpoints": [] }, { "device_id": {"device_uuid": {"uuid": "X2-XR-CONSTELLATION"}}, "device_type": "xr-constellation", "device_config": {"config_rules": [ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.19.219.44"}}, {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "443"}}, {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"username\": \"xr-user-1\", \"password\": \"xr-user-1\", \"hub_module_name\": \"XR HUB 2\", \"consistency-mode\": \"lifecycle\"}"}} ]}, "device_operational_status": 1, "device_drivers": [6], "device_endpoints": [] } } ], ], "links": [ "links": [ Loading
src/webui/service/service/routes.py +179 −3 Original line number Original line Diff line number Diff line Loading @@ -12,27 +12,56 @@ # See the License for the specific language governing permissions and # See the License for the specific language governing permissions and # limitations under the License. # limitations under the License. from contextlib import contextmanager import json import grpc import grpc from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for from collections import defaultdict from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for, request from common.proto.context_pb2 import ( from common.proto.context_pb2 import ( IsolationLevelEnum, Service, ServiceId, ServiceTypeEnum, ServiceStatusEnum, Connection) IsolationLevelEnum, Service, ServiceId, ServiceTypeEnum, ServiceStatusEnum, Connection, Empty, DeviceDriverEnum, ConfigActionEnum, Device, DeviceList) from common.tools.context_queries.Context import get_context from common.tools.context_queries.Context import get_context from common.tools.context_queries.Topology import get_topology from common.tools.context_queries.EndPoint import get_endpoint_names from common.tools.context_queries.EndPoint import get_endpoint_names from common.tools.context_queries.Service import get_service from common.tools.context_queries.Service import get_service from context.client.ContextClient import ContextClient from context.client.ContextClient import ContextClient from service.client.ServiceClient import ServiceClient from service.client.ServiceClient import ServiceClient from typing import Optional, Set from common.tools.object_factory.Topology import json_topology_id from common.tools.object_factory.ConfigRule import json_config_rule_set from common.tools.object_factory.Context import json_context_id service = Blueprint('service', __name__, url_prefix='/service') service = Blueprint('service', __name__, url_prefix='/service') context_client = ContextClient() context_client = ContextClient() service_client = ServiceClient() service_client = ServiceClient() @contextmanager def connected_client(c): try: c.connect() yield c finally: c.close() # Context client must be in connected state when calling this function def get_device_drivers_in_use(topology_uuid: str, context_uuid: str) -> Set[str]: active_drivers = set() grpc_topology = get_topology(context_client, topology_uuid, context_uuid=context_uuid, rw_copy=False) topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids} grpc_devices: DeviceList = context_client.ListDevices(Empty()) for device in grpc_devices.devices: if device.device_id.device_uuid.uuid in topo_device_uuids: for driver in device.device_drivers: active_drivers.add(DeviceDriverEnum.Name(driver)) return active_drivers @service.get('/') @service.get('/') def home(): def home(): if 'context_uuid' not in session or 'topology_uuid' not in session: if 'context_uuid' not in session or 'topology_uuid' not in session: flash("Please select a context!", "warning") flash("Please select a context!", "warning") return redirect(url_for("main.home")) return redirect(url_for("main.home")) context_uuid = session['context_uuid'] context_uuid = session['context_uuid'] topology_uuid = session['topology_uuid'] context_client.connect() context_client.connect() Loading @@ -44,10 +73,12 @@ def home(): try: try: services = context_client.ListServices(context_obj.context_id) services = context_client.ListServices(context_obj.context_id) services = services.services services = services.services active_drivers = get_device_drivers_in_use(topology_uuid, context_uuid) except grpc.RpcError as e: except grpc.RpcError as e: if e.code() != grpc.StatusCode.NOT_FOUND: raise if e.code() != grpc.StatusCode.NOT_FOUND: raise if e.details() != 'Context({:s}) not found'.format(context_uuid): raise if e.details() != 'Context({:s}) not found'.format(context_uuid): raise services, device_names, endpoints_data = list(), dict(), dict() services, device_names, endpoints_data = list(), dict(), dict() active_drivers = set() else: else: endpoint_ids = list() endpoint_ids = list() for service_ in services: for service_ in services: Loading @@ -57,7 +88,7 @@ def home(): context_client.close() context_client.close() return render_template( return render_template( 'service/home.html', services=services, device_names=device_names, endpoints_data=endpoints_data, 'service/home.html', services=services, device_names=device_names, endpoints_data=endpoints_data, ste=ServiceTypeEnum, sse=ServiceStatusEnum) ste=ServiceTypeEnum, sse=ServiceStatusEnum, active_drivers=active_drivers) @service.route('add', methods=['GET', 'POST']) @service.route('add', methods=['GET', 'POST']) Loading @@ -67,6 +98,151 @@ def add(): #return render_template('service/home.html') #return render_template('service/home.html') def get_hub_module_name(dev: Device) -> Optional[str]: for cr in dev.device_config.config_rules: if cr.action == ConfigActionEnum.CONFIGACTION_SET and cr.custom and cr.custom.resource_key == "_connect/settings": try: cr_dict = json.loads(cr.custom.resource_value) if "hub_module_name" in cr_dict: return cr_dict["hub_module_name"] except json.JSONDecodeError: pass return None @service.route('add-xr', methods=['GET', 'POST']) def add_xr(): ### FIXME: copypaste if 'context_uuid' not in session or 'topology_uuid' not in session: flash("Please select a context!", "warning") return redirect(url_for("main.home")) context_uuid = session['context_uuid'] topology_uuid = session['topology_uuid'] context_client.connect() grpc_topology = get_topology(context_client, topology_uuid, context_uuid=context_uuid, rw_copy=False) if grpc_topology is None: flash('Context({:s})/Topology({:s}) not found'.format(str(context_uuid), str(topology_uuid)), 'danger') return redirect(url_for("main.home")) else: topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids} grpc_devices= context_client.ListDevices(Empty()) devices = [ device for device in grpc_devices.devices if device.device_id.device_uuid.uuid in topo_device_uuids and DeviceDriverEnum.DEVICEDRIVER_XR in device.device_drivers ] devices.sort(key=lambda dev: dev.name) hub_interfaces_by_device = defaultdict(list) leaf_interfaces_by_device = defaultdict(list) constellation_name_to_uuid = {} dev_ep_to_uuid = {} ep_uuid_to_name = {} for d in devices: constellation_name_to_uuid[d.name] = d.device_id.device_uuid.uuid hm_name = get_hub_module_name(d) if hm_name is not None: hm_if_prefix= hm_name + "|" for ep in d.device_endpoints: dev_ep_to_uuid[(d.name, ep.name)] = ep.endpoint_id.endpoint_uuid.uuid if ep.name.startswith(hm_if_prefix): hub_interfaces_by_device[d.name].append(ep.name) else: leaf_interfaces_by_device[d.name].append(ep.name) ep_uuid_to_name[ep.endpoint_id.endpoint_uuid.uuid] = (d.name, ep.name) hub_interfaces_by_device[d.name].sort() leaf_interfaces_by_device[d.name].sort() # Find out what endpoints are already used so that they can be disabled # in the create screen context_obj = get_context(context_client, context_uuid, rw_copy=False) if context_obj is None: flash('Context({:s}) not found'.format(str(context_uuid)), 'danger') return redirect(request.url) services = context_client.ListServices(context_obj.context_id) ep_used_by={} for service in services.services: if service.service_type == ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE: for ep in service.service_endpoint_ids: ep_uuid = ep.endpoint_uuid.uuid if ep_uuid in ep_uuid_to_name: dev_name, ep_name = ep_uuid_to_name[ep_uuid] ep_used_by[f"{ep_name}@{dev_name}"] = service.name context_client.close() if request.method != 'POST': return render_template('service/add-xr.html', devices=devices, hub_if=hub_interfaces_by_device, leaf_if=leaf_interfaces_by_device, ep_used_by=ep_used_by) else: service_name = request.form["service_name"] if service_name == "": flash(f"Service name must be specified", 'danger') constellation = request.form["constellation"] constellation_uuid = constellation_name_to_uuid.get(constellation, None) if constellation_uuid is None: flash(f"Invalid constellation \"{constellation}\"", 'danger') hub_if = request.form["hubif"] hub_if_uuid = dev_ep_to_uuid.get((constellation, hub_if), None) if hub_if_uuid is None: flash(f"Invalid hub interface \"{hub_if}\"", 'danger') leaf_if = request.form["leafif"] leaf_if_uuid = dev_ep_to_uuid.get((constellation, leaf_if), None) if leaf_if_uuid is None: flash(f"Invalid leaf interface \"{leaf_if}\"", 'danger') if service_name == "" or constellation_uuid is None or hub_if_uuid is None or leaf_if_uuid is None: return redirect(request.url) json_context_uuid=json_context_id(context_uuid) sr = { "name": service_name, "service_id": { "context_id": {"context_uuid": {"uuid": context_uuid}}, "service_uuid": {"uuid": service_name} }, 'service_type' : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, "service_endpoint_ids": [ {'device_id': {'device_uuid': {'uuid': constellation_uuid}}, 'endpoint_uuid': {'uuid': hub_if_uuid}, 'topology_id': json_topology_id("admin", context_id=json_context_uuid)}, {'device_id': {'device_uuid': {'uuid': constellation_uuid}}, 'endpoint_uuid': {'uuid': leaf_if_uuid}, 'topology_id': json_topology_id("admin", context_id=json_context_uuid)} ], 'service_status' : {'service_status': ServiceStatusEnum.SERVICESTATUS_PLANNED}, 'service_constraints' : [], } json_tapi_settings = { 'capacity_value' : 50.0, 'capacity_unit' : 'GHz', 'layer_proto_name': 'PHOTONIC_MEDIA', 'layer_proto_qual': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC', 'direction' : 'UNIDIRECTIONAL', } config_rule = json_config_rule_set('/settings', json_tapi_settings) with connected_client(service_client) as sc: endpoints, sr['service_endpoint_ids'] = sr['service_endpoint_ids'], [] try: create_response = sc.CreateService(Service(**sr)) except Exception as e: flash(f'Failure to update service name {service_name} with endpoints and configuration, exception {str(e)}', 'danger') return redirect(request.url) sr['service_endpoint_ids'] = endpoints sr['service_config'] = {'config_rules': [config_rule]} try: update_response = sc.UpdateService(Service(**sr)) flash(f'Created service {update_response.service_uuid.uuid}', 'success') except Exception as e: flash(f'Failure to update service {create_response.service_uuid.uuid} with endpoints and configuration, exception {str(e)}', 'danger') return redirect(request.url) return redirect(url_for('service.home')) @service.get('<path:service_uuid>/detail') @service.get('<path:service_uuid>/detail') def detail(service_uuid: str): def detail(service_uuid: str): if 'context_uuid' not in session or 'topology_uuid' not in session: if 'context_uuid' not in session or 'topology_uuid' not in session: Loading
src/webui/service/templates/service/add-xr.html 0 → 100644 +105 −0 Original line number Original line Diff line number Diff line <!-- Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) 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. --> {% extends 'base.html' %} {% block content %} <script> var js_hub_if = JSON.parse('{{hub_if | tojson | safe}}'); var js_leaf_if = JSON.parse('{{leaf_if | tojson | safe}}'); var js_ep_used_by = JSON.parse('{{ep_used_by | tojson | safe}}'); function clear_select_except_first(s) { while (s.options.length > 1) { s.remove(1); } } function add_ep_to_select(sel, dev_name, ep_name) { used_by = js_ep_used_by[ep_name + "@" + dev_name]; var o; if (used_by === undefined) { o = new Option(ep_name, ep_name) } else { o = new Option(ep_name + " (used by " + used_by + ")", ep_name) o.disabled=true } sel.add(o); } function constellationSelected() { const constellation_select = document.getElementById('constellation'); const hubif_select = document.getElementById('hubif'); const leafif_select = document.getElementById('leafif'); clear_select_except_first(hubif_select) clear_select_except_first(leafif_select) if (constellation_select.value) { const hub_ifs=js_hub_if[constellation_select.value] for (const hi of hub_ifs) { add_ep_to_select(hubif_select, constellation_select.value, hi); } const leaf_ifs=js_leaf_if[constellation_select.value] for (const li of leaf_ifs) { add_ep_to_select(leafif_select, constellation_select.value, li); } } } </script> <h1>Add XR Service</h1> <form action="#" method="post"> <fieldset class="form-group row mb-3"> <label for="service_name" class="col-sm-3 col-form-label">Service name:</label> <div class="col-sm-9"> <input type="text" id="service_name" name="service_name" class="form-control"> </div> </fieldset> <fieldset class="form-group row mb-3"> <label for="constellation" class="col-sm-3 col-form-label">Constellation:</label> <div class="col-sm-9"> <select name="constellation" id="constellation" onchange="constellationSelected()" class="form-select"> <option value="">(choose constellation)</option> {% for dev in devices %} <option value="{{dev.name}}">{{dev.name}}</option> {% endfor %} </select> </div> </fieldset> <fieldset class="form-group row mb-3"> <label for="hubif" class="col-sm-3 col-form-label">Hub Endpoint:</label> <div class="col-sm-9"> <select name="hubif" id="hubif" class="col-sm-8 form-select"> <option value="">(choose hub endpoint)</option> </select> </div> </fieldset> <fieldset class="form-group row mb-3"> <label for="leafif" class="col-sm-3 col-form-label">Leaf Endpoint:</label> <div class="col-sm-9"> <select name="leafif" id="leafif" class="col-sm-8 form-select"> <option value="">(choose leaf endpoint)</option> </select> </div> </fieldset> <input type="submit" class="btn btn-primary" value="Create"> </form> {% endblock %}
src/webui/service/templates/service/home.html +12 −0 Original line number Original line Diff line number Diff line Loading @@ -26,6 +26,18 @@ Add New Service Add New Service </a> </a> </div> --> </div> --> <!-- Only display XR service addition button if there are XR constellations. Otherwise it might confuse user, as other service types do not have GUI to add service yet. --> {% if "DEVICEDRIVER_XR" in active_drivers %} <div class="col"> <a href="{{ url_for('service.add_xr') }}" class="btn btn-primary" style="margin-bottom: 10px;"> <i class="bi bi-plus"></i> Add New XR Service </a> </div> {% endif %} <div class="col"> <div class="col"> {{ services | length }} services found in context <i>{{ session['context_uuid'] }}</i> {{ services | length }} services found in context <i>{{ session['context_uuid'] }}</i> </div> </div> Loading