diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py
index be40e64ecd25a5c46c23d5ec0a73a2484b65691d..b89766b29b54acc62b2850d366bb862bfe4b15f7 100644
--- a/src/device/service/DeviceServiceServicerImpl.py
+++ b/src/device/service/DeviceServiceServicerImpl.py
@@ -12,11 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from typing import Dict
 import grpc, logging
 from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method
 from common.method_wrappers.ServiceExceptions import NotFoundException, OperationFailedException
 from common.proto.context_pb2 import (
-    Device, DeviceConfig, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty)
+    Device, DeviceConfig, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty, Link)
 from common.proto.device_pb2 import MonitoringSettings
 from common.proto.device_pb2_grpc import DeviceServiceServicer
 from common.tools.context_queries.Device import get_device
@@ -27,7 +28,7 @@ from .driver_api.DriverInstanceCache import DriverInstanceCache, get_driver
 from .monitoring.MonitoringLoops import MonitoringLoops
 from .ErrorMessages import ERROR_MISSING_DRIVER, ERROR_MISSING_KPI
 from .Tools import (
-    check_connect_rules, check_no_endpoints, compute_rules_to_add_delete, configure_rules, deconfigure_rules,
+    check_connect_rules, check_no_endpoints, compute_rules_to_add_delete, configure_rules, deconfigure_rules, get_device_manager_uuid,
     populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints, populate_initial_config_rules,
     subscribe_kpi, unsubscribe_kpi, update_endpoints)
 
@@ -73,9 +74,16 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
 
             errors = []
 
+            # Sub-devices and sub-links are exposed by intermediate controllers or represent mgmt links.
+            # They are used to assist in path computation algorithms, and/or to identify dependencies
+            # (which controller is in charge of which sub-device).
+            new_sub_devices : Dict[str, Device] = dict()
+            new_sub_links : Dict[str, Link] = dict()
+
             if len(device.device_endpoints) == 0:
                 # created from request, populate endpoints using driver
-                errors.extend(populate_endpoints(device, driver, self.monitoring_loops))
+                errors.extend(populate_endpoints(
+                    device, driver, self.monitoring_loops, new_sub_devices, new_sub_links))
 
             if len(device.device_config.config_rules) == len(connection_config_rules):
                 # created from request, populate config rules using driver
@@ -87,12 +95,20 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
                 for error in errors: LOGGER.error(error)
                 raise OperationFailedException('AddDevice', extra_details=errors)
 
+            device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED
             device_id = context_client.SetDevice(device)
 
+            for sub_device in new_sub_devices.values():
+                context_client.SetDevice(sub_device)
+
+            for sub_links in new_sub_links.values():
+                context_client.SetLink(sub_links)
+
             # Update endpoint monitoring resources with UUIDs
             device_with_uuids = context_client.GetDevice(device_id)
             populate_endpoint_monitoring_resources(device_with_uuids, self.monitoring_loops)
 
+            context_client.close()
             return device_id
         finally:
             self.mutex_queues.signal_done(device_uuid)
@@ -109,6 +125,12 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
             if device is None:
                 raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice')
 
+            device_manager_uuid = get_device_manager_uuid(device)
+            if device_manager_uuid is not None:
+                device = get_device(context_client, device_manager_uuid, rw_copy=True)
+                if device is None:
+                    raise NotFoundException('Device', device_manager_uuid, extra_details='loading in ConfigureDevice')
+
             driver : _Driver = get_driver(self.driver_instance_cache, device)
             if driver is None:
                 msg = ERROR_MISSING_DRIVER.format(device_uuid=str(device_uuid))
diff --git a/src/device/service/ErrorMessages.py b/src/device/service/ErrorMessages.py
index 1fbea721fdc52bdf759581c0525b30b1206ae844..bb7702e4e629bad43df4870d923f0a1829378e2e 100644
--- a/src/device/service/ErrorMessages.py
+++ b/src/device/service/ErrorMessages.py
@@ -14,9 +14,9 @@
 
 _DEVICE_ID          = 'DeviceId({device_uuid:s})'
 _ENDPOINT_ID        = 'EndpointId({endpoint_uuid:s})'
-_ENDPOINT_DATA      = 'EndpointId({endpoint_data:s})'
 _KPI                = 'Kpi({kpi_uuid:s})'
 _DEVICE_ENDPOINT_ID = _DEVICE_ID + '/' + _ENDPOINT_ID
+_RESOURCE           = 'Resource({resource_data:s})'
 _RESOURCE_KEY       = 'Resource(key={resource_key:s})'
 _RESOURCE_KEY_VALUE = 'Resource(key={resource_key:s}, value={resource_value:s})'
 _SUBSCRIPTION       = 'Subscription(key={subscr_key:s}, duration={subscr_duration:s}, interval={subscr_interval:s})'
@@ -26,7 +26,8 @@ _ERROR              = 'Error({error:s})'
 ERROR_MISSING_DRIVER = _DEVICE_ID + ' has not been added to this Device instance'
 ERROR_MISSING_KPI    = _KPI + ' not found'
 
-ERROR_BAD_ENDPOINT   = _DEVICE_ID + ': GetConfig retrieved malformed ' + _ENDPOINT_DATA
+ERROR_BAD_RESOURCE   = _DEVICE_ID + ': GetConfig retrieved malformed ' + _RESOURCE
+ERROR_UNSUP_RESOURCE = _DEVICE_ID + ': GetConfig retrieved unsupported ' + _RESOURCE
 
 ERROR_GET            = _DEVICE_ID + ': Unable to Get ' + _RESOURCE_KEY + '; ' + _ERROR
 ERROR_GET_INIT       = _DEVICE_ID + ': Unable to Get Initial ' + _RESOURCE_KEY + '; ' + _ERROR
diff --git a/src/device/service/Tools.py b/src/device/service/Tools.py
index 571e8acdab7fc243c22923a69202c89db88c8ce3..1dccea3ab9774fe6682fa346c8c3d20107b46fbf 100644
--- a/src/device/service/Tools.py
+++ b/src/device/service/Tools.py
@@ -13,19 +13,20 @@
 # limitations under the License.
 
 import json, logging
-from typing import Any, Dict, List, Tuple, Union
+from typing import Any, Dict, List, Optional, Tuple, Union
 from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME
 from common.method_wrappers.ServiceExceptions import InvalidArgumentException
-from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig
+from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig, Link
 from common.proto.device_pb2 import MonitoringSettings
 from common.proto.kpi_sample_types_pb2 import KpiSampleType
 from common.tools.grpc.ConfigRules import update_config_rule_custom
 from common.tools.grpc.Tools import grpc_message_to_json
+from context.client.ContextClient import ContextClient
 from .driver_api._Driver import _Driver, RESOURCE_ENDPOINTS
 from .monitoring.MonitoringLoops import MonitoringLoops
 from .ErrorMessages import (
-    ERROR_BAD_ENDPOINT, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET,
-    ERROR_SUBSCRIBE, ERROR_UNSUBSCRIBE
+    ERROR_BAD_RESOURCE, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET,
+    ERROR_SUBSCRIBE, ERROR_UNSUBSCRIBE, ERROR_UNSUP_RESOURCE
 )
 
 LOGGER = logging.getLogger(__name__)
@@ -77,19 +78,51 @@ def check_no_endpoints(device_endpoints) -> None:
         extra_details='RPC method AddDevice does not accept Endpoints. Endpoints are discovered through '\
                         'interrogation of the physical device.')
 
