DEVICEDRIVER_XR = 6;
*/
DEVICEDRIVER_XR(6),
+ /**
+ * DEVICEDRIVER_IETF_L2VPN = 7;
+ */
+ DEVICEDRIVER_IETF_L2VPN(7),
UNRECOGNIZED(-1),
;
@@ -212,6 +216,10 @@ public final class ContextOuterClass {
* DEVICEDRIVER_XR = 6;
*/
public static final int DEVICEDRIVER_XR_VALUE = 6;
+ /**
+ * DEVICEDRIVER_IETF_L2VPN = 7;
+ */
+ public static final int DEVICEDRIVER_IETF_L2VPN_VALUE = 7;
public final int getNumber() {
@@ -245,6 +253,7 @@ public final class ContextOuterClass {
case 4: return DEVICEDRIVER_IETF_NETWORK_TOPOLOGY;
case 5: return DEVICEDRIVER_ONF_TR_352;
case 6: return DEVICEDRIVER_XR;
+ case 7: return DEVICEDRIVER_IETF_L2VPN;
default: return null;
}
}
diff --git a/src/automation/target/kubernetes/kubernetes.yml b/src/automation/target/kubernetes/kubernetes.yml
index 4dacf3998c3991a441dc374ca6c6abc29e8d3b80..73e6b1d7be076dbcf55014ae3accbc1e29e0c8e8 100644
--- a/src/automation/target/kubernetes/kubernetes.yml
+++ b/src/automation/target/kubernetes/kubernetes.yml
@@ -27,8 +27,9 @@ spec:
- name: grpc
port: 5050
targetPort: 5050
- - name: http
- port: 8080
+ - name: metrics
+ protocol: TCP
+ port: 9192
targetPort: 8080
selector:
app.kubernetes.io/name: automationservice
@@ -84,7 +85,7 @@ spec:
name: grpc
protocol: TCP
- containerPort: 8080
- name: http
+ name: metrics
protocol: TCP
readinessProbe:
failureThreshold: 3
@@ -96,3 +97,29 @@ spec:
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
+ resources:
+ requests:
+ cpu: 50m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 2048Mi
+---
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: automationservice-hpa
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: automationservice
+ minReplicas: 1
+ maxReplicas: 10
+ metrics:
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: 80
diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py
index 99255defdb6b5ee155607536a2e13d23b97b2d3a..bb8948585f163aeb84ee758b8581bc6509d29799 100644
--- a/src/common/DeviceTypes.py
+++ b/src/common/DeviceTypes.py
@@ -25,9 +25,12 @@ class DeviceTypeEnum(Enum):
EMULATED_OPEN_LINE_SYSTEM = 'emu-open-line-system'
EMULATED_OPTICAL_ROADM = 'emu-optical-roadm'
EMULATED_OPTICAL_TRANSPONDER = 'emu-optical-transponder'
+ EMULATED_OPTICAL_SPLITTER = 'emu-optical-splitter' # passive component required for XR Constellation
EMULATED_P4_SWITCH = 'emu-p4-switch'
+ EMULATED_PACKET_RADIO_ROUTER = 'emu-packet-radio-router'
EMULATED_PACKET_ROUTER = 'emu-packet-router'
EMULATED_PACKET_SWITCH = 'emu-packet-switch'
+ EMULATED_XR_CONSTELLATION = 'emu-xr-constellation'
# Real device types
DATACENTER = 'datacenter'
@@ -36,6 +39,10 @@ class DeviceTypeEnum(Enum):
OPTICAL_ROADM = 'optical-roadm'
OPTICAL_TRANSPONDER = 'optical-transponder'
P4_SWITCH = 'p4-switch'
+ PACKET_RADIO_ROUTER = 'packet-radio-router'
PACKET_ROUTER = 'packet-router'
PACKET_SWITCH = 'packet-switch'
- XR_CONSTELLATION = 'xr-constellation'
\ No newline at end of file
+ XR_CONSTELLATION = 'xr-constellation'
+
+ # ETSI TeraFlowSDN controller
+ TERAFLOWSDN_CONTROLLER = 'teraflowsdn'
diff --git a/src/common/message_broker/backend/nats/NatsBackendThread.py b/src/common/message_broker/backend/nats/NatsBackendThread.py
index e59e4d6835ef662e4b0ed9f92d79a45c22954a6f..0bedd2b242f7eeaa1585d0eb41c5a0bd9efe07e5 100644
--- a/src/common/message_broker/backend/nats/NatsBackendThread.py
+++ b/src/common/message_broker/backend/nats/NatsBackendThread.py
@@ -12,10 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import asyncio, nats, nats.errors, queue, threading
+import asyncio, logging, nats, nats.errors, queue, threading
from typing import List
from common.message_broker.Message import Message
+LOGGER = logging.getLogger(__name__)
+
class NatsBackendThread(threading.Thread):
def __init__(self, nats_uri : str) -> None:
self._nats_uri = nats_uri
@@ -32,7 +34,9 @@ class NatsBackendThread(threading.Thread):
self._tasks_terminated.set()
async def _run_publisher(self) -> None:
+ LOGGER.info('[_run_publisher] NATS URI: {:s}'.format(str(self._nats_uri)))
client = await nats.connect(servers=[self._nats_uri])
+ LOGGER.info('[_run_publisher] Connected!')
while not self._terminate.is_set():
try:
message : Message = await self._publish_queue.get()
@@ -47,8 +51,11 @@ class NatsBackendThread(threading.Thread):
async def _run_subscriber(
self, topic_name : str, timeout : float, out_queue : queue.Queue[Message], unsubscribe : threading.Event
) -> None:
+ LOGGER.info('[_run_subscriber] NATS URI: {:s}'.format(str(self._nats_uri)))
client = await nats.connect(servers=[self._nats_uri])
+ LOGGER.info('[_run_subscriber] Connected!')
subscription = await client.subscribe(topic_name)
+ LOGGER.info('[_run_subscriber] Subscribed!')
while not self._terminate.is_set() and not unsubscribe.is_set():
try:
message = await subscription.next_msg(timeout)
diff --git a/src/common/tools/descriptor/Loader.py b/src/common/tools/descriptor/Loader.py
index 0e1d8c7371e87b47bfc47a4242e00039add48e7f..1e238510c98b83bebde8167711b988d7476e5a99 100644
--- a/src/common/tools/descriptor/Loader.py
+++ b/src/common/tools/descriptor/Loader.py
@@ -222,13 +222,13 @@ class DescriptorLoader:
self.__topologies_add = get_descriptors_add_topologies(self.__topologies)
if self.__dummy_mode:
- self._dummy_mode()
+ self._load_dummy_mode()
else:
- self._normal_mode()
+ self._load_normal_mode()
return self.__results
- def _dummy_mode(self) -> None:
+ def _load_dummy_mode(self) -> None:
# Dummy Mode: used to pre-load databases (WebUI debugging purposes) with no smart or automated tasks.
self.__ctx_cli.connect()
self._process_descr('context', 'add', self.__ctx_cli.SetContext, Context, self.__contexts_add )
@@ -242,7 +242,7 @@ class DescriptorLoader:
self._process_descr('topology', 'update', self.__ctx_cli.SetTopology, Topology, self.__topologies )
#self.__ctx_cli.close()
- def _normal_mode(self) -> None:
+ def _load_normal_mode(self) -> None:
# Normal mode: follows the automated workflows in the different components
assert len(self.__connections) == 0, 'in normal mode, connections should not be set'
@@ -321,7 +321,35 @@ class DescriptorLoader:
response = self.__ctx_cli.ListSlices(ContextId(**json_context_id(context_uuid)))
assert len(response.slices) == num_slices
- def unload(self) -> None:
+ def _unload_dummy_mode(self) -> None:
+ # Dummy Mode: used to pre-load databases (WebUI debugging purposes) with no smart or automated tasks.
+ self.__ctx_cli.connect()
+
+ for _, slice_list in self.slices.items():
+ for slice_ in slice_list:
+ self.__ctx_cli.RemoveSlice(SliceId(**slice_['slice_id']))
+
+ for _, service_list in self.services.items():
+ for service in service_list:
+ self.__ctx_cli.RemoveService(ServiceId(**service['service_id']))
+
+ for link in self.links:
+ self.__ctx_cli.RemoveLink(LinkId(**link['link_id']))
+
+ for device in self.devices:
+ self.__ctx_cli.RemoveDevice(DeviceId(**device['device_id']))
+
+ for _, topology_list in self.topologies.items():
+ for topology in topology_list:
+ self.__ctx_cli.RemoveTopology(TopologyId(**topology['topology_id']))
+
+ for context in self.contexts:
+ self.__ctx_cli.RemoveContext(ContextId(**context['context_id']))
+
+ #self.__ctx_cli.close()
+
+ def _unload_normal_mode(self) -> None:
+ # Normal mode: follows the automated workflows in the different components
self.__ctx_cli.connect()
self.__dev_cli.connect()
self.__svc_cli.connect()
@@ -348,6 +376,17 @@ class DescriptorLoader:
for context in self.contexts:
self.__ctx_cli.RemoveContext(ContextId(**context['context_id']))
+ #self.__ctx_cli.close()
+ #self.__dev_cli.close()
+ #self.__svc_cli.close()
+ #self.__slc_cli.close()
+
+ def unload(self) -> None:
+ if self.__dummy_mode:
+ self._unload_dummy_mode()
+ else:
+ self._unload_normal_mode()
+
def compose_notifications(results : TypeResults) -> TypeNotificationList:
notifications = []
for entity_name, action_name, num_ok, error_list in results:
diff --git a/src/common/tools/object_factory/Device.py b/src/common/tools/object_factory/Device.py
index 0cc4555d455bf28ac2143a5d58b87e084a8360c7..66c87b14dd866d44b5d48addf93d172aea962f8e 100644
--- a/src/common/tools/object_factory/Device.py
+++ b/src/common/tools/object_factory/Device.py
@@ -43,6 +43,9 @@ DEVICE_MICROWAVE_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY]
DEVICE_P4_TYPE = DeviceTypeEnum.P4_SWITCH.value
DEVICE_P4_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_P4]
+DEVICE_TFS_TYPE = DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value
+DEVICE_TFS_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN]
+
def json_device_id(device_uuid : str):
return {'device_uuid': {'uuid': device_uuid}}
@@ -120,6 +123,13 @@ def json_device_p4_disabled(
return json_device(
device_uuid, DEVICE_P4_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers)
+def json_device_tfs_disabled(
+ device_uuid : str, endpoints : List[Dict] = [], config_rules : List[Dict] = [],
+ drivers : List[Dict] = DEVICE_TFS_DRIVERS
+ ):
+ return json_device(
+ device_uuid, DEVICE_TFS_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers)
+
def json_device_connect_rules(address : str, port : int, settings : 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 c0442d8770c682ac1eea032980b58e7028be90c4..ba82e535ec958104bd14abf625eb6cd38c2a08ee 100644
--- a/src/common/type_checkers/Assertions.py
+++ b/src/common/type_checkers/Assertions.py
@@ -33,6 +33,7 @@ def validate_device_driver_enum(message):
'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY',
'DEVICEDRIVER_ONF_TR_352',
'DEVICEDRIVER_XR',
+ 'DEVICEDRIVER_IETF_L2VPN',
]
def validate_device_operational_status_enum(message):
diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py b/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py
index f95b532af4ba01968d17bc3958e1cffbf84a5e7f..ed25dbab3cd6b07ef73d64c5d37ad64e85353c02 100644
--- a/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py
+++ b/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py
@@ -74,4 +74,18 @@ BEARER_MAPPINGS = {
'R3:1/3': ('R3', '1/3', '5.3.1.3', None, 0, None, None, None, None),
'R4:1/2': ('R4', '1/2', '5.4.1.2', None, 0, None, None, None, None),
'R4:1/3': ('R4', '1/3', '5.4.1.3', None, 0, None, None, None, None),
+
+ # OFC'23
+ 'PE1:1/1': ('PE1', '1/1', '10.1.1.1', None, 0, None, None, None, None),
+ 'PE1:1/2': ('PE1', '1/2', '10.1.1.2', None, 0, None, None, None, None),
+ 'PE2:1/1': ('PE2', '1/1', '10.2.1.1', None, 0, None, None, None, None),
+ 'PE2:1/2': ('PE2', '1/2', '10.2.1.2', None, 0, None, None, None, None),
+ 'PE3:1/1': ('PE3', '1/1', '10.3.1.1', None, 0, None, None, None, None),
+ 'PE3:1/2': ('PE3', '1/2', '10.3.1.2', None, 0, None, None, None, None),
+ 'PE4:1/1': ('PE4', '1/1', '10.4.1.1', None, 0, None, None, None, None),
+ 'PE4:1/2': ('PE4', '1/2', '10.4.1.2', None, 0, None, None, None, None),
+
+ 'R149:eth-1/0/22': ('R149', 'eth-1/0/22', '5.5.5.5', None, 0, None, None, '5.5.5.1', '100'),
+ 'R155:eth-1/0/22': ('R155', 'eth-1/0/22', '5.5.5.1', None, 0, None, None, '5.5.5.5', '100'),
+ 'R199:eth-1/0/21': ('R199', 'eth-1/0/21', '5.5.5.6', None, 0, None, None, '5.5.5.5', '100'),
}
diff --git a/src/context/client/ContextClient.py b/src/context/client/ContextClient.py
index 7c3832d6b3ea7de0a495faee143b73179e8da5b9..13d9dc0035b45845bf11367e02c8830b5151c1d6 100644
--- a/src/context/client/ContextClient.py
+++ b/src/context/client/ContextClient.py
@@ -21,11 +21,11 @@ from common.tools.grpc.Tools import grpc_message_to_json_string
from common.proto.context_pb2 import (
Connection, ConnectionEvent, ConnectionId, ConnectionIdList, ConnectionList,
Context, ContextEvent, ContextId, ContextIdList, ContextList,
- Device, DeviceEvent, DeviceId, DeviceIdList, DeviceList,
+ Device, DeviceEvent, DeviceFilter, DeviceId, DeviceIdList, DeviceList,
Empty, EndPointIdList, EndPointNameList,
Link, LinkEvent, LinkId, LinkIdList, LinkList,
- Service, ServiceEvent, ServiceId, ServiceIdList, ServiceList,
- Slice, SliceEvent, SliceId, SliceIdList, SliceList,
+ Service, ServiceEvent, ServiceFilter, ServiceId, ServiceIdList, ServiceList,
+ Slice, SliceEvent, SliceFilter, SliceId, SliceIdList, SliceList,
Topology, TopologyDetails, TopologyEvent, TopologyId, TopologyIdList, TopologyList)
from common.proto.context_pb2_grpc import ContextServiceStub
from common.proto.context_policy_pb2_grpc import ContextPolicyServiceStub
@@ -185,6 +185,13 @@ class ContextClient:
LOGGER.debug('RemoveDevice result: {:s}'.format(grpc_message_to_json_string(response)))
return response
+ @RETRY_DECORATOR
+ def SelectDevice(self, request: DeviceFilter) -> DeviceList:
+ LOGGER.debug('SelectDevice request: {:s}'.format(grpc_message_to_json_string(request)))
+ response = self.stub.SelectDevice(request)
+ LOGGER.debug('SelectDevice result: {:s}'.format(grpc_message_to_json_string(response)))
+ return response
+
@RETRY_DECORATOR
def GetDeviceEvents(self, request: Empty) -> Iterator[DeviceEvent]:
LOGGER.debug('GetDeviceEvents request: {:s}'.format(grpc_message_to_json_string(request)))
@@ -283,6 +290,13 @@ class ContextClient:
LOGGER.debug('RemoveService result: {:s}'.format(grpc_message_to_json_string(response)))
return response
+ @RETRY_DECORATOR
+ def SelectService(self, request: ServiceFilter) -> ServiceList:
+ LOGGER.debug('SelectService request: {:s}'.format(grpc_message_to_json_string(request)))
+ response = self.stub.SelectService(request)
+ LOGGER.debug('SelectService result: {:s}'.format(grpc_message_to_json_string(response)))
+ return response
+
@RETRY_DECORATOR
def GetServiceEvents(self, request: Empty) -> Iterator[ServiceEvent]:
LOGGER.debug('GetServiceEvents request: {:s}'.format(grpc_message_to_json_string(request)))
@@ -332,6 +346,13 @@ class ContextClient:
LOGGER.debug('RemoveSlice result: {:s}'.format(grpc_message_to_json_string(response)))
return response
+ @RETRY_DECORATOR
+ def SelectSlice(self, request: SliceFilter) -> SliceList:
+ LOGGER.debug('SelectSlice request: {:s}'.format(grpc_message_to_json_string(request)))
+ response = self.stub.SelectSlice(request)
+ LOGGER.debug('SelectSlice result: {:s}'.format(grpc_message_to_json_string(response)))
+ return response
+
@RETRY_DECORATOR
def GetSliceEvents(self, request: Empty) -> Iterator[SliceEvent]:
LOGGER.debug('GetSliceEvents request: {:s}'.format(grpc_message_to_json_string(request)))
diff --git a/src/context/service/ContextServiceServicerImpl.py b/src/context/service/ContextServiceServicerImpl.py
index 6fe00f917cf8b338f0934e2a268fa757d2055865..789ee7a78c6bcff3e62a6dd373bd58dbb2e7a960 100644
--- a/src/context/service/ContextServiceServicerImpl.py
+++ b/src/context/service/ContextServiceServicerImpl.py
@@ -18,11 +18,11 @@ from common.message_broker.MessageBroker import MessageBroker
from common.proto.context_pb2 import (
Connection, ConnectionEvent, ConnectionId, ConnectionIdList, ConnectionList,
Context, ContextEvent, ContextId, ContextIdList, ContextList,
- Device, DeviceEvent, DeviceId, DeviceIdList, DeviceList,
+ Device, DeviceEvent, DeviceFilter, DeviceId, DeviceIdList, DeviceList,
Empty, EndPointIdList, EndPointNameList, EventTypeEnum,
Link, LinkEvent, LinkId, LinkIdList, LinkList,
- Service, ServiceEvent, ServiceId, ServiceIdList, ServiceList,
- Slice, SliceEvent, SliceId, SliceIdList, SliceList,
+ Service, ServiceEvent, ServiceFilter, ServiceId, ServiceIdList, ServiceList,
+ Slice, SliceEvent, SliceFilter, SliceId, SliceIdList, SliceList,
Topology, TopologyDetails, TopologyEvent, TopologyId, TopologyIdList, TopologyList)
from common.proto.policy_pb2 import PolicyRuleIdList, PolicyRuleId, PolicyRuleList, PolicyRule
from common.proto.context_pb2_grpc import ContextServiceServicer
@@ -31,13 +31,13 @@ from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_m
from .database.Connection import (
connection_delete, connection_get, connection_list_ids, connection_list_objs, connection_set)
from .database.Context import context_delete, context_get, context_list_ids, context_list_objs, context_set
-from .database.Device import device_delete, device_get, device_list_ids, device_list_objs, device_set
+from .database.Device import device_delete, device_get, device_list_ids, device_list_objs, device_select, device_set
from .database.EndPoint import endpoint_list_names
from .database.Link import link_delete, link_get, link_list_ids, link_list_objs, link_set
from .database.PolicyRule import (
policyrule_delete, policyrule_get, policyrule_list_ids, policyrule_list_objs, policyrule_set)
-from .database.Service import service_delete, service_get, service_list_ids, service_list_objs, service_set
-from .database.Slice import slice_delete, slice_get, slice_list_ids, slice_list_objs, slice_set, slice_unset
+from .database.Service import service_delete, service_get, service_list_ids, service_list_objs, service_select, service_set
+from .database.Slice import slice_delete, slice_get, slice_list_ids, slice_list_objs, slice_select, slice_set, slice_unset
from .database.Topology import (
topology_delete, topology_get, topology_get_details, topology_list_ids, topology_list_objs, topology_set)
from .Events import (
@@ -161,6 +161,10 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer
notify_event(self.messagebroker, TOPIC_DEVICE, event_type, {'device_id': device_id})
return Empty()
+ @safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
+ def SelectDevices(self, request : DeviceFilter, context : grpc.ServicerContext) -> DeviceList:
+ return DeviceList(devices=device_select(self.db_engine, request))
+
@safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
def GetDeviceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[DeviceEvent]:
for message in self.messagebroker.consume({TOPIC_DEVICE}, consume_timeout=CONSUME_TIMEOUT):
@@ -235,6 +239,10 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer
notify_event(self.messagebroker, TOPIC_SERVICE, event_type, {'service_id': service_id})
return Empty()
+ @safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
+ def SelectService(self, request : ServiceFilter, context : grpc.ServicerContext) -> ServiceList:
+ return ServiceList(services=service_select(self.db_engine, request))
+
@safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
def GetServiceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[ServiceEvent]:
for message in self.messagebroker.consume({TOPIC_SERVICE}, consume_timeout=CONSUME_TIMEOUT):
@@ -278,6 +286,10 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer
notify_event(self.messagebroker, TOPIC_SLICE, event_type, {'slice_id': slice_id})
return Empty()
+ @safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
+ def SelectSlice(self, request : SliceFilter, context : grpc.ServicerContext) -> SliceList:
+ return SliceList(slices=slice_select(self.db_engine, request))
+
@safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
def GetSliceEvents(self, request : Empty, context : grpc.ServicerContext) -> Iterator[SliceEvent]:
for message in self.messagebroker.consume({TOPIC_SLICE}, consume_timeout=CONSUME_TIMEOUT):
diff --git a/src/context/service/database/Connection.py b/src/context/service/database/Connection.py
index a3edb8ea2838d9203a810677da495893a2cd6973..80d3b3a6d437986741ee5308205d8a902e897c40 100644
--- a/src/context/service/database/Connection.py
+++ b/src/context/service/database/Connection.py
@@ -16,7 +16,7 @@ import datetime, logging, re
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.engine import Engine
from sqlalchemy.exc import IntegrityError
-from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy.orm import Session, selectinload, sessionmaker
from sqlalchemy_cockroachdb import run_transaction
from typing import Dict, List, Optional, Tuple
from common.proto.context_pb2 import Connection, ConnectionId, ServiceId
@@ -40,7 +40,11 @@ def connection_list_ids(db_engine : Engine, request : ServiceId) -> List[Dict]:
def connection_list_objs(db_engine : Engine, request : ServiceId) -> List[Dict]:
_,service_uuid = service_get_uuid(request, allow_random=False)
def callback(session : Session) -> List[Dict]:
- obj_list : List[ConnectionModel] = session.query(ConnectionModel).filter_by(service_uuid=service_uuid).all()
+ obj_list : List[ConnectionModel] = session.query(ConnectionModel)\
+ .options(selectinload(ConnectionModel.connection_service))\
+ .options(selectinload(ConnectionModel.connection_endpoints))\
+ .options(selectinload(ConnectionModel.connection_subservices))\
+ .filter_by(service_uuid=service_uuid).all()
return [obj.dump() for obj in obj_list]
return run_transaction(sessionmaker(bind=db_engine), callback)
@@ -48,6 +52,9 @@ def connection_get(db_engine : Engine, request : ConnectionId) -> Dict:
connection_uuid = connection_get_uuid(request, allow_random=False)
def callback(session : Session) -> Optional[Dict]:
obj : Optional[ConnectionModel] = session.query(ConnectionModel)\
+ .options(selectinload(ConnectionModel.connection_service))\
+ .options(selectinload(ConnectionModel.connection_endpoints))\
+ .options(selectinload(ConnectionModel.connection_subservices))\
.filter_by(connection_uuid=connection_uuid).one_or_none()
return None if obj is None else obj.dump()
obj = run_transaction(sessionmaker(bind=db_engine), callback)
diff --git a/src/context/service/database/Context.py b/src/context/service/database/Context.py
index 9e05e54b38d3772ece2d87de0d98fb5a216088de..4654095034749e1de985705b242ba9fa05a82f6a 100644
--- a/src/context/service/database/Context.py
+++ b/src/context/service/database/Context.py
@@ -15,7 +15,7 @@
import datetime, logging
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.engine import Engine
-from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy.orm import Session, selectinload, sessionmaker
from sqlalchemy_cockroachdb import run_transaction
from typing import Dict, List, Optional, Tuple
from common.proto.context_pb2 import Context, ContextId
@@ -34,14 +34,22 @@ def context_list_ids(db_engine : Engine) -> List[Dict]:
def context_list_objs(db_engine : Engine) -> List[Dict]:
def callback(session : Session) -> List[Dict]:
- obj_list : List[ContextModel] = session.query(ContextModel).all()
+ obj_list : List[ContextModel] = session.query(ContextModel)\
+ .options(selectinload(ContextModel.topologies))\
+ .options(selectinload(ContextModel.services))\
+ .options(selectinload(ContextModel.slices))\
+ .all()
return [obj.dump() for obj in obj_list]
return run_transaction(sessionmaker(bind=db_engine), callback)
def context_get(db_engine : Engine, request : ContextId) -> Dict:
context_uuid = context_get_uuid(request, allow_random=False)
def callback(session : Session) -> Optional[Dict]:
- obj : Optional[ContextModel] = session.query(ContextModel).filter_by(context_uuid=context_uuid).one_or_none()
+ obj : Optional[ContextModel] = session.query(ContextModel)\
+ .options(selectinload(ContextModel.topologies))\
+ .options(selectinload(ContextModel.services))\
+ .options(selectinload(ContextModel.slices))\
+ .filter_by(context_uuid=context_uuid).one_or_none()
return None if obj is None else obj.dump()
obj = run_transaction(sessionmaker(bind=db_engine), callback)
if obj is None:
diff --git a/src/context/service/database/Device.py b/src/context/service/database/Device.py
index c5a19c9c4b0bca4f85ffe1211dbefc6b218d518e..3e106bc158ab804c7eada7284e9d1b883eb66264 100644
--- a/src/context/service/database/Device.py
+++ b/src/context/service/database/Device.py
@@ -15,12 +15,12 @@
import datetime, logging
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.engine import Engine
-from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy.orm import Session, selectinload, sessionmaker
from sqlalchemy_cockroachdb import run_transaction
from typing import Dict, List, Optional, Set, Tuple
from common.method_wrappers.ServiceExceptions import InvalidArgumentException, NotFoundException
-from common.proto.context_pb2 import Device, DeviceId, TopologyId
-from common.tools.grpc.Tools import grpc_message_to_json_string
+from common.proto.context_pb2 import Device, DeviceFilter, DeviceId, TopologyId
+#from common.tools.grpc.Tools import grpc_message_to_json_string
from common.tools.object_factory.Device import json_device_id
from context.service.database.uuids.Topology import topology_get_uuid
from .models.DeviceModel import DeviceModel
@@ -43,14 +43,22 @@ def device_list_ids(db_engine : Engine) -> List[Dict]:
def device_list_objs(db_engine : Engine) -> List[Dict]:
def callback(session : Session) -> List[Dict]:
- obj_list : List[DeviceModel] = session.query(DeviceModel).all()
+ obj_list : List[DeviceModel] = session.query(DeviceModel)\
+ .options(selectinload(DeviceModel.endpoints))\
+ .options(selectinload(DeviceModel.config_rules))\
+ .all()
+ #.options(selectinload(DeviceModel.components))\
return [obj.dump() for obj in obj_list]
return run_transaction(sessionmaker(bind=db_engine), callback)
def device_get(db_engine : Engine, request : DeviceId) -> Dict:
device_uuid = device_get_uuid(request, allow_random=False)
def callback(session : Session) -> Optional[Dict]:
- obj : Optional[DeviceModel] = session.query(DeviceModel).filter_by(device_uuid=device_uuid).one_or_none()
+ obj : Optional[DeviceModel] = session.query(DeviceModel)\
+ .options(selectinload(DeviceModel.endpoints))\
+ .options(selectinload(DeviceModel.config_rules))\
+ .filter_by(device_uuid=device_uuid).one_or_none()
+ #.options(selectinload(DeviceModel.components))\
return None if obj is None else obj.dump()
obj = run_transaction(sessionmaker(bind=db_engine), callback)
if obj is None:
@@ -163,7 +171,9 @@ def device_set(db_engine : Engine, request : Device) -> Tuple[Dict, bool]:
endpoint_updates = session.execute(stmt).fetchall()
updated_endpoints = any([(updated_at > created_at) for created_at,updated_at in endpoint_updates])
- if len(related_topologies) > 0:
+ if not updated or len(related_topologies) > 1:
+ # Only update topology-device relations when device is created (not updated) or when endpoints are
+ # modified (len(related_topologies) > 1).
session.execute(insert(TopologyDeviceModel).values(related_topologies).on_conflict_do_nothing(
index_elements=[TopologyDeviceModel.topology_uuid, TopologyDeviceModel.device_uuid]
))
@@ -182,3 +192,22 @@ def device_delete(db_engine : Engine, request : DeviceId) -> Tuple[Dict, bool]:
return num_deleted > 0
deleted = run_transaction(sessionmaker(bind=db_engine), callback)
return json_device_id(device_uuid),deleted
+
+def device_select(db_engine : Engine, request : DeviceFilter) -> List[Dict]:
+ device_uuids = [
+ device_get_uuid(device_id, allow_random=False)
+ for device_id in request.device_ids.device_ids
+ ]
+ dump_params = dict(
+ include_endpoints =request.include_endpoints,
+ include_config_rules=request.include_config_rules,
+ include_components =request.include_components,
+ )
+ def callback(session : Session) -> List[Dict]:
+ query = session.query(DeviceModel)
+ if request.include_endpoints : query = query.options(selectinload(DeviceModel.endpoints))
+ if request.include_config_rules: query = query.options(selectinload(DeviceModel.config_rules))
+ #if request.include_components : query = query.options(selectinload(DeviceModel.components))
+ obj_list : List[DeviceModel] = query.filter(DeviceModel.device_uuid.in_(device_uuids)).all()
+ return [obj.dump(**dump_params) for obj in obj_list]
+ return run_transaction(sessionmaker(bind=db_engine), callback)
diff --git a/src/context/service/database/EndPoint.py b/src/context/service/database/EndPoint.py
index e2f86893abdf62c9675a83b2a80ceed1227b85d4..b0df3bb8101a7b64a148e916178b1c9a77d511af 100644
--- a/src/context/service/database/EndPoint.py
+++ b/src/context/service/database/EndPoint.py
@@ -14,7 +14,7 @@
import logging
from sqlalchemy.engine import Engine
-from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy.orm import Session, selectinload, sessionmaker
from sqlalchemy_cockroachdb import run_transaction
from typing import Dict, List
from common.proto.context_pb2 import EndPointIdList
@@ -29,7 +29,8 @@ def endpoint_list_names(db_engine : Engine, request : EndPointIdList) -> List[Di
for endpoint_id in request.endpoint_ids
}
def callback(session : Session) -> List[Dict]:
- obj_list : List[EndPointModel] = \
- session.query(EndPointModel).filter(EndPointModel.endpoint_uuid.in_(endpoint_uuids)).all()
+ obj_list : List[EndPointModel] = session.query(EndPointModel)\
+ .options(selectinload(EndPointModel.device))\
+ .filter(EndPointModel.endpoint_uuid.in_(endpoint_uuids)).all()
return [obj.dump_name() for obj in obj_list]
return run_transaction(sessionmaker(bind=db_engine), callback)
diff --git a/src/context/service/database/Link.py b/src/context/service/database/Link.py
index 8d195cb1d548368c4b1d55f70a3d728ee6fd052e..f5bfc9dea5fb81fa8becfedc8ce1e4e0f59e7292 100644
--- a/src/context/service/database/Link.py
+++ b/src/context/service/database/Link.py
@@ -15,7 +15,7 @@
import datetime, logging
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.engine import Engine
-from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy.orm import Session, selectinload, sessionmaker
from sqlalchemy_cockroachdb import run_transaction
from typing import Dict, List, Optional, Set, Tuple
from common.proto.context_pb2 import Link, LinkId
@@ -36,14 +36,18 @@ def link_list_ids(db_engine : Engine) -> List[Dict]:
def link_list_objs(db_engine : Engine) -> List[Dict]:
def callback(session : Session) -> List[Dict]:
- obj_list : List[LinkModel] = session.query(LinkModel).all()
+ obj_list : List[LinkModel] = session.query(LinkModel)\
+ .options(selectinload(LinkModel.link_endpoints))\
+ .all()
return [obj.dump() for obj in obj_list]
return run_transaction(sessionmaker(bind=db_engine), callback)
def link_get(db_engine : Engine, request : LinkId) -> Dict:
link_uuid = link_get_uuid(request, allow_random=False)
def callback(session : Session) -> Optional[Dict]:
- obj : Optional[LinkModel] = session.query(LinkModel).filter_by(link_uuid=link_uuid).one_or_none()
+ obj : Optional[LinkModel] = session.query(LinkModel)\
+ .options(selectinload(LinkModel.link_endpoints))\
+ .filter_by(link_uuid=link_uuid).one_or_none()
return None if obj is None else obj.dump()
obj = run_transaction(sessionmaker(bind=db_engine), callback)
if obj is None:
@@ -64,13 +68,14 @@ def link_set(db_engine : Engine, request : Link) -> Tuple[Dict, bool]:
topology_uuids : Set[str] = set()
related_topologies : List[Dict] = list()
link_endpoints_data : List[Dict] = list()
- for endpoint_id in request.link_endpoint_ids:
+ for i,endpoint_id in enumerate(request.link_endpoint_ids):
endpoint_topology_uuid, _, endpoint_uuid = endpoint_get_uuid(
endpoint_id, allow_random=False)
link_endpoints_data.append({
'link_uuid' : link_uuid,
'endpoint_uuid': endpoint_uuid,
+ 'position' : i,
})
if endpoint_topology_uuid not in topology_uuids:
diff --git a/src/context/service/database/PolicyRule.py b/src/context/service/database/PolicyRule.py
index e95cec4ae533795b23b8fd4e2f26ac9000c1bcce..13f0a2698c17874e1e15f4d6a1d527d366141f56 100644
--- a/src/context/service/database/PolicyRule.py
+++ b/src/context/service/database/PolicyRule.py
@@ -15,7 +15,7 @@
import datetime, json
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.engine import Engine
-from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy.orm import Session, selectinload, sessionmaker
from sqlalchemy_cockroachdb import run_transaction
from typing import Dict, List, Optional, Set, Tuple
from common.proto.policy_pb2 import PolicyRule, PolicyRuleId, PolicyRuleIdList, PolicyRuleList
@@ -31,14 +31,15 @@ from .uuids.Service import service_get_uuid
def policyrule_list_ids(db_engine : Engine) -> List[Dict]:
def callback(session : Session) -> List[Dict]:
obj_list : List[PolicyRuleModel] = session.query(PolicyRuleModel).all()
- #.options(selectinload(PolicyRuleModel.topology)).filter_by(context_uuid=context_uuid).one_or_none()
return [obj.dump_id() for obj in obj_list]
return run_transaction(sessionmaker(bind=db_engine), callback)
def policyrule_list_objs(db_engine : Engine) -> List[Dict]:
def callback(session : Session) -> List[Dict]:
- obj_list : List[PolicyRuleModel] = session.query(PolicyRuleModel).all()
- #.options(selectinload(PolicyRuleModel.topology)).filter_by(context_uuid=context_uuid).one_or_none()
+ obj_list : List[PolicyRuleModel] = session.query(PolicyRuleModel)\
+ .options(selectinload(PolicyRuleModel.policyrule_service))\
+ .options(selectinload(PolicyRuleModel.policyrule_devices))\
+ .all()
return [obj.dump() for obj in obj_list]
return run_transaction(sessionmaker(bind=db_engine), callback)
@@ -46,6 +47,8 @@ def policyrule_get(db_engine : Engine, request : PolicyRuleId) -> PolicyRule:
policyrule_uuid = policyrule_get_uuid(request, allow_random=False)
def callback(session : Session) -> Optional[Dict]:
obj : Optional[PolicyRuleModel] = session.query(PolicyRuleModel)\
+ .options(selectinload(PolicyRuleModel.policyrule_service))\
+ .options(selectinload(PolicyRuleModel.policyrule_devices))\
.filter_by(policyrule_uuid=policyrule_uuid).one_or_none()
return None if obj is None else obj.dump()
obj = run_transaction(sessionmaker(bind=db_engine), callback)
diff --git a/src/context/service/database/Service.py b/src/context/service/database/Service.py
index a81a80c3c2398fed16842bcc3d8aa16342edb72b..32484a3095c3d937392f580597339fe047d36e3f 100644
--- a/src/context/service/database/Service.py
+++ b/src/context/service/database/Service.py
@@ -15,10 +15,10 @@
import datetime, logging
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.engine import Engine
-from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy.orm import Session, selectinload, sessionmaker
from sqlalchemy_cockroachdb import run_transaction
from typing import Dict, List, Optional, Tuple
-from common.proto.context_pb2 import ContextId, Service, ServiceId
+from common.proto.context_pb2 import ContextId, Service, ServiceFilter, ServiceId
from common.method_wrappers.ServiceExceptions import InvalidArgumentException, NotFoundException
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Service import json_service_id
@@ -43,14 +43,22 @@ def service_list_ids(db_engine : Engine, request : ContextId) -> List[Dict]:
def service_list_objs(db_engine : Engine, request : ContextId) -> List[Dict]:
context_uuid = context_get_uuid(request, allow_random=False)
def callback(session : Session) -> List[Dict]:
- obj_list : List[ServiceModel] = session.query(ServiceModel).filter_by(context_uuid=context_uuid).all()
+ obj_list : List[ServiceModel] = session.query(ServiceModel)\
+ .options(selectinload(ServiceModel.service_endpoints))\
+ .options(selectinload(ServiceModel.constraints))\
+ .options(selectinload(ServiceModel.config_rules))\
+ .filter_by(context_uuid=context_uuid).all()
return [obj.dump() for obj in obj_list]
return run_transaction(sessionmaker(bind=db_engine), callback)
def service_get(db_engine : Engine, request : ServiceId) -> Dict:
_,service_uuid = service_get_uuid(request, allow_random=False)
def callback(session : Session) -> Optional[Dict]:
- obj : Optional[ServiceModel] = session.query(ServiceModel).filter_by(service_uuid=service_uuid).one_or_none()
+ obj : Optional[ServiceModel] = session.query(ServiceModel)\
+ .options(selectinload(ServiceModel.service_endpoints))\
+ .options(selectinload(ServiceModel.constraints))\
+ .options(selectinload(ServiceModel.config_rules))\
+ .filter_by(service_uuid=service_uuid).one_or_none()
return None if obj is None else obj.dump()
obj = run_transaction(sessionmaker(bind=db_engine), callback)
if obj is None:
@@ -91,6 +99,7 @@ def service_set(db_engine : Engine, request : Service) -> Tuple[Dict, bool]:
service_endpoints_data.append({
'service_uuid' : service_uuid,
'endpoint_uuid': endpoint_uuid,
+ 'position' : i,
})
constraints = compose_constraints_data(request.service_constraints, now, service_uuid=service_uuid)
@@ -144,3 +153,22 @@ def service_delete(db_engine : Engine, request : ServiceId) -> Tuple[Dict, bool]
return num_deleted > 0
deleted = run_transaction(sessionmaker(bind=db_engine), callback)
return json_service_id(service_uuid, json_context_id(context_uuid)),deleted
+
+def service_select(db_engine : Engine, request : ServiceFilter) -> List[Dict]:
+ service_uuids = [
+ service_get_uuid(service_id, allow_random=False)[1]
+ for service_id in request.service_ids.service_ids
+ ]
+ dump_params = dict(
+ include_endpoint_ids=request.include_endpoint_ids,
+ include_constraints =request.include_constraints,
+ include_config_rules=request.include_config_rules,
+ )
+ def callback(session : Session) -> List[Dict]:
+ query = session.query(ServiceModel)
+ if request.include_endpoint_ids: query = query.options(selectinload(ServiceModel.service_endpoints))
+ if request.include_constraints : query = query.options(selectinload(ServiceModel.constraints))
+ if request.include_config_rules: query = query.options(selectinload(ServiceModel.config_rules))
+ obj_list : List[ServiceModel] = query.filter(ServiceModel.service_uuid.in_(service_uuids)).all()
+ return [obj.dump(**dump_params) for obj in obj_list]
+ return run_transaction(sessionmaker(bind=db_engine), callback)
diff --git a/src/context/service/database/Slice.py b/src/context/service/database/Slice.py
index 1d6781d53f7c85d8cb878b1b38b0de65b4ef5726..abd140024f2a13289c7af6a3bafe363a8247e053 100644
--- a/src/context/service/database/Slice.py
+++ b/src/context/service/database/Slice.py
@@ -16,10 +16,10 @@ import datetime, logging
from sqlalchemy import and_
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.engine import Engine
-from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy.orm import Session, selectinload, sessionmaker
from sqlalchemy_cockroachdb import run_transaction
from typing import Dict, List, Optional, Set, Tuple
-from common.proto.context_pb2 import ContextId, Slice, SliceId
+from common.proto.context_pb2 import ContextId, Slice, SliceFilter, SliceId
from common.method_wrappers.ServiceExceptions import InvalidArgumentException, NotFoundException
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Slice import json_slice_id
@@ -44,14 +44,26 @@ def slice_list_ids(db_engine : Engine, request : ContextId) -> List[Dict]:
def slice_list_objs(db_engine : Engine, request : ContextId) -> List[Dict]:
context_uuid = context_get_uuid(request, allow_random=False)
def callback(session : Session) -> List[Dict]:
- obj_list : List[SliceModel] = session.query(SliceModel).filter_by(context_uuid=context_uuid).all()
+ obj_list : List[SliceModel] = session.query(SliceModel)\
+ .options(selectinload(SliceModel.slice_endpoints))\
+ .options(selectinload(SliceModel.slice_services))\
+ .options(selectinload(SliceModel.slice_subslices))\
+ .options(selectinload(SliceModel.constraints))\
+ .options(selectinload(SliceModel.config_rules))\
+ .filter_by(context_uuid=context_uuid).all()
return [obj.dump() for obj in obj_list]
return run_transaction(sessionmaker(bind=db_engine), callback)
def slice_get(db_engine : Engine, request : SliceId) -> Dict:
_,slice_uuid = slice_get_uuid(request, allow_random=False)
def callback(session : Session) -> Optional[Dict]:
- obj : Optional[SliceModel] = session.query(SliceModel).filter_by(slice_uuid=slice_uuid).one_or_none()
+ obj : Optional[SliceModel] = session.query(SliceModel)\
+ .options(selectinload(SliceModel.slice_endpoints))\
+ .options(selectinload(SliceModel.slice_services))\
+ .options(selectinload(SliceModel.slice_subslices))\
+ .options(selectinload(SliceModel.constraints))\
+ .options(selectinload(SliceModel.config_rules))\
+ .filter_by(slice_uuid=slice_uuid).one_or_none()
return None if obj is None else obj.dump()
obj = run_transaction(sessionmaker(bind=db_engine), callback)
if obj is None:
@@ -91,6 +103,7 @@ def slice_set(db_engine : Engine, request : Slice) -> Tuple[Dict, bool]:
slice_endpoints_data.append({
'slice_uuid' : slice_uuid,
'endpoint_uuid': endpoint_uuid,
+ 'position' : i,
})
slice_services_data : List[Dict] = list()
@@ -239,3 +252,26 @@ def slice_delete(db_engine : Engine, request : SliceId) -> Tuple[Dict, bool]:
return num_deleted > 0
deleted = run_transaction(sessionmaker(bind=db_engine), callback)
return json_slice_id(slice_uuid, json_context_id(context_uuid)),deleted
+
+def slice_select(db_engine : Engine, request : SliceFilter) -> List[Dict]:
+ slice_uuids = [
+ slice_get_uuid(slice_id, allow_random=False)[1]
+ for slice_id in request.slice_ids.slice_ids
+ ]
+ dump_params = dict(
+ include_endpoint_ids=request.include_endpoint_ids,
+ include_constraints =request.include_constraints,
+ include_service_ids =request.include_service_ids,
+ include_subslice_ids=request.include_subslice_ids,
+ include_config_rules=request.include_config_rules,
+ )
+ def callback(session : Session) -> List[Dict]:
+ query = session.query(SliceModel)
+ if request.include_endpoint_ids: query = query.options(selectinload(SliceModel.slice_endpoints))
+ if request.include_service_ids : query = query.options(selectinload(SliceModel.slice_services))
+ if request.include_subslice_ids: query = query.options(selectinload(SliceModel.slice_subslices))
+ if request.include_constraints : query = query.options(selectinload(SliceModel.constraints))
+ if request.include_config_rules: query = query.options(selectinload(SliceModel.config_rules))
+ obj_list : List[SliceModel] = query.filter(SliceModel.slice_uuid.in_(slice_uuids)).all()
+ return [obj.dump(**dump_params) for obj in obj_list]
+ return run_transaction(sessionmaker(bind=db_engine), callback)
diff --git a/src/context/service/database/Topology.py b/src/context/service/database/Topology.py
index e2c6e2e996ac9321d0d8b9ae2ecea018b650632f..4440299b63f68613854e79998270872389d385cb 100644
--- a/src/context/service/database/Topology.py
+++ b/src/context/service/database/Topology.py
@@ -15,14 +15,16 @@
import datetime, logging
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.engine import Engine
-from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy.orm import Session, selectinload, sessionmaker
from sqlalchemy_cockroachdb import run_transaction
from typing import Dict, List, Optional, Tuple
from common.proto.context_pb2 import ContextId, Topology, TopologyId
from common.method_wrappers.ServiceExceptions import NotFoundException
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Topology import json_topology_id
-from .models.TopologyModel import TopologyModel
+from .models.DeviceModel import DeviceModel
+from .models.LinkModel import LinkModel
+from .models.TopologyModel import TopologyDeviceModel, TopologyLinkModel, TopologyModel
from .uuids.Context import context_get_uuid
from .uuids.Topology import topology_get_uuid
@@ -38,7 +40,10 @@ def topology_list_ids(db_engine : Engine, request : ContextId) -> List[Dict]:
def topology_list_objs(db_engine : Engine, request : ContextId) -> List[Dict]:
context_uuid = context_get_uuid(request, allow_random=False)
def callback(session : Session) -> List[Dict]:
- obj_list : List[TopologyModel] = session.query(TopologyModel).filter_by(context_uuid=context_uuid).all()
+ obj_list : List[TopologyModel] = session.query(TopologyModel)\
+ .options(selectinload(TopologyModel.topology_devices))\
+ .options(selectinload(TopologyModel.topology_links))\
+ .filter_by(context_uuid=context_uuid).all()
return [obj.dump() for obj in obj_list]
return run_transaction(sessionmaker(bind=db_engine), callback)
@@ -46,6 +51,8 @@ def topology_get(db_engine : Engine, request : TopologyId) -> Dict:
_,topology_uuid = topology_get_uuid(request, allow_random=False)
def callback(session : Session) -> Optional[Dict]:
obj : Optional[TopologyModel] = session.query(TopologyModel)\
+ .options(selectinload(TopologyModel.topology_devices))\
+ .options(selectinload(TopologyModel.topology_links))\
.filter_by(topology_uuid=topology_uuid).one_or_none()
return None if obj is None else obj.dump()
obj = run_transaction(sessionmaker(bind=db_engine), callback)
@@ -62,7 +69,10 @@ def topology_get_details(db_engine : Engine, request : TopologyId) -> Dict:
_,topology_uuid = topology_get_uuid(request, allow_random=False)
def callback(session : Session) -> Optional[Dict]:
obj : Optional[TopologyModel] = session.query(TopologyModel)\
+ .options(selectinload(TopologyModel.topology_devices, TopologyDeviceModel.device, DeviceModel.endpoints))\
+ .options(selectinload(TopologyModel.topology_links, TopologyLinkModel.link, LinkModel.link_endpoints))\
.filter_by(topology_uuid=topology_uuid).one_or_none()
+ #.options(selectinload(DeviceModel.components))\
return None if obj is None else obj.dump_details()
obj = run_transaction(sessionmaker(bind=db_engine), callback)
if obj is None:
diff --git a/src/context/service/database/models/ConnectionModel.py b/src/context/service/database/models/ConnectionModel.py
index 156e33c6bb32e237af241035f1d9672b0b419222..f71d4177893d146af2f413781b51930c9909d827 100644
--- a/src/context/service/database/models/ConnectionModel.py
+++ b/src/context/service/database/models/ConnectionModel.py
@@ -59,8 +59,8 @@ class ConnectionEndPointModel(_Base):
endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True, index=True)
position = Column(Integer, nullable=False)
- connection = relationship('ConnectionModel', back_populates='connection_endpoints', lazy='joined')
- endpoint = relationship('EndPointModel', lazy='joined') # back_populates='connection_endpoints'
+ connection = relationship('ConnectionModel', back_populates='connection_endpoints') #, lazy='joined'
+ endpoint = relationship('EndPointModel', lazy='selectin') # back_populates='connection_endpoints'
__table_args__ = (
CheckConstraint(position >= 0, name='check_position_value'),
@@ -72,5 +72,5 @@ class ConnectionSubServiceModel(_Base):
connection_uuid = Column(ForeignKey('connection.connection_uuid', ondelete='CASCADE' ), primary_key=True)
subservice_uuid = Column(ForeignKey('service.service_uuid', ondelete='RESTRICT'), primary_key=True, index=True)
- connection = relationship('ConnectionModel', back_populates='connection_subservices', lazy='joined')
- subservice = relationship('ServiceModel', lazy='joined') # back_populates='connection_subservices'
+ connection = relationship('ConnectionModel', back_populates='connection_subservices') #, lazy='joined'
+ subservice = relationship('ServiceModel', lazy='selectin') # back_populates='connection_subservices'
diff --git a/src/context/service/database/models/DeviceModel.py b/src/context/service/database/models/DeviceModel.py
index 2124386d16e2e33aec58f5b39bf0f89e3c6589f1..24130841d2bafde3608f2fa1cbdd476d28acba46 100644
--- a/src/context/service/database/models/DeviceModel.py
+++ b/src/context/service/database/models/DeviceModel.py
@@ -16,7 +16,7 @@ import operator
from sqlalchemy import Column, DateTime, Enum, String
from sqlalchemy.dialects.postgresql import ARRAY, UUID
from sqlalchemy.orm import relationship
-from typing import Dict
+from typing import Dict, List
from .enums.DeviceDriver import ORM_DeviceDriverEnum
from .enums.DeviceOperationalStatus import ORM_DeviceOperationalStatusEnum
from ._Base import _Base
@@ -39,19 +39,29 @@ class DeviceModel(_Base):
def dump_id(self) -> Dict:
return {'device_uuid': {'uuid': self.device_uuid}}
- def dump(self) -> Dict:
- return {
+ def dump_endpoints(self) -> List[Dict]:
+ return [endpoint.dump() for endpoint in self.endpoints]
+
+ def dump_config_rules(self) -> Dict:
+ return {'config_rules': [
+ config_rule.dump()
+ for config_rule in sorted(self.config_rules, key=operator.attrgetter('position'))
+ ]}
+
+ #def dump_components(self) -> List[Dict]:
+ # return []
+
+ def dump(self,
+ include_endpoints : bool = True, include_config_rules : bool = True, include_components : bool = True,
+ ) -> Dict:
+ result = {
'device_id' : self.dump_id(),
'name' : self.device_name,
'device_type' : self.device_type,
'device_operational_status': self.device_operational_status.value,
'device_drivers' : [driver.value for driver in self.device_drivers],
- 'device_config' : {'config_rules': [
- config_rule.dump()
- for config_rule in sorted(self.config_rules, key=operator.attrgetter('position'))
- ]},
- 'device_endpoints' : [
- endpoint.dump()
- for endpoint in self.endpoints
- ],
}
+ if include_endpoints: result['device_endpoints'] = self.dump_endpoints()
+ if include_config_rules: result['device_config'] = self.dump_config_rules()
+ #if include_components: result['components'] = self.dump_components()
+ return result
diff --git a/src/context/service/database/models/EndPointModel.py b/src/context/service/database/models/EndPointModel.py
index 12ba7e10e7c3d5789f9bf16ad7b4f50c35a36bf5..a079f9900e39fdf3a4329e604f4e596e7f5d1f89 100644
--- a/src/context/service/database/models/EndPointModel.py
+++ b/src/context/service/database/models/EndPointModel.py
@@ -31,8 +31,8 @@ class EndPointModel(_Base):
created_at = Column(DateTime, nullable=False)
updated_at = Column(DateTime, nullable=False)
- device = relationship('DeviceModel', back_populates='endpoints')
- topology = relationship('TopologyModel')
+ device = relationship('DeviceModel', back_populates='endpoints') # lazy='selectin'
+ topology = relationship('TopologyModel', lazy='selectin')
#link_endpoints = relationship('LinkEndPointModel', back_populates='endpoint' )
#service_endpoints = relationship('ServiceEndPointModel', back_populates='endpoint' )
diff --git a/src/context/service/database/models/LinkModel.py b/src/context/service/database/models/LinkModel.py
index ee591f5c8404cd7f0f6c97651b5f731a51c43303..9c16da3c9146f28352e8b4f7a6f9ab85f870c8b7 100644
--- a/src/context/service/database/models/LinkModel.py
+++ b/src/context/service/database/models/LinkModel.py
@@ -12,7 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from sqlalchemy import Column, DateTime, ForeignKey, String
+import operator
+from sqlalchemy import CheckConstraint, Column, DateTime, ForeignKey, Integer, String
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from typing import Dict
@@ -38,7 +39,7 @@ class LinkModel(_Base):
'name' : self.link_name,
'link_endpoint_ids': [
link_endpoint.endpoint.dump_id()
- for link_endpoint in self.link_endpoints
+ for link_endpoint in sorted(self.link_endpoints, key=operator.attrgetter('position'))
],
}
@@ -47,6 +48,11 @@ class LinkEndPointModel(_Base):
link_uuid = Column(ForeignKey('link.link_uuid', ondelete='CASCADE' ), primary_key=True)
endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True, index=True)
+ position = Column(Integer, nullable=False)
- link = relationship('LinkModel', back_populates='link_endpoints', lazy='joined')
- endpoint = relationship('EndPointModel', lazy='joined') # back_populates='link_endpoints'
+ link = relationship('LinkModel', back_populates='link_endpoints') #, lazy='selectin'
+ endpoint = relationship('EndPointModel', lazy='selectin') # back_populates='link_endpoints'
+
+ __table_args__ = (
+ CheckConstraint(position >= 0, name='check_position_value'),
+ )
diff --git a/src/context/service/database/models/PolicyRuleModel.py b/src/context/service/database/models/PolicyRuleModel.py
index 2f0c8a326a57a05ab1fd623a968dea0bc39d9e76..32364e289cf68fe760c60eb27cde933f7cf448a4 100644
--- a/src/context/service/database/models/PolicyRuleModel.py
+++ b/src/context/service/database/models/PolicyRuleModel.py
@@ -64,7 +64,7 @@ class PolicyRuleModel(_Base):
'deviceList': [{'device_uuid': {'uuid': pr_d.device_uuid}} for pr_d in self.policyrule_devices],
}
if self.policyrule_kind == PolicyRuleKindEnum.SERVICE:
- result['serviceId'] = self.policyrule_service.dump_id(),
+ result['serviceId'] = self.policyrule_service.dump_id()
return {self.policyrule_kind.value: result}
class PolicyRuleDeviceModel(_Base):
@@ -74,4 +74,4 @@ class PolicyRuleDeviceModel(_Base):
device_uuid = Column(ForeignKey('device.device_uuid', ondelete='RESTRICT'), primary_key=True, index=True)
#policyrule = relationship('PolicyRuleModel', lazy='joined') # back_populates='policyrule_devices'
- device = relationship('DeviceModel', lazy='joined') # back_populates='policyrule_devices'
+ device = relationship('DeviceModel', lazy='selectin') # back_populates='policyrule_devices'
diff --git a/src/context/service/database/models/ServiceModel.py b/src/context/service/database/models/ServiceModel.py
index 09ff381b5eb374ea752590bba5403fe816319036..ef6e1b06aaaa616ede6f9633e4e0d7fc0aabf336 100644
--- a/src/context/service/database/models/ServiceModel.py
+++ b/src/context/service/database/models/ServiceModel.py
@@ -13,10 +13,10 @@
# limitations under the License.
import operator
-from sqlalchemy import Column, DateTime, Enum, ForeignKey, String
+from sqlalchemy import CheckConstraint, Column, DateTime, Enum, ForeignKey, Integer, String
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
-from typing import Dict
+from typing import Dict, List
from .enums.ServiceStatus import ORM_ServiceStatusEnum
from .enums.ServiceType import ORM_ServiceTypeEnum
from ._Base import _Base
@@ -32,10 +32,10 @@ class ServiceModel(_Base):
created_at = Column(DateTime, nullable=False)
updated_at = Column(DateTime, nullable=False)
- context = relationship('ContextModel', back_populates='services')
- service_endpoints = relationship('ServiceEndPointModel') # lazy='joined', back_populates='service'
- constraints = relationship('ConstraintModel', passive_deletes=True) # lazy='joined', back_populates='service'
- config_rules = relationship('ConfigRuleModel', passive_deletes=True) # lazy='joined', back_populates='service'
+ context = relationship('ContextModel', back_populates='services', lazy='selectin')
+ service_endpoints = relationship('ServiceEndPointModel') # lazy='selectin', back_populates='service'
+ constraints = relationship('ConstraintModel', passive_deletes=True) # lazy='selectin', back_populates='service'
+ config_rules = relationship('ConfigRuleModel', passive_deletes=True) # lazy='selectin', back_populates='service'
def dump_id(self) -> Dict:
return {
@@ -43,31 +43,48 @@ class ServiceModel(_Base):
'service_uuid': {'uuid': self.service_uuid},
}
- def dump(self) -> Dict:
- return {
- 'service_id' : self.dump_id(),
- 'name' : self.service_name,
- 'service_type' : self.service_type.value,
- 'service_status' : {'service_status': self.service_status.value},
- 'service_endpoint_ids': [
- service_endpoint.endpoint.dump_id()
- for service_endpoint in self.service_endpoints
- ],
- 'service_constraints' : [
- constraint.dump()
- for constraint in sorted(self.constraints, key=operator.attrgetter('position'))
- ],
- 'service_config' : {'config_rules': [
- config_rule.dump()
- for config_rule in sorted(self.config_rules, key=operator.attrgetter('position'))
- ]},
+ def dump_endpoint_ids(self) -> List[Dict]:
+ return [
+ service_endpoint.endpoint.dump_id()
+ for service_endpoint in sorted(self.service_endpoints, key=operator.attrgetter('position'))
+ ]
+
+ def dump_constraints(self) -> List[Dict]:
+ return [
+ constraint.dump()
+ for constraint in sorted(self.constraints, key=operator.attrgetter('position'))
+ ]
+
+ def dump_config_rules(self) -> Dict:
+ return {'config_rules': [
+ config_rule.dump()
+ for config_rule in sorted(self.config_rules, key=operator.attrgetter('position'))
+ ]}
+
+ def dump(
+ self, include_endpoint_ids : bool = True, include_constraints : bool = True, include_config_rules : bool = True
+ ) -> Dict:
+ result = {
+ 'service_id' : self.dump_id(),
+ 'name' : self.service_name,
+ 'service_type' : self.service_type.value,
+ 'service_status': {'service_status': self.service_status.value},
}
+ if include_endpoint_ids: result['service_endpoint_ids'] = self.dump_endpoint_ids()
+ if include_constraints: result['service_constraints'] = self.dump_constraints()
+ if include_config_rules: result['service_config'] = self.dump_config_rules()
+ return result
class ServiceEndPointModel(_Base):
__tablename__ = 'service_endpoint'
service_uuid = Column(ForeignKey('service.service_uuid', ondelete='CASCADE' ), primary_key=True)
endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True, index=True)
+ position = Column(Integer, nullable=False)
+
+ service = relationship('ServiceModel', back_populates='service_endpoints') # lazy='selectin'
+ endpoint = relationship('EndPointModel', lazy='selectin') # back_populates='service_endpoints'
- service = relationship('ServiceModel', back_populates='service_endpoints', lazy='joined')
- endpoint = relationship('EndPointModel', lazy='joined') # back_populates='service_endpoints'
+ __table_args__ = (
+ CheckConstraint(position >= 0, name='check_position_value'),
+ )
diff --git a/src/context/service/database/models/SliceModel.py b/src/context/service/database/models/SliceModel.py
index 2d6c884169154fee8d44c26464416c6708c650b1..423af244e186301cf3132eea3fc7cbea16bf9fe9 100644
--- a/src/context/service/database/models/SliceModel.py
+++ b/src/context/service/database/models/SliceModel.py
@@ -13,10 +13,10 @@
# limitations under the License.
import operator
-from sqlalchemy import Column, DateTime, Enum, ForeignKey, String
+from sqlalchemy import CheckConstraint, Column, DateTime, Enum, ForeignKey, Integer, String
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
-from typing import Dict
+from typing import Dict, List
from .enums.SliceStatus import ORM_SliceStatusEnum
from ._Base import _Base
@@ -32,13 +32,13 @@ class SliceModel(_Base):
created_at = Column(DateTime, nullable=False)
updated_at = Column(DateTime, nullable=False)
- context = relationship('ContextModel', back_populates='slices')
- slice_endpoints = relationship('SliceEndPointModel') # lazy='joined', back_populates='slice'
- slice_services = relationship('SliceServiceModel') # lazy='joined', back_populates='slice'
+ context = relationship('ContextModel', back_populates='slices', lazy='selectin')
+ slice_endpoints = relationship('SliceEndPointModel') # lazy='selectin', back_populates='slice'
+ slice_services = relationship('SliceServiceModel') # lazy='selectin', back_populates='slice'
slice_subslices = relationship(
'SliceSubSliceModel', primaryjoin='slice.c.slice_uuid == slice_subslice.c.slice_uuid')
- constraints = relationship('ConstraintModel', passive_deletes=True) # lazy='joined', back_populates='slice'
- config_rules = relationship('ConfigRuleModel', passive_deletes=True) # lazy='joined', back_populates='slice'
+ constraints = relationship('ConstraintModel', passive_deletes=True) # lazy='selectin', back_populates='slice'
+ config_rules = relationship('ConfigRuleModel', passive_deletes=True) # lazy='selectin', back_populates='slice'
def dump_id(self) -> Dict:
return {
@@ -46,45 +46,73 @@ class SliceModel(_Base):
'slice_uuid': {'uuid': self.slice_uuid},
}
- def dump(self) -> Dict:
+
+ def dump_endpoint_ids(self) -> List[Dict]:
+ return [
+ slice_endpoint.endpoint.dump_id()
+ for slice_endpoint in sorted(self.slice_endpoints, key=operator.attrgetter('position'))
+ ]
+
+ def dump_constraints(self) -> List[Dict]:
+ return [
+ constraint.dump()
+ for constraint in sorted(self.constraints, key=operator.attrgetter('position'))
+ ]
+
+ def dump_config_rules(self) -> Dict:
+ return {'config_rules': [
+ config_rule.dump()
+ for config_rule in sorted(self.config_rules, key=operator.attrgetter('position'))
+ ]}
+
+ def dump_service_ids(self) -> List[Dict]:
+ return [
+ slice_service.service.dump_id()
+ for slice_service in self.slice_services
+ ]
+
+ def dump_subslice_ids(self) -> List[Dict]:
+ return [
+ slice_subslice.subslice.dump_id()
+ for slice_subslice in self.slice_subslices
+ ]
+
+ def dump_owner_id(self) -> Dict:
return {
- 'slice_id' : self.dump_id(),
- 'name' : self.slice_name,
- 'slice_status' : {'slice_status': self.slice_status.value},
- 'slice_endpoint_ids': [
- slice_endpoint.endpoint.dump_id()
- for slice_endpoint in self.slice_endpoints
- ],
- 'slice_constraints' : [
- constraint.dump()
- for constraint in sorted(self.constraints, key=operator.attrgetter('position'))
- ],
- 'slice_config' : {'config_rules': [
- config_rule.dump()
- for config_rule in sorted(self.config_rules, key=operator.attrgetter('position'))
- ]},
- 'slice_service_ids': [
- slice_service.service.dump_id()
- for slice_service in self.slice_services
- ],
- 'slice_subslice_ids': [
- slice_subslice.subslice.dump_id()
- for slice_subslice in self.slice_subslices
- ],
- 'slice_owner': {
- 'owner_uuid': {'uuid': self.slice_owner_uuid},
- 'owner_string': self.slice_owner_string
- }
+ 'owner_uuid': {'uuid': self.slice_owner_uuid},
+ 'owner_string': self.slice_owner_string
+ }
+
+ def dump(
+ self, include_endpoint_ids : bool = True, include_constraints : bool = True, include_service_ids : bool = True,
+ include_subslice_ids : bool = True, include_config_rules : bool = True
+ ) -> Dict:
+ result = {
+ 'slice_id' : self.dump_id(),
+ 'name' : self.slice_name,
+ 'slice_status': {'slice_status': self.slice_status.value},
+ 'slice_owner' : self.dump_owner_id()
}
+ if include_endpoint_ids: result['slice_endpoint_ids'] = self.dump_endpoint_ids()
+ if include_constraints : result['slice_constraints' ] = self.dump_constraints()
+ if include_service_ids : result['slice_service_ids' ] = self.dump_service_ids()
+ if include_subslice_ids: result['slice_subslice_ids'] = self.dump_subslice_ids()
+ if include_config_rules: result['slice_config' ] = self.dump_config_rules()
+ return result
class SliceEndPointModel(_Base):
__tablename__ = 'slice_endpoint'
slice_uuid = Column(ForeignKey('slice.slice_uuid', ondelete='CASCADE' ), primary_key=True)
endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True, index=True)
+ position = Column(Integer, nullable=False)
+
+ slice = relationship('SliceModel', back_populates='slice_endpoints') #, lazy='selectin'
+ endpoint = relationship('EndPointModel', lazy='selectin') # back_populates='slice_endpoints'
- slice = relationship('SliceModel', back_populates='slice_endpoints', lazy='joined')
- endpoint = relationship('EndPointModel', lazy='joined') # back_populates='slice_endpoints'
+ __table_args__ = (
+ CheckConstraint(position >= 0, name='check_position_value'),
+ )
class SliceServiceModel(_Base):
__tablename__ = 'slice_service'
@@ -92,8 +120,8 @@ class SliceServiceModel(_Base):
slice_uuid = Column(ForeignKey('slice.slice_uuid', ondelete='CASCADE' ), primary_key=True)
service_uuid = Column(ForeignKey('service.service_uuid', ondelete='RESTRICT'), primary_key=True, index=True)
- slice = relationship('SliceModel', back_populates='slice_services', lazy='joined')
- service = relationship('ServiceModel', lazy='joined') # back_populates='slice_services'
+ slice = relationship('SliceModel', back_populates='slice_services') # , lazy='selectin'
+ service = relationship('ServiceModel', lazy='selectin') # back_populates='slice_services'
class SliceSubSliceModel(_Base):
__tablename__ = 'slice_subslice'
@@ -102,5 +130,5 @@ class SliceSubSliceModel(_Base):
subslice_uuid = Column(ForeignKey('slice.slice_uuid', ondelete='CASCADE'), primary_key=True, index=True)
slice = relationship(
- 'SliceModel', foreign_keys='SliceSubSliceModel.slice_uuid', back_populates='slice_subslices', lazy='joined')
- subslice = relationship('SliceModel', foreign_keys='SliceSubSliceModel.subslice_uuid', lazy='joined')
+ 'SliceModel', foreign_keys='SliceSubSliceModel.slice_uuid', back_populates='slice_subslices') #, lazy='selectin'
+ subslice = relationship('SliceModel', foreign_keys='SliceSubSliceModel.subslice_uuid', lazy='selectin')
diff --git a/src/context/service/database/models/TopologyModel.py b/src/context/service/database/models/TopologyModel.py
index 7dc2333f0a9b979f251c173d850a235dcb822d91..0ed4a038bcf4426f4cf112bd03c5cb36cb42c822 100644
--- a/src/context/service/database/models/TopologyModel.py
+++ b/src/context/service/database/models/TopologyModel.py
@@ -27,7 +27,7 @@ class TopologyModel(_Base):
created_at = Column(DateTime, nullable=False)
updated_at = Column(DateTime, nullable=False)
- context = relationship('ContextModel', back_populates='topologies')
+ context = relationship('ContextModel', back_populates='topologies', lazy='selectin')
topology_devices = relationship('TopologyDeviceModel') # back_populates='topology'
topology_links = relationship('TopologyLinkModel' ) # back_populates='topology'
@@ -46,11 +46,19 @@ class TopologyModel(_Base):
}
def dump_details(self) -> Dict:
+ devices = [
+ td.device.dump(include_config_rules=False, include_components=False)
+ for td in self.topology_devices
+ ]
+ links = [
+ tl.link.dump()
+ for tl in self.topology_links
+ ]
return {
'topology_id': self.dump_id(),
'name' : self.topology_name,
- 'devices' : [td.device.dump() for td in self.topology_devices],
- 'links' : [tl.link.dump() for tl in self.topology_links ],
+ 'devices' : devices,
+ 'links' : links,
}
class TopologyDeviceModel(_Base):
@@ -59,8 +67,8 @@ class TopologyDeviceModel(_Base):
topology_uuid = Column(ForeignKey('topology.topology_uuid', ondelete='RESTRICT'), primary_key=True, index=True)
device_uuid = Column(ForeignKey('device.device_uuid', ondelete='CASCADE' ), primary_key=True, index=True)
- #topology = relationship('TopologyModel', lazy='joined') # back_populates='topology_devices'
- device = relationship('DeviceModel', lazy='joined') # back_populates='topology_devices'
+ #topology = relationship('TopologyModel', lazy='selectin') # back_populates='topology_devices'
+ device = relationship('DeviceModel', lazy='selectin') # back_populates='topology_devices'
class TopologyLinkModel(_Base):
__tablename__ = 'topology_link'
@@ -68,5 +76,5 @@ class TopologyLinkModel(_Base):
topology_uuid = Column(ForeignKey('topology.topology_uuid', ondelete='RESTRICT'), primary_key=True, index=True)
link_uuid = Column(ForeignKey('link.link_uuid', ondelete='CASCADE' ), primary_key=True, index=True)
- #topology = relationship('TopologyModel', lazy='joined') # back_populates='topology_links'
- link = relationship('LinkModel', lazy='joined') # back_populates='topology_links'
+ #topology = relationship('TopologyModel', lazy='selectin') # back_populates='topology_links'
+ link = relationship('LinkModel', lazy='selectin') # back_populates='topology_links'
diff --git a/src/context/service/database/models/enums/DeviceDriver.py b/src/context/service/database/models/enums/DeviceDriver.py
index 6997e7dfbff6bc1d4b6452a28f11cdac9aae412f..a612803e235de2c6d2d8c91052416a675a3a3085 100644
--- a/src/context/service/database/models/enums/DeviceDriver.py
+++ b/src/context/service/database/models/enums/DeviceDriver.py
@@ -24,6 +24,7 @@ class ORM_DeviceDriverEnum(enum.Enum):
IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY
ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352
XR = DeviceDriverEnum.DEVICEDRIVER_XR
+ IETF_L2VPN = DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN
grpc_to_enum__device_driver = functools.partial(
grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum)
diff --git a/src/device/requirements.in b/src/device/requirements.in
index ec29fc7a30278625e950f3eed608281f8c7c5cb8..50b941160937aa09976dd3dda4afab6c69d309bb 100644
--- a/src/device/requirements.in
+++ b/src/device/requirements.in
@@ -29,6 +29,7 @@ xmltodict==0.12.0
tabulate
ipaddress
macaddress
+websockets==10.4
# 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/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py
index be40e64ecd25a5c46c23d5ec0a73a2484b65691d..205d769acb76992aeba33fc54b7e7b8fbbdc8d06 100644
--- a/src/device/service/DeviceServiceServicerImpl.py
+++ b/src/device/service/DeviceServiceServicerImpl.py
@@ -12,11 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from typing import Dict
import grpc, logging
from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method
from common.method_wrappers.ServiceExceptions import NotFoundException, OperationFailedException
from common.proto.context_pb2 import (
- Device, DeviceConfig, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty)
+ Device, DeviceConfig, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty, Link)
from common.proto.device_pb2 import MonitoringSettings
from common.proto.device_pb2_grpc import DeviceServiceServicer
from common.tools.context_queries.Device import get_device
@@ -28,8 +29,8 @@ from .monitoring.MonitoringLoops import MonitoringLoops
from .ErrorMessages import ERROR_MISSING_DRIVER, ERROR_MISSING_KPI
from .Tools import (
check_connect_rules, check_no_endpoints, compute_rules_to_add_delete, configure_rules, deconfigure_rules,
- populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints, populate_initial_config_rules,
- subscribe_kpi, unsubscribe_kpi, update_endpoints)
+ get_device_controller_uuid, populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints,
+ populate_initial_config_rules, subscribe_kpi, unsubscribe_kpi, update_endpoints)
LOGGER = logging.getLogger(__name__)
@@ -73,9 +74,16 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
errors = []
+ # Sub-devices and sub-links are exposed by intermediate controllers or represent mgmt links.
+ # They are used to assist in path computation algorithms, and/or to identify dependencies
+ # (which controller is in charge of which sub-device).
+ new_sub_devices : Dict[str, Device] = dict()
+ new_sub_links : Dict[str, Link] = dict()
+
if len(device.device_endpoints) == 0:
# created from request, populate endpoints using driver
- errors.extend(populate_endpoints(device, driver, self.monitoring_loops))
+ errors.extend(populate_endpoints(
+ device, driver, self.monitoring_loops, new_sub_devices, new_sub_links))
if len(device.device_config.config_rules) == len(connection_config_rules):
# created from request, populate config rules using driver
@@ -87,12 +95,20 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
for error in errors: LOGGER.error(error)
raise OperationFailedException('AddDevice', extra_details=errors)
+ device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED
device_id = context_client.SetDevice(device)
+ for sub_device in new_sub_devices.values():
+ context_client.SetDevice(sub_device)
+
+ for sub_links in new_sub_links.values():
+ context_client.SetLink(sub_links)
+
# Update endpoint monitoring resources with UUIDs
device_with_uuids = context_client.GetDevice(device_id)
populate_endpoint_monitoring_resources(device_with_uuids, self.monitoring_loops)
+ context_client.close()
return device_id
finally:
self.mutex_queues.signal_done(device_uuid)
@@ -109,6 +125,13 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
if device is None:
raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice')
+ device_controller_uuid = get_device_controller_uuid(device)
+ if device_controller_uuid is not None:
+ device = get_device(context_client, device_controller_uuid, rw_copy=True)
+ if device is None:
+ raise NotFoundException(
+ 'Device', device_controller_uuid, extra_details='loading in ConfigureDevice')
+
driver : _Driver = get_driver(self.driver_instance_cache, device)
if driver is None:
msg = ERROR_MISSING_DRIVER.format(device_uuid=str(device_uuid))
@@ -137,6 +160,12 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
for error in errors: LOGGER.error(error)
raise OperationFailedException('ConfigureDevice', extra_details=errors)
+ # Context Performance+Scalability enhancement:
+ # This method, besides P4 logic, does not add/update/delete endpoints.
+ # Remove endpoints to reduce number of inserts done by Context.
+ # TODO: Add logic to inspect endpoints and keep only those ones modified with respect to Context.
+ del device.device_endpoints[:]
+
# Note: Rules are updated by configure_rules() and deconfigure_rules() methods.
device_id = context_client.SetDevice(device)
return device_id
diff --git a/src/device/service/ErrorMessages.py b/src/device/service/ErrorMessages.py
index 1fbea721fdc52bdf759581c0525b30b1206ae844..bb7702e4e629bad43df4870d923f0a1829378e2e 100644
--- a/src/device/service/ErrorMessages.py
+++ b/src/device/service/ErrorMessages.py
@@ -14,9 +14,9 @@
_DEVICE_ID = 'DeviceId({device_uuid:s})'
_ENDPOINT_ID = 'EndpointId({endpoint_uuid:s})'
-_ENDPOINT_DATA = 'EndpointId({endpoint_data:s})'
_KPI = 'Kpi({kpi_uuid:s})'
_DEVICE_ENDPOINT_ID = _DEVICE_ID + '/' + _ENDPOINT_ID
+_RESOURCE = 'Resource({resource_data:s})'
_RESOURCE_KEY = 'Resource(key={resource_key:s})'
_RESOURCE_KEY_VALUE = 'Resource(key={resource_key:s}, value={resource_value:s})'
_SUBSCRIPTION = 'Subscription(key={subscr_key:s}, duration={subscr_duration:s}, interval={subscr_interval:s})'
@@ -26,7 +26,8 @@ _ERROR = 'Error({error:s})'
ERROR_MISSING_DRIVER = _DEVICE_ID + ' has not been added to this Device instance'
ERROR_MISSING_KPI = _KPI + ' not found'
-ERROR_BAD_ENDPOINT = _DEVICE_ID + ': GetConfig retrieved malformed ' + _ENDPOINT_DATA
+ERROR_BAD_RESOURCE = _DEVICE_ID + ': GetConfig retrieved malformed ' + _RESOURCE
+ERROR_UNSUP_RESOURCE = _DEVICE_ID + ': GetConfig retrieved unsupported ' + _RESOURCE
ERROR_GET = _DEVICE_ID + ': Unable to Get ' + _RESOURCE_KEY + '; ' + _ERROR
ERROR_GET_INIT = _DEVICE_ID + ': Unable to Get Initial ' + _RESOURCE_KEY + '; ' + _ERROR
diff --git a/src/device/service/Tools.py b/src/device/service/Tools.py
index 571e8acdab7fc243c22923a69202c89db88c8ce3..cd3af07e3324e50ff43eb5e653c4c46771a5507e 100644
--- a/src/device/service/Tools.py
+++ b/src/device/service/Tools.py
@@ -13,19 +13,20 @@
# limitations under the License.
import json, logging
-from typing import Any, Dict, List, Tuple, Union
+from typing import Any, Dict, List, Optional, Tuple, Union
from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME
from common.method_wrappers.ServiceExceptions import InvalidArgumentException
-from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig
+from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig, Link
from common.proto.device_pb2 import MonitoringSettings
from common.proto.kpi_sample_types_pb2 import KpiSampleType
from common.tools.grpc.ConfigRules import update_config_rule_custom
from common.tools.grpc.Tools import grpc_message_to_json
+from context.client.ContextClient import ContextClient
from .driver_api._Driver import _Driver, RESOURCE_ENDPOINTS
from .monitoring.MonitoringLoops import MonitoringLoops
from .ErrorMessages import (
- ERROR_BAD_ENDPOINT, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET,
- ERROR_SUBSCRIBE, ERROR_UNSUBSCRIBE
+ ERROR_BAD_RESOURCE, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET,
+ ERROR_SUBSCRIBE, ERROR_UNSUBSCRIBE, ERROR_UNSUP_RESOURCE
)
LOGGER = logging.getLogger(__name__)
@@ -77,19 +78,51 @@ def check_no_endpoints(device_endpoints) -> None:
extra_details='RPC method AddDevice does not accept Endpoints. Endpoints are discovered through '\
'interrogation of the physical device.')
-def populate_endpoints(device : Device, driver : _Driver, monitoring_loops : MonitoringLoops) -> List[str]:
+def get_device_controller_uuid(device : Device) -> Optional[str]:
+ for config_rule in device.device_config.config_rules:
+ if config_rule.WhichOneof('config_rule') != 'custom': continue
+ if config_rule.custom.resource_key != '_controller': continue
+ device_controller_id = json.loads(config_rule.custom.resource_value)
+ return device_controller_id['uuid']
+ return None
+
+def populate_endpoints(
+ device : Device, driver : _Driver, monitoring_loops : MonitoringLoops,
+ new_sub_devices : Dict[str, Device], new_sub_links : Dict[str, Link]
+) -> List[str]:
device_uuid = device.device_id.device_uuid.uuid
+ device_name = device.name
resources_to_get = [RESOURCE_ENDPOINTS]
results_getconfig = driver.GetConfig(resources_to_get)
+ LOGGER.debug('results_getconfig = {:s}'.format(str(results_getconfig)))
+
+ # first quick pass to identify need of mgmt endpoints and links
+ add_mgmt_port = False
+ for resource_data in results_getconfig:
+ if len(resource_data) != 2: continue
+ resource_key, _ = resource_data
+ if resource_key.startswith('/devices/device'):
+ add_mgmt_port = True
+ break
+
+ if add_mgmt_port:
+ # add mgmt port to main device
+ device_mgmt_endpoint = device.device_endpoints.add()
+ device_mgmt_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+ device_mgmt_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+ device_mgmt_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
+ device_mgmt_endpoint.endpoint_id.endpoint_uuid.uuid = 'mgmt'
+ device_mgmt_endpoint.name = 'mgmt'
+ device_mgmt_endpoint.endpoint_type = 'mgmt'
errors : List[str] = list()
- for endpoint in results_getconfig:
- if len(endpoint) != 2:
- errors.append(ERROR_BAD_ENDPOINT.format(device_uuid=device_uuid, endpoint_data=str(endpoint)))
+ for resource_data in results_getconfig:
+ if len(resource_data) != 2:
+ errors.append(ERROR_BAD_RESOURCE.format(device_uuid=device_uuid, resource_data=str(resource_data)))
continue
- resource_key, resource_value = endpoint
+ resource_key, resource_value = resource_data
if isinstance(resource_value, Exception):
errors.append(ERROR_GET.format(
device_uuid=device_uuid, resource_key=str(resource_key), error=str(resource_value)))
@@ -97,19 +130,88 @@ def populate_endpoints(device : Device, driver : _Driver, monitoring_loops : Mon
if resource_value is None:
continue
- endpoint_uuid = resource_value.get('uuid')
-
- device_endpoint = device.device_endpoints.add()
- device_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
- device_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
- device_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
- device_endpoint.endpoint_id.endpoint_uuid.uuid = endpoint_uuid
- device_endpoint.endpoint_type = resource_value.get('type')
+ if resource_key.startswith('/devices/device'):
+ # create sub-device
+ _sub_device_uuid = resource_value['uuid']
+ _sub_device = Device()
+ _sub_device.device_id.device_uuid.uuid = _sub_device_uuid # pylint: disable=no-member
+ _sub_device.name = resource_value['name']
+ _sub_device.device_type = resource_value['type']
+ _sub_device.device_operational_status = resource_value['status']
+
+ # Sub-devices should not have a driver assigned. Instead, they should have
+ # a config rule specifying their controller.
+ #_sub_device.device_drivers.extend(resource_value['drivers']) # pylint: disable=no-member
+ controller_config_rule = _sub_device.device_config.config_rules.add()
+ controller_config_rule.action = ConfigActionEnum.CONFIGACTION_SET
+ controller_config_rule.custom.resource_key = '_controller'
+ controller = {'uuid': device_uuid, 'name': device_name}
+ controller_config_rule.custom.resource_value = json.dumps(controller, indent=0, sort_keys=True)
+
+ new_sub_devices[_sub_device_uuid] = _sub_device
+
+ # add mgmt port to sub-device
+ _sub_device_mgmt_endpoint = _sub_device.device_endpoints.add() # pylint: disable=no-member
+ _sub_device_mgmt_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+ _sub_device_mgmt_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+ _sub_device_mgmt_endpoint.endpoint_id.device_id.device_uuid.uuid = _sub_device_uuid
+ _sub_device_mgmt_endpoint.endpoint_id.endpoint_uuid.uuid = 'mgmt'
+ _sub_device_mgmt_endpoint.name = 'mgmt'
+ _sub_device_mgmt_endpoint.endpoint_type = 'mgmt'
+
+ # add mgmt link
+ _mgmt_link_uuid = '{:s}/{:s}=={:s}/{:s}'.format(device_name, 'mgmt', _sub_device.name, 'mgmt')
+ _mgmt_link = Link()
+ _mgmt_link.link_id.link_uuid.uuid = _mgmt_link_uuid # pylint: disable=no-member
+ _mgmt_link.name = _mgmt_link_uuid
+ _mgmt_link.link_endpoint_ids.append(device_mgmt_endpoint.endpoint_id) # pylint: disable=no-member
+ _mgmt_link.link_endpoint_ids.append(_sub_device_mgmt_endpoint.endpoint_id) # pylint: disable=no-member
+ new_sub_links[_mgmt_link_uuid] = _mgmt_link
+
+ elif resource_key.startswith('/endpoints/endpoint'):
+ endpoint_uuid = resource_value['uuid']
+ _device_uuid = resource_value.get('device_uuid')
+ endpoint_name = resource_value.get('name')
+
+ if _device_uuid is None:
+ # add endpoint to current device
+ device_endpoint = device.device_endpoints.add()
+ device_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
+ else:
+ # add endpoint to specified device
+ device_endpoint = new_sub_devices[_device_uuid].device_endpoints.add()
+ device_endpoint.endpoint_id.device_id.device_uuid.uuid = _device_uuid
+
+ device_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+ device_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+
+ device_endpoint.endpoint_id.endpoint_uuid.uuid = endpoint_uuid
+ if endpoint_name is not None: device_endpoint.name = endpoint_name
+ device_endpoint.endpoint_type = resource_value.get('type', '-')
+
+ sample_types : Dict[int, str] = resource_value.get('sample_types', {})
+ for kpi_sample_type, monitor_resource_key in sample_types.items():
+ device_endpoint.kpi_sample_types.append(kpi_sample_type)
+ monitoring_loops.add_resource_key(device_uuid, endpoint_uuid, kpi_sample_type, monitor_resource_key)
+
+ elif resource_key.startswith('/links/link'):
+ # create sub-link
+ _sub_link_uuid = resource_value['uuid']
+ _sub_link = Link()
+ _sub_link.link_id.link_uuid.uuid = _sub_link_uuid # pylint: disable=no-member
+ _sub_link.name = resource_value['name']
+ new_sub_links[_sub_link_uuid] = _sub_link
+
+ for device_uuid,endpoint_uuid in resource_value['endpoints']:
+ _sub_link_endpoint_id = _sub_link.link_endpoint_ids.add() # pylint: disable=no-member
+ _sub_link_endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+ _sub_link_endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+ _sub_link_endpoint_id.device_id.device_uuid.uuid = device_uuid
+ _sub_link_endpoint_id.endpoint_uuid.uuid = endpoint_uuid
- sample_types : Dict[int, str] = resource_value.get('sample_types', {})
- for kpi_sample_type, monitor_resource_key in sample_types.items():
- device_endpoint.kpi_sample_types.append(kpi_sample_type)
- monitoring_loops.add_resource_key(device_uuid, endpoint_uuid, kpi_sample_type, monitor_resource_key)
+ else:
+ errors.append(ERROR_UNSUP_RESOURCE.format(device_uuid=device_uuid, resource_data=str(resource_data)))
+ continue
return errors
diff --git a/src/device/service/driver_api/ImportTopologyEnum.py b/src/device/service/driver_api/ImportTopologyEnum.py
new file mode 100644
index 0000000000000000000000000000000000000000..06f0ff9c2db1f1baccc4b46c5babc4458ca6ffb6
--- /dev/null
+++ b/src/device/service/driver_api/ImportTopologyEnum.py
@@ -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 enum import Enum
+from typing import Dict
+
+class ImportTopologyEnum(Enum):
+ # While importing underlying resources, the driver just imports endpoints and exposes them directly.
+ DISABLED = 'disabled'
+
+ # While importing underlying resources, the driver just imports imports sub-devices but not links
+ # connecting them. The endpoints are exposed in virtual nodes representing the sub-devices.
+ # (a remotely-controlled transport domain might exist between nodes)
+ DEVICES = 'devices'
+
+ # While importing underlying resources, the driver just imports imports sub-devices and links
+ # connecting them. The endpoints are exposed in virtual nodes representing the sub-devices.
+ # (enables to define constrained connectivity between the sub-devices)
+ TOPOLOGY = 'topology'
+
+def get_import_topology(settings : Dict, default : ImportTopologyEnum = ImportTopologyEnum.DISABLED):
+ str_import_topology = settings.get('import_topology')
+ if str_import_topology is None: return default
+ import_topology = ImportTopologyEnum._value2member_map_.get(str_import_topology) # pylint: disable=no-member
+ if import_topology is None: raise Exception('Unexpected setting value')
+ return import_topology
diff --git a/src/device/service/driver_api/_Driver.py b/src/device/service/driver_api/_Driver.py
index cc9f7a2c63f2f841b864cbe4fa596464a6783cec..947bc8570a941f8f666c87647d89c315b1bd202a 100644
--- a/src/device/service/driver_api/_Driver.py
+++ b/src/device/service/driver_api/_Driver.py
@@ -22,6 +22,7 @@ RESOURCE_ENDPOINTS = '__endpoints__'
RESOURCE_INTERFACES = '__interfaces__'
RESOURCE_NETWORK_INSTANCES = '__network_instances__'
RESOURCE_ROUTING_POLICIES = '__routing_policies__'
+RESOURCE_SERVICES = '__services__'
RESOURCE_ACL = '__acl__'
diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py
index 469abcad387dc055ba17770e4f405db1d1ceaa3b..b3b485a471899dd96a4985fedb4bb6ede2432921 100644
--- a/src/device/service/drivers/__init__.py
+++ b/src/device/service/drivers/__init__.py
@@ -74,6 +74,15 @@ DRIVERS.append(
#}
]))
+from .ietf_l2vpn.IetfL2VpnDriver import IetfL2VpnDriver # pylint: disable=wrong-import-position
+DRIVERS.append(
+ (IetfL2VpnDriver, [
+ {
+ FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.TERAFLOWSDN_CONTROLLER,
+ FilterFieldEnum.DRIVER: DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN,
+ }
+ ]))
+
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_l2vpn/IetfL2VpnDriver.py b/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py
new file mode 100644
index 0000000000000000000000000000000000000000..96dfd2c15f6b359e254a6d6a24dfe42a546833ce
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py
@@ -0,0 +1,188 @@
+# 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, threading
+from typing import Any, Iterator, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.tools.object_factory.Device import json_device_id
+from common.tools.object_factory.EndPoint import json_endpoint_id
+from common.type_checkers.Checkers import chk_string, chk_type
+from device.service.driver_api._Driver import _Driver, RESOURCE_ENDPOINTS, RESOURCE_SERVICES
+from device.service.drivers.ietf_l2vpn.TfsDebugApiClient import TfsDebugApiClient
+from .Tools import connection_point, wim_mapping
+from .WimconnectorIETFL2VPN import WimconnectorIETFL2VPN
+
+LOGGER = logging.getLogger(__name__)
+
+def service_exists(wim : WimconnectorIETFL2VPN, service_uuid : str) -> bool:
+ try:
+ wim.get_connectivity_service_status(service_uuid)
+ return True
+ except: # pylint: disable=bare-except
+ return False
+
+ALL_RESOURCE_KEYS = [
+ RESOURCE_ENDPOINTS,
+ RESOURCE_SERVICES,
+]
+
+SERVICE_TYPE = 'ELINE'
+
+METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': 'ietf_l2vpn'})
+
+class IetfL2VpnDriver(_Driver):
+ def __init__(self, address: str, port: int, **settings) -> None: # pylint: disable=super-init-not-called
+ self.__lock = threading.Lock()
+ self.__started = threading.Event()
+ self.__terminate = threading.Event()
+ username = settings.get('username')
+ password = settings.get('password')
+ scheme = settings.get('scheme', 'http')
+ wim = {'wim_url': '{:s}://{:s}:{:d}'.format(scheme, address, int(port))}
+ wim_account = {'user': username, 'password': password}
+ # Mapping updated dynamically with each request
+ config = {'mapping_not_needed': False, 'service_endpoint_mapping': []}
+ self.dac = TfsDebugApiClient(address, int(port), scheme=scheme, username=username, password=password)
+ self.wim = WimconnectorIETFL2VPN(wim, wim_account, config=config)
+ self.conn_info = {} # internal database emulating OSM storage provided to WIM Connectors
+
+ def Connect(self) -> bool:
+ with self.__lock:
+ try:
+ self.wim.check_credentials()
+ except Exception: # pylint: disable=broad-except
+ LOGGER.exception('Exception checking credentials')
+ 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:
+ self.wim.check_credentials()
+ if len(resource_keys) == 0: resource_keys = ALL_RESOURCE_KEYS
+ for i, resource_key in enumerate(resource_keys):
+ str_resource_name = 'resource_key[#{:d}]'.format(i)
+ try:
+ chk_string(str_resource_name, resource_key, allow_empty=False)
+ if resource_key == RESOURCE_ENDPOINTS:
+ # return endpoints through debug-api and list-devices method
+ results.extend(self.dac.get_devices_endpoints())
+ elif resource_key == RESOURCE_SERVICES:
+ # return all services through
+ reply = self.wim.get_all_active_connectivity_services()
+ results.extend(reply.json())
+ else:
+ # assume single-service retrieval
+ reply = self.wim.get_connectivity_service(resource_key)
+ results.append(reply.json())
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unhandled error processing resource_key({:s})'.format(str(resource_key)))
+ 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:
+ self.wim.check_credentials()
+ for resource in resources:
+ LOGGER.info('resource = {:s}'.format(str(resource)))
+ resource_key,resource_value = resource
+ try:
+ resource_value = json.loads(resource_value)
+ service_uuid = resource_value['uuid']
+
+ if service_exists(self.wim, service_uuid):
+ exc = NotImplementedError('IETF L2VPN Service Update is still not supported')
+ results.append((resource[0], exc))
+ continue
+
+ src_device_name = resource_value['src_device_name']
+ src_endpoint_name = resource_value['src_endpoint_name']
+ dst_device_name = resource_value['dst_device_name']
+ dst_endpoint_name = resource_value['dst_endpoint_name']
+ encap_type = resource_value['encapsulation_type']
+ vlan_id = resource_value['vlan_id']
+
+ src_endpoint_id = json_endpoint_id(json_device_id(src_device_name), src_endpoint_name)
+ src_service_endpoint_id, src_mapping = wim_mapping('1', src_endpoint_id)
+ self.wim.mappings[src_service_endpoint_id] = src_mapping
+
+ dst_endpoint_id = json_endpoint_id(json_device_id(dst_device_name), dst_endpoint_name)
+ dst_service_endpoint_id, dst_mapping = wim_mapping('2', dst_endpoint_id)
+ self.wim.mappings[dst_service_endpoint_id] = dst_mapping
+
+ connection_points = [
+ connection_point(src_service_endpoint_id, encap_type, vlan_id),
+ connection_point(dst_service_endpoint_id, encap_type, vlan_id),
+ ]
+
+ self.wim.create_connectivity_service(service_uuid, SERVICE_TYPE, connection_points)
+ results.append((resource_key, True))
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unhandled error processing resource_key({:s})'.format(str(resource_key)))
+ results.append((resource_key, 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:
+ self.wim.check_credentials()
+ for resource in resources:
+ LOGGER.info('resource = {:s}'.format(str(resource)))
+ resource_key,resource_value = resource
+ try:
+ resource_value = json.loads(resource_value)
+ service_uuid = resource_value['uuid']
+
+ if service_exists(self.wim, service_uuid):
+ self.wim.delete_connectivity_service(service_uuid)
+ results.append((resource_key, True))
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unhandled error processing resource_key({:s})'.format(str(resource_key)))
+ results.append((resource_key, e))
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+ # TODO: IETF L2VPN 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 L2VPN 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 L2VPN does not support monitoring by now
+ return []
diff --git a/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py b/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py
new file mode 100644
index 0000000000000000000000000000000000000000..4bf40af030fda990f96efe0ff8ab2ce54f82c312
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py
@@ -0,0 +1,92 @@
+# 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, requests
+from requests.auth import HTTPBasicAuth
+from typing import Dict, List, Optional
+
+GET_DEVICES_URL = '{:s}://{:s}:{:d}/restconf/debug-api/devices'
+TIMEOUT = 30
+
+HTTP_OK_CODES = {
+ 200, # OK
+ 201, # Created
+ 202, # Accepted
+ 204, # No Content
+}
+
+MAPPING_STATUS = {
+ 'DEVICEOPERATIONALSTATUS_UNDEFINED': 0,
+ 'DEVICEOPERATIONALSTATUS_DISABLED' : 1,
+ 'DEVICEOPERATIONALSTATUS_ENABLED' : 2,
+}
+
+MAPPING_DRIVER = {
+ 'DEVICEDRIVER_UNDEFINED' : 0,
+ 'DEVICEDRIVER_OPENCONFIG' : 1,
+ 'DEVICEDRIVER_TRANSPORT_API' : 2,
+ 'DEVICEDRIVER_P4' : 3,
+ 'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY': 4,
+ 'DEVICEDRIVER_ONF_TR_352' : 5,
+ 'DEVICEDRIVER_XR' : 6,
+ 'DEVICEDRIVER_IETF_L2VPN' : 7,
+}
+
+MSG_ERROR = 'Could not retrieve devices in remote TeraFlowSDN instance({:s}). status_code={:s} reply={:s}'
+
+LOGGER = logging.getLogger(__name__)
+
+class TfsDebugApiClient:
+ def __init__(
+ self, address : str, port : int, scheme : str = 'http',
+ username : Optional[str] = None, password : Optional[str] = None
+ ) -> None:
+ self._url = GET_DEVICES_URL.format(scheme, address, port)
+ self._auth = HTTPBasicAuth(username, password) if username is not None and password is not None else None
+
+ def get_devices_endpoints(self) -> List[Dict]:
+ reply = requests.get(self._url, timeout=TIMEOUT, verify=False, auth=self._auth)
+ if reply.status_code not in HTTP_OK_CODES:
+ msg = MSG_ERROR.format(str(self._url), str(reply.status_code), str(reply))
+ LOGGER.error(msg)
+ raise Exception(msg)
+
+ result = list()
+ for json_device in reply.json()['devices']:
+ device_uuid : str = json_device['device_id']['device_uuid']['uuid']
+ device_type : str = json_device['device_type']
+ #if not device_type.startswith('emu-'): device_type = 'emu-' + device_type
+ device_status = json_device['device_operational_status']
+ device_url = '/devices/device[{:s}]'.format(device_uuid)
+ device_data = {
+ 'uuid': json_device['device_id']['device_uuid']['uuid'],
+ 'name': json_device['name'],
+ 'type': device_type,
+ 'status': MAPPING_STATUS[device_status],
+ 'drivers': [MAPPING_DRIVER[driver] for driver in json_device['device_drivers']],
+ }
+ result.append((device_url, device_data))
+
+ for json_endpoint in json_device['device_endpoints']:
+ endpoint_uuid = json_endpoint['endpoint_id']['endpoint_uuid']['uuid']
+ endpoint_url = '/endpoints/endpoint[{:s}]'.format(endpoint_uuid)
+ endpoint_data = {
+ 'device_uuid': device_uuid,
+ 'uuid': endpoint_uuid,
+ 'name': json_endpoint['name'],
+ 'type': json_endpoint['endpoint_type'],
+ }
+ result.append((endpoint_url, endpoint_data))
+
+ return result
diff --git a/src/device/service/drivers/ietf_l2vpn/Tools.py b/src/device/service/drivers/ietf_l2vpn/Tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..45dfa23c984e175c01efa77371e94454b98ea94e
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/Tools.py
@@ -0,0 +1,48 @@
+# 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 typing import Dict, Optional
+
+def compose_service_endpoint_id(site_id : str, endpoint_id : Dict):
+ device_uuid = endpoint_id['device_id']['device_uuid']['uuid']
+ endpoint_uuid = endpoint_id['endpoint_uuid']['uuid']
+ return ':'.join([site_id, device_uuid, endpoint_uuid])
+
+def wim_mapping(site_id, ce_endpoint_id, pe_device_id : Optional[Dict] = None, priority=None, redundant=[]):
+ ce_device_uuid = ce_endpoint_id['device_id']['device_uuid']['uuid']
+ ce_endpoint_uuid = ce_endpoint_id['endpoint_uuid']['uuid']
+ service_endpoint_id = compose_service_endpoint_id(site_id, ce_endpoint_id)
+ if pe_device_id is None:
+ bearer = '{:s}:{:s}'.format(ce_device_uuid, ce_endpoint_uuid)
+ else:
+ pe_device_uuid = pe_device_id['device_uuid']['uuid']
+ bearer = '{:s}:{:s}'.format(ce_device_uuid, pe_device_uuid)
+ mapping = {
+ 'service_endpoint_id': service_endpoint_id,
+ 'datacenter_id': site_id, 'device_id': ce_device_uuid, 'device_interface_id': ce_endpoint_uuid,
+ 'service_mapping_info': {
+ 'site-id': site_id,
+ 'bearer': {'bearer-reference': bearer},
+ }
+ }
+ if priority is not None: mapping['service_mapping_info']['priority'] = priority
+ if len(redundant) > 0: mapping['service_mapping_info']['redundant'] = redundant
+ return service_endpoint_id, mapping
+
+def connection_point(service_endpoint_id : str, encapsulation_type : str, vlan_id : int):
+ return {
+ 'service_endpoint_id': service_endpoint_id,
+ 'service_endpoint_encapsulation_type': encapsulation_type,
+ 'service_endpoint_encapsulation_info': {'vlan': vlan_id}
+ }
diff --git a/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py b/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py
new file mode 100644
index 0000000000000000000000000000000000000000..34ff184c022f379e7420de237bd08fc1dc6282a6
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py
@@ -0,0 +1,565 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 Telefonica
+# All Rights Reserved.
+#
+# Contributors: Oscar Gonzalez de Dios, Manuel Lopez Bravo, Guillermo Pajares Martin
+# 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.
+#
+# This work has been performed in the context of the Metro-Haul project -
+# funded by the European Commission under Grant number 761727 through the
+# Horizon 2020 program.
+##
+"""The SDN/WIM connector is responsible for establishing wide area network
+connectivity.
+
+This SDN/WIM connector implements the standard IETF RFC 8466 "A YANG Data
+ Model for Layer 2 Virtual Private Network (L2VPN) Service Delivery"
+
+It receives the endpoints and the necessary details to request
+the Layer 2 service.
+"""
+import requests
+import uuid
+import logging
+import copy
+#from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
+from .sdnconn import SdnConnectorBase, SdnConnectorError
+
+"""Check layer where we move it"""
+
+
+class WimconnectorIETFL2VPN(SdnConnectorBase):
+ def __init__(self, wim, wim_account, config=None, logger=None):
+ """IETF L2VPN WIM connector
+
+ Arguments: (To be completed)
+ wim (dict): WIM record, as stored in the database
+ wim_account (dict): WIM account record, as stored in the database
+ """
+ self.logger = logging.getLogger("ro.sdn.ietfl2vpn")
+ super().__init__(wim, wim_account, config, logger)
+ self.headers = {"Content-Type": "application/json"}
+ self.mappings = {
+ m["service_endpoint_id"]: m for m in self.service_endpoint_mapping
+ }
+ self.user = wim_account.get("user")
+ self.passwd = wim_account.get("password")
+
+ if self.user is not None and self.passwd is not None:
+ self.auth = (self.user, self.passwd)
+ else:
+ self.auth = None
+
+ self.logger.info("IETFL2VPN Connector Initialized.")
+
+ def check_credentials(self):
+ endpoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+ self.wim["wim_url"]
+ )
+
+ try:
+ response = requests.get(endpoint, auth=self.auth)
+ http_code = response.status_code
+ except requests.exceptions.RequestException as e:
+ raise SdnConnectorError(e.response, http_code=503)
+
+ if http_code != 200:
+ raise SdnConnectorError("Failed while authenticating", http_code=http_code)
+
+ self.logger.info("Credentials checked")
+
+ def get_connectivity_service_status(self, service_uuid, conn_info=None):
+ """Monitor the status of the connectivity service stablished
+
+ Arguments:
+ service_uuid: Connectivity service unique identifier
+
+ Returns:
+ Examples::
+ {'sdn_status': 'ACTIVE'}
+ {'sdn_status': 'INACTIVE'}
+ {'sdn_status': 'DOWN'}
+ {'sdn_status': 'ERROR'}
+ """
+ try:
+ self.logger.info("Sending get connectivity service stuatus")
+ servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+ self.wim["wim_url"], service_uuid
+ )
+ response = requests.get(servicepoint, auth=self.auth)
+ self.logger.warning('response.status_code={:s}'.format(str(response.status_code)))
+ if response.status_code != requests.codes.ok:
+ raise SdnConnectorError(
+ "Unable to obtain connectivity servcice status",
+ http_code=response.status_code,
+ )
+
+ service_status = {"sdn_status": "ACTIVE"}
+
+ return service_status
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def search_mapp(self, connection_point):
+ id = connection_point["service_endpoint_id"]
+ if id not in self.mappings:
+ raise SdnConnectorError("Endpoint {} not located".format(str(id)))
+ else:
+ return self.mappings[id]
+
+ def create_connectivity_service(self, service_uuid, service_type, connection_points, **kwargs):
+ """Stablish WAN connectivity between the endpoints
+
+ Arguments:
+ service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
+ ``L3``.
+ connection_points (list): each point corresponds to
+ an entry point from the DC to the transport network. One
+ connection point serves to identify the specific access and
+ some other service parameters, such as encapsulation type.
+ Represented by a dict as follows::
+
+ {
+ "service_endpoint_id": ..., (str[uuid])
+ "service_endpoint_encapsulation_type": ...,
+ (enum: none, dot1q, ...)
+ "service_endpoint_encapsulation_info": {
+ ... (dict)
+ "vlan": ..., (int, present if encapsulation is dot1q)
+ "vni": ... (int, present if encapsulation is vxlan),
+ "peers": [(ipv4_1), (ipv4_2)]
+ (present if encapsulation is vxlan)
+ }
+ }
+
+ The service endpoint ID should be previously informed to the WIM
+ engine in the RO when the WIM port mapping is registered.
+
+ Keyword Arguments:
+ bandwidth (int): value in kilobytes
+ latency (int): value in milliseconds
+
+ Other QoS might be passed as keyword arguments.
+
+ Returns:
+ tuple: ``conn_info``:
+ - *conn_info* (dict or None): Information to be stored at the
+ database (or ``None``). This information will be provided to
+ the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
+ **MUST** be JSON/YAML-serializable (plain data structures).
+
+ Raises:
+ SdnConnectorException: In case of error.
+ """
+ SETTINGS = { # min_endpoints, max_endpoints, vpn_service_type
+ 'ELINE': (2, 2, 'vpws'), # Virtual Private Wire Service
+ 'ELAN' : (2, None, 'vpls'), # Virtual Private LAN Service
+ }
+ settings = SETTINGS.get(service_type)
+ if settings is None: raise NotImplementedError('Unsupported service_type({:s})'.format(str(service_type)))
+ min_endpoints, max_endpoints, vpn_service_type = settings
+
+ if max_endpoints is not None and len(connection_points) > max_endpoints:
+ msg = "Connections between more than {:d} endpoints are not supported for service_type {:s}"
+ raise SdnConnectorError(msg.format(max_endpoints, service_type))
+
+ if min_endpoints is not None and len(connection_points) < min_endpoints:
+ msg = "Connections must be of at least {:d} endpoints for service_type {:s}"
+ raise SdnConnectorError(msg.format(min_endpoints, service_type))
+
+ """First step, create the vpn service"""
+ vpn_service = {}
+ vpn_service["vpn-id"] = service_uuid
+ vpn_service["vpn-svc-type"] = vpn_service_type
+ vpn_service["svc-topo"] = "any-to-any"
+ vpn_service["customer-name"] = "osm"
+ vpn_service_list = []
+ vpn_service_list.append(vpn_service)
+ vpn_service_l = {"ietf-l2vpn-svc:vpn-service": vpn_service_list}
+ response_service_creation = None
+ conn_info = []
+ self.logger.info("Sending vpn-service :{}".format(vpn_service_l))
+
+ try:
+ endpoint_service_creation = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+ self.wim["wim_url"]
+ )
+ )
+ response_service_creation = requests.post(
+ endpoint_service_creation,
+ headers=self.headers,
+ json=vpn_service_l,
+ auth=self.auth,
+ )
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError(
+ "Request to create service Timeout", http_code=408
+ )
+
+ if response_service_creation.status_code == 409:
+ raise SdnConnectorError(
+ "Service already exists",
+ http_code=response_service_creation.status_code,
+ )
+ elif response_service_creation.status_code != requests.codes.created:
+ raise SdnConnectorError(
+ "Request to create service not accepted",
+ http_code=response_service_creation.status_code,
+ )
+
+ self.logger.info('connection_points = {:s}'.format(str(connection_points)))
+
+ # Check if protected paths are requested
+ extended_connection_points = []
+ for connection_point in connection_points:
+ extended_connection_points.append(connection_point)
+
+ connection_point_wan_info = self.search_mapp(connection_point)
+ service_mapping_info = connection_point_wan_info.get('service_mapping_info', {})
+ redundant_service_endpoint_ids = service_mapping_info.get('redundant')
+
+ if redundant_service_endpoint_ids is None: continue
+ if len(redundant_service_endpoint_ids) == 0: continue
+
+ for redundant_service_endpoint_id in redundant_service_endpoint_ids:
+ redundant_connection_point = copy.deepcopy(connection_point)
+ redundant_connection_point['service_endpoint_id'] = redundant_service_endpoint_id
+ extended_connection_points.append(redundant_connection_point)
+
+ self.logger.info('extended_connection_points = {:s}'.format(str(extended_connection_points)))
+
+ """Second step, create the connections and vpn attachments"""
+ for connection_point in extended_connection_points:
+ connection_point_wan_info = self.search_mapp(connection_point)
+ site_network_access = {}
+ connection = {}
+
+ if connection_point["service_endpoint_encapsulation_type"] != "none":
+ if (
+ connection_point["service_endpoint_encapsulation_type"]
+ == "dot1q"
+ ):
+ """The connection is a VLAN"""
+ connection["encapsulation-type"] = "dot1q-vlan-tagged"
+ tagged = {}
+ tagged_interf = {}
+ service_endpoint_encapsulation_info = connection_point[
+ "service_endpoint_encapsulation_info"
+ ]
+
+ if service_endpoint_encapsulation_info["vlan"] is None:
+ raise SdnConnectorError("VLAN must be provided")
+
+ tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info[
+ "vlan"
+ ]
+ tagged["dot1q-vlan-tagged"] = tagged_interf
+ connection["tagged-interface"] = tagged
+ else:
+ raise NotImplementedError("Encapsulation type not implemented")
+
+ site_network_access["connection"] = connection
+ self.logger.info("Sending connection:{}".format(connection))
+ vpn_attach = {}
+ vpn_attach["vpn-id"] = service_uuid
+ vpn_attach["site-role"] = vpn_service["svc-topo"] + "-role"
+ site_network_access["vpn-attachment"] = vpn_attach
+ self.logger.info("Sending vpn-attachement :{}".format(vpn_attach))
+ uuid_sna = str(uuid.uuid4())
+ site_network_access["network-access-id"] = uuid_sna
+ site_network_access["bearer"] = connection_point_wan_info[
+ "service_mapping_info"
+ ]["bearer"]
+
+ access_priority = connection_point_wan_info["service_mapping_info"].get("priority")
+ if access_priority is not None:
+ availability = {}
+ availability["access-priority"] = access_priority
+ availability["single-active"] = [None]
+ site_network_access["availability"] = availability
+
+ constraint = {}
+ constraint['constraint-type'] = 'end-to-end-diverse'
+ constraint['target'] = {'all-other-accesses': [None]}
+
+ access_diversity = {}
+ access_diversity['constraints'] = {'constraint': []}
+ access_diversity['constraints']['constraint'].append(constraint)
+ site_network_access["access-diversity"] = access_diversity
+
+ site_network_accesses = {}
+ site_network_access_list = []
+ site_network_access_list.append(site_network_access)
+ site_network_accesses[
+ "ietf-l2vpn-svc:site-network-access"
+ ] = site_network_access_list
+ conn_info_d = {}
+ conn_info_d["site"] = connection_point_wan_info["service_mapping_info"][
+ "site-id"
+ ]
+ conn_info_d["site-network-access-id"] = site_network_access[
+ "network-access-id"
+ ]
+ conn_info_d["mapping"] = None
+ conn_info.append(conn_info_d)
+
+ try:
+ endpoint_site_network_access_creation = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
+ "sites/site={}/site-network-accesses/".format(
+ self.wim["wim_url"],
+ connection_point_wan_info["service_mapping_info"][
+ "site-id"
+ ],
+ )
+ )
+ response_endpoint_site_network_access_creation = requests.post(
+ endpoint_site_network_access_creation,
+ headers=self.headers,
+ json=site_network_accesses,
+ auth=self.auth,
+ )
+
+ if (
+ response_endpoint_site_network_access_creation.status_code
+ == 409
+ ):
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+
+ raise SdnConnectorError(
+ "Site_Network_Access with ID '{}' already exists".format(
+ site_network_access["network-access-id"]
+ ),
+ http_code=response_endpoint_site_network_access_creation.status_code,
+ )
+ elif (
+ response_endpoint_site_network_access_creation.status_code
+ == 400
+ ):
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+
+ raise SdnConnectorError(
+ "Site {} does not exist".format(
+ connection_point_wan_info["service_mapping_info"][
+ "site-id"
+ ]
+ ),
+ http_code=response_endpoint_site_network_access_creation.status_code,
+ )
+ elif (
+ response_endpoint_site_network_access_creation.status_code
+ != requests.codes.created
+ and response_endpoint_site_network_access_creation.status_code
+ != requests.codes.no_content
+ ):
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+
+ raise SdnConnectorError(
+ "Request not accepted",
+ http_code=response_endpoint_site_network_access_creation.status_code,
+ )
+ except requests.exceptions.ConnectionError:
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ return conn_info
+
+ def delete_connectivity_service(self, service_uuid, conn_info=None):
+ """Disconnect multi-site endpoints previously connected
+
+ This method should receive as the first argument the UUID generated by
+ the ``create_connectivity_service``
+ """
+ try:
+ self.logger.info("Sending delete")
+ servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+ self.wim["wim_url"], service_uuid
+ )
+ response = requests.delete(servicepoint, auth=self.auth)
+
+ if response.status_code != requests.codes.no_content:
+ raise SdnConnectorError(
+ "Error in the request", http_code=response.status_code
+ )
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def edit_connectivity_service(
+ self, service_uuid, conn_info=None, connection_points=None, **kwargs
+ ):
+ """Change an existing connectivity service, see
+ ``create_connectivity_service``"""
+ # sites = {"sites": {}}
+ # site_list = []
+ vpn_service = {}
+ vpn_service["svc-topo"] = "any-to-any"
+ counter = 0
+
+ for connection_point in connection_points:
+ site_network_access = {}
+ connection_point_wan_info = self.search_mapp(connection_point)
+ params_site = {}
+ params_site["site-id"] = connection_point_wan_info["service_mapping_info"][
+ "site-id"
+ ]
+ params_site["site-vpn-flavor"] = "site-vpn-flavor-single"
+ device_site = {}
+ device_site["device-id"] = connection_point_wan_info["device-id"]
+ params_site["devices"] = device_site
+ # network_access = {}
+ connection = {}
+
+ if connection_point["service_endpoint_encapsulation_type"] != "none":
+ if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
+ """The connection is a VLAN"""
+ connection["encapsulation-type"] = "dot1q-vlan-tagged"
+ tagged = {}
+ tagged_interf = {}
+ service_endpoint_encapsulation_info = connection_point[
+ "service_endpoint_encapsulation_info"
+ ]
+
+ if service_endpoint_encapsulation_info["vlan"] is None:
+ raise SdnConnectorError("VLAN must be provided")
+
+ tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info[
+ "vlan"
+ ]
+ tagged["dot1q-vlan-tagged"] = tagged_interf
+ connection["tagged-interface"] = tagged
+ else:
+ raise NotImplementedError("Encapsulation type not implemented")
+
+ site_network_access["connection"] = connection
+ vpn_attach = {}
+ vpn_attach["vpn-id"] = service_uuid
+ vpn_attach["site-role"] = vpn_service["svc-topo"] + "-role"
+ site_network_access["vpn-attachment"] = vpn_attach
+ uuid_sna = conn_info[counter]["site-network-access-id"]
+ site_network_access["network-access-id"] = uuid_sna
+ site_network_access["bearer"] = connection_point_wan_info[
+ "service_mapping_info"
+ ]["bearer"]
+ site_network_accesses = {}
+ site_network_access_list = []
+ site_network_access_list.append(site_network_access)
+ site_network_accesses[
+ "ietf-l2vpn-svc:site-network-access"
+ ] = site_network_access_list
+
+ try:
+ endpoint_site_network_access_edit = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
+ "sites/site={}/site-network-accesses/".format(
+ self.wim["wim_url"],
+ connection_point_wan_info["service_mapping_info"]["site-id"],
+ )
+ )
+ response_endpoint_site_network_access_creation = requests.put(
+ endpoint_site_network_access_edit,
+ headers=self.headers,
+ json=site_network_accesses,
+ auth=self.auth,
+ )
+
+ if response_endpoint_site_network_access_creation.status_code == 400:
+ raise SdnConnectorError(
+ "Service does not exist",
+ http_code=response_endpoint_site_network_access_creation.status_code,
+ )
+ elif (
+ response_endpoint_site_network_access_creation.status_code != 201
+ and response_endpoint_site_network_access_creation.status_code
+ != 204
+ ):
+ raise SdnConnectorError(
+ "Request no accepted",
+ http_code=response_endpoint_site_network_access_creation.status_code,
+ )
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ counter += 1
+
+ return None
+
+ def clear_all_connectivity_services(self):
+ """Delete all WAN Links corresponding to a WIM"""
+ try:
+ self.logger.info("Sending clear all connectivity services")
+ servicepoint = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+ self.wim["wim_url"]
+ )
+ )
+ response = requests.delete(servicepoint, auth=self.auth)
+
+ if response.status_code != requests.codes.no_content:
+ raise SdnConnectorError(
+ "Unable to clear all connectivity services",
+ http_code=response.status_code,
+ )
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def get_all_active_connectivity_services(self):
+ """Provide information about all active connections provisioned by a
+ WIM
+ """
+ try:
+ self.logger.info("Sending get all connectivity services")
+ servicepoint = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+ self.wim["wim_url"]
+ )
+ )
+ response = requests.get(servicepoint, auth=self.auth)
+
+ if response.status_code != requests.codes.ok:
+ raise SdnConnectorError(
+ "Unable to get all connectivity services",
+ http_code=response.status_code,
+ )
+
+ return response
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def get_connectivity_service(self, service_uuid, conn_info=None):
+ """Provide information about a specific connection provisioned by a WIM.
+
+ This method should receive as the first argument the UUID generated by
+ the ``create_connectivity_service``
+ """
+ try:
+ self.logger.info("Sending get connectivity service")
+ servicepoint = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+ self.wim["wim_url"], service_uuid
+ )
+ )
+ response = requests.get(servicepoint, auth=self.auth)
+
+ if response.status_code != requests.codes.ok:
+ raise SdnConnectorError(
+ "Unable to get connectivity service {:s}".format(str(service_uuid)),
+ http_code=response.status_code,
+ )
+
+ return response
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
diff --git a/src/device/service/drivers/ietf_l2vpn/__init__.py b/src/device/service/drivers/ietf_l2vpn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..38d04994fb0fa1951fb465bc127eb72659dc2eaf
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/__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_l2vpn/acknowledgements.txt b/src/device/service/drivers/ietf_l2vpn/acknowledgements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3a7ed47ad6626ad13f4176bd696ec7e7dbab20ee
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/acknowledgements.txt
@@ -0,0 +1,3 @@
+IETF L2VPN Driver is based on source code taken from:
+https://osm.etsi.org/gitlab/osm/ro/-/blob/master/RO-plugin/osm_ro_plugin/sdnconn.py
+https://osm.etsi.org/gitlab/osm/ro/-/blob/master/RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn/wimconn_ietfl2vpn.py
diff --git a/src/device/service/drivers/ietf_l2vpn/sdnconn.py b/src/device/service/drivers/ietf_l2vpn/sdnconn.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1849c9ef3e1a1260ff42bbadabc99f91a6435d7
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/sdnconn.py
@@ -0,0 +1,242 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 University of Bristol - High Performance Networks Research
+# Group
+# All Rights Reserved.
+#
+# Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
+# Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: DEVICEDRIVER_XR = 6;
*/
DEVICEDRIVER_XR(6),
+ /**
+ * DEVICEDRIVER_IETF_L2VPN = 7;
+ */
+ DEVICEDRIVER_IETF_L2VPN(7),
UNRECOGNIZED(-1),
;
@@ -212,6 +216,10 @@ public final class ContextOuterClass {
* DEVICEDRIVER_XR = 6;
*/
public static final int DEVICEDRIVER_XR_VALUE = 6;
+ /**
+ * DEVICEDRIVER_IETF_L2VPN = 7;
+ */
+ public static final int DEVICEDRIVER_IETF_L2VPN_VALUE = 7;
public final int getNumber() {
@@ -245,6 +253,7 @@ public final class ContextOuterClass {
case 4: return DEVICEDRIVER_IETF_NETWORK_TOPOLOGY;
case 5: return DEVICEDRIVER_ONF_TR_352;
case 6: return DEVICEDRIVER_XR;
+ case 7: return DEVICEDRIVER_IETF_L2VPN;
default: return null;
}
}
diff --git a/src/policy/target/kubernetes/kubernetes.yml b/src/policy/target/kubernetes/kubernetes.yml
index 40516e5cc3fdd1fb993a1248ad36ea7551edfc40..72da09ecaf1de9d080d686c63c0f18c88f09e8b4 100644
--- a/src/policy/target/kubernetes/kubernetes.yml
+++ b/src/policy/target/kubernetes/kubernetes.yml
@@ -17,16 +17,16 @@ apiVersion: v1
kind: Service
metadata:
annotations:
- app.quarkus.io/commit-id: e369fc6b4de63303f91e1fd3de0b6a591a86c0f5
- app.quarkus.io/build-timestamp: 2022-11-18 - 12:56:37 +0000
+ app.quarkus.io/commit-id: 8065cee75be759e14af792737179537096de5e11
+ app.quarkus.io/build-timestamp: 2023-03-30 - 13:49:59 +0000
labels:
app.kubernetes.io/name: policyservice
app: policyservice
name: policyservice
spec:
ports:
- - name: http
- port: 8080
+ - name: metrics
+ port: 9192
targetPort: 8080
- name: grpc
port: 6060
@@ -39,8 +39,8 @@ apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
- app.quarkus.io/commit-id: e369fc6b4de63303f91e1fd3de0b6a591a86c0f5
- app.quarkus.io/build-timestamp: 2022-11-22 - 14:10:01 +0000
+ app.quarkus.io/commit-id: 8065cee75be759e14af792737179537096de5e11
+ app.quarkus.io/build-timestamp: 2023-03-30 - 13:49:59 +0000
labels:
app: policyservice
app.kubernetes.io/name: policyservice
@@ -53,8 +53,8 @@ spec:
template:
metadata:
annotations:
- app.quarkus.io/commit-id: e369fc6b4de63303f91e1fd3de0b6a591a86c0f5
- app.quarkus.io/build-timestamp: 2022-11-22 - 14:10:01 +0000
+ app.quarkus.io/commit-id: 8065cee75be759e14af792737179537096de5e11
+ app.quarkus.io/build-timestamp: 2023-03-30 - 13:49:59 +0000
labels:
app: policyservice
app.kubernetes.io/name: policyservice
@@ -65,12 +65,12 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- - name: MONITORING_SERVICE_HOST
- value: monitoringservice
- - name: CONTEXT_SERVICE_HOST
- value: contextservice
- name: SERVICE_SERVICE_HOST
value: serviceservice
+ - name: CONTEXT_SERVICE_HOST
+ value: contextservice
+ - name: MONITORING_SERVICE_HOST
+ value: monitoringservice
image: labs.etsi.org:5050/tfs/controller/policy:0.1.0
imagePullPolicy: Always
livenessProbe:
@@ -86,10 +86,10 @@ spec:
name: policyservice
ports:
- containerPort: 8080
- name: http
+ name: metrics
protocol: TCP
- containerPort: 6060
- name: grpc
+ name: grpc-server
protocol: TCP
readinessProbe:
failureThreshold: 3
@@ -101,3 +101,29 @@ spec:
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
+ resources:
+ requests:
+ cpu: 50m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 2048Mi
+---
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: policyservice-hpa
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: policyservice
+ minReplicas: 1
+ maxReplicas: 10
+ metrics:
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: 80
\ No newline at end of file
diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py
index a73ec53f37d68e0414eeb1df146373c6906273c5..3ec71dc64536e28457c4f1adbf3679186285786d 100644
--- a/src/service/service/service_handler_api/FilterFields.py
+++ b/src/service/service/service_handler_api/FilterFields.py
@@ -33,7 +33,8 @@ DEVICE_DRIVER_VALUES = {
DeviceDriverEnum.DEVICEDRIVER_P4,
DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY,
DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352,
- DeviceDriverEnum.DEVICEDRIVER_XR
+ DeviceDriverEnum.DEVICEDRIVER_XR,
+ DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN,
}
# Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified
diff --git a/src/service/service/service_handler_api/ServiceHandlerFactory.py b/src/service/service/service_handler_api/ServiceHandlerFactory.py
index 6aa21b49920254383fad5f28aa234b6ec0cad5a3..64ea204a2600a71b08c8c373a15640f5e2134787 100644
--- a/src/service/service/service_handler_api/ServiceHandlerFactory.py
+++ b/src/service/service/service_handler_api/ServiceHandlerFactory.py
@@ -73,6 +73,9 @@ class ServiceHandlerFactory:
if field_indice is None: continue
if not isinstance(field_values, Iterable) or isinstance(field_values, str):
field_values = [field_values]
+ if len(field_values) == 0:
+ # do not allow empty fields; might cause wrong selection
+ raise UnsatisfiedFilterException(filter_fields)
field_enum_values = FILTER_FIELD_ALLOWED_VALUES.get(field_name)
diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py
index 4c9059779d6b7031685e1de76b0a7ed651af6c5f..257bc138fe932e7e5abee00981848248039d0b3f 100644
--- a/src/service/service/service_handlers/__init__.py
+++ b/src/service/service/service_handlers/__init__.py
@@ -15,12 +15,14 @@
from common.proto.context_pb2 import DeviceDriverEnum, ServiceTypeEnum
from ..service_handler_api.FilterFields import FilterFieldEnum
from .l2nm_emulated.L2NMEmulatedServiceHandler import L2NMEmulatedServiceHandler
+from .l2nm_ietfl2vpn.L2NM_IETFL2VPN_ServiceHandler import L2NM_IETFL2VPN_ServiceHandler
from .l2nm_openconfig.L2NMOpenConfigServiceHandler import L2NMOpenConfigServiceHandler
from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler
from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler
+from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler
from .p4.p4_service_handler import P4ServiceHandler
from .tapi_tapi.TapiServiceHandler import TapiServiceHandler
-from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler
+from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler
SERVICE_HANDLERS = [
(L2NMEmulatedServiceHandler, [
@@ -50,13 +52,19 @@ SERVICE_HANDLERS = [
(TapiServiceHandler, [
{
FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
- FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API, DeviceDriverEnum.DEVICEDRIVER_XR],
+ FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API],
+ }
+ ]),
+ (TapiXrServiceHandler, [
+ {
+ FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
+ FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_XR],
}
]),
(MicrowaveServiceHandler, [
{
FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L2NM,
- FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY,
+ FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352],
}
]),
(P4ServiceHandler, [
@@ -65,4 +73,10 @@ SERVICE_HANDLERS = [
FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4,
}
]),
+ (L2NM_IETFL2VPN_ServiceHandler, [
+ {
+ FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L2NM,
+ FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN],
+ }
+ ]),
]
diff --git a/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py b/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py
index c2ea6e213ee8d18b4507089fb2762c913e03039a..ac44574ad60242b0acf21ba824ea448d5ec30bf1 100644
--- a/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py
+++ b/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py
@@ -21,15 +21,18 @@ def setup_config_rules(
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
- json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ if service_settings is None: return []
+ if endpoint_settings is None: return []
- mtu = json_settings.get('mtu', 1450 ) # 1512
+ #json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
+
+ #mtu = json_settings.get('mtu', 1450 ) # 1512
#address_families = json_settings.get('address_families', [] ) # ['IPV4']
#bgp_as = json_settings.get('bgp_as', 0 ) # 65000
#bgp_route_target = json_settings.get('bgp_route_target', '0:0') # 65000:333
- router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10'
+ #router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10'
#route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0' ) # '60001:801'
sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1
vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400
@@ -43,17 +46,17 @@ def setup_config_rules(
connection_point_id = 'VC-1'
json_config_rules = [
- json_config_rule_set(
- '/network_instance[default]',
- {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
+ #json_config_rule_set(
+ # '/network_instance[default]',
+ # {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
- json_config_rule_set(
- '/network_instance[default]/protocols[OSPF]',
- {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
+ #json_config_rule_set(
+ # '/network_instance[default]/protocols[OSPF]',
+ # {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
- json_config_rule_set(
- '/network_instance[default]/protocols[STATIC]',
- {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
+ #json_config_rule_set(
+ # '/network_instance[default]/protocols[STATIC]',
+ # {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
json_config_rule_set(
'/network_instance[{:s}]'.format(network_instance_name),
@@ -66,7 +69,7 @@ def setup_config_rules(
json_config_rule_set(
'/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
{'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
- 'subinterface': sub_interface_index}),
+ 'subinterface': sub_interface_index}),
json_config_rule_set(
'/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
@@ -80,15 +83,18 @@ def teardown_config_rules(
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
- #json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ if service_settings is None: return []
+ if endpoint_settings is None: return []
+
+ #json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
#mtu = json_settings.get('mtu', 1450 ) # 1512
#address_families = json_settings.get('address_families', [] ) # ['IPV4']
#bgp_as = json_settings.get('bgp_as', 0 ) # 65000
#bgp_route_target = json_settings.get('bgp_route_target', '0:0') # 65000:333
- router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10'
+ #router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10'
#route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0' ) # '60001:801'
sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1
#vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400
@@ -111,24 +117,24 @@ def teardown_config_rules(
{'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
'subinterface': sub_interface_index}),
- json_config_rule_delete(
- '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
- {'name': if_cirid_name, 'index': sub_interface_index}),
-
json_config_rule_delete(
'/network_instance[{:s}]'.format(network_instance_name),
{'name': network_instance_name}),
json_config_rule_delete(
- '/network_instance[default]/protocols[STATIC]',
- {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
+ '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
+ {'name': if_cirid_name, 'index': sub_interface_index}),
- json_config_rule_delete(
- '/network_instance[default]/protocols[OSPF]',
- {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
+ #json_config_rule_delete(
+ # '/network_instance[default]/protocols[STATIC]',
+ # {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
- json_config_rule_delete(
- '/network_instance[default]',
- {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
+ #json_config_rule_delete(
+ # '/network_instance[default]/protocols[OSPF]',
+ # {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
+
+ #json_config_rule_delete(
+ # '/network_instance[default]',
+ # {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
]
return json_config_rules
diff --git a/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py b/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py
index 9de6c607b1336a4b3fb43867efc16d30048177e0..0a2261ceb4e1a63c984cf833121e3a87e13c0e9f 100644
--- a/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py
+++ b/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py
@@ -75,10 +75,12 @@ class L2NMEmulatedServiceHandler(_ServiceHandler):
service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
settings, endpoint_settings)
- del device_obj.device_config.config_rules[:]
- for json_config_rule in json_config_rules:
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ if len(json_config_rules) > 0:
+ del device_obj.device_config.config_rules[:]
+ for json_config_rule in json_config_rules:
+ device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(device_obj)
+
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint)))
@@ -110,10 +112,12 @@ class L2NMEmulatedServiceHandler(_ServiceHandler):
service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
settings, endpoint_settings)
- del device_obj.device_config.config_rules[:]
- for json_config_rule in json_config_rules:
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ if len(json_config_rules) > 0:
+ del device_obj.device_config.config_rules[:]
+ for json_config_rule in json_config_rules:
+ device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(device_obj)
+
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint)))
diff --git a/src/service/service/service_handlers/l2nm_ietfl2vpn/L2NM_IETFL2VPN_ServiceHandler.py b/src/service/service/service_handlers/l2nm_ietfl2vpn/L2NM_IETFL2VPN_ServiceHandler.py
new file mode 100644
index 0000000000000000000000000000000000000000..880a6c5a20d618c9d9f21701b7ef3886dbb9fc21
--- /dev/null
+++ b/src/service/service/service_handlers/l2nm_ietfl2vpn/L2NM_IETFL2VPN_ServiceHandler.py
@@ -0,0 +1,173 @@
+# 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
+from typing import Any, Dict, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.proto.context_pb2 import ConfigRule, DeviceId, Service
+from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
+from common.tools.object_factory.Device import json_device_id
+from common.type_checkers.Checkers import chk_type
+from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
+from service.service.service_handler_api._ServiceHandler import _ServiceHandler
+from service.service.service_handler_api.SettingsHandler import SettingsHandler
+from service.service.task_scheduler.TaskExecutor import TaskExecutor
+
+LOGGER = logging.getLogger(__name__)
+
+METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'tapi_tapi'})
+
+class L2NM_IETFL2VPN_ServiceHandler(_ServiceHandler):
+ def __init__( # pylint: disable=super-init-not-called
+ self, service : Service, task_executor : TaskExecutor, **settings
+ ) -> None:
+ self.__service = service
+ self.__task_executor = task_executor
+ self.__settings_handler = SettingsHandler(service.service_config, **settings)
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetEndpoint(
+ self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+ ) -> List[Union[bool, Exception]]:
+
+ chk_type('endpoints', endpoints, list)
+ if len(endpoints) < 2: return []
+
+ service_uuid = self.__service.service_id.service_uuid.uuid
+ settings = self.__settings_handler.get('/settings')
+ json_settings : Dict = {} if settings is None else settings.value
+ encap_type = json_settings.get('encapsulation_type', '')
+ vlan_id = json_settings.get('vlan_id', 100)
+
+ results = []
+ try:
+ src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_endpoint = get_endpoint_matching(src_device, src_endpoint_uuid)
+ src_controller = self.__task_executor.get_device_controller(src_device)
+
+ dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[-1])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_endpoint = get_endpoint_matching(dst_device, dst_endpoint_uuid)
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
+
+ json_config_rule = json_config_rule_set('/services/service[{:s}]'.format(service_uuid), {
+ 'uuid' : service_uuid,
+ 'src_device_name' : src_device.name,
+ 'src_endpoint_name' : src_endpoint.name,
+ 'dst_device_name' : dst_device.name,
+ 'dst_endpoint_name' : dst_endpoint.name,
+ 'encapsulation_type': encap_type,
+ 'vlan_id' : vlan_id,
+ })
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteEndpoint(
+ self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+ ) -> List[Union[bool, Exception]]:
+
+ chk_type('endpoints', endpoints, list)
+ if len(endpoints) < 2: return []
+
+ service_uuid = self.__service.service_id.service_uuid.uuid
+
+ results = []
+ try:
+ src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_controller = self.__task_executor.get_device_controller(src_device)
+
+ dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
+
+ json_config_rule = json_config_rule_delete('/services/service[{:s}]'.format(service_uuid), {
+ 'uuid': service_uuid
+ })
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to DeleteEndpoint for Service({:s})'.format(str(service_uuid)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('constraints', constraints, list)
+ if len(constraints) == 0: return []
+
+ msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+ LOGGER.warning(msg.format(str(constraints)))
+ return [True for _ in range(len(constraints))]
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('constraints', constraints, list)
+ if len(constraints) == 0: return []
+
+ msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+ LOGGER.warning(msg.format(str(constraints)))
+ return [True for _ in range(len(constraints))]
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('resources', resources, list)
+ if len(resources) == 0: return []
+
+ results = []
+ for resource in resources:
+ try:
+ resource_value = json.loads(resource[1])
+ self.__settings_handler.set(resource[0], resource_value)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('resources', resources, list)
+ if len(resources) == 0: return []
+
+ results = []
+ for resource in resources:
+ try:
+ self.__settings_handler.delete(resource[0])
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource)))
+ results.append(e)
+
+ return results
diff --git a/src/service/service/service_handlers/l2nm_ietfl2vpn/__init__.py b/src/service/service/service_handlers/l2nm_ietfl2vpn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/service/service/service_handlers/l2nm_ietfl2vpn/__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/service/service/service_handlers/l2nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py
index 07e78d73631342d101d77697098e83961c7dcf26..63e818a8303f9939abe8f776cd34b3066cb84ec1 100644
--- a/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py
+++ b/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py
@@ -20,16 +20,13 @@ def setup_config_rules(
service_uuid : str, connection_uuid : str, device_uuid : str, endpoint_uuid : str, endpoint_name : str,
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
-
+
if service_settings is None: return []
if endpoint_settings is None: return []
- json_settings : Dict = service_settings.value
+ #json_settings : Dict = service_settings.value
json_endpoint_settings : Dict = endpoint_settings.value
- json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
-
#mtu = json_settings.get('mtu', 1450 ) # 1512
#address_families = json_settings.get('address_families', [] ) # ['IPV4']
#bgp_as = json_settings.get('bgp_as', 0 ) # 65000
@@ -41,43 +38,32 @@ def setup_config_rules(
vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400
#address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1'
#address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30
- remote_router = json_endpoint_settings.get('remote_router', '5.5.5.5') # '5.5.5.5'
- circuit_id = json_endpoint_settings.get('circuit_id', '111' ) # '111'
-
+ remote_router = json_endpoint_settings.get('remote_router', '0.0.0.0') # '5.5.5.5'
+ circuit_id = json_endpoint_settings.get('circuit_id', '000' ) # '111'
if_cirid_name = '{:s}.{:s}'.format(endpoint_name, str(circuit_id))
network_instance_name = 'ELAN-AC:{:s}'.format(str(circuit_id))
connection_point_id = 'VC-1'
json_config_rules = [
-
+
json_config_rule_set(
'/network_instance[{:s}]'.format(network_instance_name),
- {'name': network_instance_name,
- 'type': 'L2VSI'}),
+ {'name': network_instance_name, 'type': 'L2VSI'}),
json_config_rule_set(
- '/interface[{:s}]/subinterface[0]'.format(if_cirid_name),
- {'name': if_cirid_name,
- 'type': 'l2vlan',
- 'index': sub_interface_index,
- 'vlan_id': vlan_id}),
+ '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
+ {'name': if_cirid_name, 'type': 'l2vlan', 'index': sub_interface_index, 'vlan_id': vlan_id}),
json_config_rule_set(
'/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
- {'name': network_instance_name,
- 'id': if_cirid_name,
- 'interface': if_cirid_name,
- 'subinterface': 0
- }),
+ {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
+ 'subinterface': sub_interface_index}),
json_config_rule_set(
'/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
- {'name': network_instance_name,
- 'connection_point': connection_point_id,
- 'VC_ID': circuit_id,
- 'remote_system': remote_router
- }),
+ {'name': network_instance_name, 'connection_point': connection_point_id, 'VC_ID': circuit_id,
+ 'remote_system': remote_router}),
]
return json_config_rules
@@ -86,8 +72,11 @@ def teardown_config_rules(
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
- #json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ if service_settings is None: return []
+ if endpoint_settings is None: return []
+
+ #json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
#mtu = json_settings.get('mtu', 1450 ) # 1512
#address_families = json_settings.get('address_families', [] ) # ['IPV4']
@@ -96,7 +85,7 @@ def teardown_config_rules(
#router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10'
#route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0' ) # '60001:801'
- #sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1
+ sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1
#vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400
#address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1'
#address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30
@@ -105,17 +94,26 @@ def teardown_config_rules(
if_cirid_name = '{:s}.{:s}'.format(endpoint_name, str(circuit_id))
network_instance_name = 'ELAN-AC:{:s}'.format(str(circuit_id))
- #connection_point_id = 'VC-1'
+ connection_point_id = 'VC-1'
json_config_rules = [
+
+ json_config_rule_delete(
+ '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
+ {'name': network_instance_name, 'connection_point': connection_point_id, 'VC_ID': circuit_id}),
+
+ json_config_rule_delete(
+ '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
+ {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
+ 'subinterface': sub_interface_index}),
+
+ json_config_rule_delete(
+ '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
+ {'name': if_cirid_name, 'index': sub_interface_index}),
+
json_config_rule_delete(
'/network_instance[{:s}]'.format(network_instance_name),
{'name': network_instance_name}),
-
- json_config_rule_delete(
- '/interface[{:s}]/subinterface[0]'.format(if_cirid_name),{
- 'name': if_cirid_name,
- }),
-
+
]
return json_config_rules
diff --git a/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py b/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py
index 903ad8cd5ae442a03d54fb49083f3837a3c8187c..f4a46112e778bd01aa76322384d8adee942aaa5b 100644
--- a/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py
+++ b/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py
@@ -21,8 +21,11 @@ def setup_config_rules(
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
- json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ if service_settings is None: return []
+ if endpoint_settings is None: return []
+
+ json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
service_short_uuid = service_uuid.split('-')[-1]
network_instance_name = '{:s}-NetInst'.format(service_short_uuid)
@@ -142,8 +145,11 @@ def teardown_config_rules(
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
- json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ if service_settings is None: return []
+ if endpoint_settings is None: return []
+
+ json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
#mtu = json_settings.get('mtu', 1450 ) # 1512
#address_families = json_settings.get('address_families', [] ) # ['IPV4']
diff --git a/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py b/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py
index 47de9c94fbb8a5ddac848336c2ed7936d0126b45..18da03b08c0ea490b60c41df3894392022ddf228 100644
--- a/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py
+++ b/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py
@@ -75,10 +75,12 @@ class L3NMEmulatedServiceHandler(_ServiceHandler):
service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
settings, endpoint_settings)
- del device_obj.device_config.config_rules[:]
- for json_config_rule in json_config_rules:
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ if len(json_config_rules) > 0:
+ del device_obj.device_config.config_rules[:]
+ for json_config_rule in json_config_rules:
+ device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(device_obj)
+
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint)))
@@ -110,10 +112,12 @@ class L3NMEmulatedServiceHandler(_ServiceHandler):
service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
settings, endpoint_settings)
- del device_obj.device_config.config_rules[:]
- for json_config_rule in json_config_rules:
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ if len(json_config_rules) > 0:
+ del device_obj.device_config.config_rules[:]
+ for json_config_rule in json_config_rules:
+ device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(device_obj)
+
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint)))
diff --git a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
index ef93dcdda8145cab15ff21c24b6318e9eb00e098..5d260bf86b82c66be8eb2f0caa683a72d8bd0ba5 100644
--- a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
+++ b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
@@ -187,8 +187,8 @@ def teardown_config_rules(
if service_settings is None: return []
if endpoint_settings is None: return []
- json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
service_short_uuid = service_uuid.split('-')[-1]
network_instance_name = '{:s}-NetInst'.format(service_short_uuid)
diff --git a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py
index ee64d2fa4ff0110aea9ee4beee97fa83915ab57d..40c87eeee2c8dd1ddd5a39162f8ff7f117344e3b 100644
--- a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py
+++ b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py
@@ -61,7 +61,7 @@ class MicrowaveServiceHandler(_ServiceHandler):
device_uuid_dst, endpoint_uuid_dst = get_device_endpoint_uuids(endpoints[1])
if device_uuid_src != device_uuid_dst:
- raise Exception('Diferent Src-Dst devices not supported by now')
+ raise Exception('Different Src-Dst devices not supported by now')
device_uuid = device_uuid_src
device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
@@ -106,7 +106,7 @@ class MicrowaveServiceHandler(_ServiceHandler):
device_uuid_dst, _ = get_device_endpoint_uuids(endpoints[1])
if device_uuid_src != device_uuid_dst:
- raise Exception('Diferent Src-Dst devices not supported by now')
+ raise Exception('Different Src-Dst devices not supported by now')
device_uuid = device_uuid_src
device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
diff --git a/src/service/service/service_handlers/p4/p4_service_handler.py b/src/service/service/service_handlers/p4/p4_service_handler.py
index 6f2cfb5a9bc4dac991eecd14ba7b6eb1218bdaa2..8d609c11c9c1c4f25c0d387290c11de36af69a9a 100644
--- a/src/service/service/service_handlers/p4/p4_service_handler.py
+++ b/src/service/service/service_handlers/p4/p4_service_handler.py
@@ -16,18 +16,35 @@
P4 service handler for the TeraFlowSDN controller.
"""
-import anytree, json, logging
-from typing import Any, Dict, List, Optional, Tuple, Union
-from common.proto.context_pb2 import ConfigActionEnum, ConfigRule, DeviceId, Service
-from common.tools.object_factory.ConfigRule import json_config_rule, json_config_rule_delete, json_config_rule_set
+import logging
+from typing import Any, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricTypeEnum, MetricsPool, metered_subclass_method, INF
+from common.proto.context_pb2 import ConfigRule, DeviceId, Service
+from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
from common.tools.object_factory.Device import json_device_id
-from common.type_checkers.Checkers import chk_type, chk_length
+from common.type_checkers.Checkers import chk_type
from service.service.service_handler_api._ServiceHandler import _ServiceHandler
-from service.service.service_handler_api.AnyTreeTools import TreeNode, delete_subnode, get_subnode, set_subnode_value
from service.service.task_scheduler.TaskExecutor import TaskExecutor
LOGGER = logging.getLogger(__name__)
+HISTOGRAM_BUCKETS = (
+ # .005, .01, .025, .05, .075, .1, .25, .5, .75, 1.0, INF
+ 0.0010, 0.0025, 0.0050, 0.0075,
+ 0.0100, 0.0250, 0.0500, 0.0750,
+ 0.1000, 0.2500, 0.5000, 0.7500,
+ 1.0000, 2.5000, 5.0000, 7.5000,
+ 10.0000, 25.000, 50.0000, 75.000,
+ 100.0, INF
+)
+METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4'})
+METRICS_POOL.get_or_create('SetEndpoint', MetricTypeEnum.HISTOGRAM_DURATION, buckets=HISTOGRAM_BUCKETS)
+METRICS_POOL.get_or_create('DeleteEndpoint', MetricTypeEnum.HISTOGRAM_DURATION, buckets=HISTOGRAM_BUCKETS)
+METRICS_POOL.get_or_create('SetConstraint', MetricTypeEnum.HISTOGRAM_DURATION, buckets=HISTOGRAM_BUCKETS)
+METRICS_POOL.get_or_create('DeleteConstraint', MetricTypeEnum.HISTOGRAM_DURATION, buckets=HISTOGRAM_BUCKETS)
+METRICS_POOL.get_or_create('SetConfig', MetricTypeEnum.HISTOGRAM_DURATION, buckets=HISTOGRAM_BUCKETS)
+METRICS_POOL.get_or_create('DeleteConfig', MetricTypeEnum.HISTOGRAM_DURATION, buckets=HISTOGRAM_BUCKETS)
+
def create_rule_set(endpoint_a, endpoint_b):
return json_config_rule_set(
'table',
@@ -99,6 +116,7 @@ class P4ServiceHandler(_ServiceHandler):
self.__service = service
self.__task_executor = task_executor # pylint: disable=unused-private-member
+ @metered_subclass_method(METRICS_POOL)
def SetEndpoint(
self, endpoints : List[Tuple[str, str, Optional[str]]],
connection_uuid : Optional[str] = None
@@ -169,6 +187,7 @@ class P4ServiceHandler(_ServiceHandler):
return results
+ @metered_subclass_method(METRICS_POOL)
def DeleteEndpoint(
self, endpoints : List[Tuple[str, str, Optional[str]]],
connection_uuid : Optional[str] = None
@@ -239,6 +258,7 @@ class P4ServiceHandler(_ServiceHandler):
return results
+ @metered_subclass_method(METRICS_POOL)
def SetConstraint(self, constraints: List[Tuple[str, Any]]) \
-> List[Union[bool, Exception]]:
""" Create/Update service constraints.
@@ -261,6 +281,7 @@ class P4ServiceHandler(_ServiceHandler):
LOGGER.warning(msg.format(str(constraints)))
return [True for _ in range(len(constraints))]
+ @metered_subclass_method(METRICS_POOL)
def DeleteConstraint(self, constraints: List[Tuple[str, Any]]) \
-> List[Union[bool, Exception]]:
""" Delete service constraints.
@@ -285,6 +306,7 @@ class P4ServiceHandler(_ServiceHandler):
LOGGER.warning(msg.format(str(constraints)))
return [True for _ in range(len(constraints))]
+ @metered_subclass_method(METRICS_POOL)
def SetConfig(self, resources: List[Tuple[str, Any]]) \
-> List[Union[bool, Exception]]:
""" Create/Update configuration for a list of service resources.
@@ -308,6 +330,7 @@ class P4ServiceHandler(_ServiceHandler):
LOGGER.warning(msg.format(str(resources)))
return [True for _ in range(len(resources))]
+ @metered_subclass_method(METRICS_POOL)
def DeleteConfig(self, resources: List[Tuple[str, Any]]) \
-> List[Union[bool, Exception]]:
""" Delete configuration for a list of service resources.
diff --git a/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py b/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py
index 8abd12b2a24c49a6c5e50cebe7a2d65dc7ce4eb1..af7d4bc949fc98f057ade66b58d8b9b38e0707ed 100644
--- a/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py
+++ b/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py
@@ -19,7 +19,7 @@ from common.proto.context_pb2 import ConfigRule, DeviceId, Service
from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
from common.tools.object_factory.Device import json_device_id
from common.type_checkers.Checkers import chk_type
-from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
+from service.service.service_handler_api.Tools import get_device_endpoint_uuids
from service.service.service_handler_api._ServiceHandler import _ServiceHandler
from service.service.service_handler_api.SettingsHandler import SettingsHandler
from service.service.task_scheduler.TaskExecutor import TaskExecutor
@@ -42,7 +42,7 @@ class TapiServiceHandler(_ServiceHandler):
) -> List[Union[bool, Exception]]:
chk_type('endpoints', endpoints, list)
- if len(endpoints) != 2: return []
+ if len(endpoints) < 2: return []
service_uuid = self.__service.service_id.service_uuid.uuid
settings = self.__settings_handler.get('/settings')
@@ -55,30 +55,33 @@ class TapiServiceHandler(_ServiceHandler):
results = []
try:
- device_uuid_src, endpoint_uuid_src = get_device_endpoint_uuids(endpoints[0])
- device_uuid_dst, endpoint_uuid_dst = get_device_endpoint_uuids(endpoints[1])
+ src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_controller = self.__task_executor.get_device_controller(src_device)
+ if src_controller is None: src_controller = src_device
- if device_uuid_src != device_uuid_dst:
- raise Exception('Diferent Src-Dst devices not supported by now')
- device_uuid = device_uuid_src
+ dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[-1])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+ if dst_controller is None: dst_controller = dst_device
- device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
- endpoint_name_src = get_endpoint_matching(device_obj, endpoint_uuid_src).name
- endpoint_name_dst = get_endpoint_matching(device_obj, endpoint_uuid_dst).name
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
json_config_rule = json_config_rule_set('/services/service[{:s}]'.format(service_uuid), {
'uuid' : service_uuid,
- 'input_sip' : endpoint_name_src,
- 'output_sip' : endpoint_name_dst,
+ 'input_sip_uuid' : src_endpoint_uuid,
+ 'output_sip_uuid' : dst_endpoint_uuid,
'capacity_unit' : capacity_unit,
'capacity_value' : capacity_value,
'layer_protocol_name' : layer_proto_name,
'layer_protocol_qualifier': layer_proto_qual,
'direction' : direction,
})
- del device_obj.device_config.config_rules[:]
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid)))
@@ -92,27 +95,32 @@ class TapiServiceHandler(_ServiceHandler):
) -> List[Union[bool, Exception]]:
chk_type('endpoints', endpoints, list)
- if len(endpoints) != 2: return []
+ if len(endpoints) < 2: return []
service_uuid = self.__service.service_id.service_uuid.uuid
results = []
try:
- device_uuid_src, _ = get_device_endpoint_uuids(endpoints[0])
- device_uuid_dst, _ = get_device_endpoint_uuids(endpoints[1])
+ src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_controller = self.__task_executor.get_device_controller(src_device)
+ if src_controller is None: src_controller = src_device
- if device_uuid_src != device_uuid_dst:
- raise Exception('Diferent Src-Dst devices not supported by now')
- device_uuid = device_uuid_src
+ dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+ if dst_controller is None: dst_controller = dst_device
- device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
json_config_rule = json_config_rule_delete('/services/service[{:s}]'.format(service_uuid), {
'uuid': service_uuid
})
- del device_obj.device_config.config_rules[:]
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to DeleteEndpoint for Service({:s})'.format(str(service_uuid)))
diff --git a/src/service/service/service_handlers/tapi_xr/TapiXrServiceHandler.py b/src/service/service/service_handlers/tapi_xr/TapiXrServiceHandler.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1e1b8a6fff9436d6cdff13b95b0ecd43f6fa661
--- /dev/null
+++ b/src/service/service/service_handlers/tapi_xr/TapiXrServiceHandler.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 json, logging
+from typing import Any, Dict, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.proto.context_pb2 import ConfigRule, DeviceId, Service
+from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
+from common.tools.object_factory.Device import json_device_id
+from common.type_checkers.Checkers import chk_type
+from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
+from service.service.service_handler_api._ServiceHandler import _ServiceHandler
+from service.service.service_handler_api.SettingsHandler import SettingsHandler
+from service.service.task_scheduler.TaskExecutor import TaskExecutor
+
+LOGGER = logging.getLogger(__name__)
+
+METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'tapi_xr'})
+
+class TapiXrServiceHandler(_ServiceHandler):
+ def __init__( # pylint: disable=super-init-not-called
+ self, service : Service, task_executor : TaskExecutor, **settings
+ ) -> None:
+ self.__service = service
+ self.__task_executor = task_executor
+ self.__settings_handler = SettingsHandler(service.service_config, **settings)
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetEndpoint(
+ self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+ ) -> List[Union[bool, Exception]]:
+
+ chk_type('endpoints', endpoints, list)
+ if len(endpoints) != 4: return []
+
+ service_uuid = self.__service.service_id.service_uuid.uuid
+ settings = self.__settings_handler.get('/settings')
+ json_settings : Dict = {} if settings is None else settings.value
+ capacity_value = json_settings.get('capacity_value', 50.0)
+ capacity_unit = json_settings.get('capacity_unit', 'GHz')
+
+ results = []
+ try:
+ src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[1])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_endpoint = get_endpoint_matching(src_device, src_endpoint_uuid)
+ src_controller = self.__task_executor.get_device_controller(src_device)
+ if src_controller is None: src_controller = src_device
+
+ dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[2])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_endpoint = get_endpoint_matching(dst_device, dst_endpoint_uuid)
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+ if dst_controller is None: dst_controller = dst_device
+
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
+
+ json_config_rule = json_config_rule_set('/services/service[{:s}]'.format(service_uuid), {
+ 'uuid' : service_uuid,
+ 'input_sip_name' : '|'.join([src_device.name, src_endpoint.name]),
+ 'output_sip_name': '|'.join([dst_device.name, dst_endpoint.name]),
+ 'capacity_unit' : capacity_unit,
+ 'capacity_value' : capacity_value,
+ })
+
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteEndpoint(
+ self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+ ) -> List[Union[bool, Exception]]:
+
+ chk_type('endpoints', endpoints, list)
+ if len(endpoints) < 2: return []
+
+ service_uuid = self.__service.service_id.service_uuid.uuid
+
+ results = []
+ try:
+ src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_controller = self.__task_executor.get_device_controller(src_device)
+ if src_controller is None: src_controller = src_device
+
+ dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+ if dst_controller is None: dst_controller = dst_device
+
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
+
+ json_config_rule = json_config_rule_delete('/services/service[{:s}]'.format(service_uuid), {
+ 'uuid': service_uuid
+ })
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to DeleteEndpoint for Service({:s})'.format(str(service_uuid)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('constraints', constraints, list)
+ if len(constraints) == 0: return []
+
+ msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+ LOGGER.warning(msg.format(str(constraints)))
+ return [True for _ in range(len(constraints))]
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('constraints', constraints, list)
+ if len(constraints) == 0: return []
+
+ msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+ LOGGER.warning(msg.format(str(constraints)))
+ return [True for _ in range(len(constraints))]
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('resources', resources, list)
+ if len(resources) == 0: return []
+
+ results = []
+ for resource in resources:
+ try:
+ resource_value = json.loads(resource[1])
+ self.__settings_handler.set(resource[0], resource_value)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('resources', resources, list)
+ if len(resources) == 0: return []
+
+ results = []
+ for resource in resources:
+ try:
+ self.__settings_handler.delete(resource[0])
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource)))
+ results.append(e)
+
+ return results
diff --git a/src/service/service/service_handlers/tapi_xr/__init__.py b/src/service/service/service_handlers/tapi_xr/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/service/service/service_handlers/tapi_xr/__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/service/service/task_scheduler/TaskExecutor.py b/src/service/service/task_scheduler/TaskExecutor.py
index 932c56e2b1934e12e7849a60c22d3ca1be7f8093..3d157e3145d4b195c0251a4ab79f710a38726569 100644
--- a/src/service/service/task_scheduler/TaskExecutor.py
+++ b/src/service/service/task_scheduler/TaskExecutor.py
@@ -12,10 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import json
from enum import Enum
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from common.method_wrappers.ServiceExceptions import NotFoundException
from common.proto.context_pb2 import Connection, ConnectionId, Device, DeviceId, Service, ServiceId
+from common.tools.object_factory.Device import json_device_id
from context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient
from service.service.service_handler_api.ServiceHandlerFactory import ServiceHandlerFactory, get_service_handler_class
@@ -103,13 +105,38 @@ class TaskExecutor:
self._device_client.ConfigureDevice(device)
self._store_grpc_object(CacheableObjectType.DEVICE, device_key, device)
- def get_devices_from_connection(self, connection : Connection) -> Dict[str, Device]:
+ def get_device_controller(self, device : Device) -> Optional[Device]:
+ json_controller = None
+ for config_rule in device.device_config.config_rules:
+ if config_rule.WhichOneof('config_rule') != 'custom': continue
+ if config_rule.custom.resource_key != '_controller': continue
+ json_controller = json.loads(config_rule.custom.resource_value)
+ break
+
+ if json_controller is None: return None
+
+ controller_uuid = json_controller['uuid']
+ controller = self.get_device(DeviceId(**json_device_id(controller_uuid)))
+ controller_uuid = controller.device_id.device_uuid.uuid
+ if controller is None: raise Exception('Device({:s}) not found'.format(str(controller_uuid)))
+ return controller
+
+ def get_devices_from_connection(
+ self, connection : Connection, exclude_managed_by_controller : bool = False
+ ) -> Dict[str, Device]:
devices = dict()
for endpoint_id in connection.path_hops_endpoint_ids:
device = self.get_device(endpoint_id.device_id)
device_uuid = endpoint_id.device_id.device_uuid.uuid
if device is None: raise Exception('Device({:s}) not found'.format(str(device_uuid)))
- devices[device_uuid] = device
+
+ controller = self.get_device_controller(device)
+ if controller is None:
+ devices[device_uuid] = device
+ else:
+ if not exclude_managed_by_controller:
+ devices[device_uuid] = device
+ devices[controller.device_id.device_uuid.uuid] = controller
return devices
# ----- Service-related methods ------------------------------------------------------------------------------------
@@ -139,6 +166,6 @@ class TaskExecutor:
def get_service_handler(
self, connection : Connection, service : Service, **service_handler_settings
) -> '_ServiceHandler':
- connection_devices = self.get_devices_from_connection(connection)
+ connection_devices = self.get_devices_from_connection(connection, exclude_managed_by_controller=True)
service_handler_class = get_service_handler_class(self._service_handler_factory, service, connection_devices)
return service_handler_class(service, self, **service_handler_settings)
diff --git a/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py b/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py
index 5a47005b304836050dd8c0882214dd9cebd5d8b5..4367ffdee4d6d5b9edfc9fd30d0d6b6f48da8a75 100644
--- a/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py
+++ b/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py
@@ -32,7 +32,7 @@ class Task_ConnectionConfigure(_Task):
def connection_id(self) -> ConnectionId: return self._connection_id
@staticmethod
- def build_key(connection_id : ConnectionId) -> str:
+ def build_key(connection_id : ConnectionId) -> str: # pylint: disable=arguments-differ
str_connection_id = get_connection_key(connection_id)
return KEY_TEMPLATE.format(connection_id=str_connection_id)
diff --git a/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py b/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py
index 5736054febd2fb9e8a36b5a2235ca3f412e0e174..70f41566ef5e69605a527cc0392b77acb866ec2c 100644
--- a/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py
+++ b/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py
@@ -32,7 +32,7 @@ class Task_ConnectionDeconfigure(_Task):
def connection_id(self) -> ConnectionId: return self._connection_id
@staticmethod
- def build_key(connection_id : ConnectionId) -> str:
+ def build_key(connection_id : ConnectionId) -> str: # pylint: disable=arguments-differ
str_connection_id = get_connection_key(connection_id)
return KEY_TEMPLATE.format(connection_id=str_connection_id)
diff --git a/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py b/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py
index 6a4e11b540cd9b85028d92cf86899ee098056c36..0f021b6ca65da1c6b5e44d8577bf9dd6875eb17a 100644
--- a/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py
+++ b/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py
@@ -28,7 +28,7 @@ class Task_ServiceDelete(_Task):
def service_id(self) -> ServiceId: return self._service_id
@staticmethod
- def build_key(service_id : ServiceId) -> str:
+ def build_key(service_id : ServiceId) -> str: # pylint: disable=arguments-differ
str_service_id = get_service_key(service_id)
return KEY_TEMPLATE.format(service_id=str_service_id)
diff --git a/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py b/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py
index 815cb33c3d540755704153b661e889fc2660d268..d5360fe85eae68085298406fc0ed19dd105f187e 100644
--- a/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py
+++ b/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py
@@ -32,7 +32,7 @@ class Task_ServiceSetStatus(_Task):
def new_status(self) -> ServiceStatusEnum: return self._new_status
@staticmethod
- def build_key(service_id : ServiceId, new_status : ServiceStatusEnum) -> str:
+ def build_key(service_id : ServiceId, new_status : ServiceStatusEnum) -> str: # pylint: disable=arguments-differ
str_service_id = get_service_key(service_id)
str_new_status = ServiceStatusEnum.Name(new_status)
return KEY_TEMPLATE.format(service_id=str_service_id, new_status=str_new_status)
diff --git a/src/slice/service/slice_grouper/SliceGrouper.py b/src/slice/service/slice_grouper/SliceGrouper.py
index 735d028993eb11e83138caebde1e32ebc830093f..2f1a791819f6a8d0951e9e93ca22d071ea66c1f7 100644
--- a/src/slice/service/slice_grouper/SliceGrouper.py
+++ b/src/slice/service/slice_grouper/SliceGrouper.py
@@ -29,6 +29,7 @@ class SliceGrouper:
def __init__(self) -> None:
self._lock = threading.Lock()
self._is_enabled = is_slice_grouping_enabled()
+ LOGGER.info('Slice Grouping: {:s}'.format('ENABLED' if self._is_enabled else 'DISABLED'))
if not self._is_enabled: return
metrics_exporter = MetricsExporter()
diff --git a/src/tests/ofc22/descriptors_emulated.json b/src/tests/ofc22/descriptors_emulated.json
index aa76edecd116ee7336fc1a2621d2bc3ae95080ce..b68b9636d58d9c80c4774e4ade557f83796ac5b5 100644
--- a/src/tests/ofc22/descriptors_emulated.json
+++ b/src/tests/ofc22/descriptors_emulated.json
@@ -97,6 +97,35 @@
{"device_id": {"device_uuid": {"uuid": "R4-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}},
{"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
]
+ },
+
+ {
+ "link_id": {"link_uuid": {"uuid": "O1-OLS==R1-EMU/13/0/0/aade6001-f00b-5e2f-a357-6a0a9d3de870"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}},
+ {"device_id": {"device_uuid": {"uuid": "R1-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "O1-OLS==R2-EMU/13/0/0/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}},
+ {"device_id": {"device_uuid": {"uuid": "R2-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "O1-OLS==R3-EMU/13/0/0/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}},
+ {"device_id": {"device_uuid": {"uuid": "R3-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "O1-OLS==R4-EMU/13/0/0/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}},
+ {"device_id": {"device_uuid": {"uuid": "R4-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}
+ ]
}
]
}
\ No newline at end of file
diff --git a/src/tests/ofc23/.gitignore b/src/tests/ofc23/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..0a3f4400d5c88b1af32c7667d69d2fdc12d5424e
--- /dev/null
+++ b/src/tests/ofc23/.gitignore
@@ -0,0 +1,2 @@
+# Add here your files containing confidential testbed details such as IP addresses, ports, usernames, passwords, etc.
+descriptors_real.json
diff --git a/src/tests/ofc23/MultiIngressController.txt b/src/tests/ofc23/MultiIngressController.txt
new file mode 100644
index 0000000000000000000000000000000000000000..190e6df7425983db43d8b1888f29861ec9056ed6
--- /dev/null
+++ b/src/tests/ofc23/MultiIngressController.txt
@@ -0,0 +1,23 @@
+# Ref: https://kubernetes.github.io/ingress-nginx/user-guide/multiple-ingress/
+# Ref: https://fabianlee.org/2021/07/29/kubernetes-microk8s-with-multiple-metallb-endpoints-and-nginx-ingress-controllers/
+
+# Check node limits
+kubectl describe nodes
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
+
+source ofc23/deploy_specs_parent.sh
+./deploy/all.sh
+
+source ofc23/deploy_specs_child.sh
+./deploy/all.sh
+
+# Manually deploy ingresses for instances
+kubectl --namespace tfs-parent apply -f ofc23/tfs-ingress-parent.yaml
+kubectl --namespace tfs-child apply -f ofc23/tfs-ingress-child.yaml
diff --git a/src/tests/ofc23/__init__.py b/src/tests/ofc23/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/tests/ofc23/__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/ofc23/delete_hierar.sh b/src/tests/ofc23/delete_hierar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4a03dad1cc29cff72347f68bc7b1a082924a9211
--- /dev/null
+++ b/src/tests/ofc23/delete_hierar.sh
@@ -0,0 +1,22 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-parent tfs-child
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
diff --git a/src/tests/ofc23/delete_sligrp.sh b/src/tests/ofc23/delete_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cce0bd53febc4765f9d455619f49ea4de8dfe870
--- /dev/null
+++ b/src/tests/ofc23/delete_sligrp.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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs
diff --git a/src/tests/ofc23/deploy_child.sh b/src/tests/ofc23/deploy_child.sh
new file mode 100755
index 0000000000000000000000000000000000000000..9b05ed88739114bf9029d8afaf491d7fec726bff
--- /dev/null
+++ b/src/tests/ofc23/deploy_child.sh
@@ -0,0 +1,29 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-child
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml
+
+# Deploy TFS for Child
+source ofc23/deploy_specs_child.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_child.sh
diff --git a/src/tests/ofc23/deploy_hierar.sh b/src/tests/ofc23/deploy_hierar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4874688ad7561156b1b5fc4c80b72a9745feb6a0
--- /dev/null
+++ b/src/tests/ofc23/deploy_hierar.sh
@@ -0,0 +1,36 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-parent tfs-child
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml
+
+# Deploy TFS for Parent
+source ofc23/deploy_specs_parent.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_parent.sh
+
+# Deploy TFS for Child
+source ofc23/deploy_specs_child.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_child.sh
diff --git a/src/tests/ofc23/deploy_parent.sh b/src/tests/ofc23/deploy_parent.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ac4a2954213cf577efe1b1a8e499635d80ea3548
--- /dev/null
+++ b/src/tests/ofc23/deploy_parent.sh
@@ -0,0 +1,29 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-parent
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml
+
+# Deploy TFS for Parent
+source ofc23/deploy_specs_parent.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_parent.sh
diff --git a/src/tests/ofc23/deploy_sligrp.sh b/src/tests/ofc23/deploy_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..62a9df5cf006af856f168add4058d63eaa905784
--- /dev/null
+++ b/src/tests/ofc23/deploy_sligrp.sh
@@ -0,0 +1,23 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-sligrp
+
+# Deploy TFS for Slice Goruping
+source ofc23/deploy_specs_sligrp.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_sligrp.sh
diff --git a/src/tests/ofc23/deploy_specs_child.sh b/src/tests/ofc23/deploy_specs_child.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4d2b3502294925d82f675263fd6bddea62ec181a
--- /dev/null
+++ b/src/tests/ofc23/deploy_specs_child.sh
@@ -0,0 +1,118 @@
+#!/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.
+
+
+# ----- TeraFlowSDN ------------------------------------------------------------
+
+# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to.
+export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/"
+
+# Set the list of components, separated by spaces, you want to build images for, and deploy.
+#automation monitoring load_generator
+export TFS_COMPONENTS="context device pathcomp service slice compute webui"
+
+# Set the tag you want to use for your images.
+export TFS_IMAGE_TAG="dev"
+
+# Set the name of the Kubernetes namespace to deploy TFS to.
+export TFS_K8S_NAMESPACE="tfs-child"
+
+# Set additional manifest files to be applied after the deployment
+export TFS_EXTRA_MANIFESTS="ofc23/tfs-ingress-child.yaml"
+
+# Set the new Grafana admin password
+export TFS_GRAFANA_PASSWORD="admin123+"
+
+# Disable skip-build flag to rebuild the Docker images.
+export TFS_SKIP_BUILD="YES"
+
+
+# ----- CockroachDB ------------------------------------------------------------
+
+# Set the namespace where CockroackDB will be deployed.
+export CRDB_NAMESPACE="crdb"
+
+# Set the external port CockroackDB Postgre SQL interface will be exposed to.
+export CRDB_EXT_PORT_SQL="26257"
+
+# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to.
+export CRDB_EXT_PORT_HTTP="8081"
+
+# Set the database username to be used by Context.
+export CRDB_USERNAME="tfs"
+
+# Set the database user's password to be used by Context.
+export CRDB_PASSWORD="tfs123"
+
+# Set the database name to be used by Context.
+export CRDB_DATABASE="tfs_child"
+
+# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing.
+# See ./deploy/all.sh or ./deploy/crdb.sh for additional details
+export CRDB_DEPLOY_MODE="single"
+
+# Disable flag for dropping database, if it exists.
+export CRDB_DROP_DATABASE_IF_EXISTS="YES"
+
+# Disable flag for re-deploying CockroachDB from scratch.
+export CRDB_REDEPLOY=""
+
+
+# ----- NATS -------------------------------------------------------------------
+
+# Set the namespace where NATS will be deployed.
+export NATS_NAMESPACE="nats-child"
+
+# Set the external port NATS Client interface will be exposed to.
+export NATS_EXT_PORT_CLIENT="4224"
+
+# Set the external port NATS HTTP Mgmt GUI interface will be exposed to.
+export NATS_EXT_PORT_HTTP="8224"
+
+# Disable flag for re-deploying NATS from scratch.
+export NATS_REDEPLOY=""
+
+
+# ----- QuestDB ----------------------------------------------------------------
+
+# Set the namespace where QuestDB will be deployed.
+export QDB_NAMESPACE="qdb-child"
+
+# Set the external port QuestDB Postgre SQL interface will be exposed to.
+export QDB_EXT_PORT_SQL="8814"
+
+# Set the external port QuestDB Influx Line Protocol interface will be exposed to.
+export QDB_EXT_PORT_ILP="9012"
+
+# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to.
+export QDB_EXT_PORT_HTTP="9002"
+
+# Set the database username to be used for QuestDB.
+export QDB_USERNAME="admin"
+
+# Set the database user's password to be used for QuestDB.
+export QDB_PASSWORD="quest"
+
+# Set the table name to be used by Monitoring for KPIs.
+export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis"
+
+# Set the table name to be used by Slice for plotting groups.
+export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups"
+
+# Disable flag for dropping tables if they exist.
+export QDB_DROP_TABLES_IF_EXIST="YES"
+
+# Disable flag for re-deploying QuestDB from scratch.
+export QDB_REDEPLOY=""
diff --git a/src/tests/ofc23/deploy_specs_parent.sh b/src/tests/ofc23/deploy_specs_parent.sh
new file mode 100755
index 0000000000000000000000000000000000000000..808f4e28734be71e6eb7fb2aced39211fd8e7f24
--- /dev/null
+++ b/src/tests/ofc23/deploy_specs_parent.sh
@@ -0,0 +1,118 @@
+#!/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.
+
+
+# ----- TeraFlowSDN ------------------------------------------------------------
+
+# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to.
+export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/"
+
+# Set the list of components, separated by spaces, you want to build images for, and deploy.
+#automation monitoring load_generator
+export TFS_COMPONENTS="context device pathcomp service slice compute webui"
+
+# Set the tag you want to use for your images.
+export TFS_IMAGE_TAG="dev"
+
+# Set the name of the Kubernetes namespace to deploy TFS to.
+export TFS_K8S_NAMESPACE="tfs-parent"
+
+# Set additional manifest files to be applied after the deployment
+export TFS_EXTRA_MANIFESTS="ofc23/tfs-ingress-parent.yaml"
+
+# Set the new Grafana admin password
+export TFS_GRAFANA_PASSWORD="admin123+"
+
+# Disable skip-build flag to rebuild the Docker images.
+export TFS_SKIP_BUILD=""
+
+
+# ----- CockroachDB ------------------------------------------------------------
+
+# Set the namespace where CockroackDB will be deployed.
+export CRDB_NAMESPACE="crdb"
+
+# Set the external port CockroackDB Postgre SQL interface will be exposed to.
+export CRDB_EXT_PORT_SQL="26257"
+
+# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to.
+export CRDB_EXT_PORT_HTTP="8081"
+
+# Set the database username to be used by Context.
+export CRDB_USERNAME="tfs"
+
+# Set the database user's password to be used by Context.
+export CRDB_PASSWORD="tfs123"
+
+# Set the database name to be used by Context.
+export CRDB_DATABASE="tfs_parent"
+
+# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing.
+# See ./deploy/all.sh or ./deploy/crdb.sh for additional details
+export CRDB_DEPLOY_MODE="single"
+
+# Disable flag for dropping database, if it exists.
+export CRDB_DROP_DATABASE_IF_EXISTS="YES"
+
+# Disable flag for re-deploying CockroachDB from scratch.
+export CRDB_REDEPLOY=""
+
+
+# ----- NATS -------------------------------------------------------------------
+
+# Set the namespace where NATS will be deployed.
+export NATS_NAMESPACE="nats-parent"
+
+# Set the external port NATS Client interface will be exposed to.
+export NATS_EXT_PORT_CLIENT="4223"
+
+# Set the external port NATS HTTP Mgmt GUI interface will be exposed to.
+export NATS_EXT_PORT_HTTP="8223"
+
+# Disable flag for re-deploying NATS from scratch.
+export NATS_REDEPLOY=""
+
+
+# ----- QuestDB ----------------------------------------------------------------
+
+# Set the namespace where QuestDB will be deployed.
+export QDB_NAMESPACE="qdb-parent"
+
+# Set the external port QuestDB Postgre SQL interface will be exposed to.
+export QDB_EXT_PORT_SQL="8813"
+
+# Set the external port QuestDB Influx Line Protocol interface will be exposed to.
+export QDB_EXT_PORT_ILP="9011"
+
+# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to.
+export QDB_EXT_PORT_HTTP="9001"
+
+# Set the database username to be used for QuestDB.
+export QDB_USERNAME="admin"
+
+# Set the database user's password to be used for QuestDB.
+export QDB_PASSWORD="quest"
+
+# Set the table name to be used by Monitoring for KPIs.
+export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis"
+
+# Set the table name to be used by Slice for plotting groups.
+export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups"
+
+# Disable flag for dropping tables if they exist.
+export QDB_DROP_TABLES_IF_EXIST="YES"
+
+# Disable flag for re-deploying QuestDB from scratch.
+export QDB_REDEPLOY=""
diff --git a/src/tests/ofc23/deploy_specs_sligrp.sh b/src/tests/ofc23/deploy_specs_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..90bea4567bd35d845abf943670f8aa33070dff57
--- /dev/null
+++ b/src/tests/ofc23/deploy_specs_sligrp.sh
@@ -0,0 +1,118 @@
+#!/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.
+
+
+# ----- TeraFlowSDN ------------------------------------------------------------
+
+# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to.
+export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/"
+
+# Set the list of components, separated by spaces, you want to build images for, and deploy.
+#automation monitoring load_generator
+export TFS_COMPONENTS="context device pathcomp service slice webui load_generator"
+
+# Set the tag you want to use for your images.
+export TFS_IMAGE_TAG="dev"
+
+# Set the name of the Kubernetes namespace to deploy TFS to.
+export TFS_K8S_NAMESPACE="tfs-sligrp"
+
+# Set additional manifest files to be applied after the deployment
+export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml"
+
+# Set the new Grafana admin password
+export TFS_GRAFANA_PASSWORD="admin123+"
+
+# Disable skip-build flag to rebuild the Docker images.
+export TFS_SKIP_BUILD=""
+
+
+# ----- CockroachDB ------------------------------------------------------------
+
+# Set the namespace where CockroackDB will be deployed.
+export CRDB_NAMESPACE="crdb"
+
+# Set the external port CockroackDB Postgre SQL interface will be exposed to.
+export CRDB_EXT_PORT_SQL="26257"
+
+# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to.
+export CRDB_EXT_PORT_HTTP="8081"
+
+# Set the database username to be used by Context.
+export CRDB_USERNAME="tfs"
+
+# Set the database user's password to be used by Context.
+export CRDB_PASSWORD="tfs123"
+
+# Set the database name to be used by Context.
+export CRDB_DATABASE="tfs_sligrp"
+
+# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing.
+# See ./deploy/all.sh or ./deploy/crdb.sh for additional details
+export CRDB_DEPLOY_MODE="single"
+
+# Disable flag for dropping database, if it exists.
+export CRDB_DROP_DATABASE_IF_EXISTS="YES"
+
+# Disable flag for re-deploying CockroachDB from scratch.
+export CRDB_REDEPLOY=""
+
+
+# ----- NATS -------------------------------------------------------------------
+
+# Set the namespace where NATS will be deployed.
+export NATS_NAMESPACE="nats-sligrp"
+
+# Set the external port NATS Client interface will be exposed to.
+export NATS_EXT_PORT_CLIENT="4222"
+
+# Set the external port NATS HTTP Mgmt GUI interface will be exposed to.
+export NATS_EXT_PORT_HTTP="8222"
+
+# Disable flag for re-deploying NATS from scratch.
+export NATS_REDEPLOY=""
+
+
+# ----- QuestDB ----------------------------------------------------------------
+
+# Set the namespace where QuestDB will be deployed.
+export QDB_NAMESPACE="qdb-sligrp"
+
+# Set the external port QuestDB Postgre SQL interface will be exposed to.
+export QDB_EXT_PORT_SQL="8812"
+
+# Set the external port QuestDB Influx Line Protocol interface will be exposed to.
+export QDB_EXT_PORT_ILP="9010"
+
+# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to.
+export QDB_EXT_PORT_HTTP="9000"
+
+# Set the database username to be used for QuestDB.
+export QDB_USERNAME="admin"
+
+# Set the database user's password to be used for QuestDB.
+export QDB_PASSWORD="quest"
+
+# Set the table name to be used by Monitoring for KPIs.
+export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis"
+
+# Set the table name to be used by Slice for plotting groups.
+export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups"
+
+# Disable flag for dropping tables if they exist.
+export QDB_DROP_TABLES_IF_EXIST="YES"
+
+# Disable flag for re-deploying QuestDB from scratch.
+export QDB_REDEPLOY=""
diff --git a/src/tests/ofc23/descriptors/adva-interfaces.txt b/src/tests/ofc23/descriptors/adva-interfaces.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a634735058aa9490ddbd43e8e9a0752fbd4a6ee6
--- /dev/null
+++ b/src/tests/ofc23/descriptors/adva-interfaces.txt
@@ -0,0 +1,89 @@
+R199
+eth-1/0/1
+eth-1/0/10
+eth-1/0/11
+eth-1/0/12
+eth-1/0/13
+eth-1/0/14
+eth-1/0/15
+eth-1/0/16
+eth-1/0/17
+eth-1/0/18
+eth-1/0/19
+eth-1/0/2
+eth-1/0/20
+eth-1/0/21
+eth-1/0/22
+eth-1/0/23
+eth-1/0/24
+eth-1/0/25
+eth-1/0/26
+eth-1/0/27
+eth-1/0/28
+eth-1/0/29
+eth-1/0/3
+eth-1/0/30
+eth-1/0/4
+eth-1/0/5
+eth-1/0/6
+eth-1/0/7
+eth-1/0/8
+eth-1/0/9
+
+R155
+eth-1/0/1
+eth-1/0/10
+eth-1/0/11
+eth-1/0/12
+eth-1/0/13
+eth-1/0/14
+eth-1/0/15
+eth-1/0/16
+eth-1/0/17
+eth-1/0/18
+eth-1/0/19
+eth-1/0/2
+eth-1/0/20
+eth-1/0/21
+eth-1/0/22
+eth-1/0/23
+eth-1/0/24
+eth-1/0/25
+eth-1/0/26
+eth-1/0/27
+eth-1/0/3
+eth-1/0/4
+eth-1/0/5
+eth-1/0/6
+eth-1/0/7
+eth-1/0/8
+eth-1/0/9
+
+R149
+eth-1/0/1
+eth-1/0/10
+eth-1/0/11
+eth-1/0/12
+eth-1/0/13
+eth-1/0/14
+eth-1/0/15
+eth-1/0/16
+eth-1/0/17
+eth-1/0/18
+eth-1/0/19
+eth-1/0/2
+eth-1/0/20
+eth-1/0/21
+eth-1/0/22
+eth-1/0/23
+eth-1/0/24
+eth-1/0/25
+eth-1/0/26
+eth-1/0/27
+eth-1/0/3
+eth-1/0/4
+eth-1/0/5
+eth-1/0/6
+eth-1/0/7
+eth-1/0/8
+eth-1/0/9
diff --git a/src/tests/ofc23/descriptors/backup/dc-2-dc-service.json b/src/tests/ofc23/descriptors/backup/dc-2-dc-service.json
new file mode 100644
index 0000000000000000000000000000000000000000..3a83afa6de81f137204aecc5f0eca476aad71e61
--- /dev/null
+++ b/src/tests/ofc23/descriptors/backup/dc-2-dc-service.json
@@ -0,0 +1,37 @@
+{
+ "services": [
+ {
+ "service_id": {
+ "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "dc-2-dc-svc"}
+ },
+ "service_type": 2,
+ "service_status": {"service_status": 1},
+ "service_endpoint_ids": [
+ {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}},
+ {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}}
+ ],
+ "service_constraints": [
+ {"sla_capacity": {"capacity_gbps": 10.0}},
+ {"sla_latency": {"e2e_latency_ms": 15.2}}
+ ],
+ "service_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "/settings", "resource_value": {
+ "address_families": ["IPV4"], "bgp_as": 65000, "bgp_route_target": "65000:123",
+ "mtu": 1512, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R149]/endpoint[eth-1/0/22]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.5",
+ "address_ip": "172.16.4.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R155]/endpoint[eth-1/0/22]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.1",
+ "address_ip": "172.16.2.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R199]/endpoint[eth-1/0/21]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.6",
+ "address_ip": "172.16.1.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}}
+ ]}
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/backup/descriptor_child.json b/src/tests/ofc23/descriptors/backup/descriptor_child.json
new file mode 100644
index 0000000000000000000000000000000000000000..eea9571531cfbebfcc53dba0679d1bd1b6900b2f
--- /dev/null
+++ b/src/tests/ofc23/descriptors/backup/descriptor_child.json
@@ -0,0 +1,183 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "R199"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.199"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 120},
+ "endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/4"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/5"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/6"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/7"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/8"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/9"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/10"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/11"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/12"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/13"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/14"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/15"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/16"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/17"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/18"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/19"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/20"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/21"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/22"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/23"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/24"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/25"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/26"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/27"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/28"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/29"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/30"}
+ ]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R155"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.155"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 120},
+ "endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/4"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/5"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/6"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/7"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/8"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/9"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/10"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/11"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/12"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/13"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/14"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/15"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/16"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/17"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/18"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/19"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/20"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/21"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/22"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/23"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/24"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/25"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/26"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/27"}
+ ]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R149"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.149"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 120},
+ "endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/4"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/5"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/6"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/7"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/8"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/9"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/10"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/11"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/12"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/13"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/14"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/15"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/16"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/17"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/18"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/19"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/20"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/21"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/22"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/23"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/24"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/25"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/26"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/27"}
+ ]
+ }}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/19==R155/eth-1/0/19"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/19==R199/eth-1/0/19"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==R149/eth-1/0/20"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/20==R199/eth-1/0/20"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/25==R155/eth-1/0/25"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==R149/eth-1/0/25"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/backup/descriptor_parent.json b/src/tests/ofc23/descriptors/backup/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..42b60e3cf09285955fbfbc567d977e60f78956be
--- /dev/null
+++ b/src/tests/ofc23/descriptors/backup/descriptor_parent.json
@@ -0,0 +1,258 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "scheme": "http", "username": "admin", "password": "admin"
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4, 5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "nms5ux", "password": "nms5ux", "timeout": 120, "scheme": "https",
+ "node_ids": ["192.168.27.139", "192.168.27.140"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+ "consistency-mode": "lifecycle", "import_topology": "devices"
+ }}}
+ ]}
+ },
+
+
+ {
+ "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth1==R149/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/22==DC1/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/9==MW/192.168.27.140:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.140:5==R149/eth-1/0/9"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.139:5==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW/192.168.27.139:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==R155/eth-1/0/25"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==R199/eth-1/0/20"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/22==DC2/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth1==R155/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/21==DC2/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth2==R199/eth-1/0/21"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/dc-2-dc-service.json b/src/tests/ofc23/descriptors/emulated/dc-2-dc-service.json
new file mode 100644
index 0000000000000000000000000000000000000000..7c3be015d0965d4bdaed8e225e79da072a7de6f3
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/dc-2-dc-service.json
@@ -0,0 +1,41 @@
+{
+ "services": [
+ {
+ "service_id": {
+ "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "dc-2-dc-svc"}
+ },
+ "service_type": 2,
+ "service_status": {"service_status": 1},
+ "service_endpoint_ids": [
+ {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}},
+ {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}}
+ ],
+ "service_constraints": [
+ {"sla_capacity": {"capacity_gbps": 10.0}},
+ {"sla_latency": {"e2e_latency_ms": 15.2}}
+ ],
+ "service_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "/settings", "resource_value": {
+ "address_families": ["IPV4"], "bgp_as": 65000, "bgp_route_target": "65000:123",
+ "mtu": 1512, "vlan_id": 300
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[PE1]/endpoint[1/1]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "10.0.0.1",
+ "address_ip": "3.3.1.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[PE2]/endpoint[1/1]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "10.0.0.2",
+ "address_ip": "3.3.2.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[PE3]/endpoint[1/1]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "10.0.0.3",
+ "address_ip": "3.3.3.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[PE4]/endpoint[1/1]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "10.0.0.4",
+ "address_ip": "3.3.4.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+ }}}
+ ]}
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/descriptor_child.json b/src/tests/ofc23/descriptors/emulated/descriptor_child.json
new file mode 100644
index 0000000000000000000000000000000000000000..1dc6fd35531db1989b9b85c846b6fc8d0524f08f
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/descriptor_child.json
@@ -0,0 +1,149 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "PE1"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "PE2"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "PE3"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "PE4"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/2/2==PE2/2/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/2/3==PE3/2/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/3"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/2/4==PE4/2/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/4"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/1"}}
+ ]
+ },
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/2/1==PE1/2/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/2/3==PE3/2/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/3"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/2/4==PE4/2/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/4"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/2"}}
+ ]
+ },
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/2/1==PE1/2/3"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/3"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/2/2==PE2/2/3"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/3"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/2/2==PE2/2/4"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/4"}}
+ ]
+ },
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/2/1==PE1/2/4"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/4"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/2/2==PE2/2/4"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/4"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/2/3==PE3/2/4"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/3"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/4"}}
+ ]
+ }
+
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/descriptor_parent.json b/src/tests/ofc23/descriptors/emulated/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..1b1f5dbfd57b2e1543e86ba8d2633a0e944fced5
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/descriptor_parent.json
@@ -0,0 +1,258 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "scheme": "http", "username": "admin", "password": "admin"
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4, 5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+ "node_ids": ["192.168.27.139", "192.168.27.140"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+ "consistency-mode": "lifecycle", "import_topology": "devices"
+ }}}
+ ]}
+ },
+
+
+ {
+ "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth1==R149/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/22==DC1/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/9==MW/192.168.27.140:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.140:5==R149/eth-1/0/9"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.139:5==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW/192.168.27.139:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==R155/eth-1/0/25"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==R199/eth-1/0/20"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/22==DC2/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth1==R155/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/21==DC2/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth2==R199/eth-1/0/21"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/descriptor_parent_noxr.json b/src/tests/ofc23/descriptors/emulated/descriptor_parent_noxr.json
new file mode 100644
index 0000000000000000000000000000000000000000..c4a6646ede081fc2f6ee449d7771de3dbcbd77ec
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/descriptor_parent_noxr.json
@@ -0,0 +1,332 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "scheme": "http", "username": "admin", "password": "admin"
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW1-2"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+ "node_ids": ["172.18.0.1", "172.18.0.2"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW3-4"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+ "node_ids": ["172.18.0.3", "172.18.0.4"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R1"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/3"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R2"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R3"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth1==PE1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/1/1==DC1/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth2==PE2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/1/1==DC1/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/1/2==MW1-2/172.18.0.1:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.1:1==PE1/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.2:1==R1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}},
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R1/1/1==MW1-2/172.18.0.2:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/1/2==MW3-4/172.18.0.3:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.3:1==PE2/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.4:1==R1/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}},
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R1/1/2==MW3-4/172.18.0.4:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R1/1/3==Optical-Splitter/common"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/3"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==R1/1/3"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/3"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R2/1/1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==R2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+ {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R3/1/1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==R3/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+ {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R2/1/2==PE3/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/1/2==R2/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R3/1/2==PE4/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/1/2==R3/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/1/1==DC2/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth1==PE3/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/1/1==DC2/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth2==PE4/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/ipm-ctrl.json b/src/tests/ofc23/descriptors/emulated/ipm-ctrl.json
new file mode 100644
index 0000000000000000000000000000000000000000..91e9de611dac2627525bb11f81755ea651887e74
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/ipm-ctrl.json
@@ -0,0 +1,25 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"topology_uuid": {"uuid": "admin"}, "context_id": {"context_uuid": {"uuid": "admin"}}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "XR-CONSTELLATION"}},
+ "device_type": "xr-constellation",
+ "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+ "consistency-mode": "lifecycle"
+ }}}
+ ]},
+ "device_operational_status": 1,
+ "device_drivers": [6],
+ "device_endpoints": []
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/old/descriptor_parent.json b/src/tests/ofc23/descriptors/emulated/old/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..413b7566292d7841777547aeb665c7eb3b8ca293
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/old/descriptor_parent.json
@@ -0,0 +1,311 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "scheme": "http", "username": "admin", "password": "admin"
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW1-2"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+ "node_ids": ["172.18.0.1", "172.18.0.2"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW3-4"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+ "node_ids": ["172.18.0.3", "172.18.0.4"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+ "consistency-mode": "lifecycle", "import_topology": "devices"
+ }}}
+ ]}
+ },
+
+
+ {
+ "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth1==PE1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/1/1==DC1/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth2==PE2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/1/1==DC1/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/1/2==MW1-2/172.18.0.1:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.1:1==PE1/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.2:1==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW1-2/172.18.0.2:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/1/2==MW3-4/172.18.0.3:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.3:1==PE2/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.4:1==OFC HUB 1/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/2==MW3-4/172.18.0.4:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==PE3/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/1/2==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==PE4/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/1/2==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/1/1==DC2/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth1==PE3/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/1/1==DC2/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth2==PE4/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/real/dc-2-dc-service.json b/src/tests/ofc23/descriptors/real/dc-2-dc-service.json
new file mode 100644
index 0000000000000000000000000000000000000000..3a83afa6de81f137204aecc5f0eca476aad71e61
--- /dev/null
+++ b/src/tests/ofc23/descriptors/real/dc-2-dc-service.json
@@ -0,0 +1,37 @@
+{
+ "services": [
+ {
+ "service_id": {
+ "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "dc-2-dc-svc"}
+ },
+ "service_type": 2,
+ "service_status": {"service_status": 1},
+ "service_endpoint_ids": [
+ {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}},
+ {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}}
+ ],
+ "service_constraints": [
+ {"sla_capacity": {"capacity_gbps": 10.0}},
+ {"sla_latency": {"e2e_latency_ms": 15.2}}
+ ],
+ "service_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "/settings", "resource_value": {
+ "address_families": ["IPV4"], "bgp_as": 65000, "bgp_route_target": "65000:123",
+ "mtu": 1512, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R149]/endpoint[eth-1/0/22]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.5",
+ "address_ip": "172.16.4.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R155]/endpoint[eth-1/0/22]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.1",
+ "address_ip": "172.16.2.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R199]/endpoint[eth-1/0/21]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.6",
+ "address_ip": "172.16.1.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}}
+ ]}
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/real/descriptor_child.json b/src/tests/ofc23/descriptors/real/descriptor_child.json
new file mode 100644
index 0000000000000000000000000000000000000000..8d695cfd2d419f263b554d0f2bf648b92cdde672
--- /dev/null
+++ b/src/tests/ofc23/descriptors/real/descriptor_child.json
@@ -0,0 +1,93 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "R199"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.199"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 86400}
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R149"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.149"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 86400}
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R155"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.155"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 86400}
+ }}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/19==R155/eth-1/0/19"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/19==R199/eth-1/0/19"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==R149/eth-1/0/20"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/20==R199/eth-1/0/20"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/25==R155/eth-1/0/25"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==R149/eth-1/0/25"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/real/descriptor_parent.json b/src/tests/ofc23/descriptors/real/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..3317d46edaf6d270125d8b094c3b6384a3dd52fd
--- /dev/null
+++ b/src/tests/ofc23/descriptors/real/descriptor_parent.json
@@ -0,0 +1,258 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "scheme": "http", "username": "admin", "password": "admin"
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4, 5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "192.168.27.136"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "nms5ux", "password": "nms5ux", "timeout": 120, "scheme": "https",
+ "node_ids": ["192.168.27.139", "192.168.27.140"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.126"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+ "consistency-mode": "lifecycle", "import_topology": "devices"
+ }}}
+ ]}
+ },
+
+
+ {
+ "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "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": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth1==R149/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/22==DC1/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/9==MW/192.168.27.140:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.140:5==R149/eth-1/0/9"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.139:5==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW/192.168.27.139:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==R155/eth-1/0/25"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==R199/eth-1/0/20"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/22==DC2/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth1==R155/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/21==DC2/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth2==R199/eth-1/0/21"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/dump_logs.sh b/src/tests/ofc23/dump_logs.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cc3162b337c1b35e9ff158b400a8d5c47931bdca
--- /dev/null
+++ b/src/tests/ofc23/dump_logs.sh
@@ -0,0 +1,39 @@
+#!/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.
+
+
+rm -rf tmp/exec
+
+echo "Collecting logs for Parent..."
+mkdir -p tmp/exec/parent
+kubectl --namespace tfs-parent logs deployments/contextservice server > tmp/exec/parent/context.log
+kubectl --namespace tfs-parent logs deployments/deviceservice server > tmp/exec/parent/device.log
+kubectl --namespace tfs-parent logs deployments/serviceservice server > tmp/exec/parent/service.log
+kubectl --namespace tfs-parent logs deployments/pathcompservice frontend > tmp/exec/parent/pathcomp-frontend.log
+kubectl --namespace tfs-parent logs deployments/pathcompservice backend > tmp/exec/parent/pathcomp-backend.log
+kubectl --namespace tfs-parent logs deployments/sliceservice server > tmp/exec/parent/slice.log
+printf "\n"
+
+echo "Collecting logs for Child..."
+mkdir -p tmp/exec/child
+kubectl --namespace tfs-child logs deployments/contextservice server > tmp/exec/child/context.log
+kubectl --namespace tfs-child logs deployments/deviceservice server > tmp/exec/child/device.log
+kubectl --namespace tfs-child logs deployments/serviceservice server > tmp/exec/child/service.log
+kubectl --namespace tfs-child logs deployments/pathcompservice frontend > tmp/exec/child/pathcomp-frontend.log
+kubectl --namespace tfs-child logs deployments/pathcompservice backend > tmp/exec/child/pathcomp-backend.log
+kubectl --namespace tfs-child logs deployments/sliceservice server > tmp/exec/child/slice.log
+printf "\n"
+
+echo "Done!"
diff --git a/src/tests/ofc23/fast_redeploy.sh b/src/tests/ofc23/fast_redeploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..58d1193ded582d4fdff3222d5bcdc0fe510a7034
--- /dev/null
+++ b/src/tests/ofc23/fast_redeploy.sh
@@ -0,0 +1,63 @@
+#!/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 tfs-parent tfs-child
+
+echo "Deploying tfs-parent ..."
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl create namespace tfs-parent > ./tmp/logs/deploy-tfs-parent.log
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/contextservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/deviceservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/pathcompservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/serviceservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/sliceservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/webuiservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ofc23/tfs-ingress-parent.yaml > ./tmp/logs/deploy-tfs-parent.log
+printf "\n"
+
+echo "Deploying tfs-child ..."
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl create namespace tfs-child > ./tmp/logs/deploy-tfs-child.log
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/contextservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/deviceservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/pathcompservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/serviceservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/sliceservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/webuiservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ofc23/tfs-ingress-child.yaml > ./tmp/logs/deploy-tfs-child.log
+printf "\n"
+
+echo "Waiting tfs-parent ..."
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/contextservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/deviceservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/pathcompservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/serviceservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/sliceservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/webuiservice
+printf "\n"
+
+echo "Waiting tfs-child ..."
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/contextservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/deviceservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/pathcompservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/serviceservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/sliceservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/webuiservice
+printf "\n"
+
+echo "Done!"
diff --git a/src/tests/ofc23/nginx-ingress-controller-child.yaml b/src/tests/ofc23/nginx-ingress-controller-child.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..00a64d75e9a9a2cb93cdbd6d89790e26b0730eb6
--- /dev/null
+++ b/src/tests/ofc23/nginx-ingress-controller-child.yaml
@@ -0,0 +1,134 @@
+# 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.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-load-balancer-microk8s-conf-child
+ namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-ingress-udp-microk8s-conf-child
+ namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-ingress-tcp-microk8s-conf-child
+ namespace: ingress
+---
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+ name: tfs-ingress-class-child
+ annotations:
+ ingressclass.kubernetes.io/is-default-class: "false"
+spec:
+ controller: tfs.etsi.org/controller-class-child
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+ name: nginx-ingress-microk8s-controller-child
+ namespace: ingress
+ labels:
+ microk8s-application: nginx-ingress-microk8s-child
+spec:
+ selector:
+ matchLabels:
+ name: nginx-ingress-microk8s-child
+ updateStrategy:
+ rollingUpdate:
+ maxSurge: 0
+ maxUnavailable: 1
+ type: RollingUpdate
+ template:
+ metadata:
+ labels:
+ name: nginx-ingress-microk8s-child
+ spec:
+ terminationGracePeriodSeconds: 60
+ restartPolicy: Always
+ serviceAccountName: nginx-ingress-microk8s-serviceaccount
+ containers:
+ - image: k8s.gcr.io/ingress-nginx/controller:v1.2.0
+ imagePullPolicy: IfNotPresent
+ name: nginx-ingress-microk8s
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 10254
+ scheme: HTTP
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ successThreshold: 1
+ failureThreshold: 3
+ timeoutSeconds: 5
+ readinessProbe:
+ httpGet:
+ path: /healthz
+ port: 10254
+ scheme: HTTP
+ periodSeconds: 10
+ successThreshold: 1
+ failureThreshold: 3
+ timeoutSeconds: 5
+ lifecycle:
+ preStop:
+ exec:
+ command:
+ - /wait-shutdown
+ securityContext:
+ capabilities:
+ add:
+ - NET_BIND_SERVICE
+ drop:
+ - ALL
+ runAsUser: 101 # www-data
+ env:
+ - name: POD_NAME
+ valueFrom:
+ fieldRef:
+ apiVersion: v1
+ fieldPath: metadata.name
+ - name: POD_NAMESPACE
+ valueFrom:
+ fieldRef:
+ apiVersion: v1
+ fieldPath: metadata.namespace
+ ports:
+ - name: http
+ containerPort: 80
+ hostPort: 8002
+ protocol: TCP
+ - name: https
+ containerPort: 443
+ hostPort: 4432
+ protocol: TCP
+ - name: health
+ containerPort: 10254
+ hostPort: 12542
+ protocol: TCP
+ args:
+ - /nginx-ingress-controller
+ - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-child
+ - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-child
+ - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-child
+ - --election-id=ingress-controller-leader-child
+ - --controller-class=tfs.etsi.org/controller-class-child
+ - --ingress-class=tfs-ingress-class-child
+ - ' '
+ - --publish-status-address=127.0.0.1
diff --git a/src/tests/ofc23/nginx-ingress-controller-parent.yaml b/src/tests/ofc23/nginx-ingress-controller-parent.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c504c2e6766c1ad5b81a2479b4d05a09ba46d906
--- /dev/null
+++ b/src/tests/ofc23/nginx-ingress-controller-parent.yaml
@@ -0,0 +1,134 @@
+# 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.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-load-balancer-microk8s-conf-parent
+ namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-ingress-udp-microk8s-conf-parent
+ namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-ingress-tcp-microk8s-conf-parent
+ namespace: ingress
+---
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+ name: tfs-ingress-class-parent
+ annotations:
+ ingressclass.kubernetes.io/is-default-class: "false"
+spec:
+ controller: tfs.etsi.org/controller-class-parent
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+ name: nginx-ingress-microk8s-controller-parent
+ namespace: ingress
+ labels:
+ microk8s-application: nginx-ingress-microk8s-parent
+spec:
+ selector:
+ matchLabels:
+ name: nginx-ingress-microk8s-parent
+ updateStrategy:
+ rollingUpdate:
+ maxSurge: 0
+ maxUnavailable: 1
+ type: RollingUpdate
+ template:
+ metadata:
+ labels:
+ name: nginx-ingress-microk8s-parent
+ spec:
+ terminationGracePeriodSeconds: 60
+ restartPolicy: Always
+ serviceAccountName: nginx-ingress-microk8s-serviceaccount
+ containers:
+ - image: k8s.gcr.io/ingress-nginx/controller:v1.2.0
+ imagePullPolicy: IfNotPresent
+ name: nginx-ingress-microk8s
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 10254
+ scheme: HTTP
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ successThreshold: 1
+ failureThreshold: 3
+ timeoutSeconds: 5
+ readinessProbe:
+ httpGet:
+ path: /healthz
+ port: 10254
+ scheme: HTTP
+ periodSeconds: 10
+ successThreshold: 1
+ failureThreshold: 3
+ timeoutSeconds: 5
+ lifecycle:
+ preStop:
+ exec:
+ command:
+ - /wait-shutdown
+ securityContext:
+ capabilities:
+ add:
+ - NET_BIND_SERVICE
+ drop:
+ - ALL
+ runAsUser: 101 # www-data
+ env:
+ - name: POD_NAME
+ valueFrom:
+ fieldRef:
+ apiVersion: v1
+ fieldPath: metadata.name
+ - name: POD_NAMESPACE
+ valueFrom:
+ fieldRef:
+ apiVersion: v1
+ fieldPath: metadata.namespace
+ ports:
+ - name: http
+ containerPort: 80
+ hostPort: 8001
+ protocol: TCP
+ - name: https
+ containerPort: 443
+ hostPort: 4431
+ protocol: TCP
+ - name: health
+ containerPort: 10254
+ hostPort: 12541
+ protocol: TCP
+ args:
+ - /nginx-ingress-controller
+ - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-parent
+ - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-parent
+ - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-parent
+ - --election-id=ingress-controller-leader-parent
+ - --controller-class=tfs.etsi.org/controller-class-parent
+ - --ingress-class=tfs-ingress-class-parent
+ - ' '
+ - --publish-status-address=127.0.0.1
diff --git a/src/tests/ofc23/show_deploy.sh b/src/tests/ofc23/show_deploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d4e112b0f494e4a6dba964eda4e31652b8548043
--- /dev/null
+++ b/src/tests/ofc23/show_deploy.sh
@@ -0,0 +1,34 @@
+#!/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.
+
+########################################################################################################################
+# Automated steps start here
+########################################################################################################################
+
+echo "Deployment Resources:"
+kubectl --namespace tfs-parent get all
+printf "\n"
+
+echo "Deployment Ingress:"
+kubectl --namespace tfs-parent get ingress
+printf "\n"
+
+echo "Deployment Resources:"
+kubectl --namespace tfs-child get all
+printf "\n"
+
+echo "Deployment Ingress:"
+kubectl --namespace tfs-child get ingress
+printf "\n"
diff --git a/src/tests/ofc23/show_deploy_sligrp.sh b/src/tests/ofc23/show_deploy_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b5e3600ba99ec2e4de4a953f99c44ed2d88bba57
--- /dev/null
+++ b/src/tests/ofc23/show_deploy_sligrp.sh
@@ -0,0 +1,26 @@
+#!/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.
+
+########################################################################################################################
+# Automated steps start here
+########################################################################################################################
+
+echo "Deployment Resources:"
+kubectl --namespace tfs get all
+printf "\n"
+
+echo "Deployment Ingress:"
+kubectl --namespace tfs get ingress
+printf "\n"
diff --git a/src/tests/ofc23/tfs-ingress-child.yaml b/src/tests/ofc23/tfs-ingress-child.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a93b9321c9f25c78e8423413c4225f78c7aee719
--- /dev/null
+++ b/src/tests/ofc23/tfs-ingress-child.yaml
@@ -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.
+
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: tfs-ingress-child
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /$2
+spec:
+ ingressClassName: tfs-ingress-class-child
+ rules:
+ - http:
+ paths:
+ - path: /webui(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: webuiservice
+ port:
+ number: 8004
+ - path: /grafana(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: webuiservice
+ port:
+ number: 3000
+ - path: /context(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: contextservice
+ port:
+ number: 8080
+ - path: /()(restconf/.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: computeservice
+ port:
+ number: 8080
diff --git a/src/tests/ofc23/tfs-ingress-parent.yaml b/src/tests/ofc23/tfs-ingress-parent.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..baf506dd90f320a1913beb8becd39164daa21370
--- /dev/null
+++ b/src/tests/ofc23/tfs-ingress-parent.yaml
@@ -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.
+
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: tfs-ingress-parent
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /$2
+spec:
+ ingressClassName: tfs-ingress-class-parent
+ rules:
+ - http:
+ paths:
+ - path: /webui(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: webuiservice
+ port:
+ number: 8004
+ - path: /grafana(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: webuiservice
+ port:
+ number: 3000
+ - path: /context(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: contextservice
+ port:
+ number: 8080
+ - path: /()(restconf/.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: computeservice
+ port:
+ number: 8080
diff --git a/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py b/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..ecac81be7e3bdff1dcbac458142ea15bf367a1a1
--- /dev/null
+++ b/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py
@@ -0,0 +1,192 @@
+# 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 IPM controller (implements minimal support)
+
+import functools, json, logging, sys, time, uuid
+from typing import Any, Dict, Optional, Tuple
+from flask import Flask, jsonify, make_response, request
+from flask_restful import Api, Resource
+
+BIND_ADDRESS = '0.0.0.0'
+BIND_PORT = 8444
+IPM_USERNAME = 'xr-user-1'
+IPM_PASSWORD = 'xr-user-1'
+STR_ENDPOINT = 'https://{:s}:{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT))
+LOG_LEVEL = logging.DEBUG
+
+CONSTELLATION = {
+ 'id': 'ofc-constellation',
+ 'hubModule': {'state': {
+ 'module': {'moduleName': 'OFC HUB 1', 'trafficMode': 'L1Mode', 'capacity': 100},
+ 'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}, {'moduleIf': {'clientIfAid': 'XR-T4'}}]
+ }},
+ 'leafModules': [
+ {'state': {
+ 'module': {'moduleName': 'OFC LEAF 1', 'trafficMode': 'L1Mode', 'capacity': 100},
+ 'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}]
+ }},
+ {'state': {
+ 'module': {'moduleName': 'OFC LEAF 2', 'trafficMode': 'L1Mode', 'capacity': 100},
+ 'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}]
+ }}
+ ]
+}
+
+CONNECTIONS : Dict[str, Any] = dict()
+STATE_NAME_TO_CONNECTION : Dict[str, str] = dict()
+
+def select_module_state(module_name : str) -> Optional[Dict]:
+ hub_module_state = CONSTELLATION.get('hubModule', {}).get('state', {})
+ if module_name == hub_module_state.get('module', {}).get('moduleName'): return hub_module_state
+ for leaf_module in CONSTELLATION.get('leafModules', []):
+ leaf_module_state = leaf_module.get('state', {})
+ if module_name == leaf_module_state.get('module', {}).get('moduleName'): return leaf_module_state
+ return None
+
+def select_endpoint(module_state : Dict, module_if : str) -> Optional[Dict]:
+ for endpoint in module_state.get('endpoints', []):
+ if module_if == endpoint.get('moduleIf', {}).get('clientIfAid'): return endpoint
+ return None
+
+def select_module_endpoint(selector : Dict) -> Optional[Tuple[Dict, Dict]]:
+ selected_module_name = selector['moduleIfSelectorByModuleName']['moduleName']
+ selected_module_if = selector['moduleIfSelectorByModuleName']['moduleClientIfAid']
+ module_state = select_module_state(selected_module_name)
+ if module_state is None: return None
+ return module_state, select_endpoint(module_state, selected_module_if)
+
+def compose_endpoint(endpoint_selector : Dict) -> Dict:
+ module, endpoint = select_module_endpoint(endpoint_selector['selector'])
+ return {
+ 'href': '/' + str(uuid.uuid4()),
+ 'state': {
+ 'moduleIf': {
+ 'moduleName': module['module']['moduleName'],
+ 'clientIfAid': endpoint['moduleIf']['clientIfAid'],
+ },
+ 'capacity': module['module']['capacity'],
+ }
+ }
+
+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 OpenIdConnect(Resource):
+ ACCESS_TOKENS = {}
+
+ def post(self):
+ if request.content_type != 'application/x-www-form-urlencoded': return make_response('bad content type', 400)
+ if request.content_length == 0: return make_response('bad content length', 400)
+ request_form = request.form
+ if request_form.get('client_id') != 'xr-web-client': return make_response('bad client_id', 403)
+ if request_form.get('client_secret') != 'xr-web-client': return make_response('bad client_secret', 403)
+ if request_form.get('grant_type') != 'password': return make_response('bad grant_type', 403)
+ if request_form.get('username') != IPM_USERNAME: return make_response('bad username', 403)
+ if request_form.get('password') != IPM_PASSWORD: return make_response('bad password', 403)
+ access_token = OpenIdConnect.ACCESS_TOKENS.setdefault(IPM_USERNAME, uuid.uuid4())
+ reply = {'access_token': access_token, 'expires_in': 86400}
+ return make_response(jsonify(reply), 200)
+
+class XrNetworks(Resource):
+ def get(self):
+ query = json.loads(request.args.get('q'))
+ hub_module_name = query.get('hubModule.state.module.moduleName')
+ if hub_module_name != 'OFC HUB 1': return make_response('unexpected hub module', 404)
+ return make_response(jsonify([CONSTELLATION]), 200)
+
+class XrNetworkConnections(Resource):
+ def get(self):
+ query = json.loads(request.args.get('q'))
+ state_name = query.get('state.name')
+ if state_name is None:
+ connections = [connection for connection in CONNECTIONS.values()]
+ else:
+ connection_uuid = STATE_NAME_TO_CONNECTION.get(state_name)
+ if connection_uuid is None: return make_response('state name not found', 404)
+ connection = CONNECTIONS.get(connection_uuid)
+ if connection is None: return make_response('connection for state name not found', 404)
+ connections = [connection]
+ return make_response(jsonify(connections), 200)
+
+ def post(self):
+ if request.content_type != 'application/json': return make_response('bad content type', 400)
+ if request.content_length == 0: return make_response('bad content length', 400)
+ request_json = request.json
+ if not isinstance(request_json, list): return make_response('content is not list', 400)
+ reply = []
+ for connection in request_json:
+ connection_uuid = str(uuid.uuid4())
+ state_name = connection['name']
+
+ if state_name is not None: STATE_NAME_TO_CONNECTION[state_name] = connection_uuid
+ CONNECTIONS[connection_uuid] = {
+ 'href': '/network-connections/{:s}'.format(str(connection_uuid)),
+ 'config': {
+ 'implicitTransportCapacity': connection['implicitTransportCapacity']
+ # 'mc': ??
+ },
+ 'state': {
+ 'name': state_name,
+ 'serviceMode': connection['serviceMode']
+ # 'outerVID' : ??
+ },
+ 'endpoints': [
+ compose_endpoint(endpoint)
+ for endpoint in connection['endpoints']
+ ]
+ }
+ reply.append(CONNECTIONS[connection_uuid])
+ return make_response(jsonify(reply), 202)
+
+class XrNetworkConnection(Resource):
+ def get(self, connection_uuid : str):
+ connection = CONNECTIONS.get(connection_uuid)
+ if connection is None: return make_response('unexpected connection id', 404)
+ return make_response(jsonify(connection), 200)
+
+ def delete(self, connection_uuid : str):
+ connection = CONNECTIONS.pop(connection_uuid, None)
+ if connection is None: return make_response('unexpected connection id', 404)
+ state_name = connection['state']['name']
+ STATE_NAME_TO_CONNECTION.pop(state_name, None)
+ return make_response(jsonify({}), 202)
+
+def main():
+ LOGGER.info('Starting...')
+
+ app = Flask(__name__)
+ app.after_request(functools.partial(log_request, LOGGER))
+
+ api = Api(app)
+ api.add_resource(OpenIdConnect, '/realms/xr-cm/protocol/openid-connect/token')
+ api.add_resource(XrNetworks, '/api/v1/xr-networks')
+ api.add_resource(XrNetworkConnections, '/api/v1/network-connections')
+ api.add_resource(XrNetworkConnection, '/api/v1/network-connections/