Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • tfs/controller
1 result
Show changes
Commits on Source (58)
Showing
with 800 additions and 114 deletions
......@@ -202,6 +202,7 @@ enum DeviceDriverEnum {
DEVICEDRIVER_IETF_L2VPN = 7;
DEVICEDRIVER_GNMI_OPENCONFIG = 8;
DEVICEDRIVER_FLEXSCALE = 9;
DEVICEDRIVER_IETF_ACTN = 10;
}
enum DeviceOperationalStatusEnum {
......
#!/bin/bash -eu
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
export JAVA_COMPONENTS="ztp policy"
export TFS_ROOT_DIR=$(dirname $(dirname $(realpath $0)))
for COMPONENT in $JAVA_COMPONENTS; do
echo "\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/"
echo
echo "[TFS] Now building" $COMPONENT
echo
echo "\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/"
cd $TFS_ROOT_DIR/src/$COMPONENT
./mvnw spotless:apply
./mvnw install -DskipUTs
done
#!/bin/bash
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
PROJECTDIR=`pwd`
cd $PROJECTDIR/src
RCFILE=$PROJECTDIR/coverage/.coveragerc
# Run unitary tests and analyze coverage of code at same time
# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0
coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \
device/tests/test_unitary_ietf_actn.py
#!/bin/bash
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
PROJECTDIR=`pwd`
cd $PROJECTDIR/src
RCFILE=$PROJECTDIR/coverage/.coveragerc
# Run unitary tests and analyze coverage of code at same time
# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0
coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \
nbi/tests/test_debug_api.py
......@@ -22,6 +22,7 @@ class DeviceTypeEnum(Enum):
# Emulated device types
EMULATED_CLIENT = 'emu-client'
EMULATED_DATACENTER = 'emu-datacenter'
EMULATED_IP_SDN_CONTROLLER = 'emu-ip-sdn-controller'
EMULATED_MICROWAVE_RADIO_SYSTEM = 'emu-microwave-radio-system'
EMULATED_OPEN_LINE_SYSTEM = 'emu-open-line-system'
EMULATED_OPTICAL_ROADM = 'emu-optical-roadm'
......@@ -36,6 +37,7 @@ class DeviceTypeEnum(Enum):
# Real device types
CLIENT = 'client'
DATACENTER = 'datacenter'
IP_SDN_CONTROLLER = 'ip-sdn-controller'
MICROWAVE_RADIO_SYSTEM = 'microwave-radio-system'
OPEN_LINE_SYSTEM = 'open-line-system'
OPTICAL_ROADM = 'optical-roadm'
......
......@@ -46,7 +46,7 @@ from slice.client.SliceClient import SliceClient
from .Tools import (
format_device_custom_config_rules, format_service_custom_config_rules, format_slice_custom_config_rules,
get_descriptors_add_contexts, get_descriptors_add_services, get_descriptors_add_slices,
get_descriptors_add_topologies, split_devices_by_rules)
get_descriptors_add_topologies, split_controllers_and_network_devices, split_devices_by_rules)
LOGGER = logging.getLogger(__name__)
LOGGERS = {
......@@ -56,9 +56,10 @@ LOGGERS = {
}
ENTITY_TO_TEXT = {
# name => singular, plural
# name => singular, plural
'context' : ('Context', 'Contexts' ),
'topology' : ('Topology', 'Topologies' ),
'controller': ('Controller', 'Controllers'),
'device' : ('Device', 'Devices' ),
'link' : ('Link', 'Links' ),
'service' : ('Service', 'Services' ),
......@@ -68,8 +69,8 @@ ENTITY_TO_TEXT = {
ACTION_TO_TEXT = {
# action => infinitive, past
'add' : ('Add', 'Added'),
'update' : ('Update', 'Updated'),
'add' : ('Add', 'Added' ),
'update' : ('Update', 'Updated' ),
'config' : ('Configure', 'Configured'),
}
......@@ -231,10 +232,12 @@ class DescriptorLoader:
def _load_dummy_mode(self) -> None:
# Dummy Mode: used to pre-load databases (WebUI debugging purposes) with no smart or automated tasks.
controllers, network_devices = split_controllers_and_network_devices(self.__devices)
self.__ctx_cli.connect()
self._process_descr('context', 'add', self.__ctx_cli.SetContext, Context, self.__contexts_add )
self._process_descr('topology', 'add', self.__ctx_cli.SetTopology, Topology, self.__topologies_add)
self._process_descr('device', 'add', self.__ctx_cli.SetDevice, Device, self.__devices )
self._process_descr('controller', 'add', self.__ctx_cli.SetDevice, Device, controllers )
self._process_descr('device', 'add', self.__ctx_cli.SetDevice, Device, network_devices )
self._process_descr('link', 'add', self.__ctx_cli.SetLink, Link, self.__links )
self._process_descr('service', 'add', self.__ctx_cli.SetService, Service, self.__services )
self._process_descr('slice', 'add', self.__ctx_cli.SetSlice, Slice, self.__slices )
......@@ -262,20 +265,23 @@ class DescriptorLoader:
self.__services_add = get_descriptors_add_services(self.__services)
self.__slices_add = get_descriptors_add_slices(self.__slices)
controllers_add, network_devices_add = split_controllers_and_network_devices(self.__devices_add)
self.__ctx_cli.connect()
self.__dev_cli.connect()
self.__svc_cli.connect()
self.__slc_cli.connect()
self._process_descr('context', 'add', self.__ctx_cli.SetContext, Context, self.__contexts_add )
self._process_descr('topology', 'add', self.__ctx_cli.SetTopology, Topology, self.__topologies_add)
self._process_descr('device', 'add', self.__dev_cli.AddDevice, Device, self.__devices_add )
self._process_descr('device', 'config', self.__dev_cli.ConfigureDevice, Device, self.__devices_config)
self._process_descr('link', 'add', self.__ctx_cli.SetLink, Link, self.__links )
self._process_descr('service', 'add', self.__svc_cli.CreateService, Service, self.__services_add )
self._process_descr('service', 'update', self.__svc_cli.UpdateService, Service, self.__services )
self._process_descr('slice', 'add', self.__slc_cli.CreateSlice, Slice, self.__slices_add )
self._process_descr('slice', 'update', self.__slc_cli.UpdateSlice, Slice, self.__slices )
self._process_descr('context', 'add', self.__ctx_cli.SetContext, Context, self.__contexts_add )
self._process_descr('topology', 'add', self.__ctx_cli.SetTopology, Topology, self.__topologies_add)
self._process_descr('controller', 'add', self.__dev_cli.AddDevice, Device, controllers_add )
self._process_descr('device', 'add', self.__dev_cli.AddDevice, Device, network_devices_add )
self._process_descr('device', 'config', self.__dev_cli.ConfigureDevice, Device, self.__devices_config)
self._process_descr('link', 'add', self.__ctx_cli.SetLink, Link, self.__links )
self._process_descr('service', 'add', self.__svc_cli.CreateService, Service, self.__services_add )
self._process_descr('service', 'update', self.__svc_cli.UpdateService, Service, self.__services )
self._process_descr('slice', 'add', self.__slc_cli.CreateSlice, Slice, self.__slices_add )
self._process_descr('slice', 'update', self.__slc_cli.UpdateSlice, Slice, self.__slices )
# By default the Context component automatically assigns devices and links to topologies based on their
# endpoints, and assigns topologies, services, and slices to contexts based on their identifiers.
......
......@@ -14,6 +14,7 @@
import copy, json
from typing import Dict, List, Optional, Tuple, Union
from common.DeviceTypes import DeviceTypeEnum
def get_descriptors_add_contexts(contexts : List[Dict]) -> List[Dict]:
contexts_add = copy.deepcopy(contexts)
......@@ -103,3 +104,24 @@ def split_devices_by_rules(devices : List[Dict]) -> Tuple[List[Dict], List[Dict]
devices_config.append(device)
return devices_add, devices_config
CONTROLLER_DEVICE_TYPES = {
DeviceTypeEnum.EMULATED_IP_SDN_CONTROLLER.value,
DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM.value,
DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM.value,
DeviceTypeEnum.IP_SDN_CONTROLLER.value,
DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value,
DeviceTypeEnum.OPEN_LINE_SYSTEM.value,
DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value,
}
def split_controllers_and_network_devices(devices : List[Dict]) -> Tuple[List[Dict], List[Dict]]:
controllers : List[Dict] = list()
network_devices : List[Dict] = list()
for device in devices:
device_type = device.get('device_type')
if device_type in CONTROLLER_DEVICE_TYPES:
controllers.append(device)
else:
network_devices.append(device)
return controllers, network_devices
......@@ -18,11 +18,12 @@
import json
from typing import Any, Dict, List, Optional, Tuple
from common.proto.context_pb2 import Constraint, EndPointId
from common.proto.context_pb2 import Constraint, ConstraintActionEnum, EndPointId
from common.tools.grpc.Tools import grpc_message_to_json_string
def update_constraint_custom_scalar(
constraints, constraint_type : str, value : Any, raise_if_differs : bool = False
constraints, constraint_type : str, value : Any, raise_if_differs : bool = False,
new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
) -> Constraint:
for constraint in constraints:
......@@ -36,6 +37,8 @@ def update_constraint_custom_scalar(
constraint.custom.constraint_type = constraint_type
json_constraint_value = None
constraint.action = new_action
if (json_constraint_value is None) or not raise_if_differs:
# missing or raise_if_differs=False, add/update it
json_constraint_value = value
......@@ -47,7 +50,10 @@ def update_constraint_custom_scalar(
constraint.custom.constraint_value = json.dumps(json_constraint_value, sort_keys=True)
return constraint
def update_constraint_custom_dict(constraints, constraint_type : str, fields : Dict[str, Tuple[Any, bool]]) -> Constraint:
def update_constraint_custom_dict(
constraints, constraint_type : str, fields : Dict[str, Tuple[Any, bool]],
new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
) -> Constraint:
# fields: Dict[field_name : str, Tuple[field_value : Any, raise_if_differs : bool]]
for constraint in constraints:
......@@ -61,6 +67,8 @@ def update_constraint_custom_dict(constraints, constraint_type : str, fields : D
constraint.custom.constraint_type = constraint_type
json_constraint_value = {}
constraint.action = new_action
for field_name,(field_value, raise_if_differs) in fields.items():
if (field_name not in json_constraint_value) or not raise_if_differs:
# missing or raise_if_differs=False, add/update it
......@@ -75,7 +83,8 @@ def update_constraint_custom_dict(constraints, constraint_type : str, fields : D
def update_constraint_endpoint_location(
constraints, endpoint_id : EndPointId,
region : Optional[str] = None, gps_position : Optional[Tuple[float, float]] = None
region : Optional[str] = None, gps_position : Optional[Tuple[float, float]] = None,
new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
) -> Constraint:
# gps_position: (latitude, longitude)
if region is not None and gps_position is not None:
......@@ -103,6 +112,8 @@ def update_constraint_endpoint_location(
_endpoint_id.topology_id.topology_uuid.uuid = topology_uuid
_endpoint_id.topology_id.context_id.context_uuid.uuid = context_uuid
constraint.action = new_action
location = constraint.endpoint_location.location
if region is not None:
location.region = region
......@@ -111,7 +122,10 @@ def update_constraint_endpoint_location(
location.gps_position.longitude = gps_position[1]
return constraint
def update_constraint_endpoint_priority(constraints, endpoint_id : EndPointId, priority : int) -> Constraint:
def update_constraint_endpoint_priority(
constraints, endpoint_id : EndPointId, priority : int,
new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
) -> Constraint:
endpoint_uuid = endpoint_id.endpoint_uuid.uuid
device_uuid = endpoint_id.device_id.device_uuid.uuid
topology_uuid = endpoint_id.topology_id.topology_uuid.uuid
......@@ -134,10 +148,15 @@ def update_constraint_endpoint_priority(constraints, endpoint_id : EndPointId, p
_endpoint_id.topology_id.topology_uuid.uuid = topology_uuid
_endpoint_id.topology_id.context_id.context_uuid.uuid = context_uuid
constraint.action = new_action
constraint.endpoint_priority.priority = priority
return constraint
def update_constraint_sla_capacity(constraints, capacity_gbps : float) -> Constraint:
def update_constraint_sla_capacity(
constraints, capacity_gbps : float,
new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
) -> Constraint:
for constraint in constraints:
if constraint.WhichOneof('constraint') != 'sla_capacity': continue
break # found, end loop
......@@ -145,10 +164,15 @@ def update_constraint_sla_capacity(constraints, capacity_gbps : float) -> Constr
# not found, add it
constraint = constraints.add() # pylint: disable=no-member
constraint.action = new_action
constraint.sla_capacity.capacity_gbps = capacity_gbps
return constraint
def update_constraint_sla_latency(constraints, e2e_latency_ms : float) -> Constraint:
def update_constraint_sla_latency(
constraints, e2e_latency_ms : float,
new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
) -> Constraint:
for constraint in constraints:
if constraint.WhichOneof('constraint') != 'sla_latency': continue
break # found, end loop
......@@ -156,11 +180,14 @@ def update_constraint_sla_latency(constraints, e2e_latency_ms : float) -> Constr
# not found, add it
constraint = constraints.add() # pylint: disable=no-member
constraint.action = new_action
constraint.sla_latency.e2e_latency_ms = e2e_latency_ms
return constraint
def update_constraint_sla_availability(
constraints, num_disjoint_paths : int, all_active : bool, availability : float
constraints, num_disjoint_paths : int, all_active : bool, availability : float,
new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
) -> Constraint:
for constraint in constraints:
if constraint.WhichOneof('constraint') != 'sla_availability': continue
......@@ -169,12 +196,17 @@ def update_constraint_sla_availability(
# not found, add it
constraint = constraints.add() # pylint: disable=no-member
constraint.action = new_action
constraint.sla_availability.num_disjoint_paths = num_disjoint_paths
constraint.sla_availability.all_active = all_active
constraint.sla_availability.availability = availability
return constraint
def update_constraint_sla_isolation(constraints, isolation_levels : List[int]) -> Constraint:
def update_constraint_sla_isolation(
constraints, isolation_levels : List[int],
new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
) -> Constraint:
for constraint in constraints:
if constraint.WhichOneof('constraint') != 'sla_isolation': continue
break # found, end loop
......@@ -182,6 +214,8 @@ def update_constraint_sla_isolation(constraints, isolation_levels : List[int]) -
# not found, add it
constraint = constraints.add() # pylint: disable=no-member
constraint.action = new_action
for isolation_level in isolation_levels:
if isolation_level in constraint.sla_isolation.isolation_level: continue
constraint.sla_isolation.isolation_level.append(isolation_level)
......
......@@ -46,6 +46,10 @@ DEVICE_P4_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_P4]
DEVICE_TFS_TYPE = DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value
DEVICE_TFS_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN]
DEVICE_IETF_ACTN_TYPE = DeviceTypeEnum.OPEN_LINE_SYSTEM.value
DEVICE_IETF_ACTN_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN]
def json_device_id(device_uuid : str):
return {'device_uuid': {'uuid': device_uuid}}
......@@ -136,6 +140,14 @@ def json_device_tfs_disabled(
device_uuid, DEVICE_TFS_TYPE, DEVICE_DISABLED, name=name, endpoints=endpoints, config_rules=config_rules,
drivers=drivers)
def json_device_ietf_actn_disabled(
device_uuid : str, name : Optional[str] = None, endpoints : List[Dict] = [], config_rules : List[Dict] = [],
drivers : List[Dict] = DEVICE_IETF_ACTN_DRIVERS
):
return json_device(
device_uuid, DEVICE_IETF_ACTN_TYPE, DEVICE_DISABLED, name=name, endpoints=endpoints, config_rules=config_rules,
drivers=drivers)
def json_device_connect_rules(address : str, port : int, settings : Dict = {}) -> List[Dict]:
return [
json_config_rule_set('_connect/address', address),
......
......@@ -12,7 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Dict
import logging
from typing import Callable, Dict
LOGGER = logging.getLogger(__name__)
# ----- Enumerations ---------------------------------------------------------------------------------------------------
def validate_config_action_enum(message):
......@@ -23,6 +26,14 @@ def validate_config_action_enum(message):
'CONFIGACTION_DELETE',
]
def validate_constraint_action_enum(message):
assert isinstance(message, str)
assert message in [
'CONSTRAINTACTION_UNDEFINED',
'CONSTRAINTACTION_SET',
'CONSTRAINTACTION_DELETE',
]
def validate_device_driver_enum(message):
assert isinstance(message, str)
assert message in [
......@@ -35,6 +46,8 @@ def validate_device_driver_enum(message):
'DEVICEDRIVER_XR',
'DEVICEDRIVER_IETF_L2VPN',
'DEVICEDRIVER_GNMI_OPENCONFIG',
'DEVICEDRIVER_FLEXSCALE',
'DEVICEDRIVER_IETF_ACTN',
]
def validate_device_operational_status_enum(message):
......@@ -64,6 +77,8 @@ def validate_service_type_enum(message):
'SERVICETYPE_L3NM',
'SERVICETYPE_L2NM',
'SERVICETYPE_TAPI_CONNECTIVITY_SERVICE',
'SERVICETYPE_TE',
'SERVICETYPE_E2E',
]
def validate_service_state_enum(message):
......@@ -77,6 +92,17 @@ def validate_service_state_enum(message):
'SERVICESTATUS_SLA_VIOLATED',
]
def validate_slice_status_enum(message):
assert isinstance(message, str)
assert message in [
'SLICESTATUS_UNDEFINED',
'SLICESTATUS_PLANNED',
'SLICESTATUS_INIT',
'SLICESTATUS_ACTIVE',
'SLICESTATUS_DEINIT',
'SLICESTATUS_SLA_VIOLATED',
]
# ----- Common ---------------------------------------------------------------------------------------------------------
def validate_uuid(message, allow_empty=False):
......@@ -114,28 +140,61 @@ def validate_config_rules(message):
assert 'config_rules' in message
for config_rule in message['config_rules']: validate_config_rule(config_rule)
CONSTRAINT_TYPES = {
'custom',
'schedule',
'endpoint_location',
'sla_capacity',
'sla_latency',
'sla_availability',
'sla_isolation',
def validate_constraint_custom(message):
assert isinstance(message, dict)
assert len(message.keys()) == 2
assert 'constraint_type' in message
assert isinstance(message['constraint_type'], str)
assert 'constraint_value' in message
assert isinstance(message['constraint_value'], str)
def validate_constraint_sla_capacity(message):
assert isinstance(message, dict)
assert len(message.keys()) == 1
assert 'capacity_gbps' in message
assert isinstance(message['capacity_gbps'], (int, float))
def validate_constraint_sla_latency(message):
assert isinstance(message, dict)
assert len(message.keys()) == 1
assert 'e2e_latency_ms' in message
assert isinstance(message['e2e_latency_ms'], (int, float))
def validate_constraint_sla_availability(message):
assert isinstance(message, dict)
assert len(message.keys()) == 3
assert 'num_disjoint_paths' in message
assert isinstance(message['num_disjoint_paths'], int)
assert message['num_disjoint_paths'] >= 0
assert 'all_active' in message
assert isinstance(message['all_active'], bool)
assert 'availability' in message
assert isinstance(message['availability'], (int, float))
assert message['availability'] >= 0 and message['availability'] <= 100
CONSTRAINT_TYPE_TO_VALIDATOR = {
'custom' : validate_constraint_custom,
#'schedule' : validate_constraint_schedule,
#'endpoint_location' : validate_constraint_endpoint_location,
#'endpoint_priority' : validate_constraint_endpoint_priority,
'sla_capacity' : validate_constraint_sla_capacity,
'sla_latency' : validate_constraint_sla_latency,
'sla_availability' : validate_constraint_sla_availability,
#'sla_isolation' : validate_constraint_sla_isolation,
#'exclusions' : validate_constraint_exclusions,
}
def validate_constraint(message):
assert isinstance(message, dict)
assert len(message.keys()) == 1
other_keys = list(message.keys())
constraint_type = other_keys[0]
assert constraint_type in CONSTRAINT_TYPES
assert constraint_type == 'custom', 'Constraint Type Validator for {:s} not implemented'.format(constraint_type)
custom : Dict = message['custom']
assert len(custom.keys()) == 2
assert 'constraint_type' in custom
assert isinstance(custom['constraint_type'], str)
assert 'constraint_value' in custom
assert isinstance(custom['constraint_value'], str)
assert len(message.keys()) == 2
assert 'action' in message
validate_constraint_action_enum(message['action'])
other_keys = set(list(message.keys()))
other_keys.discard('action')
constraint_type = other_keys.pop()
validator : Callable = CONSTRAINT_TYPE_TO_VALIDATOR.get(constraint_type)
assert validator is not None, 'Constraint Type Validator for {:s} not implemented'.format(constraint_type)
validator(message[constraint_type])
# ----- Identifiers ----------------------------------------------------------------------------------------------------
......@@ -192,6 +251,15 @@ def validate_connection_id(message):
assert 'connection_uuid' in message
validate_uuid(message['connection_uuid'])
def validate_slice_id(message, context_uuid = None):
assert isinstance(message, dict)
assert len(message.keys()) == 2
assert 'context_id' in message
validate_context_id(message['context_id'])
if context_uuid is not None: assert message['context_id']['context_uuid']['uuid'] == context_uuid
assert 'slice_uuid' in message
validate_uuid(message['slice_uuid'])
# ----- Lists of Identifiers -------------------------------------------------------------------------------------------
......@@ -209,6 +277,13 @@ def validate_service_ids(message, context_uuid=None):
assert isinstance(message['service_ids'], list)
for service_id in message['service_ids']: validate_service_id(service_id, context_uuid=context_uuid)
def validate_slice_ids(message, context_uuid=None):
assert isinstance(message, dict)
assert len(message.keys()) == 1
assert 'slice_ids' in message
assert isinstance(message['slice_ids'], list)
for slice_id in message['slice_ids']: validate_slice_id(slice_id, context_uuid=context_uuid)
def validate_topology_ids(message, context_uuid=None):
assert isinstance(message, dict)
assert len(message.keys()) == 1
......@@ -242,16 +317,21 @@ def validate_connection_ids(message):
def validate_context(message):
assert isinstance(message, dict)
assert len(message.keys()) == 3
assert len(message.keys()) == 5
assert 'context_id' in message
validate_context_id(message['context_id'])
context_uuid = message['context_id']['context_uuid']['uuid']
assert 'service_ids' in message
assert isinstance(message['service_ids'], list)
for service_id in message['service_ids']: validate_service_id(service_id, context_uuid=context_uuid)
assert 'name' in message
assert isinstance(message['name'], str)
assert 'topology_ids' in message
assert isinstance(message['topology_ids'], list)
for topology_id in message['topology_ids']: validate_topology_id(topology_id, context_uuid=context_uuid)
assert 'service_ids' in message
assert isinstance(message['service_ids'], list)
for service_id in message['service_ids']: validate_service_id(service_id, context_uuid=context_uuid)
assert 'slice_ids' in message
assert isinstance(message['slice_ids'], list)
for slice_id in message['slice_ids']: validate_slice_id(slice_id, context_uuid=context_uuid)
def validate_service_state(message):
assert isinstance(message, dict)
......@@ -259,11 +339,19 @@ def validate_service_state(message):
assert 'service_status' in message
validate_service_state_enum(message['service_status'])
def validate_slice_status(message):
assert isinstance(message, dict)
assert len(message.keys()) == 1
assert 'slice_status' in message
validate_slice_status_enum(message['slice_status'])
def validate_service(message):
assert isinstance(message, dict)
assert len(message.keys()) == 6
assert len(message.keys()) == 7
assert 'service_id' in message
validate_service_id(message['service_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'service_type' in message
validate_service_type_enum(message['service_type'])
assert 'service_endpoint_ids' in message
......@@ -277,11 +365,44 @@ def validate_service(message):
assert 'service_config' in message
validate_config_rules(message['service_config'])
def validate_slice(message):
assert isinstance(message, dict)
assert len(message.keys()) in {8, 9}
assert 'slice_id' in message
validate_slice_id(message['slice_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'slice_endpoint_ids' in message
assert isinstance(message['slice_endpoint_ids'], list)
for endpoint_id in message['slice_endpoint_ids']: validate_endpoint_id(endpoint_id)
assert 'slice_constraints' in message
assert isinstance(message['slice_constraints'], list)
for constraint in message['slice_constraints']: validate_constraint(constraint)
assert 'slice_service_ids' in message
assert isinstance(message['slice_service_ids'], list)
for service_id in message['slice_service_ids']: validate_service_id(service_id)
assert 'slice_subslice_ids' in message
assert isinstance(message['slice_subslice_ids'], list)
for slice_id in message['slice_subslice_ids']: validate_slice_id(slice_id)
assert 'slice_status' in message
validate_slice_status(message['slice_status'])
assert 'slice_config' in message
validate_config_rules(message['slice_config'])
if len(message.keys()) == 9:
assert 'slice_owner' in message
assert isinstance(message['slice_owner'], dict)
assert 'owner_uuid' in message['slice_owner']
validate_uuid(message['slice_owner']['owner_uuid'])
assert 'owner_string' in message['slice_owner']
assert isinstance(message['slice_owner']['owner_string'], str)
def validate_topology(message, num_devices=None, num_links=None):
assert isinstance(message, dict)
assert len(message.keys()) == 3
assert len(message.keys()) == 4
assert 'topology_id' in message
validate_topology_id(message['topology_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'device_ids' in message
assert isinstance(message['device_ids'], list)
if num_devices is not None: assert len(message['device_ids']) == num_devices
......@@ -293,20 +414,49 @@ def validate_topology(message, num_devices=None, num_links=None):
def validate_endpoint(message):
assert isinstance(message, dict)
assert len(message.keys()) == 3
assert len(message.keys()) == 4
assert 'endpoint_id' in message
validate_endpoint_id(message['endpoint_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'endpoint_type' in message
assert isinstance(message['endpoint_type'], str)
assert 'kpi_sample_types' in message
assert isinstance(message['kpi_sample_types'], list)
for kpi_sample_type in message['kpi_sample_types']: validate_kpi_sample_types_enum(kpi_sample_type)
def validate_component(component):
assert isinstance(component, dict)
assert len(component.keys()) == 5
assert 'component_uuid' in component
validate_uuid(component['component_uuid'])
assert 'name' in component
assert isinstance(component['name'], str)
assert 'type' in component
assert isinstance(component['type'], str)
assert 'attributes' in component
assert isinstance(component['attributes'], dict)
for k,v in component['attributes'].items():
assert isinstance(k, str)
assert isinstance(v, str)
assert 'parent' in component
assert isinstance(component['parent'], str)
def validate_link_attributes(link_attributes):
assert isinstance(link_attributes, dict)
assert len(link_attributes.keys()) == 2
assert 'total_capacity_gbps' in link_attributes
assert isinstance(link_attributes['total_capacity_gbps'], (int, float))
assert 'used_capacity_gbps' in link_attributes
assert isinstance(link_attributes['used_capacity_gbps'], (int, float))
def validate_device(message):
assert isinstance(message, dict)
assert len(message.keys()) == 6
assert len(message.keys()) in {8, 9}
assert 'device_id' in message
validate_device_id(message['device_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'device_type' in message
assert isinstance(message['device_type'], str)
assert 'device_config' in message
......@@ -319,19 +469,30 @@ def validate_device(message):
assert 'device_endpoints' in message
assert isinstance(message['device_endpoints'], list)
for endpoint in message['device_endpoints']: validate_endpoint(endpoint)
assert 'components' in message
assert isinstance(message['components'], list)
for component in message['components']: validate_component(component)
if len(message.keys()) == 9:
assert 'controller_id' in message
if len(message['controller_id']) > 0:
validate_device_id(message['controller_id'])
def validate_link(message):
assert isinstance(message, dict)
assert len(message.keys()) == 2
assert len(message.keys()) == 4
assert 'link_id' in message
validate_link_id(message['link_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'link_endpoint_ids' in message
assert isinstance(message['link_endpoint_ids'], list)
for endpoint_id in message['link_endpoint_ids']: validate_endpoint_id(endpoint_id)
assert 'attributes' in message
validate_link_attributes(message['attributes'])
def validate_connection(message):
assert isinstance(message, dict)
assert len(message.keys()) == 4
assert len(message.keys()) in {4, 5}
assert 'connection_id' in message
validate_connection_id(message['connection_id'])
assert 'service_id' in message
......@@ -342,6 +503,50 @@ def validate_connection(message):
assert 'sub_service_ids' in message
assert isinstance(message['sub_service_ids'], list)
for sub_service_id in message['sub_service_ids']: validate_service_id(sub_service_id)
if len(message.keys()) == 5:
assert 'settings' in message
assert isinstance(message['settings'], dict)
# TODO: improve validation of data types, especially for uint values, IP/MAC addresses, TCP/UDP ports, etc.
if 'l0' in message['settings']:
assert isinstance(message['settings']['l0'], dict)
if 'lsp_symbolic_name' in message['settings']['l0']:
assert isinstance(message['settings']['l0']['lsp_symbolic_name'], str)
if 'l2' in message['settings']:
assert isinstance(message['settings']['l2'], dict)
if 'src_mac_address' in message['settings']['l2']:
assert isinstance(message['settings']['l2']['src_mac_address'], str)
if 'dst_mac_address' in message['settings']['l2']:
assert isinstance(message['settings']['l2']['dst_mac_address'], str)
if 'ether_type' in message['settings']['l2']:
assert isinstance(message['settings']['l2']['ether_type'], int)
if 'vlan_id' in message['settings']['l2']:
assert isinstance(message['settings']['l2']['vlan_id'], int)
if 'mpls_label' in message['settings']['l2']:
assert isinstance(message['settings']['l2']['mpls_label'], int)
if 'mpls_traffic_class' in message['settings']['l2']:
assert isinstance(message['settings']['l2']['mpls_traffic_class'], int)
if 'l3' in message['settings']:
assert isinstance(message['settings']['l3'], dict)
if 'src_ip_address' in message['settings']['l3']:
assert isinstance(message['settings']['l3']['src_ip_address'], str)
if 'dst_ip_address' in message['settings']['l3']:
assert isinstance(message['settings']['l3']['dst_ip_address'], str)
if 'dscp' in message['settings']['l3']:
assert isinstance(message['settings']['l3']['dscp'], int)
if 'protocol' in message['settings']['l3']:
assert isinstance(message['settings']['l3']['protocol'], int)
if 'ttl' in message['settings']['l3']:
assert isinstance(message['settings']['l3']['ttl'], int)
if 'l4' in message['settings']:
assert isinstance(message['settings']['l4'], dict)
if 'src_port' in message['settings']['l4']:
assert isinstance(message['settings']['l4']['src_port'], int)
if 'dst_port' in message['settings']['l4']:
assert isinstance(message['settings']['l4']['dst_port'], int)
if 'tcp_flags' in message['settings']['l4']:
assert isinstance(message['settings']['l4']['tcp_flags'], int)
if 'ttl' in message['settings']['l4']:
assert isinstance(message['settings']['l4']['ttl'], int)
# ----- Lists of Objects -----------------------------------------------------------------------------------------------
......@@ -360,6 +565,13 @@ def validate_services(message):
assert isinstance(message['services'], list)
for service in message['services']: validate_service(service)
def validate_slices(message):
assert isinstance(message, dict)
assert len(message.keys()) == 1
assert 'slices' in message
assert isinstance(message['slices'], list)
for slice_ in message['slices']: validate_slice(slice_)
def validate_topologies(message):
assert isinstance(message, dict)
assert len(message.keys()) == 1
......
......@@ -127,7 +127,7 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer
return device_list_objs(self.db_engine)
@safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
def GetDevice(self, request : ContextId, context : grpc.ServicerContext) -> Device:
def GetDevice(self, request : DeviceId, context : grpc.ServicerContext) -> Device:
return device_get(self.db_engine, request)
@safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
......
......@@ -32,6 +32,7 @@ class ORM_DeviceDriverEnum(enum.Enum):
IETF_L2VPN = DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN
GNMI_OPENCONFIG = DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG
FLEXSCALE = DeviceDriverEnum.DEVICEDRIVER_FLEXSCALE
IETF_ACTN = DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN
grpc_to_enum__device_driver = functools.partial(
grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum)
......@@ -48,15 +48,26 @@ unit_test device:
- build device
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- if docker network list | grep teraflowbridge; then echo "teraflowbridge is already created"; else docker network create -d bridge teraflowbridge; fi
- if docker container ls | grep $IMAGE_NAME; then docker rm -f $IMAGE_NAME; else echo "$IMAGE_NAME image is not in the system"; fi
- >
if docker network list | grep teraflowbridge; then
echo "teraflowbridge is already created";
else
docker network create -d bridge teraflowbridge;
fi
- >
if docker container ls | grep $IMAGE_NAME; then
docker rm -f $IMAGE_NAME;
else
echo "$IMAGE_NAME image is not in the system";
fi
script:
- docker pull "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG"
- docker run --name $IMAGE_NAME -d -p 2020:2020 -v "$PWD/src/$IMAGE_NAME/tests:/opt/results" --network=teraflowbridge $CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG
- sleep 5
- docker ps -a
- docker logs $IMAGE_NAME
- docker exec -i $IMAGE_NAME bash -c "coverage run -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_unitary_emulated.py --junitxml=/opt/results/${IMAGE_NAME}_report.xml"
- docker exec -i $IMAGE_NAME bash -c "coverage run --append -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_unitary_emulated.py --junitxml=/opt/results/${IMAGE_NAME}_report_emulated.xml"
- docker exec -i $IMAGE_NAME bash -c "coverage run --append -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_unitary_ietf_actn.py --junitxml=/opt/results/${IMAGE_NAME}_report_ietf_actn.xml"
- docker exec -i $IMAGE_NAME bash -c "coverage report --include='${IMAGE_NAME}/*' --show-missing"
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/'
after_script:
......@@ -77,7 +88,7 @@ unit_test device:
artifacts:
when: always
reports:
junit: src/$IMAGE_NAME/tests/${IMAGE_NAME}_report.xml
junit: src/$IMAGE_NAME/tests/${IMAGE_NAME}_report_*.xml
## Deployment of the service in Kubernetes Cluster
#deploy device:
......
......@@ -66,5 +66,11 @@ COPY src/context/. context/
COPY src/device/. device/
COPY src/monitoring/. monitoring/
RUN mkdir -p tests/tools/mock_ietf_actn_sdn_ctrl
RUN touch tests/__init__.py
RUN touch tests/tools/__init__.py
RUN touch tests/tools/mock_ietf_actn_sdn_ctrl/__init__.py
COPY src/tests/tools/mock_ietf_actn_sdn_ctrl/. tests/tools/mock_ietf_actn_sdn_ctrl/
# Start the service
ENTRYPOINT ["python", "-m", "device.service"]
......@@ -16,7 +16,12 @@
anytree==2.8.0
APScheduler==3.10.1
cryptography==36.0.2
deepdiff==6.7.*
deepmerge==1.1.*
#fastcache==1.1.0
Flask==2.1.3
Flask-HTTPAuth==4.5.0
Flask-RESTful==0.3.9
Jinja2==3.0.3
ncclient==0.6.13
p4runtime==1.3.0
......@@ -32,9 +37,10 @@ tabulate
ipaddress
macaddress
yattag
pyang
pyang==2.6.0
git+https://github.com/robshakir/pyangbind.git
websockets==10.4
werkzeug==2.3.7
# pip's dependency resolver does not take into account installed packages.
# p4runtime does not specify the version of grpcio/protobuf it needs, so it tries to install latest one
......
......@@ -73,6 +73,13 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED
device.device_drivers.extend(request.device_drivers) # pylint: disable=no-member
device.device_config.CopyFrom(request.device_config) # pylint: disable=no-member
if request.HasField('controller_id'):
controller_id = request.controller_id
if controller_id.HasField('device_uuid'):
controller_device_uuid = controller_id.device_uuid.uuid
device.controller_id.device_uuid.uuid = controller_device_uuid
device_id = context_client.SetDevice(device)
device = get_device(context_client, device_id.device_uuid.uuid, rw_copy=True)
......
......@@ -84,6 +84,15 @@ DRIVERS.append(
}
]))
from .ietf_actn.IetfActnDriver import IetfActnDriver # pylint: disable=wrong-import-position
DRIVERS.append(
(IetfActnDriver, [
{
FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.OPEN_LINE_SYSTEM,
FilterFieldEnum.DRIVER: DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN,
}
]))
if LOAD_ALL_DEVICE_DRIVERS:
from .openconfig.OpenConfigDriver import OpenConfigDriver # pylint: disable=wrong-import-position
DRIVERS.append(
......
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json, logging, requests, threading
from typing import Any, Iterator, List, Optional, Tuple, Union
from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
from common.type_checkers.Checkers import chk_string, chk_type
from device.service.driver_api._Driver import _Driver, RESOURCE_ENDPOINTS, RESOURCE_SERVICES
from .handlers.EthtServiceHandler import EthtServiceHandler
from .handlers.OsuTunnelHandler import OsuTunnelHandler
from .handlers.RestApiClient import RestApiClient
from .Tools import get_etht_services, get_osu_tunnels, parse_resource_key
LOGGER = logging.getLogger(__name__)
ALL_RESOURCE_KEYS = [
RESOURCE_ENDPOINTS,
RESOURCE_SERVICES,
]
DRIVER_NAME = 'ietf_actn'
METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME})
class IetfActnDriver(_Driver):
def __init__(self, address: str, port: int, **settings) -> None:
super().__init__(DRIVER_NAME, address, port, **settings)
self.__lock = threading.Lock()
self.__started = threading.Event()
self.__terminate = threading.Event()
self._rest_api_client = RestApiClient(address, port, settings=settings)
self._handler_osu_tunnel = OsuTunnelHandler(self._rest_api_client)
self._handler_etht_service = EthtServiceHandler(self._rest_api_client)
def Connect(self) -> bool:
with self.__lock:
if self.__started.is_set(): return True
try:
self._rest_api_client.get('Check Credentials', '')
except requests.exceptions.Timeout:
LOGGER.exception('Timeout exception checking connectivity')
return False
except Exception: # pylint: disable=broad-except
LOGGER.exception('Unhandled exception checking connectivity')
return False
else:
self.__started.set()
return True
def Disconnect(self) -> bool:
with self.__lock:
self.__terminate.set()
return True
@metered_subclass_method(METRICS_POOL)
def GetInitialConfig(self) -> List[Tuple[str, Any]]:
with self.__lock:
return []
@metered_subclass_method(METRICS_POOL)
def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]:
chk_type('resources', resource_keys, list)
results = []
with self.__lock:
if len(resource_keys) == 0: resource_keys = ALL_RESOURCE_KEYS
for i, resource_key in enumerate(resource_keys):
chk_string('resource_key[#{:d}]'.format(i), resource_key, allow_empty=False)
try:
_results = list()
if resource_key == RESOURCE_ENDPOINTS:
# Add mgmt endpoint by default
resource_key = '/endpoints/endpoint[mgmt]'
resource_value = {'uuid': 'mgmt', 'name': 'mgmt', 'type': 'mgmt'}
results.append((resource_key, resource_value))
elif resource_key == RESOURCE_SERVICES:
get_osu_tunnels(self._handler_osu_tunnel, _results)
get_etht_services(self._handler_etht_service, _results)
else:
# check if resource key is for a specific OSU tunnel or ETHT service, and get them accordingly
osu_tunnel_name, etht_service_name = parse_resource_key(resource_key)
if osu_tunnel_name is not None:
get_osu_tunnels(self._handler_osu_tunnel, _results, osu_tunnel_name=osu_tunnel_name)
if etht_service_name is not None:
get_etht_services(self._handler_etht_service, _results, etht_service_name=etht_service_name)
results.extend(_results)
except Exception as e:
results.append((resource_key, e))
return results
@metered_subclass_method(METRICS_POOL)
def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
results = []
if len(resources) == 0: return results
with self.__lock:
for resource_key, resource_value in resources:
LOGGER.info('resource: key({:s}) => value({:s})'.format(str(resource_key), str(resource_value)))
try:
_results = list()
if isinstance(resource_value, str): resource_value = json.loads(resource_value)
osu_tunnel_name, etht_service_name = parse_resource_key(resource_key)
if osu_tunnel_name is not None:
succeeded = self._handler_osu_tunnel.update(resource_value)
_results.append(succeeded)
if etht_service_name is not None:
succeeded = self._handler_etht_service.update(resource_value)
_results.append(succeeded)
results.extend(_results)
except Exception as e:
results.append(e)
return results
@metered_subclass_method(METRICS_POOL)
def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
results = []
if len(resources) == 0: return results
with self.__lock:
for resource_key, resource_value in resources:
LOGGER.info('resource: key({:s}) => value({:s})'.format(str(resource_key), str(resource_value)))
try:
_results = list()
if isinstance(resource_value, str): resource_value = json.loads(resource_value)
osu_tunnel_name, etht_service_name = parse_resource_key(resource_key)
if osu_tunnel_name is not None:
succeeded = self._handler_osu_tunnel.delete(osu_tunnel_name)
_results.append(succeeded)
if etht_service_name is not None:
succeeded = self._handler_etht_service.delete(etht_service_name)
_results.append(succeeded)
results.extend(_results)
except Exception as e:
results.append(e)
return results
@metered_subclass_method(METRICS_POOL)
def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
# TODO: IETF ACTN does not support monitoring by now
return [False for _ in subscriptions]
@metered_subclass_method(METRICS_POOL)
def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
# TODO: IETF ACTN does not support monitoring by now
return [False for _ in subscriptions]
def GetState(
self, blocking=False, terminate : Optional[threading.Event] = None
) -> Iterator[Tuple[float, str, Any]]:
# TODO: IETF ACTN does not support monitoring by now
return []
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging, re
from typing import Any, List, Optional, Tuple, Union
from .handlers.EthtServiceHandler import EthtServiceHandler
from .handlers.OsuTunnelHandler import OsuTunnelHandler
LOGGER = logging.getLogger(__name__)
RE_OSU_TUNNEL = re.compile(r'^\/osu\_tunnels\/osu\_tunnel\[([^\]]+)\]$')
RE_ETHT_SERVICE = re.compile(r'^\/etht\_services\/etht\_service\[([^\]]+)\]$')
def parse_resource_key(resource_key : str) -> Tuple[Optional[str], Optional[str]]:
re_match_osu_tunnel = RE_OSU_TUNNEL.match(resource_key)
osu_tunnel_name = None if re_match_osu_tunnel is None else re_match_osu_tunnel.group(1)
re_match_etht_service = RE_ETHT_SERVICE.match(resource_key)
etht_service_name = None if re_match_etht_service is None else re_match_etht_service.group(1)
return osu_tunnel_name, etht_service_name
def get_osu_tunnels(
handler_osu_tunnel : OsuTunnelHandler, results : List[Tuple[str, Union[Any, None, Exception]]],
osu_tunnel_name : Optional[str] = None
) -> None:
osu_tunnels = handler_osu_tunnel.get(osu_tunnel_name=osu_tunnel_name)
for osu_tunnel in osu_tunnels:
osu_tunnel_name = osu_tunnel['name']
resource_key = '/osu_tunnels/osu_tunnel[{:s}]'.format(osu_tunnel_name)
results.append((resource_key, osu_tunnel))
def get_etht_services(
handler_etht_service : EthtServiceHandler, results : List[Tuple[str, Union[Any, None, Exception]]],
etht_service_name : Optional[str] = None
) -> None:
etht_services = handler_etht_service.get(etht_service_name=etht_service_name)
for etht_service in etht_services:
etht_service_name = etht_service['name']
resource_key = '/etht_services/etht_service[{:s}]'.format(etht_service_name)
results.append((resource_key, etht_service))