diff --git a/src/tests/tools/load_gen/Constants.py b/src/tests/tools/load_gen/Constants.py new file mode 100644 index 0000000000000000000000000000000000000000..28c1c65be3c03a073abeefc84bdb3731b1eaf581 --- /dev/null +++ b/src/tests/tools/load_gen/Constants.py @@ -0,0 +1,17 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# 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. + +SERVICE_TYPE_L2NM = 'l2nm' +SERVICE_TYPE_L3NM = 'l3nm' +SERVICE_TYPE_TAPI = 'tapi' diff --git a/src/tests/tools/load_gen/Parameters.py b/src/tests/tools/load_gen/Parameters.py index f3fe742df51b4cb0d89d84a32a6b808949ce2479..fa2ac01f3b7a5ffd79f515962a54945402405b60 100644 --- a/src/tests/tools/load_gen/Parameters.py +++ b/src/tests/tools/load_gen/Parameters.py @@ -12,14 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional +from typing import List, Optional class Parameters: def __init__( - self, num_services : int, offered_load : Optional[float] = None, inter_arrival_time : Optional[float] = None, - holding_time : Optional[float] = None, dry_mode : bool = False + self, num_services : int, service_types : List[str], offered_load : Optional[float] = None, + inter_arrival_time : Optional[float] = None, holding_time : Optional[float] = None, + dry_mode : bool = False ) -> None: self._num_services = num_services + self._service_types = service_types self._offered_load = offered_load self._inter_arrival_time = inter_arrival_time self._holding_time = holding_time @@ -35,6 +37,9 @@ class Parameters: @property def num_services(self): return self._num_services + @property + def service_types(self): return self._service_types + @property def offered_load(self): return self._offered_load diff --git a/src/tests/tools/load_gen/ServiceGenerator.py b/src/tests/tools/load_gen/ServiceGenerator.py index d21c345a315a8c3faff02647b80f3806a10b316c..6322251718e9e1b55de50a2c5e524a10edf7a6d0 100644 --- a/src/tests/tools/load_gen/ServiceGenerator.py +++ b/src/tests/tools/load_gen/ServiceGenerator.py @@ -19,17 +19,28 @@ from common.tools.object_factory.Constraint import json_constraint_custom from common.tools.object_factory.ConfigRule import json_config_rule_set from common.tools.object_factory.Device import json_device_id from common.tools.object_factory.EndPoint import json_endpoint_id -from common.tools.object_factory.Service import json_service_l2nm_planned +from common.tools.object_factory.Service import ( + json_service_l2nm_planned, json_service_l3nm_planned, json_service_tapi_planned) from context.client.ContextClient import ContextClient +from .Constants import SERVICE_TYPE_L2NM, SERVICE_TYPE_L3NM, SERVICE_TYPE_TAPI +from .Parameters import Parameters LOGGER = logging.getLogger(__name__) +ENDPOINT_COMPATIBILITY = { + 'PHOTONIC_MEDIA:FLEX:G_6_25GHZ:INPUT': 'PHOTONIC_MEDIA:FLEX:G_6_25GHZ:OUTPUT', + 'PHOTONIC_MEDIA:DWDM:G_50GHZ:INPUT' : 'PHOTONIC_MEDIA:DWDM:G_50GHZ:OUTPUT', +} + class ServiceGenerator: - def __init__(self) -> None: + def __init__(self, parameters : Parameters) -> None: + self._parameters = parameters self._lock = threading.Lock() self._num_services = 0 self._available_device_endpoints : Dict[str, Set[str]] = dict() self._used_device_endpoints : Dict[str, Dict[str, str]] = dict() + self._endpoint_ids_to_types : Dict[Tuple[str, str], str] = dict() + self._endpoint_types_to_ids : Dict[str, Set[Tuple[str, str]]] = dict() def initialize(self) -> None: with self._lock: @@ -44,7 +55,10 @@ class ServiceGenerator: _endpoints = self._available_device_endpoints.setdefault(device_uuid, set()) for endpoint in device.device_endpoints: endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid + endpoint_type = endpoint.endpoint_type _endpoints.add(endpoint_uuid) + self._endpoint_ids_to_types.setdefault((device_uuid, endpoint_uuid), endpoint_type) + self._endpoint_types_to_ids.setdefault(endpoint_type, set()).add((device_uuid, endpoint_uuid)) links = context_client.ListLinks(Empty()) for link in links.links: @@ -55,6 +69,15 @@ class ServiceGenerator: _endpoints.discard(endpoint_uuid) if len(_endpoints) == 0: self._available_device_endpoints.pop(device_uuid, None) + endpoint_type = self._endpoint_ids_to_types.pop((device_uuid, endpoint_uuid), None) + if endpoint_type is None: continue + + if endpoint_type not in self._endpoint_types_to_ids: continue + endpoints_for_type = self._endpoint_types_to_ids[endpoint_type] + endpoint_key = (device_uuid, endpoint_uuid) + if endpoint_key not in endpoints_for_type: continue + endpoints_for_type.discard(endpoint_key) + @property def num_services_generated(self): return self._num_services @@ -68,15 +91,42 @@ class ServiceGenerator: LOGGER.info('[dump_state] used_device_endpoints = {:s}'.format(json.dumps(self._used_device_endpoints))) def _use_device_endpoint( - self, service_uuid : str, exclude_device_uuids : Set[str] = set() + self, service_uuid : str, endpoint_types : Optional[Set[str]] = None, exclude_device_uuids : Set[str] = set() ) -> Optional[Tuple[str, str]]: with self._lock: - elegible_device_endpoints = { - device_uuid:device_endpoint_uuids - for device_uuid,device_endpoint_uuids in self._available_device_endpoints.items() - if device_uuid not in exclude_device_uuids and len(device_endpoint_uuids) > 0 - } - if len(elegible_device_endpoints) == 0: return None + compatible_endpoints : Set[Tuple[str, str]] = set() + elegible_device_endpoints : Dict[str, Set[str]] = {} + + if endpoint_types is None: + # allow all + elegible_device_endpoints : Dict[str, Set[str]] = { + device_uuid:device_endpoint_uuids + for device_uuid,device_endpoint_uuids in self._available_device_endpoints.items() + if device_uuid not in exclude_device_uuids and len(device_endpoint_uuids) > 0 + } + else: + # allow only compatible endpoints + for endpoint_type in endpoint_types: + if endpoint_type not in self._endpoint_types_to_ids: continue + compatible_endpoints.update(self._endpoint_types_to_ids[endpoint_type]) + + for device_uuid,device_endpoint_uuids in self._available_device_endpoints.items(): + if device_uuid in exclude_device_uuids or len(device_endpoint_uuids) == 0: continue + for endpoint_uuid in device_endpoint_uuids: + endpoint_key = (device_uuid,endpoint_uuid) + if endpoint_key not in compatible_endpoints: continue + elegible_device_endpoints.setdefault(device_uuid, set()).add(endpoint_uuid) + + if len(elegible_device_endpoints) == 0: + LOGGER.warning(' '.join([ + '>> No endpoint is available:', + 'endpoint_types={:s}'.format(str(endpoint_types)), + 'exclude_device_uuids={:s}'.format(str(exclude_device_uuids)), + 'self._endpoint_types_to_ids={:s}'.format(str(self._endpoint_types_to_ids)), + 'self._available_device_endpoints={:s}'.format(str(self._available_device_endpoints)), + 'compatible_endpoints={:s}'.format(str(compatible_endpoints)), + ])) + return None device_uuid = random.choice(list(elegible_device_endpoints.keys())) device_endpoint_uuids = elegible_device_endpoints.get(device_uuid) endpoint_uuid = random.choice(list(device_endpoint_uuids)) @@ -95,47 +145,124 @@ class ServiceGenerator: num_service = self._num_services #service_uuid = str(uuid.uuid4()) service_uuid = 'svc_{:d}'.format(num_service) - src = self._use_device_endpoint(service_uuid) - if src is None: return None + + # choose service type + service_type = random.choice(self._parameters.service_types) + + # choose source endpoint + src_endpoint_types = set(ENDPOINT_COMPATIBILITY.keys()) if service_type in {SERVICE_TYPE_TAPI} else None + src = self._use_device_endpoint(service_uuid, endpoint_types=src_endpoint_types) + if src is None: + LOGGER.warning('>> No source endpoint is available') + return None src_device_uuid,src_endpoint_uuid = src - dst = self._use_device_endpoint(service_uuid, exclude_device_uuids={src_device_uuid}) + + # identify compatible destination endpoint types + src_endpoint_type = self._endpoint_ids_to_types.get((src_device_uuid,src_endpoint_uuid)) + dst_endpoint_type = ENDPOINT_COMPATIBILITY.get(src_endpoint_type) + + # identify expluded destination devices + exclude_device_uuids = {} if service_type in {SERVICE_TYPE_TAPI} else {src_device_uuid} + + # choose feasible destination endpoint + dst = self._use_device_endpoint( + service_uuid, endpoint_types={dst_endpoint_type}, exclude_device_uuids=exclude_device_uuids) + + # if destination endpoint not found, release source, and terminate current service generation if dst is None: + LOGGER.warning('>> No destination endpoint is available') self._release_device_endpoint(src_device_uuid, src_endpoint_uuid) return None + + # compose endpoints dst_device_uuid,dst_endpoint_uuid = dst endpoint_ids = [ json_endpoint_id(json_device_id(src_device_uuid), src_endpoint_uuid), json_endpoint_id(json_device_id(dst_device_uuid), dst_endpoint_uuid), ] - constraints = [ - json_constraint_custom('bandwidth[gbps]', '10.0'), - json_constraint_custom('latency[ms]', '20.0'), - ] - vlan_id = num_service % 1000 - circuit_id = '{:03d}'.format(vlan_id) - src_router_id = '.'.join([src_device_uuid.replace('R', ''), '0'] + src_endpoint_uuid.split('/')) - dst_router_id = '.'.join([dst_device_uuid.replace('R', ''), '0'] + dst_endpoint_uuid.split('/')) - config_rules = [ - json_config_rule_set('/settings', { - 'mtu': 1512 - }), - json_config_rule_set('/device[{:s}]/endpoint[{:s}]/settings'.format(src_device_uuid, src_endpoint_uuid), { - 'router_id': src_router_id, - 'sub_interface_index': vlan_id, - 'vlan_id': vlan_id, - 'remote_router': dst_router_id, - 'circuit_id': circuit_id, - }), - json_config_rule_set('/device[{:s}]/endpoint[{:s}]/settings'.format(dst_device_uuid, dst_endpoint_uuid), { - 'router_id': dst_router_id, - 'sub_interface_index': vlan_id, - 'vlan_id': vlan_id, - 'remote_router': src_router_id, - 'circuit_id': circuit_id, - }), - ] - return json_service_l2nm_planned( - service_uuid, endpoint_ids=endpoint_ids, constraints=constraints, config_rules=config_rules) + + if service_type == SERVICE_TYPE_L2NM: + constraints = [ + json_constraint_custom('bandwidth[gbps]', '10.0'), + json_constraint_custom('latency[ms]', '20.0'), + ] + vlan_id = num_service % 1000 + circuit_id = '{:03d}'.format(vlan_id) + src_router_id = '10.0.0.{:d}'.format(int(src_device_uuid.replace('R', ''))) + dst_router_id = '10.0.0.{:d}'.format(int(src_device_uuid.replace('R', ''))) + config_rules = [ + json_config_rule_set('/settings', { + 'mtu': 1512 + }), + json_config_rule_set('/device[{:s}]/endpoint[{:s}]/settings'.format(src_device_uuid, src_endpoint_uuid), { + 'router_id': src_router_id, + 'sub_interface_index': vlan_id, + 'vlan_id': vlan_id, + 'remote_router': dst_router_id, + 'circuit_id': circuit_id, + }), + json_config_rule_set('/device[{:s}]/endpoint[{:s}]/settings'.format(dst_device_uuid, dst_endpoint_uuid), { + 'router_id': dst_router_id, + 'sub_interface_index': vlan_id, + 'vlan_id': vlan_id, + 'remote_router': src_router_id, + 'circuit_id': circuit_id, + }), + ] + return json_service_l2nm_planned( + service_uuid, endpoint_ids=endpoint_ids, constraints=constraints, config_rules=config_rules) + + elif service_type == SERVICE_TYPE_L3NM: + constraints = [ + json_constraint_custom('bandwidth[gbps]', '10.0'), + json_constraint_custom('latency[ms]', '20.0'), + ] + vlan_id = num_service % 1000 + bgp_as = 60000 + (num_service % 10000) + bgp_route_target = '{:5d}:{:03d}'.format(bgp_as, 333) + route_distinguisher = '{:5d}:{:03d}'.format(bgp_as, vlan_id) + src_router_id = '10.0.0.{:d}'.format(int(src_device_uuid.replace('R', ''))) + dst_router_id = '10.0.0.{:d}'.format(int(src_device_uuid.replace('R', ''))) + src_address_ip = '.'.join([src_device_uuid.replace('R', ''), '0'] + src_endpoint_uuid.split('/')) + dst_address_ip = '.'.join([dst_device_uuid.replace('R', ''), '0'] + dst_endpoint_uuid.split('/')) + config_rules = [ + json_config_rule_set('/settings', { + 'mtu' : 1512, + 'bgp_as' : bgp_as, + 'bgp_route_target': bgp_route_target, + }), + json_config_rule_set('/device[{:s}]/endpoint[{:s}]/settings'.format(src_device_uuid, src_endpoint_uuid), { + 'router_id' : src_router_id, + 'route_distinguisher': route_distinguisher, + 'sub_interface_index': vlan_id, + 'vlan_id' : vlan_id, + 'address_ip' : src_address_ip, + 'address_prefix' : 16, + }), + json_config_rule_set('/device[{:s}]/endpoint[{:s}]/settings'.format(dst_device_uuid, dst_endpoint_uuid), { + 'router_id' : dst_router_id, + 'route_distinguisher': route_distinguisher, + 'sub_interface_index': vlan_id, + 'vlan_id' : vlan_id, + 'address_ip' : dst_address_ip, + 'address_prefix' : 16, + }), + ] + return json_service_l3nm_planned( + service_uuid, endpoint_ids=endpoint_ids, constraints=constraints, config_rules=config_rules) + + elif service_type == SERVICE_TYPE_TAPI: + config_rules = [ + json_config_rule_set('/settings', { + 'capacity_value' : 50.0, + 'capacity_unit' : 'GHz', + 'layer_proto_name': 'PHOTONIC_MEDIA', + 'layer_proto_qual': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC', + 'direction' : 'UNIDIRECTIONAL', + }), + ] + return json_service_tapi_planned( + service_uuid, endpoint_ids=endpoint_ids, constraints=[], config_rules=config_rules) def release_service(self, json_service : Dict) -> None: for endpoint_id in json_service['service_endpoint_ids']: diff --git a/src/tests/tools/load_gen/__main__.py b/src/tests/tools/load_gen/__main__.py index 7a81dbcba7cd42b703ecb078751db6d0fa705750..28ffb2a9a6c965a308f7ecc9856b901c6bfc652e 100644 --- a/src/tests/tools/load_gen/__main__.py +++ b/src/tests/tools/load_gen/__main__.py @@ -13,6 +13,7 @@ # limitations under the License. import logging, sys +from .Constants import SERVICE_TYPE_L2NM, SERVICE_TYPE_L3NM, SERVICE_TYPE_TAPI from .Parameters import Parameters from .ServiceGenerator import ServiceGenerator from .ServiceScheduler import ServiceScheduler @@ -23,14 +24,19 @@ LOGGER = logging.getLogger(__name__) def main(): LOGGER.info('Starting...') parameters = Parameters( - num_services = 100, - offered_load = 50, - holding_time = 10, - dry_mode = False, # in dry mode, no request is sent to TeraFlowSDN + num_services = 100, + service_types = [ + #SERVICE_TYPE_L2NM, + #SERVICE_TYPE_L3NM, + SERVICE_TYPE_TAPI, + ], + offered_load = 50, + holding_time = 10, + dry_mode = False, # in dry mode, no request is sent to TeraFlowSDN ) LOGGER.info('Initializing Generator...') - service_generator = ServiceGenerator() + service_generator = ServiceGenerator(parameters) service_generator.initialize() LOGGER.info('Running Schedule...')