diff --git a/proto/context.proto b/proto/context.proto index 7570a4596a0abd254d93c77131f03fa432cf09c7..d5022ac292f04cd2e9b80f690be3077e7aedd868 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -202,6 +202,7 @@ enum DeviceDriverEnum { DEVICEDRIVER_IETF_L2VPN = 7; DEVICEDRIVER_GNMI_OPENCONFIG = 8; DEVICEDRIVER_FLEXSCALE = 9; + DEVICEDRIVER_IETF_ACTN = 10; } enum DeviceOperationalStatusEnum { diff --git a/proto/generate_code_java.sh b/proto/generate_code_java.sh new file mode 100755 index 0000000000000000000000000000000000000000..4a3dbcf128b28e9feb1f96f95b7541b0f32cc664 --- /dev/null +++ b/proto/generate_code_java.sh @@ -0,0 +1,32 @@ +#!/bin/bash -eu +# 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. + + +export JAVA_COMPONENTS="ztp policy" + +export TFS_ROOT_DIR=$(dirname $(dirname $(realpath $0))) + +for COMPONENT in $JAVA_COMPONENTS; do + echo "\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/" + echo + echo "[TFS] Now building" $COMPONENT + echo + echo "\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/" + cd $TFS_ROOT_DIR/src/$COMPONENT + + ./mvnw spotless:apply + ./mvnw install -DskipUTs +done + diff --git a/scripts/run_tests_locally-device-ietf-actn.sh b/scripts/run_tests_locally-device-ietf-actn.sh new file mode 100755 index 0000000000000000000000000000000000000000..8e602b31d9465821dfd798e8038de9b78f7dedc6 --- /dev/null +++ b/scripts/run_tests_locally-device-ietf-actn.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# 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. + + +PROJECTDIR=`pwd` + +cd $PROJECTDIR/src +RCFILE=$PROJECTDIR/coverage/.coveragerc + +# Run unitary tests and analyze coverage of code at same time +# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0 +coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ + device/tests/test_unitary_ietf_actn.py diff --git a/scripts/run_tests_locally-nbi-debug-api.sh b/scripts/run_tests_locally-nbi-debug-api.sh new file mode 100755 index 0000000000000000000000000000000000000000..218bad8c57b508cee4fc5cfe40f0f6d484dff32e --- /dev/null +++ b/scripts/run_tests_locally-nbi-debug-api.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# 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. + + +PROJECTDIR=`pwd` + +cd $PROJECTDIR/src +RCFILE=$PROJECTDIR/coverage/.coveragerc + +# Run unitary tests and analyze coverage of code at same time +# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0 +coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ + nbi/tests/test_debug_api.py diff --git a/src/common/tests/MockServicerImpl_Context.py b/src/common/tests/MockServicerImpl_Context.py index 98a216850a757e0c21c44d2e295cb4b93c538e05..464517a767f31d95277dc8205cf49f527fe96a48 100644 --- a/src/common/tests/MockServicerImpl_Context.py +++ b/src/common/tests/MockServicerImpl_Context.py @@ -25,12 +25,13 @@ from common.proto.context_pb2 import ( Slice, SliceEvent, SliceFilter, SliceId, SliceIdList, SliceList, Topology, TopologyDetails, TopologyEvent, TopologyId, TopologyIdList, TopologyList) from common.proto.context_pb2_grpc import ContextServiceServicer +from common.proto.policy_pb2 import PolicyRule, PolicyRuleId, PolicyRuleIdList, PolicyRuleList from common.tools.grpc.Tools import grpc_message_to_json, grpc_message_to_json_string from common.tools.object_factory.Device import json_device_id from common.tools.object_factory.Link import json_link_id from .InMemoryObjectDatabase import InMemoryObjectDatabase from .MockMessageBroker import ( - TOPIC_CONNECTION, TOPIC_CONTEXT, TOPIC_DEVICE, TOPIC_LINK, TOPIC_SERVICE, TOPIC_SLICE, TOPIC_TOPOLOGY, + TOPIC_CONNECTION, TOPIC_CONTEXT, TOPIC_DEVICE, TOPIC_LINK, TOPIC_SERVICE, TOPIC_SLICE, TOPIC_TOPOLOGY, TOPIC_POLICY, MockMessageBroker, notify_event) LOGGER = logging.getLogger(__name__) @@ -62,58 +63,58 @@ class MockServicerImpl_Context(ContextServiceServicer): # ----- Context ---------------------------------------------------------------------------------------------------- - def ListContextIds(self, request: Empty, context : grpc.ServicerContext) -> ContextIdList: + def ListContextIds(self, request : Empty, context : grpc.ServicerContext) -> ContextIdList: LOGGER.debug('[ListContextIds] request={:s}'.format(grpc_message_to_json_string(request))) reply = ContextIdList(context_ids=[context.context_id for context in self.obj_db.get_entries('context')]) LOGGER.debug('[ListContextIds] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def ListContexts(self, request: Empty, context : grpc.ServicerContext) -> ContextList: + def ListContexts(self, request : Empty, context : grpc.ServicerContext) -> ContextList: LOGGER.debug('[ListContexts] request={:s}'.format(grpc_message_to_json_string(request))) reply = ContextList(contexts=self.obj_db.get_entries('context')) LOGGER.debug('[ListContexts] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetContext(self, request: ContextId, context : grpc.ServicerContext) -> Context: + def GetContext(self, request : ContextId, context : grpc.ServicerContext) -> Context: LOGGER.debug('[GetContext] request={:s}'.format(grpc_message_to_json_string(request))) reply = self.obj_db.get_entry('context', request.context_uuid.uuid, context) LOGGER.debug('[GetContext] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def SetContext(self, request: Context, context : grpc.ServicerContext) -> ContextId: + def SetContext(self, request : Context, context : grpc.ServicerContext) -> ContextId: LOGGER.debug('[SetContext] request={:s}'.format(grpc_message_to_json_string(request))) reply,_ = self._set(request, 'context', request.context_id.context_uuid.uuid, 'context_id', TOPIC_CONTEXT) LOGGER.debug('[SetContext] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def RemoveContext(self, request: ContextId, context : grpc.ServicerContext) -> Empty: + def RemoveContext(self, request : ContextId, context : grpc.ServicerContext) -> Empty: LOGGER.debug('[RemoveContext] request={:s}'.format(grpc_message_to_json_string(request))) reply = self._del(request, 'context', request.context_uuid.uuid, 'context_id', TOPIC_CONTEXT, context) LOGGER.debug('[RemoveContext] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetContextEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[ContextEvent]: + def GetContextEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[ContextEvent]: LOGGER.debug('[GetContextEvents] request={:s}'.format(grpc_message_to_json_string(request))) for message in self.msg_broker.consume({TOPIC_CONTEXT}): yield ContextEvent(**json.loads(message.content)) # ----- Topology --------------------------------------------------------------------------------------------------- - def ListTopologyIds(self, request: ContextId, context : grpc.ServicerContext) -> TopologyIdList: + def ListTopologyIds(self, request : ContextId, context : grpc.ServicerContext) -> TopologyIdList: LOGGER.debug('[ListTopologyIds] request={:s}'.format(grpc_message_to_json_string(request))) topologies = self.obj_db.get_entries('topology[{:s}]'.format(str(request.context_uuid.uuid))) reply = TopologyIdList(topology_ids=[topology.topology_id for topology in topologies]) LOGGER.debug('[ListTopologyIds] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def ListTopologies(self, request: ContextId, context : grpc.ServicerContext) -> TopologyList: + def ListTopologies(self, request : ContextId, context : grpc.ServicerContext) -> TopologyList: LOGGER.debug('[ListTopologies] request={:s}'.format(grpc_message_to_json_string(request))) topologies = self.obj_db.get_entries('topology[{:s}]'.format(str(request.context_uuid.uuid))) reply = TopologyList(topologies=[topology for topology in topologies]) LOGGER.debug('[ListTopologies] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetTopology(self, request: TopologyId, context : grpc.ServicerContext) -> Topology: + def GetTopology(self, request : TopologyId, context : grpc.ServicerContext) -> Topology: LOGGER.debug('[GetTopology] request={:s}'.format(grpc_message_to_json_string(request))) container_name = 'topology[{:s}]'.format(str(request.context_id.context_uuid.uuid)) reply = self.obj_db.get_entry(container_name, request.topology_uuid.uuid, context) @@ -143,7 +144,7 @@ class MockServicerImpl_Context(ContextServiceServicer): LOGGER.debug('[GetTopologyDetails] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def SetTopology(self, request: Topology, context : grpc.ServicerContext) -> TopologyId: + def SetTopology(self, request : Topology, context : grpc.ServicerContext) -> TopologyId: LOGGER.debug('[SetTopology] request={:s}'.format(grpc_message_to_json_string(request))) context_uuid = str(request.topology_id.context_id.context_uuid.uuid) container_name = 'topology[{:s}]'.format(context_uuid) @@ -164,10 +165,12 @@ class MockServicerImpl_Context(ContextServiceServicer): rw_request = Topology() rw_request.CopyFrom(request) + # pylint: disable=no-member del rw_request.device_ids[:] for device_uuid in sorted(device_uuids): rw_request.device_ids.append(DeviceId(**json_device_id(device_uuid))) + # pylint: disable=no-member del rw_request.link_ids[:] for link_uuid in sorted(link_uuids): rw_request.link_ids.append(LinkId(**json_link_id(link_uuid))) @@ -181,12 +184,14 @@ class MockServicerImpl_Context(ContextServiceServicer): if _topology_id.topology_uuid.uuid == topology_uuid: break else: # topology not found, add it - context_.topology_ids.add().topology_uuid.uuid = topology_uuid + topology_id = context_.topology_ids.add() + topology_id.context_id.context_uuid.uuid = context_uuid + topology_id.topology_uuid.uuid = topology_uuid LOGGER.debug('[SetTopology] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def RemoveTopology(self, request: TopologyId, context : grpc.ServicerContext) -> Empty: + def RemoveTopology(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: LOGGER.debug('[RemoveTopology] request={:s}'.format(grpc_message_to_json_string(request))) context_uuid = str(request.context_id.context_uuid.uuid) container_name = 'topology[{:s}]'.format(context_uuid) @@ -195,39 +200,40 @@ class MockServicerImpl_Context(ContextServiceServicer): context_ = self.obj_db.get_entry('context', context_uuid, context) for _topology_id in context_.topology_ids: - if _topology_id.topology_uuid.uuid == topology_uuid: - context_.topology_ids.remove(_topology_id) - break + if _topology_id.context_id.context_uuid.uuid != context_uuid: continue + if _topology_id.topology_uuid.uuid != topology_uuid: continue + context_.topology_ids.remove(_topology_id) + break LOGGER.debug('[RemoveTopology] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetTopologyEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[TopologyEvent]: + def GetTopologyEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[TopologyEvent]: LOGGER.debug('[GetTopologyEvents] request={:s}'.format(grpc_message_to_json_string(request))) for message in self.msg_broker.consume({TOPIC_TOPOLOGY}): yield TopologyEvent(**json.loads(message.content)) # ----- Device ----------------------------------------------------------------------------------------------------- - def ListDeviceIds(self, request: Empty, context : grpc.ServicerContext) -> DeviceIdList: + def ListDeviceIds(self, request : Empty, context : grpc.ServicerContext) -> DeviceIdList: LOGGER.debug('[ListDeviceIds] request={:s}'.format(grpc_message_to_json_string(request))) reply = DeviceIdList(device_ids=[device.device_id for device in self.obj_db.get_entries('device')]) LOGGER.debug('[ListDeviceIds] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def ListDevices(self, request: Empty, context : grpc.ServicerContext) -> DeviceList: + def ListDevices(self, request : Empty, context : grpc.ServicerContext) -> DeviceList: LOGGER.debug('[ListDevices] request={:s}'.format(grpc_message_to_json_string(request))) reply = DeviceList(devices=self.obj_db.get_entries('device')) LOGGER.debug('[ListDevices] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetDevice(self, request: DeviceId, context : grpc.ServicerContext) -> Device: + def GetDevice(self, request : DeviceId, context : grpc.ServicerContext) -> Device: LOGGER.debug('[GetDevice] request={:s}'.format(grpc_message_to_json_string(request))) reply = self.obj_db.get_entry('device', request.device_uuid.uuid, context) LOGGER.debug('[GetDevice] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def SetDevice(self, request: Context, context : grpc.ServicerContext) -> DeviceId: + def SetDevice(self, request : Context, context : grpc.ServicerContext) -> DeviceId: LOGGER.debug('[SetDevice] request={:s}'.format(grpc_message_to_json_string(request))) device_uuid = request.device_id.device_uuid.uuid reply, device = self._set(request, 'device', device_uuid, 'device_id', TOPIC_DEVICE) @@ -253,7 +259,7 @@ class MockServicerImpl_Context(ContextServiceServicer): LOGGER.debug('[SetDevice] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def RemoveDevice(self, request: DeviceId, context : grpc.ServicerContext) -> Empty: + def RemoveDevice(self, request : DeviceId, context : grpc.ServicerContext) -> Empty: LOGGER.debug('[RemoveDevice] request={:s}'.format(grpc_message_to_json_string(request))) device_uuid = request.device_uuid.uuid device = self.obj_db.get_entry('device', device_uuid, context) @@ -279,7 +285,7 @@ class MockServicerImpl_Context(ContextServiceServicer): LOGGER.debug('[RemoveDevice] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetDeviceEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[DeviceEvent]: + def GetDeviceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[DeviceEvent]: LOGGER.debug('[GetDeviceEvents] request={:s}'.format(grpc_message_to_json_string(request))) for message in self.msg_broker.consume({TOPIC_DEVICE}): yield DeviceEvent(**json.loads(message.content)) @@ -313,25 +319,25 @@ class MockServicerImpl_Context(ContextServiceServicer): # ----- Link ------------------------------------------------------------------------------------------------------- - def ListLinkIds(self, request: Empty, context : grpc.ServicerContext) -> LinkIdList: + def ListLinkIds(self, request : Empty, context : grpc.ServicerContext) -> LinkIdList: LOGGER.debug('[ListLinkIds] request={:s}'.format(grpc_message_to_json_string(request))) reply = LinkIdList(link_ids=[link.link_id for link in self.obj_db.get_entries('link')]) LOGGER.debug('[ListLinkIds] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def ListLinks(self, request: Empty, context : grpc.ServicerContext) -> LinkList: + def ListLinks(self, request : Empty, context : grpc.ServicerContext) -> LinkList: LOGGER.debug('[ListLinks] request={:s}'.format(grpc_message_to_json_string(request))) reply = LinkList(links=self.obj_db.get_entries('link')) LOGGER.debug('[ListLinks] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetLink(self, request: LinkId, context : grpc.ServicerContext) -> Link: + def GetLink(self, request : LinkId, context : grpc.ServicerContext) -> Link: LOGGER.debug('[GetLink] request={:s}'.format(grpc_message_to_json_string(request))) reply = self.obj_db.get_entry('link', request.link_uuid.uuid, context) LOGGER.debug('[GetLink] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def SetLink(self, request: Context, context : grpc.ServicerContext) -> LinkId: + def SetLink(self, request : Context, context : grpc.ServicerContext) -> LinkId: LOGGER.debug('[SetLink] request={:s}'.format(grpc_message_to_json_string(request))) link_uuid = request.link_id.link_uuid.uuid reply, link = self._set(request, 'link', link_uuid, 'link_id', TOPIC_LINK) @@ -357,7 +363,7 @@ class MockServicerImpl_Context(ContextServiceServicer): LOGGER.debug('[SetLink] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def RemoveLink(self, request: LinkId, context : grpc.ServicerContext) -> Empty: + def RemoveLink(self, request : LinkId, context : grpc.ServicerContext) -> Empty: LOGGER.debug('[RemoveLink] request={:s}'.format(grpc_message_to_json_string(request))) link_uuid = request.link_uuid.uuid link = self.obj_db.get_entry('link', link_uuid, context) @@ -383,35 +389,35 @@ class MockServicerImpl_Context(ContextServiceServicer): LOGGER.debug('[RemoveLink] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetLinkEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[LinkEvent]: + def GetLinkEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[LinkEvent]: LOGGER.debug('[GetLinkEvents] request={:s}'.format(grpc_message_to_json_string(request))) for message in self.msg_broker.consume({TOPIC_LINK}): yield LinkEvent(**json.loads(message.content)) # ----- Slice ------------------------------------------------------------------------------------------------------ - def ListSliceIds(self, request: ContextId, context : grpc.ServicerContext) -> SliceIdList: + def ListSliceIds(self, request : ContextId, context : grpc.ServicerContext) -> SliceIdList: LOGGER.debug('[ListSliceIds] request={:s}'.format(grpc_message_to_json_string(request))) slices = self.obj_db.get_entries('slice[{:s}]'.format(str(request.context_uuid.uuid))) reply = SliceIdList(slice_ids=[slice.slice_id for slice in slices]) LOGGER.debug('[ListSliceIds] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def ListSlices(self, request: ContextId, context : grpc.ServicerContext) -> SliceList: + def ListSlices(self, request : ContextId, context : grpc.ServicerContext) -> SliceList: LOGGER.debug('[ListSlices] request={:s}'.format(grpc_message_to_json_string(request))) slices = self.obj_db.get_entries('slice[{:s}]'.format(str(request.context_uuid.uuid))) reply = SliceList(slices=[slice for slice in slices]) LOGGER.debug('[ListSlices] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetSlice(self, request: SliceId, context : grpc.ServicerContext) -> Slice: + def GetSlice(self, request : SliceId, context : grpc.ServicerContext) -> Slice: LOGGER.debug('[GetSlice] request={:s}'.format(grpc_message_to_json_string(request))) container_name = 'slice[{:s}]'.format(str(request.context_id.context_uuid.uuid)) reply = self.obj_db.get_entry(container_name, request.slice_uuid.uuid, context) LOGGER.debug('[GetSlice] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def SetSlice(self, request: Slice, context : grpc.ServicerContext) -> SliceId: + def SetSlice(self, request : Slice, context : grpc.ServicerContext) -> SliceId: LOGGER.debug('[SetSlice] request={:s}'.format(grpc_message_to_json_string(request))) context_uuid = str(request.slice_id.context_id.context_uuid.uuid) container_name = 'slice[{:s}]'.format(context_uuid) @@ -423,12 +429,14 @@ class MockServicerImpl_Context(ContextServiceServicer): if _slice_id.slice_uuid.uuid == slice_uuid: break else: # slice not found, add it - context_.slice_ids.add().slice_uuid.uuid = slice_uuid + slice_id = context_.slice_ids.add() + slice_id.context_id.context_uuid.uuid = context_uuid + slice_id.slice_uuid.uuid = slice_uuid LOGGER.debug('[SetSlice] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def RemoveSlice(self, request: SliceId, context : grpc.ServicerContext) -> Empty: + def RemoveSlice(self, request : SliceId, context : grpc.ServicerContext) -> Empty: LOGGER.debug('[RemoveSlice] request={:s}'.format(grpc_message_to_json_string(request))) context_uuid = str(request.context_id.context_uuid.uuid) container_name = 'slice[{:s}]'.format(context_uuid) @@ -437,14 +445,15 @@ class MockServicerImpl_Context(ContextServiceServicer): context_ = self.obj_db.get_entry('context', context_uuid, context) for _slice_id in context_.slice_ids: - if _slice_id.slice_uuid.uuid == slice_uuid: - context_.slice_ids.remove(_slice_id) - break + if _slice_id.context_id.context_uuid.uuid != context_uuid: continue + if _slice_id.slice_uuid.uuid != slice_uuid: continue + context_.slice_ids.remove(_slice_id) + break LOGGER.debug('[RemoveSlice] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetSliceEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[SliceEvent]: + def GetSliceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[SliceEvent]: LOGGER.debug('[GetSliceEvents] request={:s}'.format(grpc_message_to_json_string(request))) for message in self.msg_broker.consume({TOPIC_SLICE}): yield SliceEvent(**json.loads(message.content)) @@ -482,28 +491,28 @@ class MockServicerImpl_Context(ContextServiceServicer): # ----- Service ---------------------------------------------------------------------------------------------------- - def ListServiceIds(self, request: ContextId, context : grpc.ServicerContext) -> ServiceIdList: + def ListServiceIds(self, request : ContextId, context : grpc.ServicerContext) -> ServiceIdList: LOGGER.debug('[ListServiceIds] request={:s}'.format(grpc_message_to_json_string(request))) services = self.obj_db.get_entries('service[{:s}]'.format(str(request.context_uuid.uuid))) reply = ServiceIdList(service_ids=[service.service_id for service in services]) LOGGER.debug('[ListServiceIds] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def ListServices(self, request: ContextId, context : grpc.ServicerContext) -> ServiceList: + def ListServices(self, request : ContextId, context : grpc.ServicerContext) -> ServiceList: LOGGER.debug('[ListServices] request={:s}'.format(grpc_message_to_json_string(request))) services = self.obj_db.get_entries('service[{:s}]'.format(str(request.context_uuid.uuid))) reply = ServiceList(services=[service for service in services]) LOGGER.debug('[ListServices] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetService(self, request: ServiceId, context : grpc.ServicerContext) -> Service: + def GetService(self, request : ServiceId, context : grpc.ServicerContext) -> Service: LOGGER.debug('[GetService] request={:s}'.format(grpc_message_to_json_string(request))) container_name = 'service[{:s}]'.format(str(request.context_id.context_uuid.uuid)) reply = self.obj_db.get_entry(container_name, request.service_uuid.uuid, context) LOGGER.debug('[GetService] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def SetService(self, request: Service, context : grpc.ServicerContext) -> ServiceId: + def SetService(self, request : Service, context : grpc.ServicerContext) -> ServiceId: LOGGER.debug('[SetService] request={:s}'.format(grpc_message_to_json_string(request))) context_uuid = str(request.service_id.context_id.context_uuid.uuid) container_name = 'service[{:s}]'.format(context_uuid) @@ -515,12 +524,14 @@ class MockServicerImpl_Context(ContextServiceServicer): if _service_id.service_uuid.uuid == service_uuid: break else: # service not found, add it - context_.service_ids.add().service_uuid.uuid = service_uuid + service_id = context_.service_ids.add() + service_id.context_id.context_uuid.uuid = context_uuid + service_id.service_uuid.uuid = service_uuid LOGGER.debug('[SetService] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def RemoveService(self, request: ServiceId, context : grpc.ServicerContext) -> Empty: + def RemoveService(self, request : ServiceId, context : grpc.ServicerContext) -> Empty: LOGGER.debug('[RemoveService] request={:s}'.format(grpc_message_to_json_string(request))) context_uuid = str(request.context_id.context_uuid.uuid) container_name = 'service[{:s}]'.format(context_uuid) @@ -529,14 +540,15 @@ class MockServicerImpl_Context(ContextServiceServicer): context_ = self.obj_db.get_entry('context', context_uuid, context) for _service_id in context_.service_ids: - if _service_id.service_uuid.uuid == service_uuid: - context_.service_ids.remove(_service_id) - break + if _service_id.context_id.context_uuid.uuid != context_uuid: continue + if _service_id.service_uuid.uuid != service_uuid: continue + context_.service_ids.remove(_service_id) + break LOGGER.debug('[RemoveService] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetServiceEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[ServiceEvent]: + def GetServiceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[ServiceEvent]: LOGGER.debug('[GetServiceEvents] request={:s}'.format(grpc_message_to_json_string(request))) for message in self.msg_broker.consume({TOPIC_SERVICE}): yield ServiceEvent(**json.loads(message.content)) @@ -569,7 +581,7 @@ class MockServicerImpl_Context(ContextServiceServicer): # ----- Connection ------------------------------------------------------------------------------------------------- - def ListConnectionIds(self, request: ServiceId, context : grpc.ServicerContext) -> ConnectionIdList: + def ListConnectionIds(self, request : ServiceId, context : grpc.ServicerContext) -> ConnectionIdList: LOGGER.debug('[ListConnectionIds] request={:s}'.format(grpc_message_to_json_string(request))) container_name = 'service_connections[{:s}/{:s}]'.format( str(request.context_id.context_uuid.uuid), str(request.service_uuid.uuid)) @@ -577,7 +589,7 @@ class MockServicerImpl_Context(ContextServiceServicer): LOGGER.debug('[ListConnectionIds] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def ListConnections(self, request: ServiceId, context : grpc.ServicerContext) -> ConnectionList: + def ListConnections(self, request : ServiceId, context : grpc.ServicerContext) -> ConnectionList: LOGGER.debug('[ListConnections] request={:s}'.format(grpc_message_to_json_string(request))) container_name = 'service_connections[{:s}/{:s}]'.format( str(request.context_id.context_uuid.uuid), str(request.service_uuid.uuid)) @@ -585,13 +597,13 @@ class MockServicerImpl_Context(ContextServiceServicer): LOGGER.debug('[ListConnections] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetConnection(self, request: ConnectionId, context : grpc.ServicerContext) -> Connection: + def GetConnection(self, request : ConnectionId, context : grpc.ServicerContext) -> Connection: LOGGER.debug('[GetConnection] request={:s}'.format(grpc_message_to_json_string(request))) reply = self.obj_db.get_entry('connection', request.connection_uuid.uuid, context) LOGGER.debug('[GetConnection] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def SetConnection(self, request: Connection, context : grpc.ServicerContext) -> ConnectionId: + def SetConnection(self, request : Connection, context : grpc.ServicerContext) -> ConnectionId: LOGGER.debug('[SetConnection] request={:s}'.format(grpc_message_to_json_string(request))) container_name = 'service_connection[{:s}/{:s}]'.format( str(request.service_id.context_id.context_uuid.uuid), str(request.service_id.service_uuid.uuid)) @@ -601,7 +613,7 @@ class MockServicerImpl_Context(ContextServiceServicer): LOGGER.debug('[SetConnection] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def RemoveConnection(self, request: ConnectionId, context : grpc.ServicerContext) -> Empty: + def RemoveConnection(self, request : ConnectionId, context : grpc.ServicerContext) -> Empty: LOGGER.debug('[RemoveConnection] request={:s}'.format(grpc_message_to_json_string(request))) connection = self.obj_db.get_entry('connection', request.connection_uuid.uuid, context) container_name = 'service_connection[{:s}/{:s}]'.format( @@ -612,6 +624,45 @@ class MockServicerImpl_Context(ContextServiceServicer): LOGGER.debug('[RemoveConnection] reply={:s}'.format(grpc_message_to_json_string(reply))) return reply - def GetConnectionEvents(self, request: Empty, context : grpc.ServicerContext) -> Iterator[ConnectionEvent]: + def GetConnectionEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[ConnectionEvent]: LOGGER.debug('[GetConnectionEvents] request={:s}'.format(grpc_message_to_json_string(request))) for message in self.msg_broker.consume({TOPIC_CONNECTION}): yield ConnectionEvent(**json.loads(message.content)) + + def ListPolicyRuleIds(self, request : Empty, context : grpc.ServicerContext): # pylint: disable=unused-argument + LOGGER.debug('[ListPolicyRuleIds] request={:s}'.format(grpc_message_to_json_string(request))) + reply = PolicyRuleIdList(policyRuleIdList=[ + getattr(policy_rule, policy_rule.WhichOneof('policy_rule')).policyRuleBasic.policyRuleId + for policy_rule in self.obj_db.get_entries('policy') + ]) + LOGGER.debug('[ListPolicyRuleIds] reply={:s}'.format(grpc_message_to_json_string(reply))) + return reply + + def ListPolicyRules(self, request : Empty, context : grpc.ServicerContext): # pylint: disable=unused-argument + LOGGER.debug('[ListPolicyRules] request={:s}'.format(grpc_message_to_json_string(request))) + reply = PolicyRuleList(policyRules=self.obj_db.get_entries('policy')) + LOGGER.debug('[ListPolicyRules] reply={:s}'.format(grpc_message_to_json_string(reply))) + return reply + + def GetPolicyRule(self, request : PolicyRuleId, context : grpc.ServicerContext): + LOGGER.debug('[GetPolicyRule] request={:s}'.format(grpc_message_to_json_string(request))) + reply = self.obj_db.get_entry('policy_rule', request.uuid.uuid, context) + LOGGER.debug('[GetPolicyRule] reply={:s}'.format(grpc_message_to_json_string(reply))) + return reply + + def SetPolicyRule(self, request : PolicyRule, context : grpc.ServicerContext): # pylint: disable=unused-argument + LOGGER.debug('[SetPolicyRule] request={:s}'.format(grpc_message_to_json_string(request))) + policy_type = request.WhichOneof('policy_rule') + policy_uuid = getattr(request, policy_type).policyRuleBasic.policyRuleId.uuid.uuid + rule_id_field = '{:s}.policyRuleBasic.policyRuleId'.format(policy_type) + reply, _ = self._set(request, 'policy', policy_uuid, rule_id_field, TOPIC_POLICY) + LOGGER.debug('[SetPolicyRule] reply={:s}'.format(grpc_message_to_json_string(reply))) + return reply + + def RemovePolicyRule(self, request : PolicyRuleId, context : grpc.ServicerContext): + LOGGER.debug('[RemovePolicyRule] request={:s}'.format(grpc_message_to_json_string(request))) + policy_type = request.WhichOneof('policy_rule') + policy_uuid = getattr(request, policy_type).policyRuleBasic.policyRuleId.uuid.uuid + rule_id_field = '{:s}.policyRuleBasic.policyRuleId'.format(policy_type) + reply = self._del(request, 'policy', policy_uuid, rule_id_field, TOPIC_CONTEXT, context) + LOGGER.debug('[RemovePolicyRule] reply={:s}'.format(grpc_message_to_json_string(reply))) + return reply diff --git a/src/common/tools/object_factory/Device.py b/src/common/tools/object_factory/Device.py index bc5c28740d5635df99c26ef56124c471d2c77d91..76959232a947ec50918afbc77a992d8af0d0723f 100644 --- a/src/common/tools/object_factory/Device.py +++ b/src/common/tools/object_factory/Device.py @@ -46,6 +46,10 @@ DEVICE_P4_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_P4] DEVICE_TFS_TYPE = DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value DEVICE_TFS_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN] +DEVICE_IETF_ACTN_TYPE = DeviceTypeEnum.OPEN_LINE_SYSTEM.value +DEVICE_IETF_ACTN_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN] + + def json_device_id(device_uuid : str): return {'device_uuid': {'uuid': device_uuid}} @@ -136,6 +140,14 @@ def json_device_tfs_disabled( device_uuid, DEVICE_TFS_TYPE, DEVICE_DISABLED, name=name, endpoints=endpoints, config_rules=config_rules, drivers=drivers) +def json_device_ietf_actn_disabled( + device_uuid : str, name : Optional[str] = None, endpoints : List[Dict] = [], config_rules : List[Dict] = [], + drivers : List[Dict] = DEVICE_IETF_ACTN_DRIVERS + ): + return json_device( + device_uuid, DEVICE_IETF_ACTN_TYPE, DEVICE_DISABLED, name=name, endpoints=endpoints, config_rules=config_rules, + drivers=drivers) + def json_device_connect_rules(address : str, port : int, settings : Dict = {}) -> List[Dict]: return [ json_config_rule_set('_connect/address', address), diff --git a/src/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py index 286ae179d325b6e70d6ebf509de92e354ba42bc8..87d8e54ee390fb1f56266317be5317731bb755b6 100644 --- a/src/common/type_checkers/Assertions.py +++ b/src/common/type_checkers/Assertions.py @@ -12,7 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict +import logging +from typing import Callable, Dict + +LOGGER = logging.getLogger(__name__) # ----- Enumerations --------------------------------------------------------------------------------------------------- def validate_config_action_enum(message): @@ -23,6 +26,14 @@ def validate_config_action_enum(message): 'CONFIGACTION_DELETE', ] +def validate_constraint_action_enum(message): + assert isinstance(message, str) + assert message in [ + 'CONSTRAINTACTION_UNDEFINED', + 'CONSTRAINTACTION_SET', + 'CONSTRAINTACTION_DELETE', + ] + def validate_device_driver_enum(message): assert isinstance(message, str) assert message in [ @@ -35,6 +46,8 @@ def validate_device_driver_enum(message): 'DEVICEDRIVER_XR', 'DEVICEDRIVER_IETF_L2VPN', 'DEVICEDRIVER_GNMI_OPENCONFIG', + 'DEVICEDRIVER_FLEXSCALE', + 'DEVICEDRIVER_IETF_ACTN', ] def validate_device_operational_status_enum(message): @@ -64,6 +77,8 @@ def validate_service_type_enum(message): 'SERVICETYPE_L3NM', 'SERVICETYPE_L2NM', 'SERVICETYPE_TAPI_CONNECTIVITY_SERVICE', + 'SERVICETYPE_TE', + 'SERVICETYPE_E2E', ] def validate_service_state_enum(message): @@ -77,6 +92,17 @@ def validate_service_state_enum(message): 'SERVICESTATUS_SLA_VIOLATED', ] +def validate_slice_status_enum(message): + assert isinstance(message, str) + assert message in [ + 'SLICESTATUS_UNDEFINED', + 'SLICESTATUS_PLANNED', + 'SLICESTATUS_INIT', + 'SLICESTATUS_ACTIVE', + 'SLICESTATUS_DEINIT', + 'SLICESTATUS_SLA_VIOLATED', + ] + # ----- Common --------------------------------------------------------------------------------------------------------- def validate_uuid(message, allow_empty=False): @@ -114,28 +140,61 @@ def validate_config_rules(message): assert 'config_rules' in message for config_rule in message['config_rules']: validate_config_rule(config_rule) -CONSTRAINT_TYPES = { - 'custom', - 'schedule', - 'endpoint_location', - 'sla_capacity', - 'sla_latency', - 'sla_availability', - 'sla_isolation', +def validate_constraint_custom(message): + assert isinstance(message, dict) + assert len(message.keys()) == 2 + assert 'constraint_type' in message + assert isinstance(message['constraint_type'], str) + assert 'constraint_value' in message + assert isinstance(message['constraint_value'], str) + +def validate_constraint_sla_capacity(message): + assert isinstance(message, dict) + assert len(message.keys()) == 1 + assert 'capacity_gbps' in message + assert isinstance(message['capacity_gbps'], (int, float)) + +def validate_constraint_sla_latency(message): + assert isinstance(message, dict) + assert len(message.keys()) == 1 + assert 'e2e_latency_ms' in message + assert isinstance(message['e2e_latency_ms'], (int, float)) + +def validate_constraint_sla_availability(message): + assert isinstance(message, dict) + assert len(message.keys()) == 3 + assert 'num_disjoint_paths' in message + assert isinstance(message['num_disjoint_paths'], int) + assert message['num_disjoint_paths'] >= 0 + assert 'all_active' in message + assert isinstance(message['all_active'], bool) + assert 'availability' in message + assert isinstance(message['availability'], (int, float)) + assert message['availability'] >= 0 and message['availability'] <= 100 + +CONSTRAINT_TYPE_TO_VALIDATOR = { + 'custom' : validate_constraint_custom, + #'schedule' : validate_constraint_schedule, + #'endpoint_location' : validate_constraint_endpoint_location, + #'endpoint_priority' : validate_constraint_endpoint_priority, + 'sla_capacity' : validate_constraint_sla_capacity, + 'sla_latency' : validate_constraint_sla_latency, + 'sla_availability' : validate_constraint_sla_availability, + #'sla_isolation' : validate_constraint_sla_isolation, + #'exclusions' : validate_constraint_exclusions, } + def validate_constraint(message): assert isinstance(message, dict) - assert len(message.keys()) == 1 - other_keys = list(message.keys()) - constraint_type = other_keys[0] - assert constraint_type in CONSTRAINT_TYPES - assert constraint_type == 'custom', 'Constraint Type Validator for {:s} not implemented'.format(constraint_type) - custom : Dict = message['custom'] - assert len(custom.keys()) == 2 - assert 'constraint_type' in custom - assert isinstance(custom['constraint_type'], str) - assert 'constraint_value' in custom - assert isinstance(custom['constraint_value'], str) + assert len(message.keys()) == 2 + assert 'action' in message + validate_constraint_action_enum(message['action']) + other_keys = set(list(message.keys())) + other_keys.discard('action') + constraint_type = other_keys.pop() + validator : Callable = CONSTRAINT_TYPE_TO_VALIDATOR.get(constraint_type) + assert validator is not None, 'Constraint Type Validator for {:s} not implemented'.format(constraint_type) + validator(message[constraint_type]) # ----- Identifiers ---------------------------------------------------------------------------------------------------- @@ -192,6 +251,15 @@ def validate_connection_id(message): assert 'connection_uuid' in message validate_uuid(message['connection_uuid']) +def validate_slice_id(message, context_uuid = None): + assert isinstance(message, dict) + assert len(message.keys()) == 2 + assert 'context_id' in message + validate_context_id(message['context_id']) + if context_uuid is not None: assert message['context_id']['context_uuid']['uuid'] == context_uuid + assert 'slice_uuid' in message + validate_uuid(message['slice_uuid']) + # ----- Lists of Identifiers ------------------------------------------------------------------------------------------- @@ -209,6 +277,13 @@ def validate_service_ids(message, context_uuid=None): assert isinstance(message['service_ids'], list) for service_id in message['service_ids']: validate_service_id(service_id, context_uuid=context_uuid) +def validate_slice_ids(message, context_uuid=None): + assert isinstance(message, dict) + assert len(message.keys()) == 1 + assert 'slice_ids' in message + assert isinstance(message['slice_ids'], list) + for slice_id in message['slice_ids']: validate_slice_id(slice_id, context_uuid=context_uuid) + def validate_topology_ids(message, context_uuid=None): assert isinstance(message, dict) assert len(message.keys()) == 1 @@ -242,16 +317,21 @@ def validate_connection_ids(message): def validate_context(message): assert isinstance(message, dict) - assert len(message.keys()) == 3 + assert len(message.keys()) == 5 assert 'context_id' in message validate_context_id(message['context_id']) context_uuid = message['context_id']['context_uuid']['uuid'] - assert 'service_ids' in message - assert isinstance(message['service_ids'], list) - for service_id in message['service_ids']: validate_service_id(service_id, context_uuid=context_uuid) + assert 'name' in message + assert isinstance(message['name'], str) assert 'topology_ids' in message assert isinstance(message['topology_ids'], list) for topology_id in message['topology_ids']: validate_topology_id(topology_id, context_uuid=context_uuid) + assert 'service_ids' in message + assert isinstance(message['service_ids'], list) + for service_id in message['service_ids']: validate_service_id(service_id, context_uuid=context_uuid) + assert 'slice_ids' in message + assert isinstance(message['slice_ids'], list) + for slice_id in message['slice_ids']: validate_slice_id(slice_id, context_uuid=context_uuid) def validate_service_state(message): assert isinstance(message, dict) @@ -259,11 +339,19 @@ def validate_service_state(message): assert 'service_status' in message validate_service_state_enum(message['service_status']) +def validate_slice_status(message): + assert isinstance(message, dict) + assert len(message.keys()) == 1 + assert 'slice_status' in message + validate_slice_status_enum(message['slice_status']) + def validate_service(message): assert isinstance(message, dict) - assert len(message.keys()) == 6 + assert len(message.keys()) == 7 assert 'service_id' in message validate_service_id(message['service_id']) + assert 'name' in message + assert isinstance(message['name'], str) assert 'service_type' in message validate_service_type_enum(message['service_type']) assert 'service_endpoint_ids' in message @@ -277,11 +365,44 @@ def validate_service(message): assert 'service_config' in message validate_config_rules(message['service_config']) +def validate_slice(message): + assert isinstance(message, dict) + assert len(message.keys()) in {8, 9} + assert 'slice_id' in message + validate_slice_id(message['slice_id']) + assert 'name' in message + assert isinstance(message['name'], str) + assert 'slice_endpoint_ids' in message + assert isinstance(message['slice_endpoint_ids'], list) + for endpoint_id in message['slice_endpoint_ids']: validate_endpoint_id(endpoint_id) + assert 'slice_constraints' in message + assert isinstance(message['slice_constraints'], list) + for constraint in message['slice_constraints']: validate_constraint(constraint) + assert 'slice_service_ids' in message + assert isinstance(message['slice_service_ids'], list) + for service_id in message['slice_service_ids']: validate_service_id(service_id) + assert 'slice_subslice_ids' in message + assert isinstance(message['slice_subslice_ids'], list) + for slice_id in message['slice_subslice_ids']: validate_slice_id(slice_id) + assert 'slice_status' in message + validate_slice_status(message['slice_status']) + assert 'slice_config' in message + validate_config_rules(message['slice_config']) + if len(message.keys()) == 9: + assert 'slice_owner' in message + assert isinstance(message['slice_owner'], dict) + assert 'owner_uuid' in message['slice_owner'] + validate_uuid(message['slice_owner']['owner_uuid']) + assert 'owner_string' in message['slice_owner'] + assert isinstance(message['slice_owner']['owner_string'], str) + def validate_topology(message, num_devices=None, num_links=None): assert isinstance(message, dict) - assert len(message.keys()) == 3 + assert len(message.keys()) == 4 assert 'topology_id' in message validate_topology_id(message['topology_id']) + assert 'name' in message + assert isinstance(message['name'], str) assert 'device_ids' in message assert isinstance(message['device_ids'], list) if num_devices is not None: assert len(message['device_ids']) == num_devices @@ -293,20 +414,49 @@ def validate_topology(message, num_devices=None, num_links=None): def validate_endpoint(message): assert isinstance(message, dict) - assert len(message.keys()) == 3 + assert len(message.keys()) == 4 assert 'endpoint_id' in message validate_endpoint_id(message['endpoint_id']) + assert 'name' in message + assert isinstance(message['name'], str) assert 'endpoint_type' in message assert isinstance(message['endpoint_type'], str) assert 'kpi_sample_types' in message assert isinstance(message['kpi_sample_types'], list) for kpi_sample_type in message['kpi_sample_types']: validate_kpi_sample_types_enum(kpi_sample_type) +def validate_component(component): + assert isinstance(component, dict) + assert len(component.keys()) == 5 + assert 'component_uuid' in component + validate_uuid(component['component_uuid']) + assert 'name' in component + assert isinstance(component['name'], str) + assert 'type' in component + assert isinstance(component['type'], str) + assert 'attributes' in component + assert isinstance(component['attributes'], dict) + for k,v in component['attributes'].items(): + assert isinstance(k, str) + assert isinstance(v, str) + assert 'parent' in component + assert isinstance(component['parent'], str) + +def validate_link_attributes(link_attributes): + assert isinstance(link_attributes, dict) + assert len(link_attributes.keys()) == 2 + assert 'total_capacity_gbps' in link_attributes + assert isinstance(link_attributes['total_capacity_gbps'], (int, float)) + assert 'used_capacity_gbps' in link_attributes + assert isinstance(link_attributes['used_capacity_gbps'], (int, float)) + def validate_device(message): assert isinstance(message, dict) - assert len(message.keys()) == 6 + assert len(message.keys()) in {8, 9} assert 'device_id' in message validate_device_id(message['device_id']) + assert 'name' in message + assert isinstance(message['name'], str) assert 'device_type' in message assert isinstance(message['device_type'], str) assert 'device_config' in message @@ -319,19 +469,30 @@ def validate_device(message): assert 'device_endpoints' in message assert isinstance(message['device_endpoints'], list) for endpoint in message['device_endpoints']: validate_endpoint(endpoint) + assert 'components' in message + assert isinstance(message['components'], list) + for component in message['components']: validate_component(component) + if len(message.keys()) == 9: + assert 'controller_id' in message + if len(message['controller_id']) > 0: + validate_device_id(message['controller_id']) def validate_link(message): assert isinstance(message, dict) - assert len(message.keys()) == 2 + assert len(message.keys()) == 4 assert 'link_id' in message validate_link_id(message['link_id']) + assert 'name' in message + assert isinstance(message['name'], str) assert 'link_endpoint_ids' in message assert isinstance(message['link_endpoint_ids'], list) for endpoint_id in message['link_endpoint_ids']: validate_endpoint_id(endpoint_id) + assert 'attributes' in message + validate_link_attributes(message['attributes']) def validate_connection(message): assert isinstance(message, dict) - assert len(message.keys()) == 4 + assert len(message.keys()) in {4, 5} assert 'connection_id' in message validate_connection_id(message['connection_id']) assert 'service_id' in message @@ -342,6 +503,50 @@ def validate_connection(message): assert 'sub_service_ids' in message assert isinstance(message['sub_service_ids'], list) for sub_service_id in message['sub_service_ids']: validate_service_id(sub_service_id) + if len(message.keys()) == 5: + assert 'settings' in message + assert isinstance(message['settings'], dict) + # TODO: improve validation of data types, especially for uint values, IP/MAC addresses, TCP/UDP ports, etc. + if 'l0' in message['settings']: + assert isinstance(message['settings']['l0'], dict) + if 'lsp_symbolic_name' in message['settings']['l0']: + assert isinstance(message['settings']['l0']['lsp_symbolic_name'], str) + if 'l2' in message['settings']: + assert isinstance(message['settings']['l2'], dict) + if 'src_mac_address' in message['settings']['l2']: + assert isinstance(message['settings']['l2']['src_mac_address'], str) + if 'dst_mac_address' in message['settings']['l2']: + assert isinstance(message['settings']['l2']['dst_mac_address'], str) + if 'ether_type' in message['settings']['l2']: + assert isinstance(message['settings']['l2']['ether_type'], int) + if 'vlan_id' in message['settings']['l2']: + assert isinstance(message['settings']['l2']['vlan_id'], int) + if 'mpls_label' in message['settings']['l2']: + assert isinstance(message['settings']['l2']['mpls_label'], int) + if 'mpls_traffic_class' in message['settings']['l2']: + assert isinstance(message['settings']['l2']['mpls_traffic_class'], int) + if 'l3' in message['settings']: + assert isinstance(message['settings']['l3'], dict) + if 'src_ip_address' in message['settings']['l3']: + assert isinstance(message['settings']['l3']['src_ip_address'], str) + if 'dst_ip_address' in message['settings']['l3']: + assert isinstance(message['settings']['l3']['dst_ip_address'], str) + if 'dscp' in message['settings']['l3']: + assert isinstance(message['settings']['l3']['dscp'], int) + if 'protocol' in message['settings']['l3']: + assert isinstance(message['settings']['l3']['protocol'], int) + if 'ttl' in message['settings']['l3']: + assert isinstance(message['settings']['l3']['ttl'], int) + if 'l4' in message['settings']: + assert isinstance(message['settings']['l4'], dict) + if 'src_port' in message['settings']['l4']: + assert isinstance(message['settings']['l4']['src_port'], int) + if 'dst_port' in message['settings']['l4']: + assert isinstance(message['settings']['l4']['dst_port'], int) + if 'tcp_flags' in message['settings']['l4']: + assert isinstance(message['settings']['l4']['tcp_flags'], int) + if 'ttl' in message['settings']['l4']: + assert isinstance(message['settings']['l4']['ttl'], int) # ----- Lists of Objects ----------------------------------------------------------------------------------------------- @@ -360,6 +565,13 @@ def validate_services(message): assert isinstance(message['services'], list) for service in message['services']: validate_service(service) +def validate_slices(message): + assert isinstance(message, dict) + assert len(message.keys()) == 1 + assert 'slices' in message + assert isinstance(message['slices'], list) + for slice_ in message['slices']: validate_slice(slice_) + def validate_topologies(message): assert isinstance(message, dict) assert len(message.keys()) == 1 diff --git a/src/context/service/database/models/enums/DeviceDriver.py b/src/context/service/database/models/enums/DeviceDriver.py index f8483360191dcea1a56cdc372b681ae2d03c9ef1..8e15bf058599eeed0629fc1249af0d052183db28 100644 --- a/src/context/service/database/models/enums/DeviceDriver.py +++ b/src/context/service/database/models/enums/DeviceDriver.py @@ -32,6 +32,7 @@ class ORM_DeviceDriverEnum(enum.Enum): IETF_L2VPN = DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN GNMI_OPENCONFIG = DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG FLEXSCALE = DeviceDriverEnum.DEVICEDRIVER_FLEXSCALE + IETF_ACTN = DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN grpc_to_enum__device_driver = functools.partial( grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/device/.gitlab-ci.yml b/src/device/.gitlab-ci.yml index 5fed57be6b7963f776425c2196e232aacddee04f..bcc2e05e50f63fd6552bb53b4d23c0ae0e2e7302 100644 --- a/src/device/.gitlab-ci.yml +++ b/src/device/.gitlab-ci.yml @@ -48,15 +48,26 @@ unit_test device: - build device before_script: - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY - - if docker network list | grep teraflowbridge; then echo "teraflowbridge is already created"; else docker network create -d bridge teraflowbridge; fi - - if docker container ls | grep $IMAGE_NAME; then docker rm -f $IMAGE_NAME; else echo "$IMAGE_NAME image is not in the system"; fi + - > + if docker network list | grep teraflowbridge; then + echo "teraflowbridge is already created"; + else + docker network create -d bridge teraflowbridge; + fi + - > + if docker container ls | grep $IMAGE_NAME; then + docker rm -f $IMAGE_NAME; + else + echo "$IMAGE_NAME image is not in the system"; + fi script: - docker pull "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" - docker run --name $IMAGE_NAME -d -p 2020:2020 -v "$PWD/src/$IMAGE_NAME/tests:/opt/results" --network=teraflowbridge $CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG - sleep 5 - docker ps -a - docker logs $IMAGE_NAME - - docker exec -i $IMAGE_NAME bash -c "coverage run -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_unitary_emulated.py --junitxml=/opt/results/${IMAGE_NAME}_report.xml" + - docker exec -i $IMAGE_NAME bash -c "coverage run --append -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_unitary_emulated.py --junitxml=/opt/results/${IMAGE_NAME}_report_emulated.xml" + - docker exec -i $IMAGE_NAME bash -c "coverage run --append -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_unitary_ietf_actn.py --junitxml=/opt/results/${IMAGE_NAME}_report_ietf_actn.xml" - docker exec -i $IMAGE_NAME bash -c "coverage report --include='${IMAGE_NAME}/*' --show-missing" coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' after_script: @@ -77,7 +88,7 @@ unit_test device: artifacts: when: always reports: - junit: src/$IMAGE_NAME/tests/${IMAGE_NAME}_report.xml + junit: src/$IMAGE_NAME/tests/${IMAGE_NAME}_report_*.xml ## Deployment of the service in Kubernetes Cluster #deploy device: diff --git a/src/device/Dockerfile b/src/device/Dockerfile index 6566625527f8ceaa8de4639d558c92572c4835cb..909ae3bd31817401412629cbc04b5c0577b40355 100644 --- a/src/device/Dockerfile +++ b/src/device/Dockerfile @@ -66,5 +66,11 @@ COPY src/context/. context/ COPY src/device/. device/ COPY src/monitoring/. monitoring/ +RUN mkdir -p tests/tools/mock_ietf_actn_sdn_ctrl +RUN touch tests/__init__.py +RUN touch tests/tools/__init__.py +RUN touch tests/tools/mock_ietf_actn_sdn_ctrl/__init__.py +COPY src/tests/tools/mock_ietf_actn_sdn_ctrl/. tests/tools/mock_ietf_actn_sdn_ctrl/ + # Start the service ENTRYPOINT ["python", "-m", "device.service"] diff --git a/src/device/requirements.in b/src/device/requirements.in index ece761571ec2ff9c3376b1062787d76047d71e7c..1a09542a3854e10fedc5be91b76546c341113d67 100644 --- a/src/device/requirements.in +++ b/src/device/requirements.in @@ -16,7 +16,12 @@ anytree==2.8.0 APScheduler==3.10.1 cryptography==36.0.2 +deepdiff==6.7.* +deepmerge==1.1.* #fastcache==1.1.0 +Flask==2.1.3 +Flask-HTTPAuth==4.5.0 +Flask-RESTful==0.3.9 Jinja2==3.0.3 ncclient==0.6.13 p4runtime==1.3.0 @@ -32,9 +37,10 @@ tabulate ipaddress macaddress yattag -pyang +pyang==2.6.0 git+https://github.com/robshakir/pyangbind.git websockets==10.4 +werkzeug==2.3.7 # pip's dependency resolver does not take into account installed packages. # p4runtime does not specify the version of grpcio/protobuf it needs, so it tries to install latest one diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index 442acf839c8e4615237d338d7b485be297d1a4ff..27c61f89f15c735b44ad2724df01e08a51dda6ba 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -84,6 +84,15 @@ DRIVERS.append( } ])) +from .ietf_actn.IetfActnDriver import IetfActnDriver # pylint: disable=wrong-import-position +DRIVERS.append( + (IetfActnDriver, [ + { + FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.OPEN_LINE_SYSTEM, + FilterFieldEnum.DRIVER: DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN, + } + ])) + if LOAD_ALL_DEVICE_DRIVERS: from .openconfig.OpenConfigDriver import OpenConfigDriver # pylint: disable=wrong-import-position DRIVERS.append( diff --git a/src/device/service/drivers/ietf_actn/IetfActnDriver.py b/src/device/service/drivers/ietf_actn/IetfActnDriver.py new file mode 100644 index 0000000000000000000000000000000000000000..a33c403f3202ca5ee3025a7b7808ad53a89ede4a --- /dev/null +++ b/src/device/service/drivers/ietf_actn/IetfActnDriver.py @@ -0,0 +1,166 @@ +# 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. + +import json, logging, requests, threading +from typing import Any, Iterator, List, Optional, Tuple, Union +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.type_checkers.Checkers import chk_string, chk_type +from device.service.driver_api._Driver import _Driver, RESOURCE_SERVICES +from .handlers.EthtServiceHandler import EthtServiceHandler +from .handlers.OsuTunnelHandler import OsuTunnelHandler +from .handlers.RestApiClient import RestApiClient +from .Tools import get_etht_services, get_osu_tunnels, parse_resource_key + +LOGGER = logging.getLogger(__name__) + +ALL_RESOURCE_KEYS = [ + RESOURCE_SERVICES, +] + +DRIVER_NAME = 'ietf_actn' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) + +class IetfActnDriver(_Driver): + def __init__(self, address: str, port: int, **settings) -> None: + super().__init__(DRIVER_NAME, address, port, **settings) + self.__lock = threading.Lock() + self.__started = threading.Event() + self.__terminate = threading.Event() + self._rest_api_client = RestApiClient(address, port, settings=settings) + self._handler_osu_tunnel = OsuTunnelHandler(self._rest_api_client) + self._handler_etht_service = EthtServiceHandler(self._rest_api_client) + + def Connect(self) -> bool: + with self.__lock: + if self.__started.is_set(): return True + try: + self._rest_api_client.get('Check Credentials', '') + except requests.exceptions.Timeout: + LOGGER.exception('Timeout exception checking connectivity') + return False + except Exception: # pylint: disable=broad-except + LOGGER.exception('Unhandled exception checking connectivity') + return False + else: + self.__started.set() + return True + + def Disconnect(self) -> bool: + with self.__lock: + self.__terminate.set() + return True + + @metered_subclass_method(METRICS_POOL) + def GetInitialConfig(self) -> List[Tuple[str, Any]]: + with self.__lock: + return [] + + @metered_subclass_method(METRICS_POOL) + def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: + chk_type('resources', resource_keys, list) + results = [] + with self.__lock: + if len(resource_keys) == 0: resource_keys = ALL_RESOURCE_KEYS + for i, resource_key in enumerate(resource_keys): + chk_string('resource_key[#{:d}]'.format(i), resource_key, allow_empty=False) + + try: + _results = list() + + if resource_key == RESOURCE_SERVICES: + get_osu_tunnels(self._handler_osu_tunnel, _results) + get_etht_services(self._handler_etht_service, _results) + else: + # check if resource key is for a specific OSU tunnel or ETHT service, and get them accordingly + osu_tunnel_name, etht_service_name = parse_resource_key(resource_key) + if osu_tunnel_name is not None: + get_osu_tunnels(self._handler_osu_tunnel, _results, osu_tunnel_name=osu_tunnel_name) + if etht_service_name is not None: + get_etht_services(self._handler_etht_service, _results, etht_service_name=etht_service_name) + + results.extend(_results) + except Exception as e: + results.append((resource_key, e)) + + return results + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + results = [] + if len(resources) == 0: return results + with self.__lock: + for resource_key, resource_value in resources: + LOGGER.info('resource: key({:s}) => value({:s})'.format(str(resource_key), str(resource_value))) + try: + _results = list() + + if isinstance(resource_value, str): resource_value = json.loads(resource_value) + osu_tunnel_name, etht_service_name = parse_resource_key(resource_key) + + if osu_tunnel_name is not None: + succeeded = self._handler_osu_tunnel.update(resource_value) + _results.append(succeeded) + + if etht_service_name is not None: + succeeded = self._handler_etht_service.update(resource_value) + _results.append(succeeded) + + results.extend(_results) + except Exception as e: + results.append(e) + + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + results = [] + if len(resources) == 0: return results + with self.__lock: + for resource_key, resource_value in resources: + LOGGER.info('resource: key({:s}) => value({:s})'.format(str(resource_key), str(resource_value))) + try: + _results = list() + + if isinstance(resource_value, str): resource_value = json.loads(resource_value) + osu_tunnel_name, etht_service_name = parse_resource_key(resource_key) + + if osu_tunnel_name is not None: + succeeded = self._handler_osu_tunnel.delete(osu_tunnel_name) + _results.append(succeeded) + + if etht_service_name is not None: + succeeded = self._handler_etht_service.delete(etht_service_name) + _results.append(succeeded) + + results.extend(_results) + except Exception as e: + results.append(e) + + return results + + @metered_subclass_method(METRICS_POOL) + def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + # TODO: IETF ACTN does not support monitoring by now + return [False for _ in subscriptions] + + @metered_subclass_method(METRICS_POOL) + def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + # TODO: IETF ACTN does not support monitoring by now + return [False for _ in subscriptions] + + def GetState( + self, blocking=False, terminate : Optional[threading.Event] = None + ) -> Iterator[Tuple[float, str, Any]]: + # TODO: IETF ACTN does not support monitoring by now + return [] diff --git a/src/device/service/drivers/ietf_actn/Tools.py b/src/device/service/drivers/ietf_actn/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..52f5b15c4d9f0c723b7e4eeeea4537d23c5bf758 --- /dev/null +++ b/src/device/service/drivers/ietf_actn/Tools.py @@ -0,0 +1,52 @@ +# 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. + +import logging, re +from typing import Any, List, Optional, Tuple, Union +from .handlers.EthtServiceHandler import EthtServiceHandler +from .handlers.OsuTunnelHandler import OsuTunnelHandler + +LOGGER = logging.getLogger(__name__) + +RE_OSU_TUNNEL = re.compile(r'^\/osu\_tunnels\/osu\_tunnel\[([^\]]+)\]$') +RE_ETHT_SERVICE = re.compile(r'^\/etht\_services\/etht\_service\[([^\]]+)\]$') + +def parse_resource_key(resource_key : str) -> Tuple[Optional[str], Optional[str]]: + re_match_osu_tunnel = RE_OSU_TUNNEL.match(resource_key) + osu_tunnel_name = None if re_match_osu_tunnel is None else re_match_osu_tunnel.group(1) + + re_match_etht_service = RE_ETHT_SERVICE.match(resource_key) + etht_service_name = None if re_match_etht_service is None else re_match_etht_service.group(1) + + return osu_tunnel_name, etht_service_name + +def get_osu_tunnels( + handler_osu_tunnel : OsuTunnelHandler, results : List[Tuple[str, Union[Any, None, Exception]]], + osu_tunnel_name : Optional[str] = None +) -> None: + osu_tunnels = handler_osu_tunnel.get(osu_tunnel_name=osu_tunnel_name) + for osu_tunnel in osu_tunnels: + osu_tunnel_name = osu_tunnel['name'] + resource_key = '/osu_tunnels/osu_tunnel[{:s}]'.format(osu_tunnel_name) + results.append((resource_key, osu_tunnel)) + +def get_etht_services( + handler_etht_service : EthtServiceHandler, results : List[Tuple[str, Union[Any, None, Exception]]], + etht_service_name : Optional[str] = None +) -> None: + etht_services = handler_etht_service.get(etht_service_name=etht_service_name) + for etht_service in etht_services: + etht_service_name = etht_service['name'] + resource_key = '/etht_services/etht_service[{:s}]'.format(etht_service_name) + results.append((resource_key, etht_service)) diff --git a/src/device/service/drivers/ietf_actn/__init__.py b/src/device/service/drivers/ietf_actn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..38d04994fb0fa1951fb465bc127eb72659dc2eaf --- /dev/null +++ b/src/device/service/drivers/ietf_actn/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/src/device/service/drivers/ietf_actn/handlers/EthtServiceHandler.py b/src/device/service/drivers/ietf_actn/handlers/EthtServiceHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..230d13797f224931f657aa32c83091f4eb1c1d63 --- /dev/null +++ b/src/device/service/drivers/ietf_actn/handlers/EthtServiceHandler.py @@ -0,0 +1,221 @@ +# 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. + +import enum, logging +from typing import Dict, List, Optional, Tuple, Union +from .RestApiClient import HTTP_STATUS_CREATED, HTTP_STATUS_NO_CONTENT, HTTP_STATUS_OK, RestApiClient + +LOGGER = logging.getLogger(__name__) + +class BandwidthProfileTypeEnum(enum.Enum): + MEF_10_BWP = 'ietf-eth-tran-types:mef-10-bwp' + +class EndpointLayerSpecificAccessTypeEnum(enum.Enum): + PORT = 'port' + +class EndpointProtectionRoleEnum(enum.Enum): + WORK = 'work' + +class OptimizationMetricRole(enum.Enum): + WORK = 'work' + +class OptimizationMetricType(enum.Enum): + PATH_METRIC_TE = 'ietf-te-types:path-metric-te' + +class OuterTagTypeEnum(enum.Enum): + CLASSIFY_C_VLAN = 'ietf-eth-tran-types:classify-c-vlan' + +class ServiceClassificationTypeEnum(enum.Enum): + VLAN_CLASSIFICATION = 'ietf-eth-tran-type:vlan-classification' + +class ServiceTypeEnum(enum.Enum): + MP2MP = 'op-mp2mp-svc' + P2MP = 'op-p2mp-svc' + +def compose_outer_tag(tag_type : OuterTagTypeEnum, vlan_value : int) -> Dict: + return {'tag-type': tag_type.value, 'vlan-value': vlan_value} + +def compose_ingress_egress_bandwidth_profile() -> Dict: + return { + 'bandwidth-profile-type': BandwidthProfileTypeEnum.MEF_10_BWP.value, + 'CIR': 10_000_000, + 'EIR': 10_000_000, + } + +def compose_layer_specific_access_type() -> Dict: + return {'access-type': EndpointLayerSpecificAccessTypeEnum.PORT.value} + +def compose_static_route(prefix : str, mask : int, next_hop : str) -> Dict: + return {'destination': prefix, 'destination-mask': mask, 'next-hop': next_hop} + +def compose_static_route_list(static_routes : List[Tuple[str, int, str]]) -> List[Dict]: + return [ + compose_static_route(prefix, mask, next_hop) + for prefix, mask, next_hop in static_routes + ] + +def compose_etht_service_endpoint( + node_id : str, tp_id : str, vlan_value : int, static_routes : List[Tuple[str, int, str]] = list() +) -> Dict: + return { + 'node-id' : node_id, + 'tp-id' : tp_id, + 'protection-role' : EndpointProtectionRoleEnum.WORK.value, + 'layer-specific' : compose_layer_specific_access_type(), + 'is-extendable' : False, + 'is-terminal' : True, + 'static-route-list' : compose_static_route_list(static_routes), + 'outer-tag' : compose_outer_tag(OuterTagTypeEnum.CLASSIFY_C_VLAN, vlan_value), + 'service-classification-type' : ServiceClassificationTypeEnum.VLAN_CLASSIFICATION.value, + 'ingress-egress-bandwidth-profile': compose_ingress_egress_bandwidth_profile(), + } + +def compose_optimizations() -> Dict: + return {'optimization-metric': [{ + 'metric-role': OptimizationMetricRole.WORK.value, + 'metric-type': OptimizationMetricType.PATH_METRIC_TE.value, + }]} + +def compose_etht_service( + name : str, service_type : ServiceTypeEnum, osu_tunnel_name : str, + src_node_id : str, src_tp_id : str, src_vlan_tag : int, dst_node_id : str, dst_tp_id : str, dst_vlan_tag : int, + src_static_routes : List[Tuple[str, int, str]] = list(), dst_static_routes : List[Tuple[str, int, str]] = list() +) -> Dict: + return {'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': [{ + 'etht-svc-name' : name, + 'etht-svc-title': name.upper(), + 'etht-svc-type' : service_type.value, + 'source-endpoints': {'source-endpoint': [ + compose_etht_service_endpoint(src_node_id, src_tp_id, src_vlan_tag, src_static_routes), + ]}, + 'destination-endpoints': {'destination-endpoint': [ + compose_etht_service_endpoint(dst_node_id, dst_tp_id, dst_vlan_tag, dst_static_routes), + ]}, + 'svc-tunnel': [{'tunnel-name': osu_tunnel_name}], + 'optimizations': compose_optimizations(), + }]}} + +class EthtServiceHandler: + def __init__(self, rest_api_client : RestApiClient) -> None: + self._rest_api_client = rest_api_client + self._object_name = 'EthtService' + self._subpath_root = '/ietf-eth-tran-service:etht-svc' + self._subpath_item = self._subpath_root + '/etht-svc-instances="{etht_service_name:s}"' + + def _rest_api_get(self, etht_service_name : Optional[str] = None) -> Union[Dict, List]: + if etht_service_name is None: + subpath_url = self._subpath_root + else: + subpath_url = self._subpath_item.format(etht_service_name=etht_service_name) + return self._rest_api_client.get( + self._object_name, subpath_url, expected_http_status={HTTP_STATUS_OK} + ) + + def _rest_api_update(self, data : Dict) -> bool: + return self._rest_api_client.update( + self._object_name, self._subpath_root, data, expected_http_status={HTTP_STATUS_CREATED} + ) + + def _rest_api_delete(self, etht_service_name : str) -> bool: + if etht_service_name is None: raise Exception('etht_service_name is None') + subpath_url = self._subpath_item.format(etht_service_name=etht_service_name) + return self._rest_api_client.delete( + self._object_name, subpath_url, expected_http_status={HTTP_STATUS_NO_CONTENT} + ) + + def get(self, etht_service_name : Optional[str] = None) -> Union[Dict, List]: + data = self._rest_api_get(etht_service_name=etht_service_name) + + if not isinstance(data, dict): raise ValueError('data should be a dict') + if 'ietf-eth-tran-service:etht-svc' not in data: + raise ValueError('data does not contain key "ietf-eth-tran-service:etht-svc"') + data = data['ietf-eth-tran-service:etht-svc'] + if 'etht-svc-instances' not in data: + raise ValueError('data["ietf-eth-tran-service:etht-svc"] does not contain key "etht-svc-instances"') + data = data['etht-svc-instances'] + if not isinstance(data, list): + raise ValueError('data["ietf-eth-tran-service:etht-svc"]["etht-svc-instances"] should be a list') + + etht_services : List[Dict] = list() + for item in data: + src_endpoints = item['source-endpoints']['source-endpoint'] + if len(src_endpoints) != 1: + MSG = 'EthtService({:s}) has zero/multiple source endpoints' + raise Exception(MSG.format(str(item))) + src_endpoint = src_endpoints[0] + + dst_endpoints = item['destination-endpoints']['destination-endpoint'] + if len(dst_endpoints) != 1: + MSG = 'EthtService({:s}) has zero/multiple destination endpoints' + raise Exception(MSG.format(str(item))) + dst_endpoint = dst_endpoints[0] + + svc_tunnels = item['svc-tunnel'] + if len(svc_tunnels) != 1: + MSG = 'EthtService({:s}) has zero/multiple service tunnels' + raise Exception(MSG.format(str(item))) + svc_tunnel = svc_tunnels[0] + + etht_service = { + 'name' : item['etht-svc-name'], + 'service_type' : item['etht-svc-type'], + 'osu_tunnel_name' : svc_tunnel['tunnel-name'], + + 'src_node_id' : src_endpoint['node-id'], + 'src_tp_id' : src_endpoint['tp-id'], + 'src_vlan_tag' : src_endpoint['outer-tag']['vlan-value'], + 'src_static_routes': [ + [static_route['destination'], static_route['destination-mask'], static_route['next-hop']] + for static_route in src_endpoint.get('static-route-list', list()) + ], + + 'dst_node_id' : dst_endpoint['node-id'], + 'dst_tp_id' : dst_endpoint['tp-id'], + 'dst_vlan_tag' : dst_endpoint['outer-tag']['vlan-value'], + 'dst_static_routes': [ + [static_route['destination'], static_route['destination-mask'], static_route['next-hop']] + for static_route in dst_endpoint.get('static-route-list', list()) + ], + } + etht_services.append(etht_service) + + return etht_services + + def update(self, parameters : Dict) -> bool: + name = parameters['name' ] + service_type = parameters['service_type' ] + osu_tunnel_name = parameters['osu_tunnel_name'] + + src_node_id = parameters['src_node_id' ] + src_tp_id = parameters['src_tp_id' ] + src_vlan_tag = parameters['src_vlan_tag' ] + src_static_routes = parameters.get('src_static_routes', []) + + dst_node_id = parameters['dst_node_id' ] + dst_tp_id = parameters['dst_tp_id' ] + dst_vlan_tag = parameters['dst_vlan_tag' ] + dst_static_routes = parameters.get('dst_static_routes', []) + + service_type = ServiceTypeEnum._value2member_map_[service_type] + + data = compose_etht_service( + name, service_type, osu_tunnel_name, + src_node_id, src_tp_id, src_vlan_tag, dst_node_id, dst_tp_id, dst_vlan_tag, + src_static_routes=src_static_routes, dst_static_routes=dst_static_routes + ) + + return self._rest_api_update(data) + + def delete(self, etht_service_name : str) -> bool: + return self._rest_api_delete(etht_service_name) diff --git a/src/device/service/drivers/ietf_actn/handlers/OsuTunnelHandler.py b/src/device/service/drivers/ietf_actn/handlers/OsuTunnelHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..bcecdf89e41c1cbb7284dbc6f7f08e1f03329c91 --- /dev/null +++ b/src/device/service/drivers/ietf_actn/handlers/OsuTunnelHandler.py @@ -0,0 +1,176 @@ +# 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. + +import enum, logging +from typing import Dict, List, Optional, Union +from .RestApiClient import HTTP_STATUS_CREATED, HTTP_STATUS_NO_CONTENT, HTTP_STATUS_OK, RestApiClient + +LOGGER = logging.getLogger(__name__) + +class EndpointProtectionRoleEnum(enum.Enum): + WORK = 'work' + +class LspProtectionTypeEnum(enum.Enum): + UNPROTECTED = 'ietf-te-types:lsp-protection-unprotected' + +class LspRestorationTypeEnum(enum.Enum): + NOT_APPLICABLE = 'ietf-te-types:lsp-restoration-not-applicable' + +class TunnelAdminStateEnum(enum.Enum): + UP = 'ietf-te-types:tunnel-admin-state-up' + +class OduTypeEnum(enum.Enum): + OSUFLEX = 'osuflex' + +def compose_osu_tunnel_endpoint( + node_id : str, tp_id : str, ttp_channel_name : str, + protection_role : EndpointProtectionRoleEnum = EndpointProtectionRoleEnum.WORK +) -> Dict: + return { + 'node-id': node_id, 'tp-id': tp_id, 'ttp-channel-name': ttp_channel_name, + 'protection-role': protection_role.value + } + +def compose_osu_tunnel_te_bandwidth_odu(odu_type : OduTypeEnum, number : int) -> Dict: + return {'layer': 'odu', 'odu-type': odu_type.value, 'number': number} + +def compose_osu_tunnel_protection( + type_ : LspProtectionTypeEnum = LspProtectionTypeEnum.UNPROTECTED, reversion_disable : bool = True +) -> Dict: + return {'protection-type': type_.value, 'protection-reversion-disable': reversion_disable} + +def compose_osu_tunnel_restoration( + type_ : LspRestorationTypeEnum = LspRestorationTypeEnum.NOT_APPLICABLE, restoration_lock : bool = False +) -> Dict: + return {'restoration-type': type_.value, 'restoration-lock': restoration_lock} + +def compose_osu_tunnel( + name : str, + src_node_id : str, src_tp_id : str, src_ttp_channel_name : str, + dst_node_id : str, dst_tp_id : str, dst_ttp_channel_name : str, + odu_type : OduTypeEnum, osuflex_number : int, + delay : int, bidirectional : bool = True, + admin_state : TunnelAdminStateEnum = TunnelAdminStateEnum.UP +) -> Dict: + return {'ietf-te:tunnel': [{ + 'name': name, + 'title': name.upper(), + 'admin-state': admin_state.value, + 'delay': delay, + 'te-bandwidth': compose_osu_tunnel_te_bandwidth_odu(odu_type, osuflex_number), + 'bidirectional': bidirectional, + 'source-endpoints': {'source-endpoint': [ + compose_osu_tunnel_endpoint(src_node_id, src_tp_id, src_ttp_channel_name), + ]}, + 'destination-endpoints': {'destination-endpoint': [ + compose_osu_tunnel_endpoint(dst_node_id, dst_tp_id, dst_ttp_channel_name), + ]}, + 'restoration': compose_osu_tunnel_restoration(), + 'protection': compose_osu_tunnel_protection(), + }]} + +class OsuTunnelHandler: + def __init__(self, rest_api_client : RestApiClient) -> None: + self._rest_api_client = rest_api_client + self._object_name = 'OsuTunnel' + self._subpath_root = '/ietf-te:te/tunnels' + self._subpath_item = self._subpath_root + '/tunnel="{osu_tunnel_name:s}"' + + def _rest_api_get(self, osu_tunnel_name : Optional[str] = None) -> Union[Dict, List]: + if osu_tunnel_name is None: + subpath_url = self._subpath_root + else: + subpath_url = self._subpath_item.format(osu_tunnel_name=osu_tunnel_name) + return self._rest_api_client.get( + self._object_name, subpath_url, expected_http_status={HTTP_STATUS_OK} + ) + + def _rest_api_update(self, data : Dict) -> bool: + return self._rest_api_client.update( + self._object_name, self._subpath_root, data, expected_http_status={HTTP_STATUS_CREATED} + ) + + def _rest_api_delete(self, osu_tunnel_name : str) -> bool: + if osu_tunnel_name is None: raise Exception('osu_tunnel_name is None') + subpath_url = self._subpath_item.format(osu_tunnel_name=osu_tunnel_name) + return self._rest_api_client.delete( + self._object_name, subpath_url, expected_http_status={HTTP_STATUS_NO_CONTENT} + ) + + def get(self, osu_tunnel_name : Optional[str] = None) -> Union[Dict, List]: + data = self._rest_api_get(osu_tunnel_name=osu_tunnel_name) + + if not isinstance(data, dict): raise ValueError('data should be a dict') + if 'ietf-te:tunnel' not in data: raise ValueError('data does not contain key "ietf-te:tunnel"') + data = data['ietf-te:tunnel'] + if not isinstance(data, list): raise ValueError('data[ietf-te:tunnel] should be a list') + + osu_tunnels : List[Dict] = list() + for item in data: + src_endpoints = item['source-endpoints']['source-endpoint'] + if len(src_endpoints) != 1: + MSG = 'OsuTunnel({:s}) has zero/multiple source endpoints' + raise Exception(MSG.format(str(item))) + src_endpoint = src_endpoints[0] + + dst_endpoints = item['destination-endpoints']['destination-endpoint'] + if len(dst_endpoints) != 1: + MSG = 'OsuTunnel({:s}) has zero/multiple destination endpoints' + raise Exception(MSG.format(str(item))) + dst_endpoint = dst_endpoints[0] + + osu_tunnel = { + 'name' : item['name'], + 'src_node_id' : src_endpoint['node-id'], + 'src_tp_id' : src_endpoint['tp-id'], + 'src_ttp_channel_name': src_endpoint['ttp-channel-name'], + 'dst_node_id' : dst_endpoint['node-id'], + 'dst_tp_id' : dst_endpoint['tp-id'], + 'dst_ttp_channel_name': dst_endpoint['ttp-channel-name'], + 'odu_type' : item['te-bandwidth']['odu-type'], + 'osuflex_number' : item['te-bandwidth']['number'], + 'delay' : item['delay'], + 'bidirectional' : item['bidirectional'], + } + osu_tunnels.append(osu_tunnel) + + return osu_tunnels + + def update(self, parameters : Dict) -> bool: + name = parameters['name' ] + + src_node_id = parameters['src_node_id' ] + src_tp_id = parameters['src_tp_id' ] + src_ttp_channel_name = parameters['src_ttp_channel_name'] + + dst_node_id = parameters['dst_node_id' ] + dst_tp_id = parameters['dst_tp_id' ] + dst_ttp_channel_name = parameters['dst_ttp_channel_name'] + + odu_type = parameters.get('odu_type', OduTypeEnum.OSUFLEX.value) + osuflex_number = parameters.get('osuflex_number', 1 ) + delay = parameters.get('delay', 20 ) + bidirectional = parameters.get('bidirectional', True ) + + odu_type = OduTypeEnum._value2member_map_[odu_type] + + data = compose_osu_tunnel( + name, src_node_id, src_tp_id, src_ttp_channel_name, dst_node_id, dst_tp_id, dst_ttp_channel_name, + odu_type, osuflex_number, delay, bidirectional=bidirectional + ) + + return self._rest_api_update(data) + + def delete(self, osu_tunnel_name : str) -> bool: + return self._rest_api_delete(osu_tunnel_name) diff --git a/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py b/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py new file mode 100644 index 0000000000000000000000000000000000000000..1eed066b90c90c7509674c92f8b13e8feefa3513 --- /dev/null +++ b/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py @@ -0,0 +1,104 @@ +# 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. + +import copy, json, logging, requests +from requests.auth import HTTPBasicAuth +from typing import Any, Dict, List, Set, Union + +LOGGER = logging.getLogger(__name__) + +DEFAULT_BASE_URL = '/restconf/v2/data' +DEFAULT_SCHEME = 'https' +DEFAULT_TIMEOUT = 120 +DEFAULT_VERIFY = False + +HTTP_STATUS_OK = 200 +HTTP_STATUS_CREATED = 201 +HTTP_STATUS_ACCEPTED = 202 +HTTP_STATUS_NO_CONTENT = 204 + +HTTP_OK_CODES = { + HTTP_STATUS_OK, + HTTP_STATUS_CREATED, + HTTP_STATUS_ACCEPTED, + HTTP_STATUS_NO_CONTENT, +} + +class RestApiClient: + def __init__(self, address : str, port : int, settings : Dict[str, Any] = dict()) -> None: + username = settings.get('username') + password = settings.get('password') + self._auth = HTTPBasicAuth(username, password) if username is not None and password is not None else None + + scheme = settings.get('scheme', DEFAULT_SCHEME ) + base_url = settings.get('base_url', DEFAULT_BASE_URL) + self._base_url = '{:s}://{:s}:{:d}{:s}'.format(scheme, address, int(port), base_url) + + self._timeout = int(settings.get('timeout', DEFAULT_TIMEOUT)) + self._verify = bool(settings.get('verify', DEFAULT_VERIFY)) + + def get( + self, object_name : str, url : str, + expected_http_status : Set[int] = {HTTP_STATUS_OK} + ) -> Union[Dict, List]: + MSG = 'Get {:s}({:s})' + LOGGER.info(MSG.format(str(object_name), str(url))) + response = requests.get( + self._base_url + url, + timeout=self._timeout, verify=self._verify, auth=self._auth + ) + LOGGER.info(' Response[{:s}]: {:s}'.format(str(response.status_code), str(response.content))) + + if response.status_code in expected_http_status: return json.loads(response.content) + + MSG = 'Could not get {:s}({:s}): status_code={:s} reply={:s}' + raise Exception(MSG.format(str(object_name), str(url), str(response.status_code), str(response))) + + def update( + self, object_name : str, url : str, data : Dict, headers : Dict[str, Any] = dict(), + expected_http_status : Set[int] = HTTP_OK_CODES + ) -> None: + headers = copy.deepcopy(headers) + if 'content-type' not in {header_name.lower() for header_name in headers.keys()}: + headers.update({'content-type': 'application/json'}) + + MSG = 'Create/Update {:s}({:s}, {:s})' + LOGGER.info(MSG.format(str(object_name), str(url), str(data))) + response = requests.post( + self._base_url + url, data=json.dumps(data), headers=headers, + timeout=self._timeout, verify=self._verify, auth=self._auth + ) + LOGGER.info(' Response[{:s}]: {:s}'.format(str(response.status_code), str(response.content))) + + if response.status_code in expected_http_status: return + + MSG = 'Could not create/update {:s}({:s}, {:s}): status_code={:s} reply={:s}' + raise Exception(MSG.format(str(object_name), str(url), str(data), str(response.status_code), str(response))) + + def delete( + self, object_name : str, url : str, + expected_http_status : Set[int] = HTTP_OK_CODES + ) -> None: + MSG = 'Delete {:s}({:s})' + LOGGER.info(MSG.format(str(object_name), str(url))) + response = requests.delete( + self._base_url + url, + timeout=self._timeout, verify=self._verify, auth=self._auth + ) + LOGGER.info(' Response[{:s}]: {:s}'.format(str(response.status_code), str(response.content))) + + if response.status_code in expected_http_status: return + + MSG = 'Could not delete {:s}({:s}): status_code={:s} reply={:s}' + raise Exception(MSG.format(str(object_name), str(url), str(response.status_code), str(response))) diff --git a/src/device/service/drivers/ietf_actn/handlers/__init__.py b/src/device/service/drivers/ietf_actn/handlers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..38d04994fb0fa1951fb465bc127eb72659dc2eaf --- /dev/null +++ b/src/device/service/drivers/ietf_actn/handlers/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/src/device/tests/data/ietf_actn/config_rules.json b/src/device/tests/data/ietf_actn/config_rules.json new file mode 100644 index 0000000000000000000000000000000000000000..d73a68674731cf4af7321044d345470c03d68134 --- /dev/null +++ b/src/device/tests/data/ietf_actn/config_rules.json @@ -0,0 +1,32 @@ +[ + {"action": 1, "custom": {"resource_key": "/osu_tunnels/osu_tunnel[osu_tunnel_1]", "resource_value": { + "name": "osu_tunnel_1", "odu_type": "osuflex", "osuflex_number": 40, "bidirectional": true, "delay": 20, + "src_node_id": "10.0.10.1", "src_tp_id": "200", "src_ttp_channel_name": "och:1-odu2:1-oduflex:1-osuflex:2", + "dst_node_id": "10.0.30.1", "dst_tp_id": "200", "dst_ttp_channel_name": "och:1-odu2:1-oduflex:3-osuflex:1" + }}}, + {"action": 1, "custom": {"resource_key": "/osu_tunnels/osu_tunnel[osu_tunnel_2]", "resource_value": { + "name": "osu_tunnel_2", "odu_type": "osuflex", "osuflex_number": 40, "bidirectional": true, "delay": 20, + "src_node_id": "10.0.10.1", "src_tp_id": "200", "src_ttp_channel_name": "och:1-odu2:1-oduflex:1-osuflex:2", + "dst_node_id": "10.0.30.1", "dst_tp_id": "200", "dst_ttp_channel_name": "och:1-odu2:1-oduflex:3-osuflex:1" + }}}, + {"action": 1, "custom": {"resource_key": "/etht_services/etht_service[etht_service_1]", "resource_value": { + "name": "etht_service_1", "osu_tunnel_name": "osu_tunnel_1", "service_type": "op-mp2mp-svc", + "src_node_id": "10.0.10.1", "src_tp_id": "200", "src_vlan_tag": 21, "src_static_routes": [ + ["128.32.10.5", 24, "128.32.33.5"], + ["128.32.20.5", 24, "128.32.33.5"] + ], + "dst_node_id": "10.0.30.1", "dst_tp_id": "200", "dst_vlan_tag": 101, "dst_static_routes": [ + ["172.1.101.22", 24, "172.10.33.5"] + ] + }}}, + {"action": 1, "custom": {"resource_key": "/etht_services/etht_service[etht_service_2]", "resource_value": { + "name": "etht_service_2", "osu_tunnel_name": "osu_tunnel_2", "service_type": "op-mp2mp-svc", + "src_node_id": "10.0.10.1", "src_tp_id": "200", "src_vlan_tag": 31, "src_static_routes": [ + ["128.32.10.5", 24, "128.32.33.5"], + ["128.32.20.5", 24, "128.32.33.5"] + ], + "dst_node_id": "10.0.30.1", "dst_tp_id": "200", "dst_vlan_tag": 201, "dst_static_routes": [ + ["172.1.101.22", 24, "172.10.33.5"] + ] + }}} +] diff --git a/src/device/tests/data/ietf_actn/deconfig_rules.json b/src/device/tests/data/ietf_actn/deconfig_rules.json new file mode 100644 index 0000000000000000000000000000000000000000..f18e5fb2db8b4d077ea68a499d61577030ce7f48 --- /dev/null +++ b/src/device/tests/data/ietf_actn/deconfig_rules.json @@ -0,0 +1,14 @@ +[ + {"action": 2, "custom": {"resource_key": "/osu_tunnels/osu_tunnel[osu_tunnel_1]", "resource_value": { + "name": "osu_tunnel_1" + }}}, + {"action": 2, "custom": {"resource_key": "/osu_tunnels/osu_tunnel[osu_tunnel_2]", "resource_value": { + "name": "osu_tunnel_2" + }}}, + {"action": 2, "custom": {"resource_key": "/etht_services/etht_service[etht_service_1]", "resource_value": { + "name": "etht_service_1" + }}}, + {"action": 2, "custom": {"resource_key": "/etht_services/etht_service[etht_service_2]", "resource_value": { + "name": "etht_service_2" + }}} +] diff --git a/src/device/tests/data/ietf_actn/expected_etht_services.json b/src/device/tests/data/ietf_actn/expected_etht_services.json new file mode 100644 index 0000000000000000000000000000000000000000..d9f41052692936fd5fffd855b1ba3f3e0478a3b6 --- /dev/null +++ b/src/device/tests/data/ietf_actn/expected_etht_services.json @@ -0,0 +1,176 @@ +{ + "ietf-eth-tran-service:etht-svc": { + "etht-svc-instances": [ + { + "etht-svc-name": "etht_service_1", + "etht-svc-title": "ETHT_SERVICE_1", + "etht-svc-type": "op-mp2mp-svc", + "source-endpoints": { + "source-endpoint": [ + { + "node-id": "10.0.10.1", + "tp-id": "200", + "protection-role": "work", + "layer-specific": { + "access-type": "port" + }, + "is-extendable": false, + "is-terminal": true, + "static-route-list": [ + { + "destination": "128.32.10.5", + "destination-mask": 24, + "next-hop": "128.32.33.5" + }, + { + "destination": "128.32.20.5", + "destination-mask": 24, + "next-hop": "128.32.33.5" + } + ], + "outer-tag": { + "tag-type": "ietf-eth-tran-types:classify-c-vlan", + "vlan-value": 21 + }, + "service-classification-type": "ietf-eth-tran-type:vlan-classification", + "ingress-egress-bandwidth-profile" : { + "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", + "CIR": 10000000, + "EIR": 10000000 + } + } + ] + }, + "destination-endpoints": { + "destination-endpoint": [ + { + "node-id": "10.0.30.1", + "tp-id": "200", + "protection-role": "work", + "layer-specific": { + "access-type": "port" + }, + "is-extendable": false, + "is-terminal": true, + "static-route-list": [ + { + "destination": "172.1.101.22", + "destination-mask": 24, + "next-hop": "172.10.33.5" + } + ], + "outer-tag": { + "tag-type": "ietf-eth-tran-types:classify-c-vlan", + "vlan-value": 101 + }, + "service-classification-type": "ietf-eth-tran-type:vlan-classification", + "ingress-egress-bandwidth-profile" : { + "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", + "CIR": 10000000, + "EIR": 10000000 + } + } + ] + }, + "svc-tunnel": [ + { + "tunnel-name": "osu_tunnel_1" + } + ], + "optimizations": { + "optimization-metric": [ + { + "metric-role": "work", + "metric-type": "ietf-te-types:path-metric-te" + } + ] + } + }, + { + "etht-svc-name": "etht_service_2", + "etht-svc-title": "ETHT_SERVICE_2", + "etht-svc-type": "op-mp2mp-svc", + "source-endpoints": { + "source-endpoint": [ + { + "node-id": "10.0.10.1", + "tp-id": "200", + "protection-role": "work", + "layer-specific": { + "access-type": "port" + }, + "is-extendable": false, + "is-terminal": true, + "static-route-list": [ + { + "destination": "128.32.10.5", + "destination-mask": 24, + "next-hop": "128.32.33.5" + }, + { + "destination": "128.32.20.5", + "destination-mask": 24, + "next-hop": "128.32.33.5" + } + ], + "outer-tag": { + "tag-type": "ietf-eth-tran-types:classify-c-vlan", + "vlan-value": 31 + }, + "service-classification-type": "ietf-eth-tran-type:vlan-classification", + "ingress-egress-bandwidth-profile" : { + "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", + "CIR": 10000000, + "EIR": 10000000 + } + } + ] + }, + "destination-endpoints": { + "destination-endpoint": [ + { + "node-id": "10.0.30.1", + "tp-id": "200", + "protection-role": "work", + "layer-specific": { + "access-type": "port" + }, + "is-extendable": false, + "is-terminal": true, + "static-route-list": [ + { + "destination": "172.1.101.22", + "destination-mask": 24, + "next-hop": "172.10.33.5" + } + ], + "outer-tag": { + "tag-type": "ietf-eth-tran-types:classify-c-vlan", + "vlan-value": 201 + }, + "service-classification-type": "ietf-eth-tran-type:vlan-classification", + "ingress-egress-bandwidth-profile" : { + "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", + "CIR": 10000000, + "EIR": 10000000 + } + } + ] + }, + "svc-tunnel": [ + { + "tunnel-name": "osu_tunnel_2" + } + ], + "optimizations": { + "optimization-metric": [ + { + "metric-role": "work", + "metric-type": "ietf-te-types:path-metric-te" + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/src/device/tests/data/ietf_actn/expected_osu_tunnels.json b/src/device/tests/data/ietf_actn/expected_osu_tunnels.json new file mode 100644 index 0000000000000000000000000000000000000000..1debf555b7219b1f62c4f04782aeaf0eefca62f9 --- /dev/null +++ b/src/device/tests/data/ietf_actn/expected_osu_tunnels.json @@ -0,0 +1,84 @@ +{ + "ietf-te:tunnel": [ + { + "name": "osu_tunnel_1", + "title": "OSU_TUNNEL_1", + "admin-state": "ietf-te-types:tunnel-admin-state-up", + "delay": 20, + "te-bandwidth": { + "layer": "odu", + "odu-type": "osuflex", + "number": 40 + }, + "bidirectional": true, + "destination-endpoints": { + "destination-endpoint": [ + { + "node-id": "10.0.30.1", + "tp-id": "200", + "ttp-channel-name": "och:1-odu2:1-oduflex:3-osuflex:1", + "protection-role": "work" + } + ] + }, + "source-endpoints": { + "source-endpoint": [ + { + "node-id": "10.0.10.1", + "tp-id": "200", + "ttp-channel-name": "och:1-odu2:1-oduflex:1-osuflex:2", + "protection-role": "work" + } + ] + }, + "restoration": { + "restoration-type": "ietf-te-types:lsp-restoration-not-applicable", + "restoration-lock": false + }, + "protection": { + "protection-type": "ietf-te-types:lsp-protection-unprotected", + "protection-reversion-disable": true + } + }, + { + "name": "osu_tunnel_2", + "title": "OSU_TUNNEL_2", + "admin-state": "ietf-te-types:tunnel-admin-state-up", + "delay": 20, + "te-bandwidth": { + "layer": "odu", + "odu-type": "osuflex", + "number": 40 + }, + "bidirectional": true, + "destination-endpoints": { + "destination-endpoint": [ + { + "node-id": "10.0.30.1", + "tp-id": "200", + "ttp-channel-name": "och:1-odu2:1-oduflex:3-osuflex:1", + "protection-role": "work" + } + ] + }, + "source-endpoints": { + "source-endpoint": [ + { + "node-id": "10.0.10.1", + "tp-id": "200", + "ttp-channel-name": "och:1-odu2:1-oduflex:1-osuflex:2", + "protection-role": "work" + } + ] + }, + "restoration": { + "restoration-type": "ietf-te-types:lsp-restoration-not-applicable", + "restoration-lock": false + }, + "protection": { + "protection-type": "ietf-te-types:lsp-protection-unprotected", + "protection-reversion-disable": true + } + } + ] +} diff --git a/src/device/tests/test_unitary_ietf_actn.py b/src/device/tests/test_unitary_ietf_actn.py new file mode 100644 index 0000000000000000000000000000000000000000..5f01a412d88bca142d2bd96ce238947844bc9087 --- /dev/null +++ b/src/device/tests/test_unitary_ietf_actn.py @@ -0,0 +1,282 @@ +# 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. + +import copy, deepdiff, json, logging, operator, os, pytest, time +from flask import Flask, jsonify, make_response +from flask_restful import Resource +from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceId +from common.tools.descriptor.Tools import format_custom_config_rules +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Device import ( + json_device_connect_rules, json_device_id, json_device_ietf_actn_disabled +) +from common.tools.service.GenericRestServer import GenericRestServer +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from device.service.DeviceService import DeviceService +from device.service.driver_api._Driver import _Driver +from tests.tools.mock_ietf_actn_sdn_ctrl.ResourceEthServices import EthService, EthServices +from tests.tools.mock_ietf_actn_sdn_ctrl.ResourceOsuTunnels import OsuTunnel, OsuTunnels + +os.environ['DEVICE_EMULATED_ONLY'] = 'TRUE' +from .PrepareTestScenario import ( # pylint: disable=unused-import + # be careful, order of symbols is important here! + mock_service, device_service, context_client, device_client, monitoring_client, test_prepare_environment +) + +DEVICE_UUID = 'DEVICE-IETF-ACTN' +DEVICE_ADDRESS = '127.0.0.1' +DEVICE_PORT = 8080 +DEVICE_USERNAME = 'admin' +DEVICE_PASSWORD = 'admin' +DEVICE_SCHEME = 'http' +DEVICE_BASE_URL = '/restconf/v2/data' +DEVICE_TIMEOUT = 120 +DEVICE_VERIFY = False + +DEVICE_ID = json_device_id(DEVICE_UUID) +DEVICE = json_device_ietf_actn_disabled(DEVICE_UUID) + +DEVICE_CONNECT_RULES = json_device_connect_rules(DEVICE_ADDRESS, DEVICE_PORT, { + 'scheme' : DEVICE_SCHEME, + 'username': DEVICE_USERNAME, + 'password': DEVICE_PASSWORD, + 'base_url': DEVICE_BASE_URL, + 'timeout' : DEVICE_TIMEOUT, + 'verify' : DEVICE_VERIFY, +}) + +DATA_FILE_CONFIG_RULES = 'device/tests/data/ietf_actn/config_rules.json' +DATA_FILE_DECONFIG_RULES = 'device/tests/data/ietf_actn/deconfig_rules.json' +DATA_FILE_EXPECTED_OSU_TUNNELS = 'device/tests/data/ietf_actn/expected_osu_tunnels.json' +DATA_FILE_EXPECTED_ETHT_SERVICES = 'device/tests/data/ietf_actn/expected_etht_services.json' + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +@pytest.fixture(scope='session') +def ietf_actn_sdn_ctrl( + device_service : DeviceService, # pylint: disable=redefined-outer-name +) -> Flask: + _rest_server = GenericRestServer(DEVICE_PORT, DEVICE_BASE_URL, bind_address=DEVICE_ADDRESS) + _rest_server.app.config['DEBUG' ] = True + _rest_server.app.config['ENV' ] = 'development' + _rest_server.app.config['SERVER_NAME'] = '{:s}:{:d}'.format(DEVICE_ADDRESS, DEVICE_PORT) + _rest_server.app.config['TESTING' ] = True + + class Root(Resource): + def get(self): + return make_response(jsonify({}), 200) + + add_rsrc = _rest_server.add_resource + add_rsrc(Root, '/') + add_rsrc(OsuTunnels, '/ietf-te:te/tunnels') + add_rsrc(OsuTunnel, '/ietf-te:te/tunnels/tunnel="<string:osu_tunnel_name>"') + add_rsrc(EthServices, '/ietf-eth-tran-service:etht-svc') + add_rsrc(EthService, '/ietf-eth-tran-service:etht-svc/etht-svc-instances="<string:etht_service_name>"') + + _rest_server.start() + time.sleep(1) # bring time for the server to start + yield _rest_server + _rest_server.shutdown() + _rest_server.join() + + +def test_device_ietf_actn_add( + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService, # pylint: disable=redefined-outer-name + ietf_actn_sdn_ctrl : GenericRestServer, # pylint: disable=redefined-outer-name +) -> None: + DEVICE_WITH_CONNECT_RULES = copy.deepcopy(DEVICE) + DEVICE_WITH_CONNECT_RULES['device_config']['config_rules'].extend(DEVICE_CONNECT_RULES) + device_client.AddDevice(Device(**DEVICE_WITH_CONNECT_RULES)) + driver_instance_cache = device_service.device_servicer.driver_instance_cache + driver: _Driver = driver_instance_cache.get(DEVICE_UUID) + assert driver is not None + + +def test_device_ietf_actn_get( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + ietf_actn_sdn_ctrl : GenericRestServer, # pylint: disable=redefined-outer-name +) -> None: + + initial_config = device_client.GetInitialConfig(DeviceId(**DEVICE_ID)) + LOGGER.info('initial_config = {:s}'.format(grpc_message_to_json_string(initial_config))) + + device_data = context_client.GetDevice(DeviceId(**DEVICE_ID)) + LOGGER.info('device_data = {:s}'.format(grpc_message_to_json_string(device_data))) + + +def test_device_ietf_actn_configure( + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService, # pylint: disable=redefined-outer-name + ietf_actn_sdn_ctrl : GenericRestServer, # pylint: disable=redefined-outer-name +) -> None: + ietf_actn_client = ietf_actn_sdn_ctrl.app.test_client() + + driver_instance_cache = device_service.device_servicer.driver_instance_cache + driver : _Driver = driver_instance_cache.get(DEVICE_UUID) + assert driver is not None + + retrieved_osu_tunnels = ietf_actn_client.get('/restconf/v2/data/ietf-te:te/tunnels') + retrieved_osu_tunnels = retrieved_osu_tunnels.json + LOGGER.info('osu_tunnels = {:s}'.format(str(retrieved_osu_tunnels))) + expected_osu_tunnels = {'ietf-te:tunnel': []} + osu_tunnels_diff = deepdiff.DeepDiff(expected_osu_tunnels, retrieved_osu_tunnels) + if len(osu_tunnels_diff) > 0: + LOGGER.error('PRE OSU TUNNELS - Differences:\n{:s}'.format(str(osu_tunnels_diff.pretty()))) + assert len(osu_tunnels_diff) == 0 + + retrieved_etht_services = ietf_actn_client.get('/restconf/v2/data/ietf-eth-tran-service:etht-svc') + retrieved_etht_services = retrieved_etht_services.json + LOGGER.info('etht_services = {:s}'.format(str(retrieved_etht_services))) + expected_etht_services = {'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': []}} + etht_services_diff = deepdiff.DeepDiff(expected_etht_services, retrieved_etht_services) + if len(etht_services_diff) > 0: + LOGGER.error('PRE ETHT SERVICES - Differences:\n{:s}'.format(str(etht_services_diff.pretty()))) + assert len(etht_services_diff) == 0 + + retrieved_driver_config_rules = sorted(driver.GetConfig(), key=operator.itemgetter(0)) + LOGGER.info('driver_config = {:s}'.format(str(retrieved_driver_config_rules))) + assert isinstance(retrieved_driver_config_rules, list) + if len(retrieved_driver_config_rules) > 0: + LOGGER.error('PRE DRIVER CONFIG RULES - Differences:\n{:s}'.format(str(retrieved_driver_config_rules))) + assert len(retrieved_driver_config_rules) == 0 + + DEVICE_WITH_CONFIG_RULES = copy.deepcopy(DEVICE) + with open(DATA_FILE_CONFIG_RULES, 'r', encoding='UTF-8') as f: + config_rules = format_custom_config_rules(json.load(f)) + DEVICE_WITH_CONFIG_RULES['device_config']['config_rules'].extend(config_rules) + device_client.ConfigureDevice(Device(**DEVICE_WITH_CONFIG_RULES)) + + retrieved_osu_tunnels = ietf_actn_client.get('/restconf/v2/data/ietf-te:te/tunnels') + retrieved_osu_tunnels = retrieved_osu_tunnels.json + LOGGER.info('osu_tunnels = {:s}'.format(str(retrieved_osu_tunnels))) + with open(DATA_FILE_EXPECTED_OSU_TUNNELS, 'r', encoding='UTF-8') as f: + expected_osu_tunnels = json.load(f) + osu_tunnels_diff = deepdiff.DeepDiff(expected_osu_tunnels, retrieved_osu_tunnels) + if len(osu_tunnels_diff) > 0: + LOGGER.error('POST OSU TUNNELS - Differences:\n{:s}'.format(str(osu_tunnels_diff.pretty()))) + assert len(osu_tunnels_diff) == 0 + + retrieved_etht_services = ietf_actn_client.get('/restconf/v2/data/ietf-eth-tran-service:etht-svc') + retrieved_etht_services = retrieved_etht_services.json + LOGGER.info('etht_services = {:s}'.format(str(retrieved_etht_services))) + with open(DATA_FILE_EXPECTED_ETHT_SERVICES, 'r', encoding='UTF-8') as f: + expected_etht_services = json.load(f) + etht_services_diff = deepdiff.DeepDiff(expected_etht_services, retrieved_etht_services) + if len(etht_services_diff) > 0: + LOGGER.error('POST ETHT SERVICES - Differences:\n{:s}'.format(str(etht_services_diff.pretty()))) + assert len(etht_services_diff) == 0 + + retrieved_driver_config_rules = sorted(driver.GetConfig(), key=operator.itemgetter(0)) + LOGGER.info('driver_config = {:s}'.format(str(retrieved_driver_config_rules))) + retrieved_driver_config_rules = [ + {'action': 1, 'custom': {'resource_key': resource_key, 'resource_value': resource_value}} + for resource_key, resource_value in retrieved_driver_config_rules + ] + with open(DATA_FILE_CONFIG_RULES, 'r', encoding='UTF-8') as f: + expected_driver_config_rules = sorted(json.load(f), key=lambda cr: cr['custom']['resource_key']) + driver_config_rules_diff = deepdiff.DeepDiff(expected_driver_config_rules, retrieved_driver_config_rules) + if len(driver_config_rules_diff) > 0: + LOGGER.error('POST DRIVER CONFIG RULES - Differences:\n{:s}'.format(str(driver_config_rules_diff.pretty()))) + assert len(driver_config_rules_diff) == 0 + + +def test_device_ietf_actn_deconfigure( + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService, # pylint: disable=redefined-outer-name + ietf_actn_sdn_ctrl : GenericRestServer, # pylint: disable=redefined-outer-name +) -> None: + ietf_actn_client = ietf_actn_sdn_ctrl.app.test_client() + + driver_instance_cache = device_service.device_servicer.driver_instance_cache + driver : _Driver = driver_instance_cache.get(DEVICE_UUID) + assert driver is not None + + retrieved_osu_tunnels = ietf_actn_client.get('/restconf/v2/data/ietf-te:te/tunnels') + retrieved_osu_tunnels = retrieved_osu_tunnels.json + LOGGER.info('osu_tunnels = {:s}'.format(str(retrieved_osu_tunnels))) + with open(DATA_FILE_EXPECTED_OSU_TUNNELS, 'r', encoding='UTF-8') as f: + expected_osu_tunnels = json.load(f) + osu_tunnels_diff = deepdiff.DeepDiff(expected_osu_tunnels, retrieved_osu_tunnels) + if len(osu_tunnels_diff) > 0: + LOGGER.error('PRE OSU TUNNELS - Differences:\n{:s}'.format(str(osu_tunnels_diff.pretty()))) + assert len(osu_tunnels_diff) == 0 + + retrieved_etht_services = ietf_actn_client.get('/restconf/v2/data/ietf-eth-tran-service:etht-svc') + retrieved_etht_services = retrieved_etht_services.json + LOGGER.info('etht_services = {:s}'.format(str(retrieved_etht_services))) + with open(DATA_FILE_EXPECTED_ETHT_SERVICES, 'r', encoding='UTF-8') as f: + expected_etht_services = json.load(f) + etht_services_diff = deepdiff.DeepDiff(expected_etht_services, retrieved_etht_services) + if len(etht_services_diff) > 0: + LOGGER.error('PRE ETHT SERVICES - Differences:\n{:s}'.format(str(etht_services_diff.pretty()))) + assert len(etht_services_diff) == 0 + + retrieved_driver_config_rules = sorted(driver.GetConfig(), key=operator.itemgetter(0)) + LOGGER.info('driver_config = {:s}'.format(str(retrieved_driver_config_rules))) + retrieved_driver_config_rules = [ + {'action': 1, 'custom': {'resource_key': resource_key, 'resource_value': resource_value}} + for resource_key, resource_value in retrieved_driver_config_rules + ] + with open(DATA_FILE_CONFIG_RULES, 'r', encoding='UTF-8') as f: + expected_driver_config_rules = sorted(json.load(f), key=lambda cr: cr['custom']['resource_key']) + driver_config_rules_diff = deepdiff.DeepDiff(expected_driver_config_rules, retrieved_driver_config_rules) + if len(driver_config_rules_diff) > 0: + LOGGER.error('PRE DRIVER CONFIG RULES - Differences:\n{:s}'.format(str(driver_config_rules_diff.pretty()))) + assert len(driver_config_rules_diff) == 0 + + DEVICE_WITH_DECONFIG_RULES = copy.deepcopy(DEVICE) + with open(DATA_FILE_DECONFIG_RULES, 'r', encoding='UTF-8') as f: + deconfig_rules = format_custom_config_rules(json.load(f)) + DEVICE_WITH_DECONFIG_RULES['device_config']['config_rules'].extend(deconfig_rules) + device_client.ConfigureDevice(Device(**DEVICE_WITH_DECONFIG_RULES)) + + retrieved_osu_tunnels = ietf_actn_client.get('/restconf/v2/data/ietf-te:te/tunnels') + retrieved_osu_tunnels = retrieved_osu_tunnels.json + LOGGER.info('osu_tunnels = {:s}'.format(str(retrieved_osu_tunnels))) + expected_osu_tunnels = {'ietf-te:tunnel': []} + osu_tunnels_diff = deepdiff.DeepDiff(expected_osu_tunnels, retrieved_osu_tunnels) + if len(osu_tunnels_diff) > 0: + LOGGER.error('POST OSU TUNNELS - Differences:\n{:s}'.format(str(osu_tunnels_diff.pretty()))) + assert len(osu_tunnels_diff) == 0 + + retrieved_etht_services = ietf_actn_client.get('/restconf/v2/data/ietf-eth-tran-service:etht-svc') + retrieved_etht_services = retrieved_etht_services.json + LOGGER.info('etht_services = {:s}'.format(str(retrieved_etht_services))) + expected_etht_services = {'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': []}} + etht_services_diff = deepdiff.DeepDiff(expected_etht_services, retrieved_etht_services) + if len(etht_services_diff) > 0: + LOGGER.error('POST ETHT SERVICES - Differences:\n{:s}'.format(str(etht_services_diff.pretty()))) + assert len(etht_services_diff) == 0 + + retrieved_driver_config_rules = sorted(driver.GetConfig(), key=operator.itemgetter(0)) + LOGGER.info('retrieved_driver_config_rules = {:s}'.format(str(retrieved_driver_config_rules))) + assert isinstance(retrieved_driver_config_rules, list) + if len(retrieved_driver_config_rules) > 0: + LOGGER.error('POST DRIVER CONFIG RULES - Differences:\n{:s}'.format(str(retrieved_driver_config_rules))) + assert len(retrieved_driver_config_rules) == 0 + + +def test_device_ietf_actn_delete( + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService, # pylint: disable=redefined-outer-name + ietf_actn_sdn_ctrl : GenericRestServer, # pylint: disable=redefined-outer-name +) -> None: + device_client.DeleteDevice(DeviceId(**DEVICE_ID)) + driver_instance_cache = device_service.device_servicer.driver_instance_cache + driver : _Driver = driver_instance_cache.get(DEVICE_UUID, {}) + assert driver is None diff --git a/src/nbi/.gitlab-ci.yml b/src/nbi/.gitlab-ci.yml index d9d790803a5cb0387d2d3447ef700154879bb615..e0cac446a33b36a4fbe65b3da1ad5767582a77c6 100644 --- a/src/nbi/.gitlab-ci.yml +++ b/src/nbi/.gitlab-ci.yml @@ -66,6 +66,7 @@ unit_test nbi: - sleep 5 - docker ps -a - docker logs $IMAGE_NAME + - docker exec -i $IMAGE_NAME bash -c "coverage run --append -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_debug_api.py --junitxml=/opt/results/${IMAGE_NAME}_report_debug_api.xml" - docker exec -i $IMAGE_NAME bash -c "coverage run --append -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_ietf_l2vpn.py --junitxml=/opt/results/${IMAGE_NAME}_report_ietf_l2vpn.xml" - docker exec -i $IMAGE_NAME bash -c "coverage run --append -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_ietf_network.py --junitxml=/opt/results/${IMAGE_NAME}_report_ietf_network.xml" - docker exec -i $IMAGE_NAME bash -c "coverage run --append -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_ietf_l3vpn.py --junitxml=/opt/results/${IMAGE_NAME}_report_ietf_l3vpn.xml" diff --git a/src/nbi/tests/data/debug_api_dummy.json b/src/nbi/tests/data/debug_api_dummy.json new file mode 100644 index 0000000000000000000000000000000000000000..d8f5137578629408556e3758c512f137fc633d6c --- /dev/null +++ b/src/nbi/tests/data/debug_api_dummy.json @@ -0,0 +1,442 @@ +{ + "dummy_mode": true, + "contexts": [ + { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "name": "admin", + "topology_ids": [ + {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + ], + "service_ids": [ + {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R1/200==R2/200"}}, + {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R1/200==R3/200"}}, + {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R2/200==R3/200"}} + ], + "slice_ids": [ + {"context_id": {"context_uuid": {"uuid": "admin"}}, "slice_uuid": {"uuid": "SLC:R1-R2-R3"}} + ] + } + ], + "topologies": [ + { + "device_ids": [ + {"device_uuid": {"uuid": "R1"}}, + {"device_uuid": {"uuid": "R2"}}, + {"device_uuid": {"uuid": "R3"}} + ], + "link_ids": [ + {"link_uuid": {"uuid": "R1/502==R2/501"}}, + {"link_uuid": {"uuid": "R1/503==R3/501"}}, + {"link_uuid": {"uuid": "R2/501==R1/502"}}, + {"link_uuid": {"uuid": "R2/503==R3/502"}}, + {"link_uuid": {"uuid": "R3/501==R1/503"}}, + {"link_uuid": {"uuid": "R3/502==R2/503"}} + ], + "name": "admin", + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "name": "R1", "device_type": "emu-packet-router", + "device_drivers": [0], "device_operational_status": 2, + "device_endpoints": [ + {"name": "200", "endpoint_type": "copper", "endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"name": "502", "endpoint_type": "optical", "endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "502"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"name": "503", "endpoint_type": "optical", "endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "503"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }} + ], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": 0}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "200", "name": "200", "type": "copper"}, + {"uuid": "502", "name": "502", "type": "optical"}, + {"uuid": "503", "name": "503", "type": "optical"} + ]}}}, + {"action": 1, "custom": {"resource_key": "/endpoints/endpoint[200]", "resource_value": { + "uuid": "200", "name": "200", "type": "copper" + }}}, + {"action": 1, "custom": {"resource_key": "/endpoints/endpoint[502]", "resource_value": { + "uuid": "502", "name": "502", "type": "optical" + }}}, + {"action": 1, "custom": {"resource_key": "/endpoints/endpoint[503]", "resource_value": { + "uuid": "503", "name": "503", "type": "optical" + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "name": "R2", "device_type": "emu-packet-router", + "device_drivers": [0], "device_operational_status": 2, + "device_endpoints": [ + {"name": "200", "endpoint_type": "copper", "endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"name": "501", "endpoint_type": "optical", "endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "501"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"name": "503", "endpoint_type": "optical", "endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "503"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }} + ], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": 0}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "200", "name": "200", "type": "copper"}, + {"uuid": "501", "name": "501", "type": "optical"}, + {"uuid": "503", "name": "503", "type": "optical"} + ]}}}, + {"action": 1, "custom": {"resource_key": "/endpoints/endpoint[200]", "resource_value": { + "uuid": "200", "name": "200", "type": "copper" + }}}, + {"action": 1, "custom": {"resource_key": "/endpoints/endpoint[501]", "resource_value": { + "uuid": "501", "name": "501", "type": "optical" + }}}, + {"action": 1, "custom": {"resource_key": "/endpoints/endpoint[503]", "resource_value": { + "uuid": "503", "name": "503", "type": "optical" + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "name": "R3", "device_type": "emu-packet-router", + "device_drivers": [0], "device_operational_status": 2, + "device_endpoints": [ + {"name": "200", "endpoint_type": "copper", "endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"name": "502", "endpoint_type": "optical", "endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "502"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"name": "503", "endpoint_type": "optical", "endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "503"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }} + ], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": 0}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "200", "name": "200", "type": "copper"}, + {"uuid": "502", "name": "502", "type": "optical"}, + {"uuid": "503", "name": "503", "type": "optical"} + ]}}}, + {"action": 1, "custom": {"resource_key": "/endpoints/endpoint[200]", "resource_value": { + "uuid": "200", "name": "200", "type": "copper" + }}}, + {"action": 1, "custom": {"resource_key": "/endpoints/endpoint[502]", "resource_value": { + "uuid": "502", "name": "502", "type": "optical" + }}}, + {"action": 1, "custom": {"resource_key": "/endpoints/endpoint[503]", "resource_value": { + "uuid": "503", "name": "503", "type": "optical" + }}} + ]} + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "R1/502==R2/501"}}, "name": "R1/502==R2/501", + "attributes": {"total_capacity_gbps": 10.0, "used_capacity_gbps": 0.0}, + "link_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "502"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "501"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R1/503==R3/501"}}, "name": "R1/503==R3/501", + "attributes": {"total_capacity_gbps": 10.0, "used_capacity_gbps": 0.0}, + "link_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "503"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "501"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2/501==R1/502"}}, "name": "R2/501==R1/502", + "attributes": {"total_capacity_gbps": 10.0, "used_capacity_gbps": 0.0}, + "link_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "501"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "502"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2/503==R3/502"}}, "name": "R2/503==R3/502", + "attributes": {"total_capacity_gbps": 10.0, "used_capacity_gbps": 0.0}, + "link_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "503"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "502"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R3/501==R1/503"}}, "name": "R3/501==R1/503", + "attributes": {"total_capacity_gbps": 10.0, "used_capacity_gbps": 0.0}, + "link_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "501"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "503"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R3/502==R2/503"}}, "name": "R3/502==R2/503", + "attributes": {"total_capacity_gbps": 10.0, "used_capacity_gbps": 0.0}, + "link_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "502"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "503"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ] + } + ], + "services": [ + { + "service_id" : {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R1/200==R2/200"}}, + "name": "SVC:R1/200==R2/200", "service_type": 1, "service_status": {"service_status": 1}, + "service_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ], + "service_constraints": [ + {"action": 1, "sla_capacity": {"capacity_gbps": 40.0}}, + {"action": 1, "sla_latency": {"e2e_latency_ms": 10.0}}, + {"action": 1, "sla_availability": {"num_disjoint_paths": 1, "all_active": true, "availability": 99.99}} + ], + "service_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "/device[R1]/endpoint[200]/settings", "resource_value": { + "ipv4_address": "10.0.1.1", "ipv4_prefix": 24 + }}}, + {"action": 1, "custom": {"resource_key": "/device[R2]/endpoint[200]/settings", "resource_value": { + "ipv4_address": "10.0.2.1", "ipv4_prefix": 24 + }}} + ]} + }, + { + "service_id" : {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R1/200==R3/200"}}, + "name": "SVC:R1/200==R3/200", "service_type": 1, "service_status": {"service_status": 1}, + "service_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ], + "service_constraints": [ + {"action": 1, "sla_capacity": {"capacity_gbps": 50.0}}, + {"action": 1, "sla_latency": {"e2e_latency_ms": 8.0}}, + {"action": 1, "sla_availability": {"num_disjoint_paths": 1, "all_active": true, "availability": 99.9}} + ], + "service_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "/device[R1]/endpoint[200]/settings", "resource_value": { + "ipv4_address": "10.0.1.1", "ipv4_prefix": 24 + }}}, + {"action": 1, "custom": {"resource_key": "/device[R3]/endpoint[200]/settings", "resource_value": { + "ipv4_address": "10.0.3.1", "ipv4_prefix": 24 + }}} + ]} + }, + { + "service_id" : {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R2/200==R3/200"}}, + "name": "SVC:R2/200==R3/200", "service_type": 1, "service_status": {"service_status": 1}, + "service_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ], + "service_constraints": [ + {"action": 1, "sla_capacity": {"capacity_gbps": 10.0}}, + {"action": 1, "sla_latency": {"e2e_latency_ms": 3.0}}, + {"action": 1, "sla_availability": {"num_disjoint_paths": 1, "all_active": true, "availability": 99.9999}} + ], + "service_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "/device[R2]/endpoint[200]/settings", "resource_value": { + "ipv4_address": "10.0.2.1", "ipv4_prefix": 24 + }}}, + {"action": 1, "custom": {"resource_key": "/device[R3]/endpoint[200]/settings", "resource_value": { + "ipv4_address": "10.0.3.1", "ipv4_prefix": 24 + }}} + ]} + } + ], + "slices": [ + { + "slice_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "slice_uuid": {"uuid": "SLC:R1-R2-R3"}}, + "name": "SLC:R1-R2-R3", + "slice_endpoint_ids": [ + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ], + "slice_constraints": [ + {"action": 1, "sla_capacity": {"capacity_gbps": 40.0}}, + {"action": 1, "sla_latency": {"e2e_latency_ms": 10.0}}, + {"action": 1, "sla_availability": {"num_disjoint_paths": 1, "all_active": true, "availability": 99.99}} + ], + "slice_service_ids": [ + {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R1/200==R2/200"}}, + {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R1/200==R3/200"}}, + {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R2/200==R3/200"}} + ], + "slice_subslice_ids": [], + "slice_status": {"slice_status" : 1}, + "slice_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "/device[R1]/endpoint[200]/settings", "resource_value": { + "ipv4_address": "10.0.1.1", "ipv4_prefix": 24 + }}}, + {"action": 1, "custom": {"resource_key": "/device[R2]/endpoint[200]/settings", "resource_value": { + "ipv4_address": "10.0.2.1", "ipv4_prefix": 24 + }}}, + {"action": 1, "custom": {"resource_key": "/device[R3]/endpoint[200]/settings", "resource_value": { + "ipv4_address": "10.0.3.1", "ipv4_prefix": 24 + }}} + ]}, + "slice_owner": {"owner_uuid": {"uuid": "TFS"}, "owner_string": "TFS:SLC:R1-R2-R3"} + } + ], + "connections": [ + { + "connection_id": {"connection_uuid": {"uuid": "CON:R1/200==R2/200:1"}}, + "service_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R1/200==R2/200"}}, + "path_hops_endpoint_ids" : [ + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "502"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "501"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ], + "sub_service_ids": [], + "settings": { + "l3": {"src_ip_address": "10.0.1.10", "dst_ip_address": "10.0.2.10", "ttl": 20} + } + }, + { + "connection_id": {"connection_uuid": {"uuid": "CON:R1/200==R3/200:1"}}, + "service_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R1/200==R3/200"}}, + "path_hops_endpoint_ids" : [ + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "503"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "501"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ], + "sub_service_ids": [], + "settings": { + "l3": {"src_ip_address": "10.0.1.10", "dst_ip_address": "10.0.3.10", "ttl": 20} + } + }, + { + "connection_id": {"connection_uuid": {"uuid": "CON:R2/200==R3/200:1"}}, + "service_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "SVC:R2/200==R3/200"}}, + "path_hops_endpoint_ids" : [ + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "503"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "502"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "200"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + } + ], + "sub_service_ids": [], + "settings": { + "l3": {"src_ip_address": "10.0.2.10", "dst_ip_address": "10.0.3.10", "ttl": 20} + } + } + ] +} diff --git a/src/nbi/tests/test_debug_api.py b/src/nbi/tests/test_debug_api.py index e284992618a96388371b69b7b0c2a8c4fcaaba70..f19531eaed3b106279c2752a55eb00bb1ca30bb7 100644 --- a/src/nbi/tests/test_debug_api.py +++ b/src/nbi/tests/test_debug_api.py @@ -12,209 +12,206 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, os, pytest, time, urllib -from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, ServiceNameEnum -from common.proto.context_pb2 import Connection, Context, Device, Link, Service, Slice, Topology -from common.proto.policy_pb2 import PolicyRuleIdList, PolicyRuleId, PolicyRuleList, PolicyRule -from common.Settings import ( - ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, ENVVAR_SUFIX_SERVICE_PORT_HTTP, get_env_var_name, - get_service_port_grpc, get_service_port_http +import logging, urllib +from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME +from common.proto.context_pb2 import ContextId +from common.tools.descriptor.Loader import ( + DescriptorLoader, check_descriptor_load_results, validate_empty_scenario ) +from common.tools.object_factory.Context import json_context_id from common.type_checkers.Assertions import ( - validate_connection, validate_connection_ids, validate_connections, validate_context, validate_context_ids, - validate_contexts, validate_device, validate_device_ids, validate_devices, validate_link, validate_link_ids, - validate_links, validate_service, validate_service_ids, validate_services, validate_topologies, validate_topology, - validate_topology_ids) + validate_connection, validate_connection_ids, validate_connections, + validate_context, validate_context_ids, validate_contexts, + validate_device, validate_device_ids, validate_devices, + validate_link, validate_link_ids, validate_links, + validate_service, validate_service_ids, validate_services, + validate_slice, validate_slice_ids, validate_slices, + validate_topologies, validate_topology, validate_topology_ids +) from context.client.ContextClient import ContextClient -from nbi.tests.PrepareTestScenario import do_rest_get_request -from .MockService_Dependencies import MockService_Dependencies -from .Objects import ( - CONNECTION_R1_R3, CONNECTION_R1_R3_ID, CONNECTION_R1_R3_UUID, CONTEXT, CONTEXT_ID, DEVICE_R1, DEVICE_R1_ID, - DEVICE_R1_UUID, DEVICE_R2, DEVICE_R2_ID, DEVICE_R2_UUID, DEVICE_R3, DEVICE_R3_ID, DEVICE_R3_UUID, LINK_R1_R2, - LINK_R1_R2_ID, LINK_R1_R2_UUID, SERVICE_R1_R2, SERVICE_R1_R2_ID, SERVICE_R1_R2_UUID, SERVICE_R1_R3, - SERVICE_R1_R3_ID, SERVICE_R1_R3_UUID, SERVICE_R2_R3, SERVICE_R2_R3_ID, SERVICE_R2_R3_UUID, SLICE_R1_R3, TOPOLOGY, - TOPOLOGY_ID, POLICY_RULE, POLICY_RULE_ID, POLICY_RULE_UUID +from nbi.service.rest_server.RestServer import RestServer +from .PrepareTestScenario import ( # pylint: disable=unused-import + # be careful, order of symbols is important here! + mock_service, nbi_service_rest, context_client, + do_rest_get_request ) +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +DESCRIPTOR_FILE = 'nbi/tests/data/debug_api_dummy.json' -@pytest.fixture(scope='session') -def mock_service(): - _service = MockService_Dependencies(MOCKSERVICE_PORT) - _service.configure_env_vars() - _service.start() - yield _service - _service.stop() +JSON_ADMIN_CONTEXT_ID = json_context_id(DEFAULT_CONTEXT_NAME) +ADMIN_CONTEXT_ID = ContextId(**JSON_ADMIN_CONTEXT_ID) +# ----- Prepare Environment -------------------------------------------------------------------------------------------- -LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) +def test_prepare_environment(context_client : ContextClient) -> None: # pylint: disable=redefined-outer-name + validate_empty_scenario(context_client) + descriptor_loader = DescriptorLoader(descriptors_file=DESCRIPTOR_FILE, context_client=context_client) + results = descriptor_loader.process() + check_descriptor_load_results(results, descriptor_loader) + descriptor_loader.validate() -LOCAL_HOST = '127.0.0.1' -GRPC_PORT = 10000 + int(get_service_port_grpc(ServiceNameEnum.CONTEXT)) # avoid privileged ports -HTTP_PORT = 10000 + int(get_service_port_http(ServiceNameEnum.CONTEXT)) # avoid privileged ports - -MOCKSERVICE_PORT = 10000 -DEVICE_SERVICE_PORT = MOCKSERVICE_PORT + get_service_port_grpc(ServiceNameEnum.DEVICE) # avoid privileged ports - -os.environ[get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_HOST )] = str(LOCAL_HOST) -os.environ[get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_GRPC)] = str(GRPC_PORT) -os.environ[get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_HTTP)] = str(HTTP_PORT) - -@pytest.fixture(scope='session') -def context_service_grpc(): - _service = ContextService(context_s_mb[0], context_s_mb[1]) - _service.start() - yield _service - _service.stop() - -@pytest.fixture(scope='session') -def context_service_rest(): - database = context_db_mb[0] - _rest_server = RestServer() - for endpoint_name, resource_class, resource_url in RESOURCES: - _rest_server.add_resource(resource_class, resource_url, endpoint=endpoint_name, resource_class_args=(database,)) - _rest_server.start() - time.sleep(1) # bring time for the server to start - yield _rest_server - _rest_server.shutdown() - _rest_server.join() - -@pytest.fixture(scope='session') -def context_client_grpc(context_service_grpc : ContextService): # pylint: disable=redefined-outer-name - _client = ContextClient() - yield _client - _client.close() - -def test_populate_database(): - client = ContextClient(host=LOCAL_HOST, port=GRPC_PORT) - client.SetContext(Context(**CONTEXT)) - client.SetTopology(Topology(**TOPOLOGY)) - client.SetDevice(Device(**DEVICE_R1)) - client.SetDevice(Device(**DEVICE_R2)) - client.SetDevice(Device(**DEVICE_R3)) - client.SetLink(Link(**LINK_R1_R2)) - client.SetLink(Link(**LINK_R1_R3)) - client.SetLink(Link(**LINK_R2_R3)) - client.SetService(Service(**SERVICE_R1_R2)) - client.SetService(Service(**SERVICE_R1_R3)) - client.SetService(Service(**SERVICE_R2_R3)) - client.SetSlice(Slice(**SLICE_R1_R3)) - client.SetConnection(Connection(**CONNECTION_R1_R3)) - -def test_rest_get_context_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_get_request('/context_ids') + # Verify the scenario has no services/slices + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.topology_ids) == 1 + assert len(response.service_ids ) == 3 + assert len(response.slice_ids ) == 1 + + +# ----- Context -------------------------------------------------------------------------------------------------------- + +def test_rest_get_context_ids(nbi_service_rest: RestServer): # pylint: disable=redefined-outer-name, unused-argument + reply = do_rest_get_request('/debug-api/context_ids') validate_context_ids(reply) -def test_rest_get_contexts(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_get_request('/contexts') +def test_rest_get_contexts(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument + reply = do_rest_get_request('/debug-api/contexts') validate_contexts(reply) -def test_rest_get_context(context_service_rest : RestServer): # pylint: disable=redefined-outer-name +def test_rest_get_context(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_get_request('/context/{:s}'.format(context_uuid)) + reply = do_rest_get_request('/debug-api/context/{:s}'.format(context_uuid)) validate_context(reply) -def test_rest_get_topology_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name + +# ----- Topology ------------------------------------------------------------------------------------------------------- + +def test_rest_get_topology_ids(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_get_request('/context/{:s}/topology_ids'.format(context_uuid)) + reply = do_rest_get_request('/debug-api/context/{:s}/topology_ids'.format(context_uuid)) validate_topology_ids(reply) -def test_rest_get_topologies(context_service_rest : RestServer): # pylint: disable=redefined-outer-name +def test_rest_get_topologies(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_get_request('/context/{:s}/topologies'.format(context_uuid)) + reply = do_rest_get_request('/debug-api/context/{:s}/topologies'.format(context_uuid)) validate_topologies(reply) -def test_rest_get_topology(context_service_rest : RestServer): # pylint: disable=redefined-outer-name +def test_rest_get_topology(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) topology_uuid = urllib.parse.quote(DEFAULT_TOPOLOGY_NAME) - reply = do_rest_get_request('/context/{:s}/topology/{:s}'.format(context_uuid, topology_uuid)) - validate_topology(reply, num_devices=3, num_links=3) + reply = do_rest_get_request('/debug-api/context/{:s}/topology/{:s}'.format(context_uuid, topology_uuid)) + validate_topology(reply, num_devices=3, num_links=6) + + +# ----- Device --------------------------------------------------------------------------------------------------------- + +def test_rest_get_device_ids(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument + reply = do_rest_get_request('/debug-api/device_ids') + validate_device_ids(reply) + +def test_rest_get_devices(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument + reply = do_rest_get_request('/debug-api/devices') + validate_devices(reply) + +def test_rest_get_device(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument + device_uuid = urllib.parse.quote('R1', safe='') + reply = do_rest_get_request('/debug-api/device/{:s}'.format(device_uuid)) + validate_device(reply) + + +# ----- Link ----------------------------------------------------------------------------------------------------------- + +def test_rest_get_link_ids(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument + reply = do_rest_get_request('/debug-api/link_ids') + validate_link_ids(reply) -def test_rest_get_service_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name +def test_rest_get_links(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument + reply = do_rest_get_request('/debug-api/links') + validate_links(reply) + +def test_rest_get_link(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument + link_uuid = urllib.parse.quote('R1/502==R2/501', safe='') + reply = do_rest_get_request('/debug-api/link/{:s}'.format(link_uuid)) + validate_link(reply) + + +# ----- Service -------------------------------------------------------------------------------------------------------- + +def test_rest_get_service_ids(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_get_request('/context/{:s}/service_ids'.format(context_uuid)) + reply = do_rest_get_request('/debug-api/context/{:s}/service_ids'.format(context_uuid)) validate_service_ids(reply) -def test_rest_get_services(context_service_rest : RestServer): # pylint: disable=redefined-outer-name +def test_rest_get_services(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_get_request('/context/{:s}/services'.format(context_uuid)) + reply = do_rest_get_request('/debug-api/context/{:s}/services'.format(context_uuid)) validate_services(reply) -def test_rest_get_service(context_service_rest : RestServer): # pylint: disable=redefined-outer-name +def test_rest_get_service(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - service_uuid = urllib.parse.quote(SERVICE_R1_R2_UUID, safe='') - reply = do_rest_get_request('/context/{:s}/service/{:s}'.format(context_uuid, service_uuid)) + service_uuid = urllib.parse.quote('SVC:R1/200==R2/200', safe='') + reply = do_rest_get_request('/debug-api/context/{:s}/service/{:s}'.format(context_uuid, service_uuid)) validate_service(reply) -def test_rest_get_slice_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_get_request('/context/{:s}/slice_ids'.format(context_uuid)) - #validate_slice_ids(reply) -def test_rest_get_slices(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - reply = do_rest_get_request('/context/{:s}/slices'.format(context_uuid)) - #validate_slices(reply) +# ----- Slice ---------------------------------------------------------------------------------------------------------- -def test_rest_get_slice(context_service_rest : RestServer): # pylint: disable=redefined-outer-name +def test_rest_get_slice_ids(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - slice_uuid = urllib.parse.quote(SLICE_R1_R3_UUID, safe='') - reply = do_rest_get_request('/context/{:s}/slice/{:s}'.format(context_uuid, slice_uuid)) - #validate_slice(reply) - -def test_rest_get_device_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_get_request('/device_ids') - validate_device_ids(reply) - -def test_rest_get_devices(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_get_request('/devices') - validate_devices(reply) + reply = do_rest_get_request('/debug-api/context/{:s}/slice_ids'.format(context_uuid)) + validate_slice_ids(reply) -def test_rest_get_device(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - device_uuid = urllib.parse.quote(DEVICE_R1_UUID, safe='') - reply = do_rest_get_request('/device/{:s}'.format(device_uuid)) - validate_device(reply) +def test_rest_get_slices(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument + context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) + reply = do_rest_get_request('/debug-api/context/{:s}/slices'.format(context_uuid)) + validate_slices(reply) -def test_rest_get_link_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_get_request('/link_ids') - validate_link_ids(reply) +def test_rest_get_slice(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument + context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) + slice_uuid = urllib.parse.quote('SLC:R1-R2-R3', safe='') + reply = do_rest_get_request('/debug-api/context/{:s}/slice/{:s}'.format(context_uuid, slice_uuid)) + validate_slice(reply) -def test_rest_get_links(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_get_request('/links') - validate_links(reply) -def test_rest_get_link(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - link_uuid = urllib.parse.quote(LINK_R1_R2_UUID, safe='') - reply = do_rest_get_request('/link/{:s}'.format(link_uuid)) - validate_link(reply) +# ----- Connection ----------------------------------------------------------------------------------------------------- -def test_rest_get_connection_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name +def test_rest_get_connection_ids(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - service_uuid = urllib.parse.quote(SERVICE_R1_R3_UUID, safe='') - reply = do_rest_get_request('/context/{:s}/service/{:s}/connection_ids'.format(context_uuid, service_uuid)) + service_uuid = urllib.parse.quote('SVC:R1/200==R2/200', safe='') + reply = do_rest_get_request('/debug-api/context/{:s}/service/{:s}/connection_ids'.format(context_uuid, service_uuid)) validate_connection_ids(reply) -def test_rest_get_connections(context_service_rest : RestServer): # pylint: disable=redefined-outer-name +def test_rest_get_connections(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument context_uuid = urllib.parse.quote(DEFAULT_CONTEXT_NAME) - service_uuid = urllib.parse.quote(SERVICE_R1_R3_UUID, safe='') - reply = do_rest_get_request('/context/{:s}/service/{:s}/connections'.format(context_uuid, service_uuid)) + service_uuid = urllib.parse.quote('SVC:R1/200==R2/200', safe='') + reply = do_rest_get_request('/debug-api/context/{:s}/service/{:s}/connections'.format(context_uuid, service_uuid)) validate_connections(reply) -def test_rest_get_connection(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - connection_uuid = urllib.parse.quote(CONNECTION_R1_R3_UUID, safe='') - reply = do_rest_get_request('/connection/{:s}'.format(connection_uuid)) +def test_rest_get_connection(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument + connection_uuid = urllib.parse.quote('CON:R1/200==R2/200:1', safe='') + reply = do_rest_get_request('/debug-api/connection/{:s}'.format(connection_uuid)) validate_connection(reply) -def test_rest_get_policyrule_ids(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_get_request('/policyrule_ids') - #validate_policyrule_ids(reply) +# ----- Policy --------------------------------------------------------------------------------------------------------- + +#def test_rest_get_policyrule_ids(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument +# reply = do_rest_get_request('/debug-api/policyrule_ids') +# validate_policyrule_ids(reply) + +#def test_rest_get_policyrules(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument +# reply = do_rest_get_request('/debug-api/policyrules') +# validate_policyrules(reply) + +#def test_rest_get_policyrule(nbi_service_rest : RestServer): # pylint: disable=redefined-outer-name, unused-argument +# policyrule_uuid_quoted = urllib.parse.quote(policyrule_uuid, safe='') +# reply = do_rest_get_request('/debug-api/policyrule/{:s}'.format(policyrule_uuid_quoted)) +# validate_policyrule(reply) + + +# ----- Cleanup Environment -------------------------------------------------------------------------------------------- -def test_rest_get_policyrules(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - reply = do_rest_get_request('/policyrules') - #validate_policyrules(reply) +def test_cleanup_environment(context_client : ContextClient) -> None: # pylint: disable=redefined-outer-name + # Verify the scenario has no services/slices + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.topology_ids) == 1 + assert len(response.service_ids ) == 3 + assert len(response.slice_ids ) == 1 -def test_rest_get_policyrule(context_service_rest : RestServer): # pylint: disable=redefined-outer-name - policyrule_uuid = urllib.parse.quote(POLICYRULE_UUID, safe='') - reply = do_rest_get_request('/policyrule/{:s}'.format(policyrule_uuid)) - #validate_policyrule(reply) + # Load descriptors and validate the base scenario + descriptor_loader = DescriptorLoader(descriptors_file=DESCRIPTOR_FILE, context_client=context_client) + descriptor_loader.validate() + descriptor_loader.unload() + validate_empty_scenario(context_client) diff --git a/src/nbi/tests/test_ietf_l3vpn.py b/src/nbi/tests/test_ietf_l3vpn.py index 17f3c3e93c627c41ddc344e55b3478d13077efb3..da9efdffd4dd6f4b3af956988aa3d51d3e855c84 100644 --- a/src/nbi/tests/test_ietf_l3vpn.py +++ b/src/nbi/tests/test_ietf_l3vpn.py @@ -21,7 +21,7 @@ from common.tools.descriptor.Loader import ( ) from common.tools.object_factory.Context import json_context_id from context.client.ContextClient import ContextClient -from nbi.service.rest_server import RestServer +from nbi.service.rest_server.RestServer import RestServer from .PrepareTestScenario import ( # pylint: disable=unused-import # be careful, order of symbols is important here! do_rest_delete_request, do_rest_get_request, do_rest_post_request, diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java b/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java index b7de8ff99fe820ad80a555c97a42accfe21b1d7b..f777c77209b0d023da5a30270ab472de6117bb59 100644 --- a/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java +++ b/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java @@ -2284,6 +2284,12 @@ public class Serializer { return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR; case IETF_L2VPN: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN; + case GNMI_OPENCONFIG: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG; + case FLEXSCALE: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_FLEXSCALE; + case IETF_ACTN: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN; case UNDEFINED: default: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED; @@ -2307,6 +2313,12 @@ public class Serializer { return DeviceDriverEnum.XR; case DEVICEDRIVER_IETF_L2VPN: return DeviceDriverEnum.IETF_L2VPN; + case DEVICEDRIVER_GNMI_OPENCONFIG: + return DeviceDriverEnum.GNMI_OPENCONFIG; + case DEVICEDRIVER_FLEXSCALE: + return DeviceDriverEnum.FLEXSCALE; + case DEVICEDRIVER_IETF_ACTN: + return DeviceDriverEnum.IETF_ACTN; case DEVICEDRIVER_UNDEFINED: case UNRECOGNIZED: default: diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/context/model/DeviceDriverEnum.java b/src/policy/src/main/java/org/etsi/tfs/policy/context/model/DeviceDriverEnum.java index 63e96a4c61769fbb6a009acf208ef7b4c81200ad..72a1d7136c00d2bac93087d7f8b8f3b7626f9803 100644 --- a/src/policy/src/main/java/org/etsi/tfs/policy/context/model/DeviceDriverEnum.java +++ b/src/policy/src/main/java/org/etsi/tfs/policy/context/model/DeviceDriverEnum.java @@ -24,5 +24,8 @@ public enum DeviceDriverEnum { IETF_NETWORK_TOPOLOGY, ONF_TR_532, XR, - IETF_L2VPN + IETF_L2VPN, + GNMI_OPENCONFIG, + FLEXSCALE, + IETF_ACTN } diff --git a/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java index f29ae3697a8842c14dc28716e325adc85a5c45af..63fb1ad7a72d74cf52148027f4fc0e0546b0b58e 100644 --- a/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java +++ b/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java @@ -3614,6 +3614,13 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.IETF_L2VPN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN), + Arguments.of( + DeviceDriverEnum.GNMI_OPENCONFIG, + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG), + Arguments.of( + DeviceDriverEnum.FLEXSCALE, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_FLEXSCALE), + Arguments.of( + DeviceDriverEnum.IETF_ACTN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } diff --git a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java index a25798b884d9006f9c1b218c133634784f8bf392..d4873899b0113a7356c1c4d6bc2ea9aae2e8b4e5 100644 --- a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java @@ -185,6 +185,14 @@ public final class ContextOuterClass { * <code>DEVICEDRIVER_GNMI_OPENCONFIG = 8;</code> */ DEVICEDRIVER_GNMI_OPENCONFIG(8), + /** + * <code>DEVICEDRIVER_FLEXSCALE = 9;</code> + */ + DEVICEDRIVER_FLEXSCALE(9), + /** + * <code>DEVICEDRIVER_IETF_ACTN = 10;</code> + */ + DEVICEDRIVER_IETF_ACTN(10), UNRECOGNIZED(-1), ; @@ -228,6 +236,14 @@ public final class ContextOuterClass { * <code>DEVICEDRIVER_GNMI_OPENCONFIG = 8;</code> */ public static final int DEVICEDRIVER_GNMI_OPENCONFIG_VALUE = 8; + /** + * <code>DEVICEDRIVER_FLEXSCALE = 9;</code> + */ + public static final int DEVICEDRIVER_FLEXSCALE_VALUE = 9; + /** + * <code>DEVICEDRIVER_IETF_ACTN = 10;</code> + */ + public static final int DEVICEDRIVER_IETF_ACTN_VALUE = 10; public final int getNumber() { @@ -263,6 +279,8 @@ public final class ContextOuterClass { case 6: return DEVICEDRIVER_XR; case 7: return DEVICEDRIVER_IETF_L2VPN; case 8: return DEVICEDRIVER_GNMI_OPENCONFIG; + case 9: return DEVICEDRIVER_FLEXSCALE; + case 10: return DEVICEDRIVER_IETF_ACTN; default: return null; } } @@ -461,6 +479,10 @@ public final class ContextOuterClass { * <code>SERVICETYPE_TE = 4;</code> */ SERVICETYPE_TE(4), + /** + * <code>SERVICETYPE_E2E = 5;</code> + */ + SERVICETYPE_E2E(5), UNRECOGNIZED(-1), ; @@ -484,6 +506,10 @@ public final class ContextOuterClass { * <code>SERVICETYPE_TE = 4;</code> */ public static final int SERVICETYPE_TE_VALUE = 4; + /** + * <code>SERVICETYPE_E2E = 5;</code> + */ + public static final int SERVICETYPE_E2E_VALUE = 5; public final int getNumber() { @@ -515,6 +541,7 @@ public final class ContextOuterClass { case 2: return SERVICETYPE_L2NM; case 3: return SERVICETYPE_TAPI_CONNECTIVITY_SERVICE; case 4: return SERVICETYPE_TE; + case 5: return SERVICETYPE_E2E; default: return null; } } @@ -75826,114 +75853,116 @@ public final class ContextOuterClass { "\0132\022.context.ContextId\022\025\n\rauthenticated\030\002" + " \001(\010*j\n\rEventTypeEnum\022\027\n\023EVENTTYPE_UNDEF" + "INED\020\000\022\024\n\020EVENTTYPE_CREATE\020\001\022\024\n\020EVENTTYP" + - "E_UPDATE\020\002\022\024\n\020EVENTTYPE_REMOVE\020\003*\231\002\n\020Dev" + + "E_UPDATE\020\002\022\024\n\020EVENTTYPE_REMOVE\020\003*\321\002\n\020Dev" + "iceDriverEnum\022\032\n\026DEVICEDRIVER_UNDEFINED\020" + "\000\022\033\n\027DEVICEDRIVER_OPENCONFIG\020\001\022\036\n\032DEVICE" + "DRIVER_TRANSPORT_API\020\002\022\023\n\017DEVICEDRIVER_P" + "4\020\003\022&\n\"DEVICEDRIVER_IETF_NETWORK_TOPOLOG" + "Y\020\004\022\033\n\027DEVICEDRIVER_ONF_TR_532\020\005\022\023\n\017DEVI" + "CEDRIVER_XR\020\006\022\033\n\027DEVICEDRIVER_IETF_L2VPN" + - "\020\007\022 \n\034DEVICEDRIVER_GNMI_OPENCONFIG\020\010*\217\001\n" + - "\033DeviceOperationalStatusEnum\022%\n!DEVICEOP" + - "ERATIONALSTATUS_UNDEFINED\020\000\022$\n DEVICEOPE" + - "RATIONALSTATUS_DISABLED\020\001\022#\n\037DEVICEOPERA" + - "TIONALSTATUS_ENABLED\020\002*\225\001\n\017ServiceTypeEn" + - "um\022\027\n\023SERVICETYPE_UNKNOWN\020\000\022\024\n\020SERVICETY" + - "PE_L3NM\020\001\022\024\n\020SERVICETYPE_L2NM\020\002\022)\n%SERVI" + - "CETYPE_TAPI_CONNECTIVITY_SERVICE\020\003\022\022\n\016SE" + - "RVICETYPE_TE\020\004*\304\001\n\021ServiceStatusEnum\022\033\n\027" + - "SERVICESTATUS_UNDEFINED\020\000\022\031\n\025SERVICESTAT" + - "US_PLANNED\020\001\022\030\n\024SERVICESTATUS_ACTIVE\020\002\022\032" + - "\n\026SERVICESTATUS_UPDATING\020\003\022!\n\035SERVICESTA" + - "TUS_PENDING_REMOVAL\020\004\022\036\n\032SERVICESTATUS_S" + - "LA_VIOLATED\020\005*\251\001\n\017SliceStatusEnum\022\031\n\025SLI" + - "CESTATUS_UNDEFINED\020\000\022\027\n\023SLICESTATUS_PLAN" + - "NED\020\001\022\024\n\020SLICESTATUS_INIT\020\002\022\026\n\022SLICESTAT" + - "US_ACTIVE\020\003\022\026\n\022SLICESTATUS_DEINIT\020\004\022\034\n\030S" + - "LICESTATUS_SLA_VIOLATED\020\005*]\n\020ConfigActio" + - "nEnum\022\032\n\026CONFIGACTION_UNDEFINED\020\000\022\024\n\020CON" + - "FIGACTION_SET\020\001\022\027\n\023CONFIGACTION_DELETE\020\002" + - "*m\n\024ConstraintActionEnum\022\036\n\032CONSTRAINTAC" + - "TION_UNDEFINED\020\000\022\030\n\024CONSTRAINTACTION_SET" + - "\020\001\022\033\n\027CONSTRAINTACTION_DELETE\020\002*\203\002\n\022Isol" + - "ationLevelEnum\022\020\n\014NO_ISOLATION\020\000\022\026\n\022PHYS" + - "ICAL_ISOLATION\020\001\022\025\n\021LOGICAL_ISOLATION\020\002\022" + - "\025\n\021PROCESS_ISOLATION\020\003\022\035\n\031PHYSICAL_MEMOR" + - "Y_ISOLATION\020\004\022\036\n\032PHYSICAL_NETWORK_ISOLAT" + - "ION\020\005\022\036\n\032VIRTUAL_RESOURCE_ISOLATION\020\006\022\037\n" + - "\033NETWORK_FUNCTIONS_ISOLATION\020\007\022\025\n\021SERVIC" + - "E_ISOLATION\020\0102\245\026\n\016ContextService\022:\n\016List" + - "ContextIds\022\016.context.Empty\032\026.context.Con" + - "textIdList\"\000\0226\n\014ListContexts\022\016.context.E" + - "mpty\032\024.context.ContextList\"\000\0224\n\nGetConte" + - "xt\022\022.context.ContextId\032\020.context.Context" + - "\"\000\0224\n\nSetContext\022\020.context.Context\032\022.con" + - "text.ContextId\"\000\0225\n\rRemoveContext\022\022.cont" + - "ext.ContextId\032\016.context.Empty\"\000\022=\n\020GetCo" + - "ntextEvents\022\016.context.Empty\032\025.context.Co" + - "ntextEvent\"\0000\001\022@\n\017ListTopologyIds\022\022.cont" + - "ext.ContextId\032\027.context.TopologyIdList\"\000" + - "\022=\n\016ListTopologies\022\022.context.ContextId\032\025" + - ".context.TopologyList\"\000\0227\n\013GetTopology\022\023" + - ".context.TopologyId\032\021.context.Topology\"\000" + - "\022E\n\022GetTopologyDetails\022\023.context.Topolog" + - "yId\032\030.context.TopologyDetails\"\000\0227\n\013SetTo" + - "pology\022\021.context.Topology\032\023.context.Topo" + - "logyId\"\000\0227\n\016RemoveTopology\022\023.context.Top" + - "ologyId\032\016.context.Empty\"\000\022?\n\021GetTopology" + - "Events\022\016.context.Empty\032\026.context.Topolog" + - "yEvent\"\0000\001\0228\n\rListDeviceIds\022\016.context.Em" + - "pty\032\025.context.DeviceIdList\"\000\0224\n\013ListDevi" + - "ces\022\016.context.Empty\032\023.context.DeviceList" + - "\"\000\0221\n\tGetDevice\022\021.context.DeviceId\032\017.con" + - "text.Device\"\000\0221\n\tSetDevice\022\017.context.Dev" + - "ice\032\021.context.DeviceId\"\000\0223\n\014RemoveDevice" + - "\022\021.context.DeviceId\032\016.context.Empty\"\000\022;\n" + - "\017GetDeviceEvents\022\016.context.Empty\032\024.conte" + - "xt.DeviceEvent\"\0000\001\022<\n\014SelectDevice\022\025.con" + - "text.DeviceFilter\032\023.context.DeviceList\"\000" + - "\022I\n\021ListEndPointNames\022\027.context.EndPoint" + - "IdList\032\031.context.EndPointNameList\"\000\0224\n\013L" + - "istLinkIds\022\016.context.Empty\032\023.context.Lin" + - "kIdList\"\000\0220\n\tListLinks\022\016.context.Empty\032\021" + - ".context.LinkList\"\000\022+\n\007GetLink\022\017.context" + - ".LinkId\032\r.context.Link\"\000\022+\n\007SetLink\022\r.co" + - "ntext.Link\032\017.context.LinkId\"\000\022/\n\nRemoveL" + - "ink\022\017.context.LinkId\032\016.context.Empty\"\000\0227" + - "\n\rGetLinkEvents\022\016.context.Empty\032\022.contex" + - "t.LinkEvent\"\0000\001\022>\n\016ListServiceIds\022\022.cont" + - "ext.ContextId\032\026.context.ServiceIdList\"\000\022" + - ":\n\014ListServices\022\022.context.ContextId\032\024.co" + - "ntext.ServiceList\"\000\0224\n\nGetService\022\022.cont" + - "ext.ServiceId\032\020.context.Service\"\000\0224\n\nSet" + - "Service\022\020.context.Service\032\022.context.Serv" + - "iceId\"\000\0226\n\014UnsetService\022\020.context.Servic" + - "e\032\022.context.ServiceId\"\000\0225\n\rRemoveService" + - "\022\022.context.ServiceId\032\016.context.Empty\"\000\022=" + - "\n\020GetServiceEvents\022\016.context.Empty\032\025.con" + - "text.ServiceEvent\"\0000\001\022?\n\rSelectService\022\026" + - ".context.ServiceFilter\032\024.context.Service" + - "List\"\000\022:\n\014ListSliceIds\022\022.context.Context" + - "Id\032\024.context.SliceIdList\"\000\0226\n\nListSlices" + - "\022\022.context.ContextId\032\022.context.SliceList" + - "\"\000\022.\n\010GetSlice\022\020.context.SliceId\032\016.conte" + - "xt.Slice\"\000\022.\n\010SetSlice\022\016.context.Slice\032\020" + - ".context.SliceId\"\000\0220\n\nUnsetSlice\022\016.conte" + - "xt.Slice\032\020.context.SliceId\"\000\0221\n\013RemoveSl" + - "ice\022\020.context.SliceId\032\016.context.Empty\"\000\022" + - "9\n\016GetSliceEvents\022\016.context.Empty\032\023.cont" + - "ext.SliceEvent\"\0000\001\0229\n\013SelectSlice\022\024.cont" + - "ext.SliceFilter\032\022.context.SliceList\"\000\022D\n" + - "\021ListConnectionIds\022\022.context.ServiceId\032\031" + - ".context.ConnectionIdList\"\000\022@\n\017ListConne" + - "ctions\022\022.context.ServiceId\032\027.context.Con" + - "nectionList\"\000\022=\n\rGetConnection\022\025.context" + - ".ConnectionId\032\023.context.Connection\"\000\022=\n\r" + - "SetConnection\022\023.context.Connection\032\025.con" + - "text.ConnectionId\"\000\022;\n\020RemoveConnection\022" + - "\025.context.ConnectionId\032\016.context.Empty\"\000" + - "\022C\n\023GetConnectionEvents\022\016.context.Empty\032" + - "\030.context.ConnectionEvent\"\0000\001b\006proto3" + "\020\007\022 \n\034DEVICEDRIVER_GNMI_OPENCONFIG\020\010\022\032\n\026" + + "DEVICEDRIVER_FLEXSCALE\020\t\022\032\n\026DEVICEDRIVER" + + "_IETF_ACTN\020\n*\217\001\n\033DeviceOperationalStatus" + + "Enum\022%\n!DEVICEOPERATIONALSTATUS_UNDEFINE" + + "D\020\000\022$\n DEVICEOPERATIONALSTATUS_DISABLED\020" + + "\001\022#\n\037DEVICEOPERATIONALSTATUS_ENABLED\020\002*\252" + + "\001\n\017ServiceTypeEnum\022\027\n\023SERVICETYPE_UNKNOW" + + "N\020\000\022\024\n\020SERVICETYPE_L3NM\020\001\022\024\n\020SERVICETYPE" + + "_L2NM\020\002\022)\n%SERVICETYPE_TAPI_CONNECTIVITY" + + "_SERVICE\020\003\022\022\n\016SERVICETYPE_TE\020\004\022\023\n\017SERVIC" + + "ETYPE_E2E\020\005*\304\001\n\021ServiceStatusEnum\022\033\n\027SER" + + "VICESTATUS_UNDEFINED\020\000\022\031\n\025SERVICESTATUS_" + + "PLANNED\020\001\022\030\n\024SERVICESTATUS_ACTIVE\020\002\022\032\n\026S" + + "ERVICESTATUS_UPDATING\020\003\022!\n\035SERVICESTATUS" + + "_PENDING_REMOVAL\020\004\022\036\n\032SERVICESTATUS_SLA_" + + "VIOLATED\020\005*\251\001\n\017SliceStatusEnum\022\031\n\025SLICES" + + "TATUS_UNDEFINED\020\000\022\027\n\023SLICESTATUS_PLANNED" + + "\020\001\022\024\n\020SLICESTATUS_INIT\020\002\022\026\n\022SLICESTATUS_" + + "ACTIVE\020\003\022\026\n\022SLICESTATUS_DEINIT\020\004\022\034\n\030SLIC" + + "ESTATUS_SLA_VIOLATED\020\005*]\n\020ConfigActionEn" + + "um\022\032\n\026CONFIGACTION_UNDEFINED\020\000\022\024\n\020CONFIG" + + "ACTION_SET\020\001\022\027\n\023CONFIGACTION_DELETE\020\002*m\n" + + "\024ConstraintActionEnum\022\036\n\032CONSTRAINTACTIO" + + "N_UNDEFINED\020\000\022\030\n\024CONSTRAINTACTION_SET\020\001\022" + + "\033\n\027CONSTRAINTACTION_DELETE\020\002*\203\002\n\022Isolati" + + "onLevelEnum\022\020\n\014NO_ISOLATION\020\000\022\026\n\022PHYSICA" + + "L_ISOLATION\020\001\022\025\n\021LOGICAL_ISOLATION\020\002\022\025\n\021" + + "PROCESS_ISOLATION\020\003\022\035\n\031PHYSICAL_MEMORY_I" + + "SOLATION\020\004\022\036\n\032PHYSICAL_NETWORK_ISOLATION" + + "\020\005\022\036\n\032VIRTUAL_RESOURCE_ISOLATION\020\006\022\037\n\033NE" + + "TWORK_FUNCTIONS_ISOLATION\020\007\022\025\n\021SERVICE_I" + + "SOLATION\020\0102\245\026\n\016ContextService\022:\n\016ListCon" + + "textIds\022\016.context.Empty\032\026.context.Contex" + + "tIdList\"\000\0226\n\014ListContexts\022\016.context.Empt" + + "y\032\024.context.ContextList\"\000\0224\n\nGetContext\022" + + "\022.context.ContextId\032\020.context.Context\"\000\022" + + "4\n\nSetContext\022\020.context.Context\032\022.contex" + + "t.ContextId\"\000\0225\n\rRemoveContext\022\022.context" + + ".ContextId\032\016.context.Empty\"\000\022=\n\020GetConte" + + "xtEvents\022\016.context.Empty\032\025.context.Conte" + + "xtEvent\"\0000\001\022@\n\017ListTopologyIds\022\022.context" + + ".ContextId\032\027.context.TopologyIdList\"\000\022=\n" + + "\016ListTopologies\022\022.context.ContextId\032\025.co" + + "ntext.TopologyList\"\000\0227\n\013GetTopology\022\023.co" + + "ntext.TopologyId\032\021.context.Topology\"\000\022E\n" + + "\022GetTopologyDetails\022\023.context.TopologyId" + + "\032\030.context.TopologyDetails\"\000\0227\n\013SetTopol" + + "ogy\022\021.context.Topology\032\023.context.Topolog" + + "yId\"\000\0227\n\016RemoveTopology\022\023.context.Topolo" + + "gyId\032\016.context.Empty\"\000\022?\n\021GetTopologyEve" + + "nts\022\016.context.Empty\032\026.context.TopologyEv" + + "ent\"\0000\001\0228\n\rListDeviceIds\022\016.context.Empty" + + "\032\025.context.DeviceIdList\"\000\0224\n\013ListDevices" + + "\022\016.context.Empty\032\023.context.DeviceList\"\000\022" + + "1\n\tGetDevice\022\021.context.DeviceId\032\017.contex" + + "t.Device\"\000\0221\n\tSetDevice\022\017.context.Device" + + "\032\021.context.DeviceId\"\000\0223\n\014RemoveDevice\022\021." + + "context.DeviceId\032\016.context.Empty\"\000\022;\n\017Ge" + + "tDeviceEvents\022\016.context.Empty\032\024.context." + + "DeviceEvent\"\0000\001\022<\n\014SelectDevice\022\025.contex" + + "t.DeviceFilter\032\023.context.DeviceList\"\000\022I\n" + + "\021ListEndPointNames\022\027.context.EndPointIdL" + + "ist\032\031.context.EndPointNameList\"\000\0224\n\013List" + + "LinkIds\022\016.context.Empty\032\023.context.LinkId" + + "List\"\000\0220\n\tListLinks\022\016.context.Empty\032\021.co" + + "ntext.LinkList\"\000\022+\n\007GetLink\022\017.context.Li" + + "nkId\032\r.context.Link\"\000\022+\n\007SetLink\022\r.conte" + + "xt.Link\032\017.context.LinkId\"\000\022/\n\nRemoveLink" + + "\022\017.context.LinkId\032\016.context.Empty\"\000\0227\n\rG" + + "etLinkEvents\022\016.context.Empty\032\022.context.L" + + "inkEvent\"\0000\001\022>\n\016ListServiceIds\022\022.context" + + ".ContextId\032\026.context.ServiceIdList\"\000\022:\n\014" + + "ListServices\022\022.context.ContextId\032\024.conte" + + "xt.ServiceList\"\000\0224\n\nGetService\022\022.context" + + ".ServiceId\032\020.context.Service\"\000\0224\n\nSetSer" + + "vice\022\020.context.Service\032\022.context.Service" + + "Id\"\000\0226\n\014UnsetService\022\020.context.Service\032\022" + + ".context.ServiceId\"\000\0225\n\rRemoveService\022\022." + + "context.ServiceId\032\016.context.Empty\"\000\022=\n\020G" + + "etServiceEvents\022\016.context.Empty\032\025.contex" + + "t.ServiceEvent\"\0000\001\022?\n\rSelectService\022\026.co" + + "ntext.ServiceFilter\032\024.context.ServiceLis" + + "t\"\000\022:\n\014ListSliceIds\022\022.context.ContextId\032" + + "\024.context.SliceIdList\"\000\0226\n\nListSlices\022\022." + + "context.ContextId\032\022.context.SliceList\"\000\022" + + ".\n\010GetSlice\022\020.context.SliceId\032\016.context." + + "Slice\"\000\022.\n\010SetSlice\022\016.context.Slice\032\020.co" + + "ntext.SliceId\"\000\0220\n\nUnsetSlice\022\016.context." + + "Slice\032\020.context.SliceId\"\000\0221\n\013RemoveSlice" + + "\022\020.context.SliceId\032\016.context.Empty\"\000\0229\n\016" + + "GetSliceEvents\022\016.context.Empty\032\023.context" + + ".SliceEvent\"\0000\001\0229\n\013SelectSlice\022\024.context" + + ".SliceFilter\032\022.context.SliceList\"\000\022D\n\021Li" + + "stConnectionIds\022\022.context.ServiceId\032\031.co" + + "ntext.ConnectionIdList\"\000\022@\n\017ListConnecti" + + "ons\022\022.context.ServiceId\032\027.context.Connec" + + "tionList\"\000\022=\n\rGetConnection\022\025.context.Co" + + "nnectionId\032\023.context.Connection\"\000\022=\n\rSet" + + "Connection\022\023.context.Connection\032\025.contex" + + "t.ConnectionId\"\000\022;\n\020RemoveConnection\022\025.c" + + "ontext.ConnectionId\032\016.context.Empty\"\000\022C\n" + + "\023GetConnectionEvents\022\016.context.Empty\032\030.c" + + "ontext.ConnectionEvent\"\0000\001b\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, diff --git a/src/policy/target/kubernetes/kubernetes.yml b/src/policy/target/kubernetes/kubernetes.yml index 5cd1f1c4c1b4ce437c16707018f83ef6ec60215a..55847f89e7c031de854d1e54336342f7a24e320a 100644 --- a/src/policy/target/kubernetes/kubernetes.yml +++ b/src/policy/target/kubernetes/kubernetes.yml @@ -3,8 +3,8 @@ apiVersion: v1 kind: Service metadata: annotations: - app.quarkus.io/commit-id: 46486023929121fc955e9550fc8fd625ded433d2 - app.quarkus.io/build-timestamp: 2023-12-15 - 11:56:20 +0000 + app.quarkus.io/commit-id: 5f8866be9cb91871607627819258b0b375410467 + app.quarkus.io/build-timestamp: 2024-01-26 - 16:40:15 +0000 prometheus.io/scrape: "true" prometheus.io/path: /q/metrics prometheus.io/port: "8080" @@ -15,12 +15,12 @@ metadata: name: policyservice spec: ports: - - name: grpc-server - port: 6060 - targetPort: 6060 - name: http port: 9192 targetPort: 8080 + - name: grpc-server + port: 6060 + targetPort: 6060 selector: app.kubernetes.io/name: policyservice type: ClusterIP @@ -29,8 +29,8 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - app.quarkus.io/commit-id: 46486023929121fc955e9550fc8fd625ded433d2 - app.quarkus.io/build-timestamp: 2023-12-15 - 11:56:20 +0000 + app.quarkus.io/commit-id: 5f8866be9cb91871607627819258b0b375410467 + app.quarkus.io/build-timestamp: 2024-01-26 - 16:40:15 +0000 prometheus.io/scrape: "true" prometheus.io/path: /q/metrics prometheus.io/port: "8080" @@ -47,8 +47,8 @@ spec: template: metadata: annotations: - app.quarkus.io/commit-id: 46486023929121fc955e9550fc8fd625ded433d2 - app.quarkus.io/build-timestamp: 2023-12-15 - 11:56:20 +0000 + app.quarkus.io/commit-id: 5f8866be9cb91871607627819258b0b375410467 + app.quarkus.io/build-timestamp: 2024-01-26 - 16:40:15 +0000 prometheus.io/scrape: "true" prometheus.io/path: /q/metrics prometheus.io/port: "8080" @@ -63,12 +63,12 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - - name: CONTEXT_SERVICE_HOST - value: contextservice - - name: MONITORING_SERVICE_HOST - value: monitoringservice - name: SERVICE_SERVICE_HOST value: serviceservice + - name: MONITORING_SERVICE_HOST + value: monitoringservice + - name: CONTEXT_SERVICE_HOST + value: contextservice image: labs.etsi.org:5050/tfs/controller/policy:0.1.0 imagePullPolicy: Always livenessProbe: @@ -83,12 +83,12 @@ spec: timeoutSeconds: 10 name: policyservice ports: - - containerPort: 6060 - name: grpc-server - protocol: TCP - containerPort: 8080 name: http protocol: TCP + - containerPort: 6060 + name: grpc-server + protocol: TCP readinessProbe: failureThreshold: 3 httpGet: diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index 35c45c99699b5bc640639cde3054ef72bbb6de50..e771e24f17b2ea97c61158b65cb62c87ee9f37c5 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -39,6 +39,7 @@ DEVICE_DRIVER_VALUES = { DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN, DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG, DeviceDriverEnum.DEVICEDRIVER_FLEXSCALE, + DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN, } # Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/Dockerfile b/src/tests/tools/mock_ietf_actn_sdn_ctrl/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..05c785fb1a353db544d58d0953bc1cc99881a693 --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/Dockerfile @@ -0,0 +1,37 @@ +# 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. + +FROM python:3.9-slim + +# Set Python to show logs as they occur +ENV PYTHONUNBUFFERED=0 + +# Get generic Python packages +RUN python3 -m pip install --upgrade pip +RUN python3 -m pip install --upgrade setuptools wheel +RUN python3 -m pip install --upgrade pip-tools + +# Create component sub-folders, and copy content +RUN mkdir -p /var/teraflow/mock_ietf_actn_sdn_ctrl +WORKDIR /var/teraflow/mock_ietf_actn_sdn_ctrl +COPY . . + +# Get specific Python packages +RUN pip-compile --quiet --output-file=requirements.txt requirements.in +RUN python3 -m pip install -r requirements.txt + +RUN python3 -m pip list + +# Start the service +ENTRYPOINT ["python", "MockIetfActnSdnCtrl.py"] diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py b/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py new file mode 100644 index 0000000000000000000000000000000000000000..c459c294ccb1c457c258a4e80018a90244702b8b --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py @@ -0,0 +1,80 @@ +# 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. + +# Mock IETF ACTN SDN controller +# ----------------------------- +# REST server implementing minimal support for: +# - IETF YANG Data Model for Transport Network Client Signals +# Ref: https://www.ietf.org/archive/id/draft-ietf-ccamp-client-signal-yang-10.html +# - IETF YANG Data Model for Traffic Engineering Tunnels, Label Switched Paths and Interfaces +# Ref: https://www.ietf.org/archive/id/draft-ietf-teas-yang-te-34.html + + +import functools, logging, sys, time +from flask import Flask, jsonify, make_response, request +from flask_restful import Api, Resource +from ResourceEthServices import EthService, EthServices +from ResourceOsuTunnels import OsuTunnel, OsuTunnels + +BIND_ADDRESS = '0.0.0.0' +BIND_PORT = 8443 +BASE_URL = '/restconf/data' +STR_ENDPOINT = 'https://{:s}:{:s}{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT), str(BASE_URL)) +LOG_LEVEL = logging.DEBUG + +logging.basicConfig(level=LOG_LEVEL, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") +LOGGER = logging.getLogger(__name__) + +logging.getLogger('werkzeug').setLevel(logging.WARNING) + +def log_request(logger : logging.Logger, response): + timestamp = time.strftime('[%Y-%b-%d %H:%M]') + logger.info('%s %s %s %s %s', timestamp, request.remote_addr, request.method, request.full_path, response.status) + return response + +class Health(Resource): + def get(self): + return make_response(jsonify({}), 200) + +def main(): + LOGGER.info('Starting...') + + app = Flask(__name__) + app.after_request(functools.partial(log_request, LOGGER)) + + api = Api(app, prefix=BASE_URL) + api.add_resource( + Health, '/' + ) + api.add_resource( + OsuTunnels, '/ietf-te:te/tunnels' + ) + api.add_resource( + OsuTunnel, '/ietf-te:te/tunnels/tunnel="<string:osu_tunnel_name>"' + ) + api.add_resource( + EthServices, '/ietf-eth-tran-service:etht-svc' + ) + api.add_resource( + EthService, '/ietf-eth-tran-service:etht-svc/etht-svc-instances="<string:etht_service_name>"' + ) + + LOGGER.info('Listening on {:s}...'.format(str(STR_ENDPOINT))) + app.run(debug=True, host=BIND_ADDRESS, port=BIND_PORT, ssl_context='adhoc') + + LOGGER.info('Bye') + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/README.md b/src/tests/tools/mock_ietf_actn_sdn_ctrl/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a12ae907e792f98413903b6637738c392506be4a --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/README.md @@ -0,0 +1,53 @@ +# Mock IETF ACTN SDN Controller + +This REST server implements very basic support for the following YANG data models: +- IETF YANG Data Model for Transport Network Client Signals (draft-ietf-ccamp-client-signal-yang-10) + - Ref: https://datatracker.ietf.org/doc/draft-ietf-ccamp-client-signal-yang/ +- IETF YANG Data Model for Traffic Engineering Tunnels, Label Switched Paths and Interfaces (draft-ietf-teas-yang-te-34) + - Ref: https://datatracker.ietf.org/doc/draft-ietf-teas-yang-te/ + +The aim of this server is to enable testing the IetfActnDeviceDriver and the IetfActnServiceHandler. +Follow the steps below to perform the test: + +## 1. Deploy TeraFlowSDN controller and the scenario +Deploy the test scenario "ietf_actn_deploy.sh": +```bash +source src/tests/tools/mock_ietf_actn_sdn_ctrl/scenario/ietf_actn_deploy.sh +./deploy/all.sh +``` + +## 2. Install requirements and run the Mock IETF ACTN SDN controller +__NOTE__: if you run the Mock IETF ACTN SDN controller from the PyEnv used for developping on the TeraFlowSDN framework, +all the requirements are already in place. Install them only if you execute it in a separate/standalone environment. + +Install the required dependencies as follows: +```bash +pip install Flask==2.1.3 Flask-RESTful==0.3.9 +``` + +Run the Mock IETF ACTN SDN Controller as follows: +```bash +python src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py +``` + +## 3. Deploy the test descriptors +Edit the descriptors to meet your environment specifications. +Edit "network_descriptors.json" and change IP address and port of the IETF ACTN SDN controller of the "ACTN" device. +- Set value of config rule "_connect/address" to the address of the host where the Mock IETF ACTN SDN controller is + running (default="192.168.1.1"). +- Set value of config rule "_connect/port" to the port where your Mock IETF ACTN SDN controller is listening on + (default="8443"). + +Upload the "network_descriptors.json" through the TeraFlowSDN WebUI. +- If not already selected, select Context(admin)/Topology(admin). +- Check that a network topology with 4 routers + 1 IETF ACTN radio system are loaded. They should form 2 rings. + +Upload the "service_descriptor.json" through the TeraFlowSDN WebUI. +- Check that 2 services have been created. +- The "actn-svc" should have a connection and be supported by a sub-service. +- The sub-service should also have a connection. +- The R1, R3, and MW devices should have configuration rules established. + +# 4. Delete the IETF ACTN service +Find the "mw-svc" on the WebUI, navigate to its details, and delete the service pressing the "Delete Service" button. +The service, sub-service, and device configuration rules should be removed. diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceEthServices.py b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceEthServices.py new file mode 100644 index 0000000000000000000000000000000000000000..0e08bbdaa48df2ae7338a96d727fdc9f876784e6 --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceEthServices.py @@ -0,0 +1,53 @@ +# 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. + +# REST-API resource implementing minimal support for "IETF YANG Data Model for Transport Network Client Signals". +# Ref: https://www.ietf.org/archive/id/draft-ietf-ccamp-client-signal-yang-10.html + +from flask import abort, jsonify, make_response, request +from flask_restful import Resource + +ETHT_SERVICES = {} + +class EthServices(Resource): + def get(self): + etht_services = [etht_service for etht_service in ETHT_SERVICES.values()] + data = {'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': etht_services}} + return make_response(jsonify(data), 200) + + def post(self): + json_request = request.get_json() + if not json_request: abort(400) + if not isinstance(json_request, dict): abort(400) + if 'ietf-eth-tran-service:etht-svc' not in json_request: abort(400) + json_request = json_request['ietf-eth-tran-service:etht-svc'] + if 'etht-svc-instances' not in json_request: abort(400) + etht_services = json_request['etht-svc-instances'] + if not isinstance(etht_services, list): abort(400) + if len(etht_services) != 1: abort(400) + etht_service = etht_services[0] + etht_service_name = etht_service['etht-svc-name'] + ETHT_SERVICES[etht_service_name] = etht_service + return make_response(jsonify({}), 201) + +class EthService(Resource): + def get(self, etht_service_name : str): + etht_service = ETHT_SERVICES.get(etht_service_name, None) + data,status = ({}, 404) if etht_service is None else (etht_service, 200) + return make_response(jsonify(data), status) + + def delete(self, etht_service_name : str): + etht_service = ETHT_SERVICES.pop(etht_service_name, None) + data,status = ({}, 404) if etht_service is None else (etht_service, 204) + return make_response(jsonify(data), status) diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceOsuTunnels.py b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceOsuTunnels.py new file mode 100644 index 0000000000000000000000000000000000000000..914f7096da4101a138ac9b0cdd2911dfcf22bb61 --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceOsuTunnels.py @@ -0,0 +1,53 @@ +# 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. + +# REST-API resource implementing minimal support for "IETF YANG Data Model for Traffic Engineering Tunnels, +# Label Switched Paths and Interfaces". +# Ref: https://www.ietf.org/archive/id/draft-ietf-teas-yang-te-34.html + + +from flask import abort, jsonify, make_response, request +from flask_restful import Resource + +OSU_TUNNELS = {} + +class OsuTunnels(Resource): + def get(self): + osu_tunnels = [osu_tunnel for osu_tunnel in OSU_TUNNELS.values()] + data = {'ietf-te:tunnel': osu_tunnels} + return make_response(jsonify(data), 200) + + def post(self): + json_request = request.get_json() + if not json_request: abort(400) + if not isinstance(json_request, dict): abort(400) + if 'ietf-te:tunnel' not in json_request: abort(400) + osu_tunnels = json_request['ietf-te:tunnel'] + if not isinstance(osu_tunnels, list): abort(400) + if len(osu_tunnels) != 1: abort(400) + osu_tunnel = osu_tunnels[0] + osu_tunnel_name = osu_tunnel['name'] + OSU_TUNNELS[osu_tunnel_name] = osu_tunnel + return make_response(jsonify({}), 201) + +class OsuTunnel(Resource): + def get(self, osu_tunnel_name : str): + osu_tunnel = OSU_TUNNELS.get(osu_tunnel_name, None) + data,status = ({}, 404) if osu_tunnel is None else (osu_tunnel, 200) + return make_response(jsonify(data), status) + + def delete(self, osu_tunnel_name : str): + osu_tunnel = OSU_TUNNELS.pop(osu_tunnel_name, None) + data,status = ({}, 404) if osu_tunnel is None else (osu_tunnel, 204) + return make_response(jsonify(data), status) diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/__init__.py b/src/tests/tools/mock_ietf_actn_sdn_ctrl/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/__init__.py @@ -0,0 +1,14 @@ +# 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. + diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/build.sh b/src/tests/tools/mock_ietf_actn_sdn_ctrl/build.sh new file mode 100755 index 0000000000000000000000000000000000000000..d9db334cbf1d361cc9cbce42f95fb10bafc609ed --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/build.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# 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. + +docker build -t mock-ietf-actn-sdn-ctrl:test -f Dockerfile . +docker tag mock-ietf-actn-sdn-ctrl:test localhost:32000/tfs/mock-ietf-actn-sdn-ctrl:test +docker push localhost:32000/tfs/mock-ietf-actn-sdn-ctrl:test diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/deploy.sh b/src/tests/tools/mock_ietf_actn_sdn_ctrl/deploy.sh new file mode 100755 index 0000000000000000000000000000000000000000..47270dc4beafc3828b69ca77eed443f2e16528a5 --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/deploy.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# 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. + +kubectl delete namespace mocks +kubectl --namespace mocks apply -f mock-ietf-actn-sdn-ctrl.yaml diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/mock-ietf-actn-sdn-ctrl.yaml b/src/tests/tools/mock_ietf_actn_sdn_ctrl/mock-ietf-actn-sdn-ctrl.yaml new file mode 100644 index 0000000000000000000000000000000000000000..32cfd922896f6e8860ae07049657b97b0da90c3a --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/mock-ietf-actn-sdn-ctrl.yaml @@ -0,0 +1,64 @@ +# 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. + +kind: Namespace +apiVersion: v1 +metadata: + name: mocks +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mock-ietf-actn-sdn-ctrl +spec: + selector: + matchLabels: + app: mock-ietf-actn-sdn-ctrl + replicas: 1 + template: + metadata: + annotations: + config.linkerd.io/skip-inbound-ports: "8443" + labels: + app: mock-ietf-actn-sdn-ctrl + spec: + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: localhost:32000/tfs/mock-ietf-actn-sdn-ctrl:test + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8443 + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: 700m + memory: 1024Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: mock-ietf-actn-sdn-ctrl + labels: + app: mock-ietf-actn-sdn-ctrl +spec: + type: ClusterIP + selector: + app: mock-ietf-actn-sdn-ctrl + ports: + - name: https + port: 8443 + targetPort: 8443 diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/requirements.in b/src/tests/tools/mock_ietf_actn_sdn_ctrl/requirements.in new file mode 100644 index 0000000000000000000000000000000000000000..d91775403366e93a4612296faa6a7d5fa527249a --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/requirements.in @@ -0,0 +1,22 @@ +# 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. + +cryptography==39.0.1 +pyopenssl==23.0.0 +Flask==2.1.3 +Flask-HTTPAuth==4.5.0 +Flask-RESTful==0.3.9 +jsonschema==4.4.0 +requests==2.27.1 +werkzeug==2.3.7 diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/run.sh b/src/tests/tools/mock_ietf_actn_sdn_ctrl/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..2697e538ec69a99da4c0fae898748ff496b5d28f --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/run.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# 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. + +python MockIetfActnSdnCtrl.py diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/build.sh b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/build.sh index 4df315cec178cef13eaa059a739bc22efc011d4d..5501614c4fd5079d98b81e7ec78bf56b581c888a 100755 --- a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/build.sh +++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/build.sh @@ -1,4 +1,17 @@ #!/bin/bash +# 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. docker build -t mock-mw-sdn-ctrl:test -f Dockerfile . docker tag mock-mw-sdn-ctrl:test localhost:32000/tfs/mock-mw-sdn-ctrl:test diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/deploy.sh b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/deploy.sh index ded232e5c50f8cd5ed448ec0193f58c43626f4ad..ed77bcfbc1200609aa6a6effeb13e723d9f9979e 100755 --- a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/deploy.sh +++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/deploy.sh @@ -1,4 +1,17 @@ #!/bin/bash +# 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. kubectl delete namespace mocks kubectl --namespace mocks apply -f mock-mw-sdn-ctrl.yaml diff --git a/src/webui/service/device/forms.py b/src/webui/service/device/forms.py index bcd5804d32927763344d08371320fdde5f2fcab7..4c04bbfe12d8d39e51bd8275021064a5a7ad4fc3 100644 --- a/src/webui/service/device/forms.py +++ b/src/webui/service/device/forms.py @@ -31,6 +31,8 @@ class AddDeviceForm(FlaskForm): device_drivers_xr = BooleanField('XR') device_drivers_ietf_l2vpn = BooleanField('IETF L2VPN') device_drivers_gnmi_openconfig = BooleanField('GNMI OPENCONFIG') + device_drivers_flexscale = BooleanField('FLEXSCALE') + device_drivers_ietf_actn = BooleanField('IETF ACTN') device_config_address = StringField('connect/address',default='127.0.0.1',validators=[DataRequired(), Length(min=5)]) device_config_port = StringField('connect/port',default='0',validators=[DataRequired(), Length(min=1)]) diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py index 2106623870add4693bc8ca7312a6c33a462f1eaf..8353fb8b772900b4197f52ce4b08f5649785de1b 100644 --- a/src/webui/service/device/routes.py +++ b/src/webui/service/device/routes.py @@ -126,6 +126,10 @@ def add(): device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN) if form.device_drivers_gnmi_openconfig.data: device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG) + if form.device_drivers_flexscale.data: + device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_FLEXSCALE) + if form.device_drivers_ietf_actn.data: + device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN) device_obj.device_drivers.extend(device_drivers) # pylint: disable=no-member try: diff --git a/src/webui/service/templates/device/add.html b/src/webui/service/templates/device/add.html index c115657aa08828849172345ca50caaeb4150fe0f..c4d7f16858b7c63bd87e730cff6d586dc702e0c9 100644 --- a/src/webui/service/templates/device/add.html +++ b/src/webui/service/templates/device/add.html @@ -92,6 +92,9 @@ {{ form.device_drivers_xr }} {{ form.device_drivers_xr.label(class="col-sm-3 col-form-label") }} {{ form.device_drivers_ietf_l2vpn }} {{ form.device_drivers_ietf_l2vpn.label(class="col-sm-3 col-form-label") }} {{ form.device_drivers_gnmi_openconfig }} {{ form.device_drivers_gnmi_openconfig.label(class="col-sm-3 col-form-label") }} + <br /> + {{ form.device_drivers_flexscale }} {{ form.device_drivers_flexscale.label(class="col-sm-3 col-form-label") }} + {{ form.device_drivers_ietf_actn }} {{ form.device_drivers_ietf_actn.label(class="col-sm-3 col-form-label") }} {% endif %} </div> </div> diff --git a/src/ztp/src/main/java/org/etsi/tfs/ztp/Serializer.java b/src/ztp/src/main/java/org/etsi/tfs/ztp/Serializer.java index cf49280a856e5fb7f4afaef394b565b02e44a8c2..feb65b77c9f45c760474f5e25a82b68eac8a7a01 100644 --- a/src/ztp/src/main/java/org/etsi/tfs/ztp/Serializer.java +++ b/src/ztp/src/main/java/org/etsi/tfs/ztp/Serializer.java @@ -863,6 +863,12 @@ public class Serializer { return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR; case IETF_L2VPN: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN; + case GNMI_OPENCONFIG: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG; + case FLEXSCALE: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_FLEXSCALE; + case IETF_ACTN: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN; case UNDEFINED: default: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED; @@ -886,6 +892,12 @@ public class Serializer { return DeviceDriverEnum.XR; case DEVICEDRIVER_IETF_L2VPN: return DeviceDriverEnum.IETF_L2VPN; + case DEVICEDRIVER_GNMI_OPENCONFIG: + return DeviceDriverEnum.GNMI_OPENCONFIG; + case DEVICEDRIVER_FLEXSCALE: + return DeviceDriverEnum.FLEXSCALE; + case DEVICEDRIVER_IETF_ACTN: + return DeviceDriverEnum.IETF_ACTN; case DEVICEDRIVER_UNDEFINED: case UNRECOGNIZED: default: diff --git a/src/ztp/src/main/java/org/etsi/tfs/ztp/context/model/DeviceDriverEnum.java b/src/ztp/src/main/java/org/etsi/tfs/ztp/context/model/DeviceDriverEnum.java index 7c87b0638272edb58ef3cd57d3aad7aa3365cca8..8e89be8a6ddc993e7d90794c756f406fa72104f2 100644 --- a/src/ztp/src/main/java/org/etsi/tfs/ztp/context/model/DeviceDriverEnum.java +++ b/src/ztp/src/main/java/org/etsi/tfs/ztp/context/model/DeviceDriverEnum.java @@ -24,5 +24,8 @@ public enum DeviceDriverEnum { IETF_NETWORK_TOPOLOGY, ONF_TR_532, XR, - IETF_L2VPN + IETF_L2VPN, + GNMI_OPENCONFIG, + FLEXSCALE, + IETF_ACTN } diff --git a/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java b/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java index 5a7887a049526e530874a597b2b0f96e2646a4f9..67048119d0b0cdb8d1fb2df2dcb2659b0870efb3 100644 --- a/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java +++ b/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java @@ -1223,6 +1223,13 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.IETF_L2VPN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN), + Arguments.of( + DeviceDriverEnum.GNMI_OPENCONFIG, + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG), + Arguments.of( + DeviceDriverEnum.FLEXSCALE, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_FLEXSCALE), + Arguments.of( + DeviceDriverEnum.IETF_ACTN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } diff --git a/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java b/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java index a25798b884d9006f9c1b218c133634784f8bf392..d4873899b0113a7356c1c4d6bc2ea9aae2e8b4e5 100644 --- a/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java @@ -185,6 +185,14 @@ public final class ContextOuterClass { * <code>DEVICEDRIVER_GNMI_OPENCONFIG = 8;</code> */ DEVICEDRIVER_GNMI_OPENCONFIG(8), + /** + * <code>DEVICEDRIVER_FLEXSCALE = 9;</code> + */ + DEVICEDRIVER_FLEXSCALE(9), + /** + * <code>DEVICEDRIVER_IETF_ACTN = 10;</code> + */ + DEVICEDRIVER_IETF_ACTN(10), UNRECOGNIZED(-1), ; @@ -228,6 +236,14 @@ public final class ContextOuterClass { * <code>DEVICEDRIVER_GNMI_OPENCONFIG = 8;</code> */ public static final int DEVICEDRIVER_GNMI_OPENCONFIG_VALUE = 8; + /** + * <code>DEVICEDRIVER_FLEXSCALE = 9;</code> + */ + public static final int DEVICEDRIVER_FLEXSCALE_VALUE = 9; + /** + * <code>DEVICEDRIVER_IETF_ACTN = 10;</code> + */ + public static final int DEVICEDRIVER_IETF_ACTN_VALUE = 10; public final int getNumber() { @@ -263,6 +279,8 @@ public final class ContextOuterClass { case 6: return DEVICEDRIVER_XR; case 7: return DEVICEDRIVER_IETF_L2VPN; case 8: return DEVICEDRIVER_GNMI_OPENCONFIG; + case 9: return DEVICEDRIVER_FLEXSCALE; + case 10: return DEVICEDRIVER_IETF_ACTN; default: return null; } } @@ -461,6 +479,10 @@ public final class ContextOuterClass { * <code>SERVICETYPE_TE = 4;</code> */ SERVICETYPE_TE(4), + /** + * <code>SERVICETYPE_E2E = 5;</code> + */ + SERVICETYPE_E2E(5), UNRECOGNIZED(-1), ; @@ -484,6 +506,10 @@ public final class ContextOuterClass { * <code>SERVICETYPE_TE = 4;</code> */ public static final int SERVICETYPE_TE_VALUE = 4; + /** + * <code>SERVICETYPE_E2E = 5;</code> + */ + public static final int SERVICETYPE_E2E_VALUE = 5; public final int getNumber() { @@ -515,6 +541,7 @@ public final class ContextOuterClass { case 2: return SERVICETYPE_L2NM; case 3: return SERVICETYPE_TAPI_CONNECTIVITY_SERVICE; case 4: return SERVICETYPE_TE; + case 5: return SERVICETYPE_E2E; default: return null; } } @@ -75826,114 +75853,116 @@ public final class ContextOuterClass { "\0132\022.context.ContextId\022\025\n\rauthenticated\030\002" + " \001(\010*j\n\rEventTypeEnum\022\027\n\023EVENTTYPE_UNDEF" + "INED\020\000\022\024\n\020EVENTTYPE_CREATE\020\001\022\024\n\020EVENTTYP" + - "E_UPDATE\020\002\022\024\n\020EVENTTYPE_REMOVE\020\003*\231\002\n\020Dev" + + "E_UPDATE\020\002\022\024\n\020EVENTTYPE_REMOVE\020\003*\321\002\n\020Dev" + "iceDriverEnum\022\032\n\026DEVICEDRIVER_UNDEFINED\020" + "\000\022\033\n\027DEVICEDRIVER_OPENCONFIG\020\001\022\036\n\032DEVICE" + "DRIVER_TRANSPORT_API\020\002\022\023\n\017DEVICEDRIVER_P" + "4\020\003\022&\n\"DEVICEDRIVER_IETF_NETWORK_TOPOLOG" + "Y\020\004\022\033\n\027DEVICEDRIVER_ONF_TR_532\020\005\022\023\n\017DEVI" + "CEDRIVER_XR\020\006\022\033\n\027DEVICEDRIVER_IETF_L2VPN" + - "\020\007\022 \n\034DEVICEDRIVER_GNMI_OPENCONFIG\020\010*\217\001\n" + - "\033DeviceOperationalStatusEnum\022%\n!DEVICEOP" + - "ERATIONALSTATUS_UNDEFINED\020\000\022$\n DEVICEOPE" + - "RATIONALSTATUS_DISABLED\020\001\022#\n\037DEVICEOPERA" + - "TIONALSTATUS_ENABLED\020\002*\225\001\n\017ServiceTypeEn" + - "um\022\027\n\023SERVICETYPE_UNKNOWN\020\000\022\024\n\020SERVICETY" + - "PE_L3NM\020\001\022\024\n\020SERVICETYPE_L2NM\020\002\022)\n%SERVI" + - "CETYPE_TAPI_CONNECTIVITY_SERVICE\020\003\022\022\n\016SE" + - "RVICETYPE_TE\020\004*\304\001\n\021ServiceStatusEnum\022\033\n\027" + - "SERVICESTATUS_UNDEFINED\020\000\022\031\n\025SERVICESTAT" + - "US_PLANNED\020\001\022\030\n\024SERVICESTATUS_ACTIVE\020\002\022\032" + - "\n\026SERVICESTATUS_UPDATING\020\003\022!\n\035SERVICESTA" + - "TUS_PENDING_REMOVAL\020\004\022\036\n\032SERVICESTATUS_S" + - "LA_VIOLATED\020\005*\251\001\n\017SliceStatusEnum\022\031\n\025SLI" + - "CESTATUS_UNDEFINED\020\000\022\027\n\023SLICESTATUS_PLAN" + - "NED\020\001\022\024\n\020SLICESTATUS_INIT\020\002\022\026\n\022SLICESTAT" + - "US_ACTIVE\020\003\022\026\n\022SLICESTATUS_DEINIT\020\004\022\034\n\030S" + - "LICESTATUS_SLA_VIOLATED\020\005*]\n\020ConfigActio" + - "nEnum\022\032\n\026CONFIGACTION_UNDEFINED\020\000\022\024\n\020CON" + - "FIGACTION_SET\020\001\022\027\n\023CONFIGACTION_DELETE\020\002" + - "*m\n\024ConstraintActionEnum\022\036\n\032CONSTRAINTAC" + - "TION_UNDEFINED\020\000\022\030\n\024CONSTRAINTACTION_SET" + - "\020\001\022\033\n\027CONSTRAINTACTION_DELETE\020\002*\203\002\n\022Isol" + - "ationLevelEnum\022\020\n\014NO_ISOLATION\020\000\022\026\n\022PHYS" + - "ICAL_ISOLATION\020\001\022\025\n\021LOGICAL_ISOLATION\020\002\022" + - "\025\n\021PROCESS_ISOLATION\020\003\022\035\n\031PHYSICAL_MEMOR" + - "Y_ISOLATION\020\004\022\036\n\032PHYSICAL_NETWORK_ISOLAT" + - "ION\020\005\022\036\n\032VIRTUAL_RESOURCE_ISOLATION\020\006\022\037\n" + - "\033NETWORK_FUNCTIONS_ISOLATION\020\007\022\025\n\021SERVIC" + - "E_ISOLATION\020\0102\245\026\n\016ContextService\022:\n\016List" + - "ContextIds\022\016.context.Empty\032\026.context.Con" + - "textIdList\"\000\0226\n\014ListContexts\022\016.context.E" + - "mpty\032\024.context.ContextList\"\000\0224\n\nGetConte" + - "xt\022\022.context.ContextId\032\020.context.Context" + - "\"\000\0224\n\nSetContext\022\020.context.Context\032\022.con" + - "text.ContextId\"\000\0225\n\rRemoveContext\022\022.cont" + - "ext.ContextId\032\016.context.Empty\"\000\022=\n\020GetCo" + - "ntextEvents\022\016.context.Empty\032\025.context.Co" + - "ntextEvent\"\0000\001\022@\n\017ListTopologyIds\022\022.cont" + - "ext.ContextId\032\027.context.TopologyIdList\"\000" + - "\022=\n\016ListTopologies\022\022.context.ContextId\032\025" + - ".context.TopologyList\"\000\0227\n\013GetTopology\022\023" + - ".context.TopologyId\032\021.context.Topology\"\000" + - "\022E\n\022GetTopologyDetails\022\023.context.Topolog" + - "yId\032\030.context.TopologyDetails\"\000\0227\n\013SetTo" + - "pology\022\021.context.Topology\032\023.context.Topo" + - "logyId\"\000\0227\n\016RemoveTopology\022\023.context.Top" + - "ologyId\032\016.context.Empty\"\000\022?\n\021GetTopology" + - "Events\022\016.context.Empty\032\026.context.Topolog" + - "yEvent\"\0000\001\0228\n\rListDeviceIds\022\016.context.Em" + - "pty\032\025.context.DeviceIdList\"\000\0224\n\013ListDevi" + - "ces\022\016.context.Empty\032\023.context.DeviceList" + - "\"\000\0221\n\tGetDevice\022\021.context.DeviceId\032\017.con" + - "text.Device\"\000\0221\n\tSetDevice\022\017.context.Dev" + - "ice\032\021.context.DeviceId\"\000\0223\n\014RemoveDevice" + - "\022\021.context.DeviceId\032\016.context.Empty\"\000\022;\n" + - "\017GetDeviceEvents\022\016.context.Empty\032\024.conte" + - "xt.DeviceEvent\"\0000\001\022<\n\014SelectDevice\022\025.con" + - "text.DeviceFilter\032\023.context.DeviceList\"\000" + - "\022I\n\021ListEndPointNames\022\027.context.EndPoint" + - "IdList\032\031.context.EndPointNameList\"\000\0224\n\013L" + - "istLinkIds\022\016.context.Empty\032\023.context.Lin" + - "kIdList\"\000\0220\n\tListLinks\022\016.context.Empty\032\021" + - ".context.LinkList\"\000\022+\n\007GetLink\022\017.context" + - ".LinkId\032\r.context.Link\"\000\022+\n\007SetLink\022\r.co" + - "ntext.Link\032\017.context.LinkId\"\000\022/\n\nRemoveL" + - "ink\022\017.context.LinkId\032\016.context.Empty\"\000\0227" + - "\n\rGetLinkEvents\022\016.context.Empty\032\022.contex" + - "t.LinkEvent\"\0000\001\022>\n\016ListServiceIds\022\022.cont" + - "ext.ContextId\032\026.context.ServiceIdList\"\000\022" + - ":\n\014ListServices\022\022.context.ContextId\032\024.co" + - "ntext.ServiceList\"\000\0224\n\nGetService\022\022.cont" + - "ext.ServiceId\032\020.context.Service\"\000\0224\n\nSet" + - "Service\022\020.context.Service\032\022.context.Serv" + - "iceId\"\000\0226\n\014UnsetService\022\020.context.Servic" + - "e\032\022.context.ServiceId\"\000\0225\n\rRemoveService" + - "\022\022.context.ServiceId\032\016.context.Empty\"\000\022=" + - "\n\020GetServiceEvents\022\016.context.Empty\032\025.con" + - "text.ServiceEvent\"\0000\001\022?\n\rSelectService\022\026" + - ".context.ServiceFilter\032\024.context.Service" + - "List\"\000\022:\n\014ListSliceIds\022\022.context.Context" + - "Id\032\024.context.SliceIdList\"\000\0226\n\nListSlices" + - "\022\022.context.ContextId\032\022.context.SliceList" + - "\"\000\022.\n\010GetSlice\022\020.context.SliceId\032\016.conte" + - "xt.Slice\"\000\022.\n\010SetSlice\022\016.context.Slice\032\020" + - ".context.SliceId\"\000\0220\n\nUnsetSlice\022\016.conte" + - "xt.Slice\032\020.context.SliceId\"\000\0221\n\013RemoveSl" + - "ice\022\020.context.SliceId\032\016.context.Empty\"\000\022" + - "9\n\016GetSliceEvents\022\016.context.Empty\032\023.cont" + - "ext.SliceEvent\"\0000\001\0229\n\013SelectSlice\022\024.cont" + - "ext.SliceFilter\032\022.context.SliceList\"\000\022D\n" + - "\021ListConnectionIds\022\022.context.ServiceId\032\031" + - ".context.ConnectionIdList\"\000\022@\n\017ListConne" + - "ctions\022\022.context.ServiceId\032\027.context.Con" + - "nectionList\"\000\022=\n\rGetConnection\022\025.context" + - ".ConnectionId\032\023.context.Connection\"\000\022=\n\r" + - "SetConnection\022\023.context.Connection\032\025.con" + - "text.ConnectionId\"\000\022;\n\020RemoveConnection\022" + - "\025.context.ConnectionId\032\016.context.Empty\"\000" + - "\022C\n\023GetConnectionEvents\022\016.context.Empty\032" + - "\030.context.ConnectionEvent\"\0000\001b\006proto3" + "\020\007\022 \n\034DEVICEDRIVER_GNMI_OPENCONFIG\020\010\022\032\n\026" + + "DEVICEDRIVER_FLEXSCALE\020\t\022\032\n\026DEVICEDRIVER" + + "_IETF_ACTN\020\n*\217\001\n\033DeviceOperationalStatus" + + "Enum\022%\n!DEVICEOPERATIONALSTATUS_UNDEFINE" + + "D\020\000\022$\n DEVICEOPERATIONALSTATUS_DISABLED\020" + + "\001\022#\n\037DEVICEOPERATIONALSTATUS_ENABLED\020\002*\252" + + "\001\n\017ServiceTypeEnum\022\027\n\023SERVICETYPE_UNKNOW" + + "N\020\000\022\024\n\020SERVICETYPE_L3NM\020\001\022\024\n\020SERVICETYPE" + + "_L2NM\020\002\022)\n%SERVICETYPE_TAPI_CONNECTIVITY" + + "_SERVICE\020\003\022\022\n\016SERVICETYPE_TE\020\004\022\023\n\017SERVIC" + + "ETYPE_E2E\020\005*\304\001\n\021ServiceStatusEnum\022\033\n\027SER" + + "VICESTATUS_UNDEFINED\020\000\022\031\n\025SERVICESTATUS_" + + "PLANNED\020\001\022\030\n\024SERVICESTATUS_ACTIVE\020\002\022\032\n\026S" + + "ERVICESTATUS_UPDATING\020\003\022!\n\035SERVICESTATUS" + + "_PENDING_REMOVAL\020\004\022\036\n\032SERVICESTATUS_SLA_" + + "VIOLATED\020\005*\251\001\n\017SliceStatusEnum\022\031\n\025SLICES" + + "TATUS_UNDEFINED\020\000\022\027\n\023SLICESTATUS_PLANNED" + + "\020\001\022\024\n\020SLICESTATUS_INIT\020\002\022\026\n\022SLICESTATUS_" + + "ACTIVE\020\003\022\026\n\022SLICESTATUS_DEINIT\020\004\022\034\n\030SLIC" + + "ESTATUS_SLA_VIOLATED\020\005*]\n\020ConfigActionEn" + + "um\022\032\n\026CONFIGACTION_UNDEFINED\020\000\022\024\n\020CONFIG" + + "ACTION_SET\020\001\022\027\n\023CONFIGACTION_DELETE\020\002*m\n" + + "\024ConstraintActionEnum\022\036\n\032CONSTRAINTACTIO" + + "N_UNDEFINED\020\000\022\030\n\024CONSTRAINTACTION_SET\020\001\022" + + "\033\n\027CONSTRAINTACTION_DELETE\020\002*\203\002\n\022Isolati" + + "onLevelEnum\022\020\n\014NO_ISOLATION\020\000\022\026\n\022PHYSICA" + + "L_ISOLATION\020\001\022\025\n\021LOGICAL_ISOLATION\020\002\022\025\n\021" + + "PROCESS_ISOLATION\020\003\022\035\n\031PHYSICAL_MEMORY_I" + + "SOLATION\020\004\022\036\n\032PHYSICAL_NETWORK_ISOLATION" + + "\020\005\022\036\n\032VIRTUAL_RESOURCE_ISOLATION\020\006\022\037\n\033NE" + + "TWORK_FUNCTIONS_ISOLATION\020\007\022\025\n\021SERVICE_I" + + "SOLATION\020\0102\245\026\n\016ContextService\022:\n\016ListCon" + + "textIds\022\016.context.Empty\032\026.context.Contex" + + "tIdList\"\000\0226\n\014ListContexts\022\016.context.Empt" + + "y\032\024.context.ContextList\"\000\0224\n\nGetContext\022" + + "\022.context.ContextId\032\020.context.Context\"\000\022" + + "4\n\nSetContext\022\020.context.Context\032\022.contex" + + "t.ContextId\"\000\0225\n\rRemoveContext\022\022.context" + + ".ContextId\032\016.context.Empty\"\000\022=\n\020GetConte" + + "xtEvents\022\016.context.Empty\032\025.context.Conte" + + "xtEvent\"\0000\001\022@\n\017ListTopologyIds\022\022.context" + + ".ContextId\032\027.context.TopologyIdList\"\000\022=\n" + + "\016ListTopologies\022\022.context.ContextId\032\025.co" + + "ntext.TopologyList\"\000\0227\n\013GetTopology\022\023.co" + + "ntext.TopologyId\032\021.context.Topology\"\000\022E\n" + + "\022GetTopologyDetails\022\023.context.TopologyId" + + "\032\030.context.TopologyDetails\"\000\0227\n\013SetTopol" + + "ogy\022\021.context.Topology\032\023.context.Topolog" + + "yId\"\000\0227\n\016RemoveTopology\022\023.context.Topolo" + + "gyId\032\016.context.Empty\"\000\022?\n\021GetTopologyEve" + + "nts\022\016.context.Empty\032\026.context.TopologyEv" + + "ent\"\0000\001\0228\n\rListDeviceIds\022\016.context.Empty" + + "\032\025.context.DeviceIdList\"\000\0224\n\013ListDevices" + + "\022\016.context.Empty\032\023.context.DeviceList\"\000\022" + + "1\n\tGetDevice\022\021.context.DeviceId\032\017.contex" + + "t.Device\"\000\0221\n\tSetDevice\022\017.context.Device" + + "\032\021.context.DeviceId\"\000\0223\n\014RemoveDevice\022\021." + + "context.DeviceId\032\016.context.Empty\"\000\022;\n\017Ge" + + "tDeviceEvents\022\016.context.Empty\032\024.context." + + "DeviceEvent\"\0000\001\022<\n\014SelectDevice\022\025.contex" + + "t.DeviceFilter\032\023.context.DeviceList\"\000\022I\n" + + "\021ListEndPointNames\022\027.context.EndPointIdL" + + "ist\032\031.context.EndPointNameList\"\000\0224\n\013List" + + "LinkIds\022\016.context.Empty\032\023.context.LinkId" + + "List\"\000\0220\n\tListLinks\022\016.context.Empty\032\021.co" + + "ntext.LinkList\"\000\022+\n\007GetLink\022\017.context.Li" + + "nkId\032\r.context.Link\"\000\022+\n\007SetLink\022\r.conte" + + "xt.Link\032\017.context.LinkId\"\000\022/\n\nRemoveLink" + + "\022\017.context.LinkId\032\016.context.Empty\"\000\0227\n\rG" + + "etLinkEvents\022\016.context.Empty\032\022.context.L" + + "inkEvent\"\0000\001\022>\n\016ListServiceIds\022\022.context" + + ".ContextId\032\026.context.ServiceIdList\"\000\022:\n\014" + + "ListServices\022\022.context.ContextId\032\024.conte" + + "xt.ServiceList\"\000\0224\n\nGetService\022\022.context" + + ".ServiceId\032\020.context.Service\"\000\0224\n\nSetSer" + + "vice\022\020.context.Service\032\022.context.Service" + + "Id\"\000\0226\n\014UnsetService\022\020.context.Service\032\022" + + ".context.ServiceId\"\000\0225\n\rRemoveService\022\022." + + "context.ServiceId\032\016.context.Empty\"\000\022=\n\020G" + + "etServiceEvents\022\016.context.Empty\032\025.contex" + + "t.ServiceEvent\"\0000\001\022?\n\rSelectService\022\026.co" + + "ntext.ServiceFilter\032\024.context.ServiceLis" + + "t\"\000\022:\n\014ListSliceIds\022\022.context.ContextId\032" + + "\024.context.SliceIdList\"\000\0226\n\nListSlices\022\022." + + "context.ContextId\032\022.context.SliceList\"\000\022" + + ".\n\010GetSlice\022\020.context.SliceId\032\016.context." + + "Slice\"\000\022.\n\010SetSlice\022\016.context.Slice\032\020.co" + + "ntext.SliceId\"\000\0220\n\nUnsetSlice\022\016.context." + + "Slice\032\020.context.SliceId\"\000\0221\n\013RemoveSlice" + + "\022\020.context.SliceId\032\016.context.Empty\"\000\0229\n\016" + + "GetSliceEvents\022\016.context.Empty\032\023.context" + + ".SliceEvent\"\0000\001\0229\n\013SelectSlice\022\024.context" + + ".SliceFilter\032\022.context.SliceList\"\000\022D\n\021Li" + + "stConnectionIds\022\022.context.ServiceId\032\031.co" + + "ntext.ConnectionIdList\"\000\022@\n\017ListConnecti" + + "ons\022\022.context.ServiceId\032\027.context.Connec" + + "tionList\"\000\022=\n\rGetConnection\022\025.context.Co" + + "nnectionId\032\023.context.Connection\"\000\022=\n\rSet" + + "Connection\022\023.context.Connection\032\025.contex" + + "t.ConnectionId\"\000\022;\n\020RemoveConnection\022\025.c" + + "ontext.ConnectionId\032\016.context.Empty\"\000\022C\n" + + "\023GetConnectionEvents\022\016.context.Empty\032\030.c" + + "ontext.ConnectionEvent\"\0000\001b\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, diff --git a/src/ztp/target/kubernetes/kubernetes.yml b/src/ztp/target/kubernetes/kubernetes.yml index f3e4a6d6dda261c4eac983552b01bb6a4f901e9f..d2a59eb05f056d69f021d897e89f1ab9cbb102ce 100644 --- a/src/ztp/target/kubernetes/kubernetes.yml +++ b/src/ztp/target/kubernetes/kubernetes.yml @@ -3,8 +3,8 @@ apiVersion: v1 kind: Service metadata: annotations: - app.quarkus.io/commit-id: 46486023929121fc955e9550fc8fd625ded433d2 - app.quarkus.io/build-timestamp: 2023-12-15 - 12:04:12 +0000 + app.quarkus.io/commit-id: 5f8866be9cb91871607627819258b0b375410467 + app.quarkus.io/build-timestamp: 2024-01-26 - 16:39:32 +0000 prometheus.io/scrape: "true" prometheus.io/path: /q/metrics prometheus.io/port: "8080" @@ -15,12 +15,12 @@ metadata: name: ztpservice spec: ports: - - name: grpc-server - port: 5050 - targetPort: 5050 - name: http port: 9192 targetPort: 8080 + - name: grpc-server + port: 5050 + targetPort: 5050 selector: app.kubernetes.io/name: ztpservice type: ClusterIP @@ -29,8 +29,8 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - app.quarkus.io/commit-id: 46486023929121fc955e9550fc8fd625ded433d2 - app.quarkus.io/build-timestamp: 2023-12-15 - 12:04:12 +0000 + app.quarkus.io/commit-id: 5f8866be9cb91871607627819258b0b375410467 + app.quarkus.io/build-timestamp: 2024-01-26 - 16:39:32 +0000 prometheus.io/scrape: "true" prometheus.io/path: /q/metrics prometheus.io/port: "8080" @@ -47,8 +47,8 @@ spec: template: metadata: annotations: - app.quarkus.io/commit-id: 46486023929121fc955e9550fc8fd625ded433d2 - app.quarkus.io/build-timestamp: 2023-12-15 - 12:04:12 +0000 + app.quarkus.io/commit-id: 5f8866be9cb91871607627819258b0b375410467 + app.quarkus.io/build-timestamp: 2024-01-26 - 16:39:32 +0000 prometheus.io/scrape: "true" prometheus.io/path: /q/metrics prometheus.io/port: "8080" @@ -81,12 +81,12 @@ spec: timeoutSeconds: 10 name: ztpservice ports: - - containerPort: 5050 - name: grpc-server - protocol: TCP - containerPort: 8080 name: http protocol: TCP + - containerPort: 5050 + name: grpc-server + protocol: TCP readinessProbe: failureThreshold: 3 httpGet: