Commit 0c029fa0 authored by Mohamad Rahhal's avatar Mohamad Rahhal
Browse files

SpineLeaf Component

-Added primarly WebUI support for SpineLeaf
parent 7ea010c5
Loading
Loading
Loading
Loading
+72 −0
Original line number Diff line number Diff line
@@ -40,9 +40,11 @@ from common.proto.context_pb2 import (
    Link, LinkId, Service, ServiceId, Slice, SliceId,
    Topology, TopologyId , OpticalLink
)
from common.proto.spineleaf_pb2 import SetDeviceRoleRequest
from common.tools.object_factory.Context import json_context_id
from context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient
from spine_leaf.client.SpineLeafClient import SpineLeafClient
from service.client.ServiceClient import ServiceClient
from slice.client.SliceClient import SliceClient
from vnt_manager.client.VNTManagerClient import VNTManagerClient
@@ -68,6 +70,7 @@ ENTITY_TO_TEXT = {
    'controller': ('Controller', 'Controllers'),
    'device'    : ('Device',     'Devices'    ),
    'link'      : ('Link',       'Links'      ),
    'spine_leaf': ('Spine Leaf', 'Spine Leafs'),
    'service'   : ('Service',    'Services'   ),
    'slice'     : ('Slice',      'Slices'     ),
    'connection': ('Connection', 'Connections'),
@@ -113,6 +116,7 @@ class DescriptorLoader:
        self, descriptors : Optional[Union[str, Dict]] = None, descriptors_file : Optional[str] = None,
        num_workers : int = 1,
        context_client : Optional[ContextClient] = None, device_client : Optional[DeviceClient] = None,
        spine_leaf_client : Optional[SpineLeafClient] = None,
        service_client : Optional[ServiceClient] = None, slice_client : Optional[SliceClient] = None,
        vntm_client : Optional[VNTManagerClient] = None
    ) -> None:
@@ -136,6 +140,7 @@ class DescriptorLoader:
        self.__devices       = self.__descriptors.get('devices'    ,  [])
        self.__links         = self.__descriptors.get('links'      ,  [])
        self.__optical_links = self.__descriptors.get('optical_links',[])
        self.__spine_leaf    = self.__descriptors.get('spine-leaf',   [])
        self.__services      = self.__descriptors.get('services'   ,  [])
        self.__slices        = self.__descriptors.get('slices'     ,  [])
        self.__ietf_slices   = self.__descriptors.get('ietf-network-slice-service:network-slice-services', {})
@@ -194,6 +199,7 @@ class DescriptorLoader:

        self.__ctx_cli = ContextClient()    if context_client is None else context_client
        self.__dev_cli = DeviceClient()     if device_client  is None else device_client
        self.__spl_cli = SpineLeafClient()  if spine_leaf_client is None else spine_leaf_client
        self.__svc_cli = ServiceClient()    if service_client is None else service_client
        self.__slc_cli = SliceClient()      if slice_client   is None else slice_client
        self.__vnt_cli = VNTManagerClient() if vntm_client    is None else vntm_client
@@ -329,6 +335,7 @@ class DescriptorLoader:
        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('connection', 'add',    self.__ctx_cli.SetConnection,  Connection,  self.__connections   )
        self._process_spine_leaf()

        # 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.
@@ -387,6 +394,8 @@ class DescriptorLoader:
            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        )

        self._process_spine_leaf()

        # 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.

@@ -403,6 +412,69 @@ class DescriptorLoader:
        #self.__dev_cli.close()
        #self.__ctx_cli.close()
        
    def _resolve_device_uuid(self, device_identifier: str, device_map: Optional[Dict[str, str]] = None) -> Optional[str]:
        if not device_identifier:
            return None

        if device_map is None:
            device_map = {}
            response = self.__ctx_cli.ListDevices(Empty())
            for device in response.devices:
                if device.HasField('device_id') and device.device_id.HasField('device_uuid'):
                    device_uuid = device.device_id.device_uuid.uuid
                    if device_uuid:
                        device_map[device_uuid] = device_uuid
                        if device.name:
                            device_map[device.name] = device_uuid

        return device_map.get(device_identifier)

    def _process_spine_leaf(self) -> None:
        if len(self.__spine_leaf) == 0:
            return

        num_ok, error_list = 0, []
        device_map: Dict[str, str] = {}
        response = self.__ctx_cli.ListDevices(Empty())
        for device in response.devices:
            if device.HasField('device_id') and device.device_id.HasField('device_uuid'):
                device_uuid = device.device_id.device_uuid.uuid
                if device_uuid:
                    device_map[device_uuid] = device_uuid
                    if device.name:
                        device_map[device.name] = device_uuid

        role_to_method = {
            'DEVICEROLE_SPINE': self.__spl_cli.SetDeviceAsSpine,
            'DEVICEROLE_LEAF': self.__spl_cli.SetDeviceAsLeaf,
            'DEVICEROLE_GATEWAY': self.__spl_cli.SetDeviceAsGateway,
        }

        for fabric in self.__spine_leaf:
            fabric_id = str(fabric.get('fabric_id', ''))
            settings = fabric.get('settings', {})
            if settings:
                LOGGER.info('SpineLeaf settings received for fabric %s: %s', fabric_id, settings)
            device_roles = fabric.get('device_roles', {})

            for device_identifier, device_role in device_roles.items():
                device_uuid = self._resolve_device_uuid(str(device_identifier), device_map)
                if not device_uuid:
                    error_list.append(f'{fabric_id}/{device_identifier}: device not found')
                    continue

                method = role_to_method.get(str(device_role))
                if method is None:
                    error_list.append(f'{fabric_id}/{device_identifier}: unsupported role {device_role}')
                    continue

                try:
                    method(SetDeviceRoleRequest(device_uuid=device_uuid, fabric_id=fabric_id))
                    num_ok += 1
                except Exception as e:  # pylint: disable=broad-except
                    error_list.append(f'{fabric_id}/{device_identifier}: {str(e)}')

        self.__results.append(('spine_leaf', 'config', num_ok, error_list))
    @staticmethod
    def worker(grpc_method, grpc_class, entity) -> Any:
        return grpc_method(grpc_class(**entity))
+2 −0
Original line number Diff line number Diff line
@@ -86,6 +86,8 @@ COPY --chown=webui:webui src/slice/__init__.py slice/__init__.py
COPY --chown=webui:webui src/slice/client/. slice/client/
COPY --chown=webui:webui src/qkd_app/__init__.py qkd_app/__init__.py
COPY --chown=webui:webui src/qkd_app/client/. qkd_app/client/
COPY --chown=webui:webui src/spine_leaf/__init__.py spine_leaf/__init__.py
COPY --chown=webui:webui src/spine_leaf/client/. spine_leaf/client/
COPY --chown=webui:webui src/bgpls_speaker/__init__.py bgpls_speaker/__init__.py
COPY --chown=webui:webui src/bgpls_speaker/client/. bgpls_speaker/client/
COPY --chown=webui:webui src/vnt_manager/__init__.py vnt_manager/__init__.py