Skip to content
Snippets Groups Projects
Commit 3e9b528c authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

DLT component - Connector:

- Added event handler to DltEventsCollector
- Implemented DLT performance assessment tool
- Reorganized tests
parent 80d89934
No related branches found
No related tags found
2 merge requests!142Release TeraFlowSDN 2.1,!112DLT performance assessment tool
Showing
with 791 additions and 4 deletions
......@@ -12,27 +12,40 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Callable, Optional
import grpc, logging, queue, threading, time
from common.proto.dlt_gateway_pb2 import DltRecordSubscription
from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription
from common.tools.grpc.Tools import grpc_message_to_json_string
from dlt.connector.client.DltGatewayClient import DltGatewayClient
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)
# This class accepts an event_handler method as attribute that can be used to pre-process and
# filter events before they reach the events_queue. Depending on the handler, the supported
# behaviors are:
# - If the handler is not set, the events are transparently added to the events_queue.
# - If returns None for an event, the event is not stored in the events_queue.
# - If returns a DltRecordEvent object for an event, the returned event is stored in the events_queue.
# - Other combinations are not supported.
class DltEventsCollector(threading.Thread):
def __init__(
self, dltgateway_client : DltGatewayClient,
log_events_received : bool = False,
event_handler : Optional[Callable[[DltRecordEvent], Optional[DltRecordEvent]]] = None,
) -> None:
super().__init__(name='DltEventsCollector', daemon=True)
self._dltgateway_client = dltgateway_client
self._log_events_received = log_events_received
self._event_handler = event_handler
self._events_queue = queue.Queue()
self._terminate = threading.Event()
self._dltgateway_stream = None
def run(self) -> None:
event_handler = self._event_handler
if event_handler is None: event_handler = lambda e: e
self._terminate.clear()
while not self._terminate.is_set():
try:
......@@ -41,6 +54,11 @@ class DltEventsCollector(threading.Thread):
for event in self._dltgateway_stream:
if self._log_events_received:
LOGGER.info('[_collect] event: {:s}'.format(grpc_message_to_json_string(event)))
event = event_handler(event)
if event is None: continue
if not isinstance(event, DltRecordEvent):
# pylint: disable=broad-exception-raised
raise Exception('Unsupported return type: {:s}'.format(str(event)))
self._events_queue.put_nowait(event)
except grpc.RpcError as e:
if e.code() == grpc.StatusCode.UNAVAILABLE: # pylint: disable=no-member
......
......@@ -14,7 +14,7 @@
# pip install grpcio==1.47.0 grpcio-tools==1.47.0 protobuf==3.20.1
# PYTHONPATH=./src python
# PYTHONPATH=/home/cttc/teraflow/src python -m dlt.connector.main_test
# PYTHONPATH=/home/cttc/teraflow/src python -m dlt.connector.tests.basic
import logging, sys, time
from common.proto.dlt_gateway_pb2 import (
......@@ -23,8 +23,8 @@ from common.proto.dlt_gateway_pb2 import (
from common.tools.object_factory.Device import json_device
from common.tools.grpc.Tools import grpc_message_to_json_string
from src.common.proto.context_pb2 import DEVICEOPERATIONALSTATUS_ENABLED, Device
from .client.DltGatewayClient import DltGatewayClient
from .client.DltEventsCollector import DltEventsCollector
from ..client.DltGatewayClient import DltGatewayClient
from ..client.DltEventsCollector import DltEventsCollector
logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger(__name__)
......
# 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.
# 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.
# vary operation: create, update, delete
# vary record type (device, link, service, slice)
# for devices, services, slices, vary size: num endpoints, num constraints, num config rules
# measure load/store time
# measure event notification time
# docker build -t dlt-gateway:test -f ./src/dlt/gateway/Dockerfile .
# docker run --name dlt-gateway -d -p 50051:50051 --network=tfs-br dlt-gateway:test
# pip install grpcio==1.47.0 grpcio-tools==1.47.0 protobuf==3.20.1
# PYTHONPATH=./src python
# PYTHONPATH=./src python -m dlt.connector.tests.performance
import functools, logging, sys, time
from common.proto.dlt_gateway_pb2 import DltRecordEvent
from dlt.connector.client.DltGatewayClient import DltGatewayClient
from dlt.connector.client.DltEventsCollector import DltEventsCollector
from .play_ground.Enums import CONTEXT_EVENT_TYPE_TO_ACTION, RECORD_TYPE_TO_ENUM
from .play_ground import PlayGround
DLT_GATEWAY_HOST = '172.254.254.2'
DLT_GATEWAY_PORT = 50051
NUM_ACTIONS = 100
DOMAIN_UUID = 'perf-test-fake-domain'
CSV_FILEPATH = 'data/perf/scenario_2/dlt/2023-05May-30/response_time'
logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger(__name__)
def _event_handler(play_ground : PlayGround, event : DltRecordEvent) -> None:
# Filter undesired/unsupported/wrong domain_uuids, actions, and record types
# Update notification time in PlayGround.PerfPoints
# Return None to prevent storing the events in the DLT Events' Collector internal queue
domain_uuid = event.record_id.domain_uuid.uuid
if domain_uuid != DOMAIN_UUID: return None
action = CONTEXT_EVENT_TYPE_TO_ACTION.get(event.event.event_type)
if action is None: return None
record_type = RECORD_TYPE_TO_ENUM.get(event.record_id.type)
if record_type is None: return None
#event_time = event.event.timestamp.timestamp
record_uuid = event.record_id.record_uuid.uuid
play_ground.perf_data.get(action, record_type, record_uuid).set_time_notified(time.time())
return None
def main() -> None:
dltgateway_client = DltGatewayClient(host=DLT_GATEWAY_HOST, port=DLT_GATEWAY_PORT)
play_ground = PlayGround(dltgateway_client, DOMAIN_UUID)
event_handler = functools.partial(_event_handler, play_ground)
dltgateway_collector = DltEventsCollector(
dltgateway_client, log_events_received=False, event_handler=event_handler)
dltgateway_collector.start()
time.sleep(3)
LOGGER.info('Starting {:d} actions...'.format(NUM_ACTIONS))
num_action = 0
while num_action < NUM_ACTIONS:
if num_action > 0 and num_action % 10 == 0:
str_stats = play_ground.perf_data.stats_to_str()
MSG = 'Running action {:d}/{:d}...\n{:s}'
LOGGER.info(MSG.format(num_action, NUM_ACTIONS, str_stats))
completed = play_ground.run_random_operation()
if not completed: continue
num_action += 1
LOGGER.info('Completed {:d} actions!'.format(NUM_ACTIONS))
dltgateway_collector.stop()
play_ground.perf_data.to_csv(CSV_FILEPATH)
return 0
if __name__ == '__main__':
sys.exit(main())
# 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, time
from typing import Dict, Union
from common.proto.context_pb2 import Device, Link, Service, Slice
from common.proto.dlt_gateway_pb2 import (
DltRecord, DltRecordId, DltRecordOperationEnum, DltRecordStatus, DltRecordTypeEnum)
from common.tools.grpc.Tools import grpc_message_to_json_string
from dlt.connector.client.DltGatewayClient import DltGatewayClient
from .PerfPoint import PerfPoint
DLT_OPERATION_CREATE = DltRecordOperationEnum.DLTRECORDOPERATION_ADD
DLT_OPERATION_UPDATE = DltRecordOperationEnum.DLTRECORDOPERATION_UPDATE
DLT_OPERATION_DELETE = DltRecordOperationEnum.DLTRECORDOPERATION_DELETE
DLT_RECORD_TYPE_DEVICE = DltRecordTypeEnum.DLTRECORDTYPE_DEVICE
DLT_RECORD_TYPE_LINK = DltRecordTypeEnum.DLTRECORDTYPE_LINK
DLT_RECORD_TYPE_SERVICE = DltRecordTypeEnum.DLTRECORDTYPE_SERVICE
DLT_RECORD_TYPE_SLICE = DltRecordTypeEnum.DLTRECORDTYPE_SLICE
def dlt_record_set(
dltgateway_client : DltGatewayClient, perf_point : PerfPoint,
operation : DltRecordOperationEnum, domain_uuid : str, objekt : Union[Device, Link, Service, Slice]
) -> DltRecordStatus:
if isinstance(objekt, Device):
record_type = DLT_RECORD_TYPE_DEVICE
record_uuid = objekt.device_id.device_uuid.uuid
elif isinstance(objekt, Link):
record_type = DLT_RECORD_TYPE_LINK
record_uuid = objekt.link_id.link_uuid.uuid
elif isinstance(objekt, Service):
record_type = DLT_RECORD_TYPE_SERVICE
record_uuid = objekt.service_id.service_uuid.uuid
elif isinstance(objekt, Slice):
record_type = DLT_RECORD_TYPE_SLICE
record_uuid = objekt.slice_id.slice_uuid.uuid
else:
raise NotImplementedError('Object({:s}) not supported'.format(str(type(objekt))))
dlt_req = DltRecord()
dlt_req.record_id.domain_uuid.uuid = domain_uuid # pylint: disable=no-member
dlt_req.record_id.type = record_type # pylint: disable=no-member
dlt_req.record_id.record_uuid.uuid = record_uuid # pylint: disable=no-member
dlt_req.operation = operation
dlt_req.data_json = grpc_message_to_json_string(objekt)
perf_point.set_time_requested(time.time())
reply = dltgateway_client.RecordToDlt(dlt_req)
perf_point.set_time_replied(time.time())
return reply
def dlt_record_found(record : DltRecord) -> bool:
return all([
len(record.record_id.domain_uuid.uuid) > 0,
record.record_id.type != DltRecordTypeEnum.DLTRECORDTYPE_UNDEFINED,
len(record.record_id.record_uuid.uuid) > 0,
#record.operation != DltRecordOperationEnum.DLTRECORDOPERATION_UNDEFINED,
len(record.data_json) > 0,
])
def dlt_record_get(
dltgateway_client : DltGatewayClient, perf_point : PerfPoint,
domain_uuid : str, record_type : DltRecordTypeEnum, record_uuid : str
) -> Dict:
dlt_rec_id = DltRecordId()
dlt_rec_id.domain_uuid.uuid = domain_uuid # pylint: disable=no-member
dlt_rec_id.type = record_type
dlt_rec_id.record_uuid.uuid = record_uuid # pylint: disable=no-member
perf_point.set_time_requested(time.time())
reply = dltgateway_client.GetFromDlt(dlt_rec_id)
perf_point.set_time_replied(time.time())
if dlt_record_found(reply): return json.loads(reply.data_json)
MSG = 'DltRecord({:s}/{:s}/{:s}) not found'
str_record_type = DltRecordTypeEnum.Name(record_type)
msg = MSG.format(str(domain_uuid), str_record_type, str(record_uuid))
raise Exception(msg) # pylint: disable=broad-exception-raised
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import enum
from common.proto.context_pb2 import EventTypeEnum
from common.proto.dlt_gateway_pb2 import DltRecordTypeEnum
class ActionEnum(enum.Enum):
CREATE = 'create'
GET = 'get'
UPDATE = 'update'
DELETE = 'delete'
class RecordTypeEnum(enum.Enum):
DEVICE = 'device'
LINK = 'link'
SERVICE = 'service'
SLICE = 'slice'
CONTEXT_EVENT_TYPE_TO_ACTION = {
EventTypeEnum.EVENTTYPE_CREATE : ActionEnum.CREATE,
EventTypeEnum.EVENTTYPE_UPDATE : ActionEnum.UPDATE,
EventTypeEnum.EVENTTYPE_REMOVE : ActionEnum.DELETE,
}
RECORD_TYPE_TO_ENUM = {
DltRecordTypeEnum.DLTRECORDTYPE_DEVICE : RecordTypeEnum.DEVICE,
DltRecordTypeEnum.DLTRECORDTYPE_LINK : RecordTypeEnum.LINK,
DltRecordTypeEnum.DLTRECORDTYPE_SERVICE : RecordTypeEnum.SERVICE,
DltRecordTypeEnum.DLTRECORDTYPE_SLICE : RecordTypeEnum.SLICE,
}
# 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 csv, prettytable
from typing import Dict, Tuple
from .Enums import ActionEnum, RecordTypeEnum
from .PerfPoint import PerfPoint
class PerfData:
def __init__(self) -> None:
self._type_count : Dict[Tuple[ActionEnum, RecordTypeEnum], int] = dict()
self._points : Dict[Tuple[ActionEnum, RecordTypeEnum, str], PerfPoint] = dict()
def get(self, action : ActionEnum, record_type : RecordTypeEnum, record_uuid : str) -> PerfPoint:
point_key = (action, record_type, record_uuid)
point = self._points.get(point_key)
if point is None:
point = PerfPoint(action, record_type, record_uuid)
self._points[point_key] = point
type_key = (action, record_type)
self._type_count[type_key] = self._type_count.get(type_key, 0) + 1
return point
def to_csv(self, filepath : str) -> None:
point : PerfPoint = self._points.values()[0]
keys = point.to_dict().keys()
with open(filepath, 'w', newline='', encoding='UTF-8') as csv_file:
dict_writer = csv.DictWriter(csv_file, keys)
dict_writer.writeheader()
dict_writer.writerows(self._points)
def stats_to_str(self) -> str:
field_names = ['action', 'record_type', 'count']
pt_stats = prettytable.PrettyTable(field_names=field_names, sortby='count', reversesort=True)
for f in field_names[0:2]: pt_stats.align[f] = 'l'
for f in field_names[2:3]: pt_stats.align[f] = 'r'
for (action,record_type),count in self._type_count.items():
pt_stats.add_row([action.value, record_type.value, count])
return pt_stats.get_string()
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Dict, Optional
from .Enums import ActionEnum, RecordTypeEnum
class PerfPoint:
def __init__(self, action : ActionEnum, record_type : RecordTypeEnum, record_uuid : str) -> None:
self.action : ActionEnum = action
self.record_type : RecordTypeEnum = record_type
self.record_uuid : str = record_uuid
self.size_bytes : Optional[int ] = None
self.num_endpoints : Optional[int ] = None
self.num_config_rules : Optional[int ] = None
self.num_constraints : Optional[int ] = None
self.num_sub_services : Optional[int ] = None
self.num_sub_slices : Optional[int ] = None
self.time_requested : Optional[float] = None
self.time_replied : Optional[float] = None
self.time_notified : Optional[float] = None
def set_size_bytes (self, size_bytes : int) -> None: self.size_bytes = size_bytes
def set_num_endpoints (self, num_endpoints : int) -> None: self.num_endpoints = num_endpoints
def set_num_config_rules(self, num_config_rules : int) -> None: self.num_config_rules = num_config_rules
def set_num_constraints (self, num_constraints : int) -> None: self.num_constraints = num_constraints
def set_num_sub_services(self, num_sub_services : int) -> None: self.num_sub_services = num_sub_services
def set_num_sub_slices (self, num_sub_slices : int) -> None: self.num_sub_slices = num_sub_slices
def set_time_requested(self, timestamp : float) -> None: self.time_requested = timestamp
def set_time_replied (self, timestamp : float) -> None: self.time_replied = timestamp
def set_time_notified (self, timestamp : float) -> None: self.time_notified = timestamp
def to_dict(self) -> Dict:
return {
'action' : self.action.value,
'record_type' : self.record_type.value,
'record_uuid' : self.record_uuid,
'size_bytes' : self.size_bytes,
'num_endpoints' : self.num_endpoints,
'num_config_rules': self.num_config_rules,
'num_constraints' : self.num_constraints,
'num_sub_services': self.num_sub_services,
'num_sub_slices' : self.num_sub_slices,
'time_requested' : self.time_requested,
'time_replied' : self.time_replied,
'time_notified' : self.time_notified,
}
# 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 random, uuid
from typing import Dict, List, Optional, Set
from common.proto.context_pb2 import (
Device, EndPoint, Link, Service, ServiceStatusEnum, ServiceTypeEnum, Slice, SliceStatusEnum
)
from common.tools.grpc.Tools import grpc_message_to_json
from common.tools.object_factory.ConfigRule import json_config_rule_set
from common.tools.object_factory.Constraint import json_constraint_custom
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Device import (
DEVICE_DISABLED, DEVICE_EMU_DRIVERS, DEVICE_EMUPR_TYPE, json_device, json_device_id
)
from common.tools.object_factory.EndPoint import json_endpoint, json_endpoint_id
from common.tools.object_factory.Link import json_link
from common.tools.object_factory.Service import json_service
from common.tools.object_factory.Slice import json_slice
from common.tools.object_factory.Topology import json_topology_id
from .Settings import (
CONFIG_RULES_MIN_ENTRIES, CONFIG_RULES_MAX_ENTRIES, CONSTRAINTS_MIN_ENTRIES, CONSTRAINTS_MAX_ENTRIES,
DEVICE_MAX_ENDPOINTS, DEVICE_MIN_ENDPOINTS, DICT_MAX_ENTRIES, DICT_MIN_ENTRIES, PATH_MAX_PARTS, PATH_MIN_PARTS,
SLICE_MAX_SUBSERVICES, SLICE_MAX_SUBSLICES, SLICE_MIN_SUBSERVICES, SLICE_MIN_SUBSLICES,
WORD_ALPHABET, WORD_MAX_LENGTH, WORD_MIN_LENGTH,
)
def random_word(length : Optional[int] = None, alphabet : Optional[str] = None) -> str:
if alphabet is None: alphabet = WORD_ALPHABET
if length is None: length = int(random.uniform(WORD_MIN_LENGTH, WORD_MAX_LENGTH))
word = []
remaining_length = length
while remaining_length > 0:
part_length = min(len(alphabet), remaining_length)
word.extend(random.sample(alphabet, part_length))
remaining_length = length - len(word)
return ''.join(word)
def random_path(
num_parts : Optional[int] = None, part_length : Optional[int] = None, alphabet : Optional[str] = None,
separator : str = '/'
) -> str:
if num_parts is None: num_parts = int(random.uniform(PATH_MIN_PARTS, PATH_MAX_PARTS))
return separator.join([random_word(length=part_length, alphabet=alphabet) for _ in range(num_parts)])
def random_dict(
num_entries : Optional[int] = None, key_length : Optional[str] = None, value_length : Optional[str] = None
) -> Dict:
if num_entries is None: num_entries = int(random.uniform(DICT_MIN_ENTRIES, DICT_MAX_ENTRIES))
return {random_word(length=key_length) : random_word(length=value_length) for _ in range(num_entries)}
def random_config_rule() -> Dict:
resource_key = random_path()
resource_value = random_dict()
return json_config_rule_set(resource_key, resource_value)
def random_config_rules(count : Optional[int] = None) -> List:
if count is None: count = int(random.uniform(CONFIG_RULES_MIN_ENTRIES, CONFIG_RULES_MAX_ENTRIES))
return [random_config_rule() for _ in range(count)]
def random_constraint() -> Dict:
constraint_type = random_path()
constraint_value = random_dict()
return json_constraint_custom(constraint_type, constraint_value)
def random_constraints(count : Optional[int] = None) -> List:
if count is None: count = int(random.uniform(CONSTRAINTS_MIN_ENTRIES, CONSTRAINTS_MAX_ENTRIES))
return [random_constraint() for _ in range(count)]
def random_endpoint(device_uuid : str) -> Dict:
topology_id = json_topology_id(str(uuid.uuid4()), context_id=json_context_id(str(uuid.uuid4())))
return json_endpoint(json_device_id(device_uuid), str(uuid.uuid4()), 'internal/copper', topology_id=topology_id)
def random_endpoints(device_uuid : str, count : Optional[int] = None) -> List:
if count is None: count = int(random.uniform(DEVICE_MIN_ENDPOINTS, DEVICE_MAX_ENDPOINTS))
return [random_endpoint(device_uuid) for _ in range(count)]
def random_endpoint_id(device_uuid : str) -> Dict:
topology_id = json_topology_id(str(uuid.uuid4()), context_id=json_context_id(str(uuid.uuid4())))
return json_endpoint_id(json_device_id(device_uuid), str(uuid.uuid4()), topology_id=topology_id)
def random_endpoint_ids(device_uuid : str, count : Optional[int] = None) -> List:
if count is None: count = int(random.uniform(DEVICE_MIN_ENDPOINTS, DEVICE_MAX_ENDPOINTS))
return [random_endpoint(device_uuid) for _ in range(count)]
def get_random_endpoint(
devices : Dict[str, Device], excluded_device_uuids : Set[str] = set()
) -> EndPoint:
device_uuids = set(devices.keys())
device_uuids = list(device_uuids.difference(excluded_device_uuids))
device_uuid = random.choice(device_uuids)
device = devices[device_uuid]
return random.choice(device.device_endpoints)
def get_random_endpoints(
devices : Dict[str, Device], count : Optional[int] = None
) -> Optional[List[EndPoint]]:
if len(devices) < count: return None # Too few devices
endpoints = list()
used_device_uuids = set()
for _ in range(count):
endpoint = get_random_endpoint(devices, used_device_uuids)
used_device_uuids.add(endpoint.endpoint_id.device_id.device_uuid.uuid)
endpoints.append(endpoint)
return endpoints
def random_device(device_uuid : Optional[str] = None) -> Device:
if device_uuid is None: device_uuid = str(uuid.uuid4())
device_type = DEVICE_EMUPR_TYPE
device_op_stat = DEVICE_DISABLED
drivers = DEVICE_EMU_DRIVERS
endpoints = random_endpoints(device_uuid)
config_rules = random_config_rules()
return Device(**json_device(
device_uuid, device_type, device_op_stat, endpoints=endpoints, config_rules=config_rules, drivers=drivers
))
def random_link(
devices : Dict[str, Device], link_uuid : Optional[str] = None
) -> Optional[Link]:
if link_uuid is None: link_uuid = str(uuid.uuid4())
endpoints = get_random_endpoints(devices, count=2)
if endpoints is None: return None
endpoint_ids = [grpc_message_to_json(endpoint.endpoint_id) for endpoint in endpoints]
return Link(**json_link(link_uuid, endpoint_ids))
def random_service(
devices : Dict[str, Device], service_uuid : Optional[str] = None
) -> Optional[Service]:
if service_uuid is None: service_uuid = str(uuid.uuid4())
context_id = json_context_id(str(uuid.uuid4()))
service_type = ServiceTypeEnum.SERVICETYPE_L2NM
service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED
endpoints = get_random_endpoints(devices, count=2)
if endpoints is None: return None
endpoint_ids = [grpc_message_to_json(endpoint.endpoint_id) for endpoint in endpoints]
constraints = random_constraints(count=3)
config_rules = random_config_rules(count=5)
return Service(**json_service(
service_uuid, service_type, context_id=context_id, status=service_status,
endpoint_ids=endpoint_ids, constraints=constraints, config_rules=config_rules))
def random_slice(
devices : Dict[str, Device], services : Dict[str, Service], slices : Dict[str, Service],
slice_uuid : Optional[str] = None
) -> Optional[Slice]:
if slice_uuid is None: slice_uuid = str(uuid.uuid4())
context_id = json_context_id(str(uuid.uuid4()))
slice_status = SliceStatusEnum.SLICESTATUS_PLANNED
endpoints = get_random_endpoints(devices, count=2)
if endpoints is None: return None
endpoint_ids = [grpc_message_to_json(endpoint.endpoint_id) for endpoint in endpoints]
constraints = random_constraints(count=3)
config_rules = random_config_rules(count=5)
num_sub_services = int(random.uniform(SLICE_MIN_SUBSERVICES, SLICE_MAX_SUBSERVICES))
num_sub_services = min(num_sub_services, len(services))
sub_services = random.sample(list(services.values()), num_sub_services) if num_sub_services > 0 else []
sub_service_ids = [grpc_message_to_json(sub_service.service_id) for sub_service in sub_services]
num_sub_slices = int(random.uniform(SLICE_MIN_SUBSLICES, SLICE_MAX_SUBSLICES))
num_sub_slices = min(num_sub_slices, len(slices))
sub_slices = random.sample(list(slices.values()), num_sub_slices) if num_sub_slices > 0 else []
sub_slice_ids = [grpc_message_to_json(sub_slice.slice_id) for sub_slice in sub_slices]
owner = {'owner_uuid': {'uuid': str(uuid.uuid4())}, 'owner_string': random_word()}
return Slice(**json_slice(
slice_uuid, context_id=context_id, status=slice_status,
endpoint_ids=endpoint_ids, constraints=constraints, config_rules=config_rules,
service_ids=sub_service_ids, subslice_ids=sub_slice_ids, owner=owner))
# 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.
WORD_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
WORD_MIN_LENGTH = 5
WORD_MAX_LENGTH = 100
PATH_MIN_PARTS = 2
PATH_MAX_PARTS = 15
DICT_MIN_ENTRIES = 1
DICT_MAX_ENTRIES = 10
DEVICE_MIN_ENDPOINTS = 2
DEVICE_MAX_ENDPOINTS = 48
CONFIG_RULES_MIN_ENTRIES = 3
CONFIG_RULES_MAX_ENTRIES = 100
CONSTRAINTS_MIN_ENTRIES = 3
CONSTRAINTS_MAX_ENTRIES = 10
SLICE_MIN_SUBSERVICES = 0
SLICE_MAX_SUBSERVICES = 3
SLICE_MIN_SUBSLICES = 0
SLICE_MAX_SUBSLICES = 3
# 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, random
from typing import Dict
from common.proto.context_pb2 import Device, Link, Service, Slice
from dlt.connector.client.DltGatewayClient import DltGatewayClient
from .Enums import ActionEnum, RecordTypeEnum
from .Dlt import (
DLT_OPERATION_CREATE, DLT_OPERATION_DELETE, DLT_OPERATION_UPDATE,
DLT_RECORD_TYPE_DEVICE, DLT_RECORD_TYPE_LINK, DLT_RECORD_TYPE_SERVICE, DLT_RECORD_TYPE_SLICE,
dlt_record_get, dlt_record_set)
from .PerfData import PerfData
from .Random import random_device, random_link, random_service, random_slice
LOGGER = logging.getLogger(__name__)
class PlayGround:
def __init__(self, dltgateway_client : DltGatewayClient, domain_uuid : str) -> None:
self._dltgateway_client = dltgateway_client
self._domain_uuid = domain_uuid
self._perf_data = PerfData()
self._devices : Dict[str, Device ] = dict()
self._links : Dict[str, Link ] = dict()
self._services : Dict[str, Service] = dict()
self._slices : Dict[str, Slice ] = dict()
@property
def perf_data(self): return self._perf_data
def run_random_operation(self) -> bool:
method = None
while method is None:
action = random.choice(list(ActionEnum.__members__.values()))
record_type = random.choice(list(RecordTypeEnum.__members__.values()))
method_name = '{:s}_{:s}'.format(action.value, record_type.value)
method = getattr(self, method_name, None)
LOGGER.info('method_name: {:s}'.format(str(method_name)))
return method()
def create_device(self) -> bool:
device = random_device()
if device is None: return False
device_uuid = device.device_id.device_uuid.uuid # pylint: disable=no-member
self._devices[device_uuid] = device
perf_point = self._perf_data.get(ActionEnum.CREATE, RecordTypeEnum.DEVICE, device_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_CREATE, self._domain_uuid, device)
return True
def get_device(self) -> bool:
if len(self._devices) == 0: return False
device_uuid = random.choice(list(self._devices.keys()))
perf_point = self._perf_data.get(ActionEnum.GET, RecordTypeEnum.DEVICE, device_uuid)
data = dlt_record_get(
self._dltgateway_client, perf_point, self._domain_uuid, DLT_RECORD_TYPE_DEVICE, device_uuid)
self._devices[device_uuid] = Device(**data)
return True
def update_device(self) -> bool:
if len(self._devices) == 0: return False
device_uuid = random.choice(list(self._devices.keys()))
device = random_device(device_uuid=device_uuid)
if device is None: return False
perf_point = self._perf_data.get(ActionEnum.UPDATE, RecordTypeEnum.DEVICE, device_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_UPDATE, self._domain_uuid, device)
self._devices[device_uuid] = device
return True
def delete_device(self) -> bool:
if len(self._devices) == 0: return False
device_uuid = random.choice(list(self._devices.keys()))
device = self._devices.pop(device_uuid, None)
if device is None: return False
perf_point = self._perf_data.get(ActionEnum.DELETE, RecordTypeEnum.DEVICE, device_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_DELETE, self._domain_uuid, device)
return True
def create_link(self) -> bool:
link = random_link(self._devices)
if link is None: return False
link_uuid = link.link_id.link_uuid.uuid # pylint: disable=no-member
perf_point = self._perf_data.get(ActionEnum.CREATE, RecordTypeEnum.LINK, link_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_CREATE, self._domain_uuid, link)
self._links[link_uuid] = link
return True
def get_link(self) -> bool:
if len(self._links) == 0: return False
link_uuid = random.choice(list(self._links.keys()))
perf_point = self._perf_data.get(ActionEnum.GET, RecordTypeEnum.LINK, link_uuid)
data = dlt_record_get(
self._dltgateway_client, perf_point, self._domain_uuid, DLT_RECORD_TYPE_LINK, link_uuid)
self._links[link_uuid] = Link(**data)
return True
def update_link(self) -> bool:
if len(self._links) == 0: return False
link_uuid = random.choice(list(self._links.keys()))
link = random_link(self._devices, link_uuid=link_uuid)
if link is None: return False
perf_point = self._perf_data.get(ActionEnum.UPDATE, RecordTypeEnum.LINK, link_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_UPDATE, self._domain_uuid, link)
self._links[link_uuid] = link
return True
def delete_link(self) -> bool:
if len(self._links) == 0: return False
link_uuid = random.choice(list(self._links.keys()))
link = self._links.pop(link_uuid, None)
if link is None: return False
perf_point = self._perf_data.get(ActionEnum.DELETE, RecordTypeEnum.LINK, link_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_DELETE, self._domain_uuid, link)
return True
def create_service(self) -> bool:
service = random_service(self._devices)
if service is None: return False
service_uuid = service.service_id.service_uuid.uuid # pylint: disable=no-member
perf_point = self._perf_data.get(ActionEnum.CREATE, RecordTypeEnum.SERVICE, service_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_CREATE, self._domain_uuid, service)
self._services[service_uuid] = service
return True
def get_service(self) -> bool:
if len(self._services) == 0: return False
service_uuid = random.choice(list(self._services.keys()))
perf_point = self._perf_data.get(ActionEnum.GET, RecordTypeEnum.SERVICE, service_uuid)
data = dlt_record_get(
self._dltgateway_client, perf_point, self._domain_uuid, DLT_RECORD_TYPE_SERVICE, service_uuid)
self._services[service_uuid] = Service(**data)
return True
def update_service(self) -> bool:
if len(self._services) == 0: return False
service_uuid = random.choice(list(self._services.keys()))
service = random_service(self._devices, service_uuid=service_uuid)
if service is None: return False
perf_point = self._perf_data.get(ActionEnum.UPDATE, RecordTypeEnum.SERVICE, service_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_UPDATE, self._domain_uuid, service)
self._services[service_uuid] = service
return True
def delete_service(self) -> bool:
if len(self._services) == 0: return False
service_uuid = random.choice(list(self._services.keys()))
service = self._services.pop(service_uuid, None)
if service is None: return False
perf_point = self._perf_data.get(ActionEnum.DELETE, RecordTypeEnum.SERVICE, service_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_DELETE, self._domain_uuid, service)
return True
def create_slice(self) -> bool:
slice_ = random_slice(self._devices, self._services, self._slices)
if slice_ is None: return False
slice_uuid = slice_.slice_id.slice_uuid.uuid # pylint: disable=no-member
perf_point = self._perf_data.get(ActionEnum.CREATE, RecordTypeEnum.SLICE, slice_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_CREATE, self._domain_uuid, slice_)
self._slices[slice_uuid] = slice_
return True
def get_slice(self) -> bool:
if len(self._slices) == 0: return False
slice_uuid = random.choice(list(self._slices.keys()))
perf_point = self._perf_data.get(ActionEnum.GET, RecordTypeEnum.SLICE, slice_uuid)
data = dlt_record_get(
self._dltgateway_client, perf_point, self._domain_uuid, DLT_RECORD_TYPE_SLICE, slice_uuid)
self._slices[slice_uuid] = Slice(**data)
return True
def update_slice(self) -> bool:
if len(self._slices) == 0: return False
slice_uuid = random.choice(list(self._slices.keys()))
slice_ = random_slice(self._devices, self._services, self._slices, slice_uuid=slice_uuid)
if slice_ is None: return False
perf_point = self._perf_data.get(ActionEnum.UPDATE, RecordTypeEnum.SLICE, slice_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_UPDATE, self._domain_uuid, slice_)
self._slices[slice_uuid] = slice_
return True
def delete_slice(self) -> bool:
if len(self._slices) == 0: return False
slice_uuid = random.choice(list(self._slices.keys()))
slice_ = self._slices.pop(slice_uuid, None)
if slice_ is None: return False
perf_point = self._perf_data.get(ActionEnum.DELETE, RecordTypeEnum.SLICE, slice_uuid)
dlt_record_set(self._dltgateway_client, perf_point, DLT_OPERATION_DELETE, self._domain_uuid, slice_)
return True
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment