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...')