Commit f5d3088b authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Merge branch 'feat/xr_device_driver_service_tool' into 'develop'

feat/xr_device_driver_service_tool

See merge request !63
parents a1ece51a 8b3a0373
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -146,6 +146,18 @@ arbitrary endpoints in the topology (with consequent underlying XR service insta
    PYTHONPATH=../../../../ ./service-cli.py list
    PYTHONPATH=../../../../ ./service-cli.py delete 43a8046a-5dec-463d-82f7-7cc3442dbf4f
```

It is also possible to create direct XR services without multi-layer services. E.g.:
```
    PYTHONPATH=../../../../  ./service-cli.py create-xr FooService X1-XR-CONSTELLATION  "XR HUB 1|XR-T1" "XR LEAF 2|XR-T1"
```

Additionally it is possible to list services and endpoints:
```
    PYTHONPATH=../../../../  ./service-cli.py list-endpoints
    PYTHONPATH=../../../../  ./service-cli.py delete 43a8046a-5dec-463d-82f7-7cc3442dbf4f
```

The PYTHONPATH is mandatory. Suitable topology JSON must have been loaded before. With the
CocroachDB persistence, it is sufficient to load the topology once and it will persist.

+182 −23
Original line number Diff line number Diff line
@@ -19,21 +19,33 @@
#
# Run in this directory with PYTHONPATH=../../../../
# E.g.:
#   Create multi-layer service (L2 VPN over XR):
#     PYTHONPATH=../../../../  ./service-cli.py create 1 R1-EMU 13/1/2 500 2 R3-EMU 13/1/2 500
#   Single-layer (XR without services on top of it):
#     PYTHONPATH=../../../../  ./service-cli.py create-xr FooService X1-XR-CONSTELLATION  "XR HUB 1|XR-T1" "XR LEAF 2|XR-T1"
#   List services:
#     PYTHONPATH=../../../../  ./service-cli.py list
#   List possible endpoints:
#     PYTHONPATH=../../../../  ./service-cli.py list-endpoints
#   Delete service (if multi-layer, always deleter highest layer!)
#     PYTHONPATH=../../../../  ./service-cli.py delete 43a8046a-5dec-463d-82f7-7cc3442dbf4f


import argparse
import logging
import traceback
from copy import deepcopy
from dataclasses import dataclass, field
from typing import Dict
from contextlib import contextmanager

from common.Settings import get_setting
from context.client.ContextClient import ContextClient
from service.client.ServiceClient import ServiceClient
from tests.tools.mock_osm.MockOSM import MockOSM
from common.proto.context_pb2 import ContextId, ServiceTypeEnum, ServiceStatusEnum
from common.proto.context_pb2 import ContextId, ServiceTypeEnum, ServiceStatusEnum, Service, Empty, ServiceId
from common.tools.grpc.Tools import grpc_message_to_json_string
from common.tools.object_factory.Context import json_context, json_context_id
from common.tools.object_factory.Topology import json_topology_id
from common.tools.object_factory.ConfigRule import json_config_rule_set

LOGGER = logging.getLogger(__name__)

@@ -48,11 +60,52 @@ def make_context_client():
    finally:
        _client.close()

@contextmanager
def make_service_client():
    try:
        _client = ServiceClient(get_setting('SERVICESERVICE_SERVICE_HOST'), get_setting('SERVICESERVICE_SERVICE_PORT_GRPC'))
        yield _client
    finally:
        _client.close()

def make_osm_wim():
    wim_url = 'http://{:s}:{:s}'.format(
        get_setting('COMPUTESERVICE_SERVICE_HOST'), str(get_setting('COMPUTESERVICE_SERVICE_PORT_HTTP')))
    return MockOSM(wim_url, WIM_MAPPING, WIM_USERNAME, WIM_PASSWORD)

@dataclass
class DevInfo:
    name: str
    uuid: str
    endpoints: Dict[str, str] = field(default_factory= dict)
    endpoints_by_uuid: Dict[str, str] = field(default_factory= dict)

    def get_endpoint_uuid_or_exit(self, ep_name: str) -> str:
        if ep_name not in self.endpoints:
            print(f"Endpoint {ep_name} does not exist in device {self.name}. See \"service-cli.py list-endpoints\"")
            exit(-1)
        return self.endpoints[ep_name]

def get_devices(cc: ContextClient) -> Dict[str, DevInfo]:
    r = cc.ListDevices(Empty())
    # print(grpc_message_to_json_string(r))

    devices = dict()
    for dev in r.devices:
        di = DevInfo(dev.name, dev.device_id.device_uuid.uuid)
        for ep in dev.device_endpoints:
            di.endpoints[ep.name] = ep.endpoint_id.endpoint_uuid.uuid
            di.endpoints_by_uuid[ep.endpoint_id.endpoint_uuid.uuid] = ep.name
        devices[dev.name] = di
    return devices

def get_endpoint_map(devices: Dict[str, DevInfo]):
    ep_map = dict()
    for dev in devices.values():
        for ep_name, ep_uuid in dev.endpoints.items():
            ep_map[ep_uuid] = (dev.name, ep_name)
    return ep_map

logging.basicConfig(level=logging.ERROR)

parser = argparse.ArgumentParser(description='TF Service Management Utility')
@@ -74,6 +127,13 @@ delete_parser = subparsers.add_parser('delete')
delete_parser.add_argument('service_uuid', type=str, help='UUID of the service to be deleted')

list_parser = subparsers.add_parser('list')
list_parser = subparsers.add_parser('list-endpoints')

create_xr_parser = subparsers.add_parser('create-xr')
create_xr_parser.add_argument('service_name', type=str, help='Service Name')
create_xr_parser.add_argument('constellation', type=str, help='XR Constellation')
create_xr_parser.add_argument('interface1', type=str, help='One endpoint of the service')
create_xr_parser.add_argument('interface2', type=str, help='Second endpoint of the service')

args = parser.parse_args()

@@ -103,12 +163,17 @@ else:
    WIM_SERVICE_CONNECTION_POINTS = []

#print(str(args))
print(f"=== WIM_SERVICE_TYPE: {WIM_SERVICE_TYPE}")
print(f"=== WIM_SERVICE_CONNECTION_POINTS: {WIM_SERVICE_CONNECTION_POINTS}")
print(f"=== WIM_MAPPING: {WIM_MAPPING}")
#print(f"=== WIM_SERVICE_TYPE: {WIM_SERVICE_TYPE}")
#print(f"=== WIM_SERVICE_CONNECTION_POINTS: {WIM_SERVICE_CONNECTION_POINTS}")
#print(f"=== WIM_MAPPING: {WIM_MAPPING}")

with make_context_client() as client:
    osm_wim = make_osm_wim();
    # We only permit one context on our demos/testing
    response = client.ListContextIds(Empty())
    assert len(response.context_ids) == 1
    context_uuid=json_context_id(response.context_ids[0].context_uuid.uuid)

    osm_wim = make_osm_wim()

    if args.command == "create":
        service_uuid = osm_wim.create_connectivity_service(WIM_SERVICE_TYPE, WIM_SERVICE_CONNECTION_POINTS)
@@ -117,28 +182,122 @@ with make_context_client() as client:
        print(f"*** Get created service status --> {str(status)}")

    elif args.command == "delete":
        service_id = {
            "context_id": context_uuid,
            "service_uuid": {
                "uuid": args.service_uuid
            }
        }

        try:
            response = client.GetService(ServiceId(**service_id))
            #print(grpc_message_to_json_string(response))

            high_level_delete = response.service_type == ServiceTypeEnum.SERVICETYPE_L2NM or response.service_type == ServiceTypeEnum.SERVICETYPE_L3NM
            print(f"Deleting service {response.name}, type {ServiceTypeEnum.Name(response.service_type)}, {high_level_delete=}")

        except:
            print(f"No service with uuid {args.service_uuid} ({service_id})")
            exit(-1)

        if high_level_delete:
            osm_wim.wim.check_credentials()
            try:
                osm_wim.wim.delete_connectivity_service(args.service_uuid)
            print(f"*** Service {args.service_uuid} is no longer present (delete was successfull or service did not exist)")
                print(f"*** Service {args.service_uuid} deleted (L2SM/L3SM layer)")
            except Exception as e:
                print(f"*** Failed to delete service {args.service_uuid}, {e}")
        else:
            with make_service_client() as service_client:
                try:
                    service_client.DeleteService(ServiceId(**service_id))
                    print(f"*** Service {args.service_uuid} deleted (low level)")
                except Exception as e:
                    print(f"*** Failed to delete service {args.service_uuid}, {e}")

    elif args.command == "create-xr":
        CONTEXT_NAME = 'admin'
        CONTEXT_ID   = json_context_id(CONTEXT_NAME)
        CONTEXT      = json_context(CONTEXT_NAME, name=CONTEXT_NAME)

        json_tapi_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',
        }
        config_rule = json_config_rule_set('/settings', json_tapi_settings)

        devices = get_devices(client)
        if args.constellation not in devices:
            print(f"Constellation {args.constellation} does not exist as a device. See \"service-cli.py list-endpoints\"")
            exit(-1)
        else:
            dev_info = devices[args.constellation]
            constellation_uuid = dev_info.uuid

        interface1_uuid = dev_info.get_endpoint_uuid_or_exit(args.interface1)
        interface2_uuid = dev_info.get_endpoint_uuid_or_exit(args.interface2)

        print(f"Constellation {args.constellation:40}: {constellation_uuid:36}")
        print(f"Interface 1   {args.interface1:40}: {interface1_uuid:36}")
        print(f"Interface 2   {args.interface2:40}: {interface2_uuid:36}")

        service_request = {
            "name": args.service_name,
            "service_id": {
                 "context_id": {"context_uuid": {"uuid": response.context_ids[0].context_uuid.uuid}},
                 "service_uuid": {"uuid": args.service_name}
            },
            'service_type'        : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
            "service_endpoint_ids": [
                {'device_id': {'device_uuid': {'uuid': constellation_uuid}}, 'endpoint_uuid': {'uuid': interface1_uuid}, 'topology_id': json_topology_id("admin", context_id=context_uuid)},
                {'device_id': {'device_uuid': {'uuid': constellation_uuid}}, 'endpoint_uuid': {'uuid': interface2_uuid}, 'topology_id': json_topology_id("admin", context_id=context_uuid)}
            ],
            'service_status'      : {'service_status': ServiceStatusEnum.SERVICESTATUS_PLANNED},
            'service_constraints' : [],
        }

        with make_service_client() as service_client:
            sr = deepcopy(service_request)
            endpoints, sr['service_endpoint_ids'] = sr['service_endpoint_ids'], []
            create_response = service_client.CreateService(Service(**sr))
            print(f'CreateService: {grpc_message_to_json_string(create_response)}')

            sr['service_endpoint_ids'] = endpoints
            #sr['service_id']['service_uuid'] = create_response
            sr['service_config'] = {'config_rules': [config_rule]}

            update_response = service_client.UpdateService(Service(**sr))
            print(f'UpdateService: {grpc_message_to_json_string(update_response)}')

    elif args.command == "list":
        devices = get_devices(client)
        ep_map = get_endpoint_map(devices)

        response = client.ListServices(ContextId(**CONTEXT_ID))

        # print('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response)))
        for service in response.services:
            scs = ""

            # See if there are endpoint constraints that might be regognizable by the user.
            # Keys do not necessarily exist, so catch exceptions and ignore those constraints
            # that we cannot easily represent.
            for sc in service.service_constraints:
                try:
                    scs += f"{sc.endpoint_location.endpoint_id.device_id.device_uuid.uuid}:{sc.endpoint_location.endpoint_id.endpoint_uuid.uuid} "
                except Exception:
                    pass

            print(f"{service.service_id.service_uuid.uuid:36}  {ServiceTypeEnum.Name(service.service_type):40}  {ServiceStatusEnum.Name(service.service_status.service_status)}  {scs}")
            ep_list = []
            for ep in service.service_endpoint_ids:
                ep_uuid = ep.endpoint_uuid.uuid
                if ep_uuid in ep_map:
                    dev_name, ep_name = ep_map[ep_uuid]
                    ep_list.append(f"{dev_name}:{ep_name}")
            ep_list.sort()
            eps = ", ".join(ep_list)

            #print(f"{service.service_id.service_uuid.uuid:36}  {ServiceTypeEnum.Name(service.service_type):40}  {service.name:40}  {ServiceStatusEnum.Name(service.service_status.service_status)}  {scs}")
            print(f"{service.service_id.service_uuid.uuid:36}  {ServiceTypeEnum.Name(service.service_type):40}  {service.name:40}  {ServiceStatusEnum.Name(service.service_status.service_status):28}  {eps}")

    elif args.command == "list-endpoints":
        devices = get_devices(client)
        for name in sorted(devices.keys()):
            dev = devices[name]
            print(f"{name:40}    {dev.uuid:36}")
            for ep_name in sorted(dev.endpoints.keys()):
                print(f"    {ep_name:40}    {dev.endpoints[ep_name]:36}")
+4 −0
Original line number Diff line number Diff line
@@ -17,7 +17,11 @@ export CONTEXTSERVICE_SERVICE_HOST=$(kubectl get service/contextservice --namesp
export CONTEXTSERVICE_SERVICE_PORT_GRPC=$(kubectl get service/contextservice --namespace tfs  -o jsonpath='{.spec.ports[?(@.name=="grpc")].port}')
export COMPUTESERVICE_SERVICE_HOST=$(kubectl get service/computeservice --namespace tfs  --template '{{.spec.clusterIP}}')
export COMPUTESERVICE_SERVICE_PORT_HTTP=$(kubectl get service/computeservice --namespace tfs  -o jsonpath='{.spec.ports[?(@.name=="http")].port}')
export SERVICESERVICE_SERVICE_HOST=$(kubectl get service/serviceservice --namespace tfs  --template '{{.spec.clusterIP}}')
export SERVICESERVICE_SERVICE_PORT_GRPC=$(kubectl get service/serviceservice --namespace tfs  -o jsonpath='{.spec.ports[?(@.name=="grpc")].port}')
echo "CONTEXTSERVICE_SERVICE_HOST=$CONTEXTSERVICE_SERVICE_HOST"
echo "CONTEXTSERVICE_SERVICE_PORT_GRPC=$CONTEXTSERVICE_SERVICE_PORT_GRPC"
echo "COMPUTESERVICE_SERVICE_HOST=$COMPUTESERVICE_SERVICE_HOST"
echo "COMPUTESERVICE_SERVICE_PORT_HTTP=$COMPUTESERVICE_SERVICE_PORT_HTTP"
echo "SERVICESERVICE_SERVICE_HOST=$SERVICESERVICE_SERVICE_HOST"
echo "SERVICESERVICE_SERVICE_PORT_GRPC=$SERVICESERVICE_SERVICE_PORT_GRPC"