From 542c5abaeb32d3efc1637fc7f9f33aa770fe862d Mon Sep 17 00:00:00 2001
From: Ville Hallivuori <VHallivuori@infinera.com>
Date: Fri, 10 Feb 2023 14:26:06 +0200
Subject: [PATCH] Add service-cli tool for improved testing of XR services

---
 src/device/service/drivers/xr/README_XR.md   |  12 ++
 src/device/service/drivers/xr/service-cli.py | 144 +++++++++++++++++++
 2 files changed, 156 insertions(+)
 create mode 100755 src/device/service/drivers/xr/service-cli.py

diff --git a/src/device/service/drivers/xr/README_XR.md b/src/device/service/drivers/xr/README_XR.md
index 108a36769..c741c3e80 100644
--- a/src/device/service/drivers/xr/README_XR.md
+++ b/src/device/service/drivers/xr/README_XR.md
@@ -137,6 +137,18 @@ Setup service by following commands in src directory. Kubernetes endpoins change
     python -m pytest --verbose tests/ofc22/tests/test_functional_create_service_xr.py 
 ```
 
+For topology different than used by the test_functional_create/delete_service_xr.py, one can also
+use service-cli.py tool in the xr module directory. It allows creation of ELINE services between
+arbitrary endpoints in the topology (with consequent underlying XR service instantiation). Run in
+*xr module directory*.  Representative examples:
+```
+    PYTHONPATH=../../../../ ./service-cli.py create 1 R1-EMU 13/1/2 500 2 R3-EMU 13/1/2 500
+    PYTHONPATH=../../../../ ./service-cli.py list
+    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.
+
 Good logs to check are:
 
 * kubectl logs   service/deviceservice     --namespace tfs
diff --git a/src/device/service/drivers/xr/service-cli.py b/src/device/service/drivers/xr/service-cli.py
new file mode 100755
index 000000000..01bd2aaa1
--- /dev/null
+++ b/src/device/service/drivers/xr/service-cli.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python3
+#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# 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.
+
+# Manage L2 services (with underlying XR connectivity) without need to use unit test
+# files or excessive JSON definitions
+#
+# Run in this directory with PYTHONPATH=../../../../
+# E.g.:
+#     PYTHONPATH=../../../../  ./service-cli.py create 1 R1-EMU 13/1/2 500 2 R3-EMU 13/1/2 500
+#     PYTHONPATH=../../../../  ./service-cli.py list
+#     PYTHONPATH=../../../../  ./service-cli.py delete 43a8046a-5dec-463d-82f7-7cc3442dbf4f
+
+
+import argparse
+import logging
+import traceback
+from contextlib import contextmanager
+
+from common.Settings import get_setting
+from context.client.ContextClient import ContextClient
+from tests.tools.mock_osm.MockOSM import MockOSM
+from common.proto.context_pb2 import ContextId, ServiceTypeEnum, ServiceStatusEnum
+from common.tools.grpc.Tools import grpc_message_to_json_string
+
+LOGGER = logging.getLogger(__name__)
+
+WIM_USERNAME = 'admin'
+WIM_PASSWORD = 'admin'
+
+@contextmanager
+def make_context_client():
+    try:
+        _client = ContextClient(get_setting('CONTEXTSERVICE_SERVICE_HOST'), get_setting('CONTEXTSERVICE_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)
+
+logging.basicConfig(level=logging.ERROR)
+
+parser = argparse.ArgumentParser(description='TF Service Management Utility')
+subparsers = parser.add_subparsers(dest="command")
+subparsers.required = True
+
+create_parser = subparsers.add_parser('create')
+create_parser.add_argument('site1', type=int, help='One endpoint of the service, e.g. 1')
+create_parser.add_argument('device1', type=str, help='One endpoint of the service, e.g. R1-EMU')
+create_parser.add_argument('interface1', type=str, help='One endpoint of the service, e.g. 13/1/2')
+create_parser.add_argument('vlan1', type=int, help='VLAN in first endpoint, e.g. 500')
+
+create_parser.add_argument('site2', type=int, help='One endpoint of the service, e.g. 2')
+create_parser.add_argument('device2', type=str, help='One endpoint of the service, e.g. R3-EMU')
+create_parser.add_argument('interface2', type=str, help='One endpoint of the service, e.g. 13/1/2')
+create_parser.add_argument('vlan2', type=int, help='VLAN in first endpoint, e.g. 500')
+
+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')
+
+args = parser.parse_args()
+
+WIM_SERVICE_TYPE = 'ELINE'
+CONTEXT_ID = {'context_uuid': {'uuid': 'admin'}}
+
+if args.command == "create":
+    endpoint1 = f"{args.device1}:{args.interface1}"
+    endpoint2 = f"{args.device2}:{args.interface2}"
+
+    WIM_MAPPING  = [
+        {'device-id': args.device1, 'service_endpoint_id': endpoint1,
+        'service_mapping_info': {'bearer': {'bearer-reference': endpoint1}, 'site-id': args.site1}},
+        {'device-id': args.device2, 'service_endpoint_id': endpoint2,
+        'service_mapping_info': {'bearer': {'bearer-reference': endpoint2}, 'site-id': args.site2}},
+    ]
+    WIM_SERVICE_CONNECTION_POINTS = [
+        {'service_endpoint_id': endpoint1,
+            'service_endpoint_encapsulation_type': 'dot1q',
+            'service_endpoint_encapsulation_info': {'vlan': args.vlan1}},
+        {'service_endpoint_id': endpoint2,
+            'service_endpoint_encapsulation_type': 'dot1q',
+            'service_endpoint_encapsulation_info': {'vlan': args.vlan2}},
+    ]
+else:
+    WIM_MAPPING = []
+    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}")
+
+with make_context_client() as client:
+    osm_wim = make_osm_wim();
+
+    if args.command == "create":
+        service_uuid = osm_wim.create_connectivity_service(WIM_SERVICE_TYPE, WIM_SERVICE_CONNECTION_POINTS)
+        print(f"*** Create connectivity service --> {service_uuid}")
+        status = osm_wim.get_connectivity_service_status(service_uuid)
+        print(f"*** Get created service status --> {str(status)}")
+
+    elif args.command == "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)")
+        except Exception as e:
+            print(f"*** Failed to delete service {args.service_uuid}, {e}")
+    elif args.command == "list":
+        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}")
+
+
-- 
GitLab