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 { ...@@ -202,6 +202,7 @@ enum DeviceDriverEnum {
DEVICEDRIVER_IETF_L2VPN = 7; DEVICEDRIVER_IETF_L2VPN = 7;
DEVICEDRIVER_GNMI_OPENCONFIG = 8; DEVICEDRIVER_GNMI_OPENCONFIG = 8;
DEVICEDRIVER_FLEXSCALE = 9; DEVICEDRIVER_FLEXSCALE = 9;
DEVICEDRIVER_IETF_ACTN = 10;
} }
enum DeviceOperationalStatusEnum { 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): ...@@ -22,6 +22,7 @@ class DeviceTypeEnum(Enum):
# Emulated device types # Emulated device types
EMULATED_CLIENT = 'emu-client' EMULATED_CLIENT = 'emu-client'
EMULATED_DATACENTER = 'emu-datacenter' EMULATED_DATACENTER = 'emu-datacenter'
EMULATED_IP_SDN_CONTROLLER = 'emu-ip-sdn-controller'
EMULATED_MICROWAVE_RADIO_SYSTEM = 'emu-microwave-radio-system' EMULATED_MICROWAVE_RADIO_SYSTEM = 'emu-microwave-radio-system'
EMULATED_OPEN_LINE_SYSTEM = 'emu-open-line-system' EMULATED_OPEN_LINE_SYSTEM = 'emu-open-line-system'
EMULATED_OPTICAL_ROADM = 'emu-optical-roadm' EMULATED_OPTICAL_ROADM = 'emu-optical-roadm'
...@@ -36,6 +37,7 @@ class DeviceTypeEnum(Enum): ...@@ -36,6 +37,7 @@ class DeviceTypeEnum(Enum):
# Real device types # Real device types
CLIENT = 'client' CLIENT = 'client'
DATACENTER = 'datacenter' DATACENTER = 'datacenter'
IP_SDN_CONTROLLER = 'ip-sdn-controller'
MICROWAVE_RADIO_SYSTEM = 'microwave-radio-system' MICROWAVE_RADIO_SYSTEM = 'microwave-radio-system'
OPEN_LINE_SYSTEM = 'open-line-system' OPEN_LINE_SYSTEM = 'open-line-system'
OPTICAL_ROADM = 'optical-roadm' OPTICAL_ROADM = 'optical-roadm'
......
...@@ -46,7 +46,7 @@ from slice.client.SliceClient import SliceClient ...@@ -46,7 +46,7 @@ from slice.client.SliceClient import SliceClient
from .Tools import ( from .Tools import (
format_device_custom_config_rules, format_service_custom_config_rules, format_slice_custom_config_rules, 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_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__) LOGGER = logging.getLogger(__name__)
LOGGERS = { LOGGERS = {
...@@ -56,9 +56,10 @@ LOGGERS = { ...@@ -56,9 +56,10 @@ LOGGERS = {
} }
ENTITY_TO_TEXT = { ENTITY_TO_TEXT = {
# name => singular, plural # name => singular, plural
'context' : ('Context', 'Contexts' ), 'context' : ('Context', 'Contexts' ),
'topology' : ('Topology', 'Topologies' ), 'topology' : ('Topology', 'Topologies' ),
'controller': ('Controller', 'Controllers'),
'device' : ('Device', 'Devices' ), 'device' : ('Device', 'Devices' ),
'link' : ('Link', 'Links' ), 'link' : ('Link', 'Links' ),
'service' : ('Service', 'Services' ), 'service' : ('Service', 'Services' ),
...@@ -68,8 +69,8 @@ ENTITY_TO_TEXT = { ...@@ -68,8 +69,8 @@ ENTITY_TO_TEXT = {
ACTION_TO_TEXT = { ACTION_TO_TEXT = {
# action => infinitive, past # action => infinitive, past
'add' : ('Add', 'Added'), 'add' : ('Add', 'Added' ),
'update' : ('Update', 'Updated'), 'update' : ('Update', 'Updated' ),
'config' : ('Configure', 'Configured'), 'config' : ('Configure', 'Configured'),
} }
...@@ -231,10 +232,12 @@ class DescriptorLoader: ...@@ -231,10 +232,12 @@ class DescriptorLoader:
def _load_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. # 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.__ctx_cli.connect()
self._process_descr('context', 'add', self.__ctx_cli.SetContext, Context, self.__contexts_add ) 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('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('link', 'add', self.__ctx_cli.SetLink, Link, self.__links )
self._process_descr('service', 'add', self.__ctx_cli.SetService, Service, self.__services ) self._process_descr('service', 'add', self.__ctx_cli.SetService, Service, self.__services )
self._process_descr('slice', 'add', self.__ctx_cli.SetSlice, Slice, self.__slices ) self._process_descr('slice', 'add', self.__ctx_cli.SetSlice, Slice, self.__slices )
...@@ -262,20 +265,23 @@ class DescriptorLoader: ...@@ -262,20 +265,23 @@ class DescriptorLoader:
self.__services_add = get_descriptors_add_services(self.__services) self.__services_add = get_descriptors_add_services(self.__services)
self.__slices_add = get_descriptors_add_slices(self.__slices) 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.__ctx_cli.connect()
self.__dev_cli.connect() self.__dev_cli.connect()
self.__svc_cli.connect() self.__svc_cli.connect()
self.__slc_cli.connect() self.__slc_cli.connect()
self._process_descr('context', 'add', self.__ctx_cli.SetContext, Context, self.__contexts_add ) 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('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('controller', 'add', self.__dev_cli.AddDevice, Device, controllers_add )
self._process_descr('device', 'config', self.__dev_cli.ConfigureDevice, Device, self.__devices_config) self._process_descr('device', 'add', self.__dev_cli.AddDevice, Device, network_devices_add )
self._process_descr('link', 'add', self.__ctx_cli.SetLink, Link, self.__links ) self._process_descr('device', 'config', self.__dev_cli.ConfigureDevice, Device, self.__devices_config)
self._process_descr('service', 'add', self.__svc_cli.CreateService, Service, self.__services_add ) self._process_descr('link', 'add', self.__ctx_cli.SetLink, Link, self.__links )
self._process_descr('service', 'update', self.__svc_cli.UpdateService, Service, self.__services ) self._process_descr('service', 'add', self.__svc_cli.CreateService, Service, self.__services_add )
self._process_descr('slice', 'add', self.__slc_cli.CreateSlice, Slice, self.__slices_add ) self._process_descr('service', 'update', self.__svc_cli.UpdateService, Service, self.__services )
self._process_descr('slice', 'update', self.__slc_cli.UpdateSlice, Slice, self.__slices ) 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 # 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. # endpoints, and assigns topologies, services, and slices to contexts based on their identifiers.
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
import copy, json import copy, json
from typing import Dict, List, Optional, Tuple, Union from typing import Dict, List, Optional, Tuple, Union
from common.DeviceTypes import DeviceTypeEnum
def get_descriptors_add_contexts(contexts : List[Dict]) -> List[Dict]: def get_descriptors_add_contexts(contexts : List[Dict]) -> List[Dict]:
contexts_add = copy.deepcopy(contexts) contexts_add = copy.deepcopy(contexts)
...@@ -103,3 +104,24 @@ def split_devices_by_rules(devices : List[Dict]) -> Tuple[List[Dict], List[Dict] ...@@ -103,3 +104,24 @@ def split_devices_by_rules(devices : List[Dict]) -> Tuple[List[Dict], List[Dict]
devices_config.append(device) devices_config.append(device)
return devices_add, devices_config 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 @@ ...@@ -18,11 +18,12 @@
import json import json
from typing import Any, Dict, List, Optional, Tuple 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 from common.tools.grpc.Tools import grpc_message_to_json_string
def update_constraint_custom_scalar( 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: ) -> Constraint:
for constraint in constraints: for constraint in constraints:
...@@ -36,6 +37,8 @@ def update_constraint_custom_scalar( ...@@ -36,6 +37,8 @@ def update_constraint_custom_scalar(
constraint.custom.constraint_type = constraint_type constraint.custom.constraint_type = constraint_type
json_constraint_value = None json_constraint_value = None
constraint.action = new_action
if (json_constraint_value is None) or not raise_if_differs: if (json_constraint_value is None) or not raise_if_differs:
# missing or raise_if_differs=False, add/update it # missing or raise_if_differs=False, add/update it
json_constraint_value = value json_constraint_value = value
...@@ -47,7 +50,10 @@ def update_constraint_custom_scalar( ...@@ -47,7 +50,10 @@ def update_constraint_custom_scalar(
constraint.custom.constraint_value = json.dumps(json_constraint_value, sort_keys=True) constraint.custom.constraint_value = json.dumps(json_constraint_value, sort_keys=True)
return constraint 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]] # fields: Dict[field_name : str, Tuple[field_value : Any, raise_if_differs : bool]]
for constraint in constraints: for constraint in constraints:
...@@ -61,6 +67,8 @@ def update_constraint_custom_dict(constraints, constraint_type : str, fields : D ...@@ -61,6 +67,8 @@ def update_constraint_custom_dict(constraints, constraint_type : str, fields : D
constraint.custom.constraint_type = constraint_type constraint.custom.constraint_type = constraint_type
json_constraint_value = {} json_constraint_value = {}
constraint.action = new_action
for field_name,(field_value, raise_if_differs) in fields.items(): for field_name,(field_value, raise_if_differs) in fields.items():
if (field_name not in json_constraint_value) or not raise_if_differs: if (field_name not in json_constraint_value) or not raise_if_differs:
# missing or raise_if_differs=False, add/update it # missing or raise_if_differs=False, add/update it
...@@ -75,7 +83,8 @@ def update_constraint_custom_dict(constraints, constraint_type : str, fields : D ...@@ -75,7 +83,8 @@ def update_constraint_custom_dict(constraints, constraint_type : str, fields : D
def update_constraint_endpoint_location( def update_constraint_endpoint_location(
constraints, endpoint_id : EndPointId, 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: ) -> Constraint:
# gps_position: (latitude, longitude) # gps_position: (latitude, longitude)
if region is not None and gps_position is not None: if region is not None and gps_position is not None:
...@@ -103,6 +112,8 @@ def update_constraint_endpoint_location( ...@@ -103,6 +112,8 @@ def update_constraint_endpoint_location(
_endpoint_id.topology_id.topology_uuid.uuid = topology_uuid _endpoint_id.topology_id.topology_uuid.uuid = topology_uuid
_endpoint_id.topology_id.context_id.context_uuid.uuid = context_uuid _endpoint_id.topology_id.context_id.context_uuid.uuid = context_uuid
constraint.action = new_action
location = constraint.endpoint_location.location location = constraint.endpoint_location.location
if region is not None: if region is not None:
location.region = region location.region = region
...@@ -111,7 +122,10 @@ def update_constraint_endpoint_location( ...@@ -111,7 +122,10 @@ def update_constraint_endpoint_location(
location.gps_position.longitude = gps_position[1] location.gps_position.longitude = gps_position[1]
return constraint 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 endpoint_uuid = endpoint_id.endpoint_uuid.uuid
device_uuid = endpoint_id.device_id.device_uuid.uuid device_uuid = endpoint_id.device_id.device_uuid.uuid
topology_uuid = endpoint_id.topology_id.topology_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 ...@@ -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.topology_uuid.uuid = topology_uuid
_endpoint_id.topology_id.context_id.context_uuid.uuid = context_uuid _endpoint_id.topology_id.context_id.context_uuid.uuid = context_uuid
constraint.action = new_action
constraint.endpoint_priority.priority = priority constraint.endpoint_priority.priority = priority
return constraint 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: for constraint in constraints:
if constraint.WhichOneof('constraint') != 'sla_capacity': continue if constraint.WhichOneof('constraint') != 'sla_capacity': continue
break # found, end loop break # found, end loop
...@@ -145,10 +164,15 @@ def update_constraint_sla_capacity(constraints, capacity_gbps : float) -> Constr ...@@ -145,10 +164,15 @@ def update_constraint_sla_capacity(constraints, capacity_gbps : float) -> Constr
# not found, add it # not found, add it
constraint = constraints.add() # pylint: disable=no-member constraint = constraints.add() # pylint: disable=no-member
constraint.action = new_action
constraint.sla_capacity.capacity_gbps = capacity_gbps constraint.sla_capacity.capacity_gbps = capacity_gbps
return constraint 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: for constraint in constraints:
if constraint.WhichOneof('constraint') != 'sla_latency': continue if constraint.WhichOneof('constraint') != 'sla_latency': continue
break # found, end loop break # found, end loop
...@@ -156,11 +180,14 @@ def update_constraint_sla_latency(constraints, e2e_latency_ms : float) -> Constr ...@@ -156,11 +180,14 @@ def update_constraint_sla_latency(constraints, e2e_latency_ms : float) -> Constr
# not found, add it # not found, add it
constraint = constraints.add() # pylint: disable=no-member constraint = constraints.add() # pylint: disable=no-member
constraint.action = new_action
constraint.sla_latency.e2e_latency_ms = e2e_latency_ms constraint.sla_latency.e2e_latency_ms = e2e_latency_ms
return constraint return constraint
def update_constraint_sla_availability( 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: ) -> Constraint:
for constraint in constraints: for constraint in constraints:
if constraint.WhichOneof('constraint') != 'sla_availability': continue if constraint.WhichOneof('constraint') != 'sla_availability': continue
...@@ -169,12 +196,17 @@ def update_constraint_sla_availability( ...@@ -169,12 +196,17 @@ def update_constraint_sla_availability(
# not found, add it # not found, add it
constraint = constraints.add() # pylint: disable=no-member constraint = constraints.add() # pylint: disable=no-member
constraint.action = new_action
constraint.sla_availability.num_disjoint_paths = num_disjoint_paths constraint.sla_availability.num_disjoint_paths = num_disjoint_paths
constraint.sla_availability.all_active = all_active constraint.sla_availability.all_active = all_active
constraint.sla_availability.availability = availability constraint.sla_availability.availability = availability
return constraint 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: for constraint in constraints:
if constraint.WhichOneof('constraint') != 'sla_isolation': continue if constraint.WhichOneof('constraint') != 'sla_isolation': continue
break # found, end loop break # found, end loop
...@@ -182,6 +214,8 @@ def update_constraint_sla_isolation(constraints, isolation_levels : List[int]) - ...@@ -182,6 +214,8 @@ def update_constraint_sla_isolation(constraints, isolation_levels : List[int]) -
# not found, add it # not found, add it
constraint = constraints.add() # pylint: disable=no-member constraint = constraints.add() # pylint: disable=no-member
constraint.action = new_action
for isolation_level in isolation_levels: for isolation_level in isolation_levels:
if isolation_level in constraint.sla_isolation.isolation_level: continue if isolation_level in constraint.sla_isolation.isolation_level: continue
constraint.sla_isolation.isolation_level.append(isolation_level) constraint.sla_isolation.isolation_level.append(isolation_level)
......
...@@ -46,6 +46,10 @@ DEVICE_P4_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_P4] ...@@ -46,6 +46,10 @@ DEVICE_P4_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_P4]
DEVICE_TFS_TYPE = DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value DEVICE_TFS_TYPE = DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value
DEVICE_TFS_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN] 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): def json_device_id(device_uuid : str):
return {'device_uuid': {'uuid': device_uuid}} return {'device_uuid': {'uuid': device_uuid}}
...@@ -136,6 +140,14 @@ def json_device_tfs_disabled( ...@@ -136,6 +140,14 @@ def json_device_tfs_disabled(
device_uuid, DEVICE_TFS_TYPE, DEVICE_DISABLED, name=name, endpoints=endpoints, config_rules=config_rules, device_uuid, DEVICE_TFS_TYPE, DEVICE_DISABLED, name=name, endpoints=endpoints, config_rules=config_rules,
drivers=drivers) 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]: def json_device_connect_rules(address : str, port : int, settings : Dict = {}) -> List[Dict]:
return [ return [
json_config_rule_set('_connect/address', address), json_config_rule_set('_connect/address', address),
......
...@@ -12,7 +12,10 @@ ...@@ -12,7 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from typing import Dict import logging
from typing import Callable, Dict
LOGGER = logging.getLogger(__name__)
# ----- Enumerations --------------------------------------------------------------------------------------------------- # ----- Enumerations ---------------------------------------------------------------------------------------------------
def validate_config_action_enum(message): def validate_config_action_enum(message):
...@@ -23,6 +26,14 @@ def validate_config_action_enum(message): ...@@ -23,6 +26,14 @@ def validate_config_action_enum(message):
'CONFIGACTION_DELETE', '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): def validate_device_driver_enum(message):
assert isinstance(message, str) assert isinstance(message, str)
assert message in [ assert message in [
...@@ -35,6 +46,8 @@ def validate_device_driver_enum(message): ...@@ -35,6 +46,8 @@ def validate_device_driver_enum(message):
'DEVICEDRIVER_XR', 'DEVICEDRIVER_XR',
'DEVICEDRIVER_IETF_L2VPN', 'DEVICEDRIVER_IETF_L2VPN',
'DEVICEDRIVER_GNMI_OPENCONFIG', 'DEVICEDRIVER_GNMI_OPENCONFIG',
'DEVICEDRIVER_FLEXSCALE',
'DEVICEDRIVER_IETF_ACTN',
] ]
def validate_device_operational_status_enum(message): def validate_device_operational_status_enum(message):
...@@ -64,6 +77,8 @@ def validate_service_type_enum(message): ...@@ -64,6 +77,8 @@ def validate_service_type_enum(message):
'SERVICETYPE_L3NM', 'SERVICETYPE_L3NM',
'SERVICETYPE_L2NM', 'SERVICETYPE_L2NM',
'SERVICETYPE_TAPI_CONNECTIVITY_SERVICE', 'SERVICETYPE_TAPI_CONNECTIVITY_SERVICE',
'SERVICETYPE_TE',
'SERVICETYPE_E2E',
] ]
def validate_service_state_enum(message): def validate_service_state_enum(message):
...@@ -77,6 +92,17 @@ def validate_service_state_enum(message): ...@@ -77,6 +92,17 @@ def validate_service_state_enum(message):
'SERVICESTATUS_SLA_VIOLATED', '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 --------------------------------------------------------------------------------------------------------- # ----- Common ---------------------------------------------------------------------------------------------------------
def validate_uuid(message, allow_empty=False): def validate_uuid(message, allow_empty=False):
...@@ -114,28 +140,61 @@ def validate_config_rules(message): ...@@ -114,28 +140,61 @@ def validate_config_rules(message):
assert 'config_rules' in message assert 'config_rules' in message
for config_rule in message['config_rules']: validate_config_rule(config_rule) for config_rule in message['config_rules']: validate_config_rule(config_rule)
CONSTRAINT_TYPES = { def validate_constraint_custom(message):
'custom', assert isinstance(message, dict)
'schedule', assert len(message.keys()) == 2
'endpoint_location', assert 'constraint_type' in message
'sla_capacity', assert isinstance(message['constraint_type'], str)
'sla_latency', assert 'constraint_value' in message
'sla_availability', assert isinstance(message['constraint_value'], str)
'sla_isolation',
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): def validate_constraint(message):
assert isinstance(message, dict) assert isinstance(message, dict)
assert len(message.keys()) == 1 assert len(message.keys()) == 2
other_keys = list(message.keys()) assert 'action' in message
constraint_type = other_keys[0] validate_constraint_action_enum(message['action'])
assert constraint_type in CONSTRAINT_TYPES other_keys = set(list(message.keys()))
assert constraint_type == 'custom', 'Constraint Type Validator for {:s} not implemented'.format(constraint_type) other_keys.discard('action')
custom : Dict = message['custom'] constraint_type = other_keys.pop()
assert len(custom.keys()) == 2 validator : Callable = CONSTRAINT_TYPE_TO_VALIDATOR.get(constraint_type)
assert 'constraint_type' in custom assert validator is not None, 'Constraint Type Validator for {:s} not implemented'.format(constraint_type)
assert isinstance(custom['constraint_type'], str) validator(message[constraint_type])
assert 'constraint_value' in custom
assert isinstance(custom['constraint_value'], str)
# ----- Identifiers ---------------------------------------------------------------------------------------------------- # ----- Identifiers ----------------------------------------------------------------------------------------------------
...@@ -192,6 +251,15 @@ def validate_connection_id(message): ...@@ -192,6 +251,15 @@ def validate_connection_id(message):
assert 'connection_uuid' in message assert 'connection_uuid' in message
validate_uuid(message['connection_uuid']) 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 ------------------------------------------------------------------------------------------- # ----- Lists of Identifiers -------------------------------------------------------------------------------------------
...@@ -209,6 +277,13 @@ def validate_service_ids(message, context_uuid=None): ...@@ -209,6 +277,13 @@ def validate_service_ids(message, context_uuid=None):
assert isinstance(message['service_ids'], list) assert isinstance(message['service_ids'], list)
for service_id in message['service_ids']: validate_service_id(service_id, context_uuid=context_uuid) 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): def validate_topology_ids(message, context_uuid=None):
assert isinstance(message, dict) assert isinstance(message, dict)
assert len(message.keys()) == 1 assert len(message.keys()) == 1
...@@ -242,16 +317,21 @@ def validate_connection_ids(message): ...@@ -242,16 +317,21 @@ def validate_connection_ids(message):
def validate_context(message): def validate_context(message):
assert isinstance(message, dict) assert isinstance(message, dict)
assert len(message.keys()) == 3 assert len(message.keys()) == 5
assert 'context_id' in message assert 'context_id' in message
validate_context_id(message['context_id']) validate_context_id(message['context_id'])
context_uuid = message['context_id']['context_uuid']['uuid'] context_uuid = message['context_id']['context_uuid']['uuid']
assert 'service_ids' in message assert 'name' in message
assert isinstance(message['service_ids'], list) assert isinstance(message['name'], str)
for service_id in message['service_ids']: validate_service_id(service_id, context_uuid=context_uuid)
assert 'topology_ids' in message assert 'topology_ids' in message
assert isinstance(message['topology_ids'], list) assert isinstance(message['topology_ids'], list)
for topology_id in message['topology_ids']: validate_topology_id(topology_id, context_uuid=context_uuid) 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): def validate_service_state(message):
assert isinstance(message, dict) assert isinstance(message, dict)
...@@ -259,11 +339,19 @@ def validate_service_state(message): ...@@ -259,11 +339,19 @@ def validate_service_state(message):
assert 'service_status' in message assert 'service_status' in message
validate_service_state_enum(message['service_status']) 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): def validate_service(message):
assert isinstance(message, dict) assert isinstance(message, dict)
assert len(message.keys()) == 6 assert len(message.keys()) == 7
assert 'service_id' in message assert 'service_id' in message
validate_service_id(message['service_id']) validate_service_id(message['service_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'service_type' in message assert 'service_type' in message
validate_service_type_enum(message['service_type']) validate_service_type_enum(message['service_type'])
assert 'service_endpoint_ids' in message assert 'service_endpoint_ids' in message
...@@ -277,11 +365,44 @@ def validate_service(message): ...@@ -277,11 +365,44 @@ def validate_service(message):
assert 'service_config' in message assert 'service_config' in message
validate_config_rules(message['service_config']) 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): def validate_topology(message, num_devices=None, num_links=None):
assert isinstance(message, dict) assert isinstance(message, dict)
assert len(message.keys()) == 3 assert len(message.keys()) == 4
assert 'topology_id' in message assert 'topology_id' in message
validate_topology_id(message['topology_id']) validate_topology_id(message['topology_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'device_ids' in message assert 'device_ids' in message
assert isinstance(message['device_ids'], list) assert isinstance(message['device_ids'], list)
if num_devices is not None: assert len(message['device_ids']) == num_devices 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): ...@@ -293,20 +414,49 @@ def validate_topology(message, num_devices=None, num_links=None):
def validate_endpoint(message): def validate_endpoint(message):
assert isinstance(message, dict) assert isinstance(message, dict)
assert len(message.keys()) == 3 assert len(message.keys()) == 4
assert 'endpoint_id' in message assert 'endpoint_id' in message
validate_endpoint_id(message['endpoint_id']) validate_endpoint_id(message['endpoint_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'endpoint_type' in message assert 'endpoint_type' in message
assert isinstance(message['endpoint_type'], str) assert isinstance(message['endpoint_type'], str)
assert 'kpi_sample_types' in message assert 'kpi_sample_types' in message
assert isinstance(message['kpi_sample_types'], list) assert isinstance(message['kpi_sample_types'], list)
for kpi_sample_type in message['kpi_sample_types']: validate_kpi_sample_types_enum(kpi_sample_type) 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): def validate_device(message):
assert isinstance(message, dict) assert isinstance(message, dict)
assert len(message.keys()) == 6 assert len(message.keys()) in {8, 9}
assert 'device_id' in message assert 'device_id' in message
validate_device_id(message['device_id']) validate_device_id(message['device_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'device_type' in message assert 'device_type' in message
assert isinstance(message['device_type'], str) assert isinstance(message['device_type'], str)
assert 'device_config' in message assert 'device_config' in message
...@@ -319,19 +469,30 @@ def validate_device(message): ...@@ -319,19 +469,30 @@ def validate_device(message):
assert 'device_endpoints' in message assert 'device_endpoints' in message
assert isinstance(message['device_endpoints'], list) assert isinstance(message['device_endpoints'], list)
for endpoint in message['device_endpoints']: validate_endpoint(endpoint) 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): def validate_link(message):
assert isinstance(message, dict) assert isinstance(message, dict)
assert len(message.keys()) == 2 assert len(message.keys()) == 4
assert 'link_id' in message assert 'link_id' in message
validate_link_id(message['link_id']) validate_link_id(message['link_id'])
assert 'name' in message
assert isinstance(message['name'], str)
assert 'link_endpoint_ids' in message assert 'link_endpoint_ids' in message
assert isinstance(message['link_endpoint_ids'], list) assert isinstance(message['link_endpoint_ids'], list)
for endpoint_id in message['link_endpoint_ids']: validate_endpoint_id(endpoint_id) 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): def validate_connection(message):
assert isinstance(message, dict) assert isinstance(message, dict)
assert len(message.keys()) == 4 assert len(message.keys()) in {4, 5}
assert 'connection_id' in message assert 'connection_id' in message
validate_connection_id(message['connection_id']) validate_connection_id(message['connection_id'])
assert 'service_id' in message assert 'service_id' in message
...@@ -342,6 +503,50 @@ def validate_connection(message): ...@@ -342,6 +503,50 @@ def validate_connection(message):
assert 'sub_service_ids' in message assert 'sub_service_ids' in message
assert isinstance(message['sub_service_ids'], list) assert isinstance(message['sub_service_ids'], list)
for sub_service_id in message['sub_service_ids']: validate_service_id(sub_service_id) 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 ----------------------------------------------------------------------------------------------- # ----- Lists of Objects -----------------------------------------------------------------------------------------------
...@@ -360,6 +565,13 @@ def validate_services(message): ...@@ -360,6 +565,13 @@ def validate_services(message):
assert isinstance(message['services'], list) assert isinstance(message['services'], list)
for service in message['services']: validate_service(service) 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): def validate_topologies(message):
assert isinstance(message, dict) assert isinstance(message, dict)
assert len(message.keys()) == 1 assert len(message.keys()) == 1
......
...@@ -127,7 +127,7 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer ...@@ -127,7 +127,7 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer
return device_list_objs(self.db_engine) return device_list_objs(self.db_engine)
@safe_and_metered_rpc_method(METRICS_POOL, LOGGER) @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) return device_get(self.db_engine, request)
@safe_and_metered_rpc_method(METRICS_POOL, LOGGER) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER)
......
...@@ -32,6 +32,7 @@ class ORM_DeviceDriverEnum(enum.Enum): ...@@ -32,6 +32,7 @@ class ORM_DeviceDriverEnum(enum.Enum):
IETF_L2VPN = DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN IETF_L2VPN = DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN
GNMI_OPENCONFIG = DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG GNMI_OPENCONFIG = DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG
FLEXSCALE = DeviceDriverEnum.DEVICEDRIVER_FLEXSCALE FLEXSCALE = DeviceDriverEnum.DEVICEDRIVER_FLEXSCALE
IETF_ACTN = DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN
grpc_to_enum__device_driver = functools.partial( grpc_to_enum__device_driver = functools.partial(
grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum)
...@@ -48,15 +48,26 @@ unit_test device: ...@@ -48,15 +48,26 @@ unit_test device:
- build device - build device
before_script: before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY - 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: script:
- docker pull "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" - 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 - 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 - sleep 5
- docker ps -a - docker ps -a
- docker logs $IMAGE_NAME - 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" - docker exec -i $IMAGE_NAME bash -c "coverage report --include='${IMAGE_NAME}/*' --show-missing"
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/'
after_script: after_script:
...@@ -77,7 +88,7 @@ unit_test device: ...@@ -77,7 +88,7 @@ unit_test device:
artifacts: artifacts:
when: always when: always
reports: 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 ## Deployment of the service in Kubernetes Cluster
#deploy device: #deploy device:
......
...@@ -66,5 +66,11 @@ COPY src/context/. context/ ...@@ -66,5 +66,11 @@ COPY src/context/. context/
COPY src/device/. device/ COPY src/device/. device/
COPY src/monitoring/. monitoring/ 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 # Start the service
ENTRYPOINT ["python", "-m", "device.service"] ENTRYPOINT ["python", "-m", "device.service"]
...@@ -16,7 +16,12 @@ ...@@ -16,7 +16,12 @@
anytree==2.8.0 anytree==2.8.0
APScheduler==3.10.1 APScheduler==3.10.1
cryptography==36.0.2 cryptography==36.0.2
deepdiff==6.7.*
deepmerge==1.1.*
#fastcache==1.1.0 #fastcache==1.1.0
Flask==2.1.3
Flask-HTTPAuth==4.5.0
Flask-RESTful==0.3.9
Jinja2==3.0.3 Jinja2==3.0.3
ncclient==0.6.13 ncclient==0.6.13
p4runtime==1.3.0 p4runtime==1.3.0
...@@ -32,9 +37,10 @@ tabulate ...@@ -32,9 +37,10 @@ tabulate
ipaddress ipaddress
macaddress macaddress
yattag yattag
pyang pyang==2.6.0
git+https://github.com/robshakir/pyangbind.git git+https://github.com/robshakir/pyangbind.git
websockets==10.4 websockets==10.4
werkzeug==2.3.7
# pip's dependency resolver does not take into account installed packages. # 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 # 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): ...@@ -73,6 +73,13 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED
device.device_drivers.extend(request.device_drivers) # pylint: disable=no-member device.device_drivers.extend(request.device_drivers) # pylint: disable=no-member
device.device_config.CopyFrom(request.device_config) # 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_id = context_client.SetDevice(device)
device = get_device(context_client, device_id.device_uuid.uuid, rw_copy=True) device = get_device(context_client, device_id.device_uuid.uuid, rw_copy=True)
......
...@@ -84,6 +84,15 @@ DRIVERS.append( ...@@ -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: if LOAD_ALL_DEVICE_DRIVERS:
from .openconfig.OpenConfigDriver import OpenConfigDriver # pylint: disable=wrong-import-position from .openconfig.OpenConfigDriver import OpenConfigDriver # pylint: disable=wrong-import-position
DRIVERS.append( 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))