-def populate_endpoints(device : Device, driver : _Driver, monitoring_loops : MonitoringLoops) -> List[str]:
+def get_device_manager_uuid(device : Device) -> Optional[str]:
+    for config_rule in device.device_config.config_rules:
+        if config_rule.WhichOneof('config_rule') != 'custom': continue
+        if config_rule.custom.resource_key != '_manager': continue
+        device_manager_id = json.loads(config_rule.custom.resource_value)
+        return device_manager_id['uuid']
+    return None
+
+def populate_endpoints(
+    device : Device, driver : _Driver, monitoring_loops : MonitoringLoops,
+    new_sub_devices : Dict[str, Device], new_sub_links : Dict[str, Link]
+) -> List[str]:
     device_uuid = device.device_id.device_uuid.uuid
+    device_name = device.name
 
     resources_to_get = [RESOURCE_ENDPOINTS]
     results_getconfig = driver.GetConfig(resources_to_get)
+    LOGGER.debug('results_getconfig = {:s}'.format(str(results_getconfig)))
+
+    # first quick pass to identify need of mgmt endpoints and links
+    add_mgmt_port = False
+    for resource_data in results_getconfig:
+        if len(resource_data) != 2: continue
+        resource_key, _ = resource_data
+        if resource_key.startswith('/devices/device'):
+            add_mgmt_port = True
+            break
+
+    if add_mgmt_port:
+        # add mgmt port to main device
+        device_mgmt_endpoint = device.device_endpoints.add()
+        device_mgmt_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+        device_mgmt_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+        device_mgmt_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
+        device_mgmt_endpoint.endpoint_id.endpoint_uuid.uuid = 'mgmt'
+        device_mgmt_endpoint.name = 'mgmt'
+        device_mgmt_endpoint.endpoint_type = 'mgmt'
 
     errors : List[str] = list()
-    for endpoint in results_getconfig:
-        if len(endpoint) != 2:
-            errors.append(ERROR_BAD_ENDPOINT.format(device_uuid=device_uuid, endpoint_data=str(endpoint)))
+    for resource_data in results_getconfig:
+        if len(resource_data) != 2:
+            errors.append(ERROR_BAD_RESOURCE.format(device_uuid=device_uuid, resource_data=str(resource_data)))
             continue
 
-        resource_key, resource_value = endpoint
+        resource_key, resource_value = resource_data
         if isinstance(resource_value, Exception):
             errors.append(ERROR_GET.format(
                 device_uuid=device_uuid, resource_key=str(resource_key), error=str(resource_value)))
@@ -97,19 +130,88 @@ def populate_endpoints(device : Device, driver : _Driver, monitoring_loops : Mon
         if resource_value is None:
             continue
 
-        endpoint_uuid = resource_value.get('uuid')
-
-        device_endpoint = device.device_endpoints.add()
-        device_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
-        device_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
-        device_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
-        device_endpoint.endpoint_id.endpoint_uuid.uuid = endpoint_uuid
-        device_endpoint.endpoint_type = resource_value.get('type')
+        if resource_key.startswith('/devices/device'):
+            # create sub-device
+            _sub_device_uuid = resource_value['uuid']
+            _sub_device = Device()
+            _sub_device.device_id.device_uuid.uuid = _sub_device_uuid           # pylint: disable=no-member
+            _sub_device.name = resource_value['name']
+            _sub_device.device_type = resource_value['type']
+            _sub_device.device_operational_status = resource_value['status']
+            
+            # Sub-devices should not have a driver assigned. Instead, they should have
+            # a config rule specifying their manager.
+            #_sub_device.device_drivers.extend(resource_value['drivers'])        # pylint: disable=no-member
+            manager_config_rule = _sub_device.device_config.config_rules.add()
+            manager_config_rule.action = ConfigActionEnum.CONFIGACTION_SET
+            manager_config_rule.custom.resource_key = '_manager'
+            manager = {'uuid': device_uuid, 'name': device_name}
+            manager_config_rule.custom.resource_value = json.dumps(manager, indent=0, sort_keys=True)
+
+            new_sub_devices[_sub_device_uuid] = _sub_device
+
+            # add mgmt port to sub-device
+            _sub_device_mgmt_endpoint = _sub_device.device_endpoints.add()      # pylint: disable=no-member
+            _sub_device_mgmt_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+            _sub_device_mgmt_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+            _sub_device_mgmt_endpoint.endpoint_id.device_id.device_uuid.uuid = _sub_device_uuid
+            _sub_device_mgmt_endpoint.endpoint_id.endpoint_uuid.uuid = 'mgmt'
+            _sub_device_mgmt_endpoint.name = 'mgmt'
+            _sub_device_mgmt_endpoint.endpoint_type = 'mgmt'
+
+            # add mgmt link
+            _mgmt_link_uuid = '{:s}/{:s}=={:s}/{:s}'.format(device_name, 'mgmt', _sub_device.name, 'mgmt')
+            _mgmt_link = Link()
+            _mgmt_link.link_id.link_uuid.uuid = _mgmt_link_uuid                         # pylint: disable=no-member
+            _mgmt_link.name = _mgmt_link_uuid
+            _mgmt_link.link_endpoint_ids.append(device_mgmt_endpoint.endpoint_id)       # pylint: disable=no-member
+            _mgmt_link.link_endpoint_ids.append(_sub_device_mgmt_endpoint.endpoint_id)  # pylint: disable=no-member
+            new_sub_links[_mgmt_link_uuid] = _mgmt_link
+
+        elif resource_key.startswith('/endpoints/endpoint'):
+            endpoint_uuid = resource_value['uuid']
+            _device_uuid = resource_value.get('device_uuid')
+            endpoint_name = resource_value.get('name')
+
+            if _device_uuid is None:
+                # add endpoint to current device
+                device_endpoint = device.device_endpoints.add()
+                device_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
+            else:
+                # add endpoint to specified device
+                device_endpoint = new_sub_devices[_device_uuid].device_endpoints.add()
+                device_endpoint.endpoint_id.device_id.device_uuid.uuid = _device_uuid
+
+            device_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+            device_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+            
+            device_endpoint.endpoint_id.endpoint_uuid.uuid = endpoint_uuid
+            if endpoint_name is not None: device_endpoint.name = endpoint_name
+            device_endpoint.endpoint_type = resource_value.get('type', '-')
+
+            sample_types : Dict[int, str] = resource_value.get('sample_types', {})
+            for kpi_sample_type, monitor_resource_key in sample_types.items():
+                device_endpoint.kpi_sample_types.append(kpi_sample_type)
+                monitoring_loops.add_resource_key(device_uuid, endpoint_uuid, kpi_sample_type, monitor_resource_key)
+
+        elif resource_key.startswith('/links/link'):
+            # create sub-link
+            _sub_link_uuid = resource_value['uuid']
+            _sub_link = Link()
+            _sub_link.link_id.link_uuid.uuid = _sub_link_uuid           # pylint: disable=no-member
+            _sub_link.name = resource_value['name']
+            new_sub_links[_sub_link_uuid] = _sub_link
+
+            for device_uuid,endpoint_uuid in resource_value['name']:
+                _sub_link_endpoint_id = _sub_link.link_endpoint_ids.add()      # pylint: disable=no-member
+                _sub_link_endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+                _sub_link_endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+                _sub_link_endpoint_id.device_id.device_uuid.uuid = device_uuid
+                _sub_link_endpoint_id.endpoint_uuid.uuid = endpoint_uuid
 
-        sample_types : Dict[int, str] = resource_value.get('sample_types', {})
-        for kpi_sample_type, monitor_resource_key in sample_types.items():
-            device_endpoint.kpi_sample_types.append(kpi_sample_type)
-            monitoring_loops.add_resource_key(device_uuid, endpoint_uuid, kpi_sample_type, monitor_resource_key)
+        else:
+            errors.append(ERROR_UNSUP_RESOURCE.format(device_uuid=device_uuid, resource_data=str(resource_data)))
+            continue
 
     return errors