diff --git a/data/.gitignore b/data/.gitignore index 128ac08dd932a4d32f69ad556adfde7a003caef7..6722cd96e785ac093e976a48df83409bf618b0a1 100644 --- a/data/.gitignore +++ b/data/.gitignore @@ -1 +1 @@ -drx30-01.xml +*.xml diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py index f46619874a8c5cd2127f9937b3742ba3a8c42af2..bdb7b7019fc494236cf02053c1f52b98291a40b8 100644 --- a/src/device/service/DeviceServiceServicerImpl.py +++ b/src/device/service/DeviceServiceServicerImpl.py @@ -1,5 +1,6 @@ +import grpc, json, logging from typing import Any, List, Tuple -import grpc, logging +from google.protobuf.json_format import MessageToDict from common.orm.Database import Database from common.orm.Factory import get_database_backend from common.orm.HighLevel import get_object, update_or_create_object @@ -12,6 +13,7 @@ from context.client.ContextClient import ContextClient from device.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig, DeviceId, Empty from device.proto.device_pb2 import MonitoringSettings from device.proto.device_pb2_grpc import DeviceServiceServicer +from device.service.drivers.openconfig.OpenConfigDriver import OpenConfigDriver from .MonitoringLoops import MonitoringLoops from .database.ConfigModel import ( ConfigModel, ConfigRuleModel, ORM_ConfigActionEnum, get_config_rules, grpc_config_rules_to_raw, update_config) @@ -22,7 +24,7 @@ from .database.DeviceModel import DeviceModel, DriverModel from .database.EndPointModel import EndPointModel from .database.KpiModel import KpiModel from .database.KpiSampleType import grpc_to_enum__kpi_sample_type -from .driver_api._Driver import _Driver +from .driver_api._Driver import _Driver, RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES from .driver_api.DriverInstanceCache import DriverInstanceCache from .driver_api.Tools import ( check_delete_errors, check_set_errors, check_subscribe_errors, check_unsubscribe_errors) @@ -59,11 +61,36 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): else: unexpected_config_rules.append(config_rule) if len(unexpected_config_rules) > 0: + unexpected_config_rules = MessageToDict( + request.device_config, including_default_value_fields=True, + preserving_proto_field_name=True, use_integers_for_enums=True) + unexpected_config_rules = unexpected_config_rules['config_rules'] + unexpected_config_rules = list(filter( + lambda cr: cr['resource_key'].replace('_connect/', '') not in connection_config_rules, + unexpected_config_rules)) + str_unexpected_config_rules = json.dumps(unexpected_config_rules, sort_keys=True) raise InvalidArgumentException( - 'device.device_config.config_rules', str(unexpected_config_rules), + 'device.device_config.config_rules', str_unexpected_config_rules, extra_details='RPC method AddDevice only accepts connection Config Rules that should start '\ 'with "_connect/" tag. Others should be configured after adding the device.') + if len(request.device_endpoints) > 0: + unexpected_endpoints = MessageToDict( + request.device_endpoints, including_default_value_fields=True, preserving_proto_field_name=True, + use_integers_for_enums=True) + str_unexpected_endpoints = json.dumps(unexpected_endpoints, sort_keys=True) + raise InvalidArgumentException( + 'device.device_endpoints', str_unexpected_endpoints, + extra_details='RPC method AddDevice does not accept endpoints. Endpoints are discovered through '\ + 'interrogation of the physical device.') + + # Remove device configuration + json_request = MessageToDict( + request, including_default_value_fields=True, preserving_proto_field_name=True, + use_integers_for_enums=True) + json_request['device_config'] = {} + request = Device(**json_request) + sync_device_from_context(device_uuid, self.context_client, self.database) db_device,_ = update_device_in_local_database(self.database, request) @@ -76,12 +103,25 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): settings=connection_config_rules) driver.Connect() - running_config_rules = driver.GetConfig() - running_config_rules = [(ORM_ConfigActionEnum.SET, rule[0], rule[1]) for rule in running_config_rules] - LOGGER.info('[AddDevice] running_config_rules = {:s}'.format(str(running_config_rules))) - - context_config_rules = get_config_rules(self.database, device_uuid, 'running') - LOGGER.info('[AddDevice] context_config_rules = {:s}'.format(str(context_config_rules))) + endpoints = driver.GetConfig([RESOURCE_ENDPOINTS]) + for _, resource_value in endpoints: + endpoint_uuid = resource_value.get('name') + endpoint_type = resource_value.get('type') + str_endpoint_key = key_to_str([device_uuid, endpoint_uuid]) + update_or_create_object( + self.database, EndPointModel, str_endpoint_key, { + 'device_fk' : db_device, + 'endpoint_uuid': endpoint_uuid, + 'endpoint_type': endpoint_type, + }) + + running_config_rules = driver.GetConfig([RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES]) + running_config_rules = [(ORM_ConfigActionEnum.SET, cr[0], json.dumps(cr[1])) for cr in running_config_rules] + #for running_config_rule in running_config_rules: + # LOGGER.info('[AddDevice] running_config_rule: {:s}'.format(str(running_config_rule))) + + #context_config_rules = get_config_rules(self.database, device_uuid, 'running') + #LOGGER.info('[AddDevice] context_config_rules = {:s}'.format(str(context_config_rules))) # TODO: Compute diff between current context config and device config. The one in device is of higher priority # (might happen another instance is updating config and context was not already updated) diff --git a/src/device/service/drivers/emulated/AnyTreeTools.py b/src/device/service/driver_api/AnyTreeTools.py similarity index 98% rename from src/device/service/drivers/emulated/AnyTreeTools.py rename to src/device/service/driver_api/AnyTreeTools.py index e7817b78921e3b91fb84ca660f022c869ac88220..df61c7e030a13a3d0d758ce51a011aaa95deb49f 100644 --- a/src/device/service/drivers/emulated/AnyTreeTools.py +++ b/src/device/service/driver_api/AnyTreeTools.py @@ -1,8 +1,6 @@ import anytree from typing import Any, List, Optional -from anytree.render import Row - class TreeNode(anytree.node.Node): def __init__(self, name, parent=None, children=None, **kwargs) -> None: super().__init__(name, parent=parent, children=children, **kwargs) diff --git a/src/device/service/driver_api/_Driver.py b/src/device/service/driver_api/_Driver.py index b79661faaf3fd773ad598ef3b20ebfaafe58bea4..909731f757af0301d5135422df8f2a14337a4ce7 100644 --- a/src/device/service/driver_api/_Driver.py +++ b/src/device/service/driver_api/_Driver.py @@ -1,5 +1,10 @@ from typing import Any, Iterator, List, Optional, Tuple, Union +# Special resource names +RESOURCE_ENDPOINTS = '__endpoints__' +RESOURCE_INTERFACES = '__interfaces__' +RESOURCE_NETWORK_INSTANCES = '__network_instances__' + class _Driver: def __init__(self, address : str, port : int, **settings) -> None: """ Initialize Driver. diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index ae0415b786af2a12ce8f33505bfc1ac774e4e463..4b3fed33ad212ac6035dc567f1559bad387d8c46 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -7,7 +7,7 @@ DRIVERS = [ (EmulatedDriver, [ { FilterFieldEnum.DEVICE_TYPE: DeviceTypeFilterFieldEnum.EMULATED, - FilterFieldEnum.DRIVER : ORM_DeviceDriverEnum.OPENCONFIG, + FilterFieldEnum.DRIVER : ORM_DeviceDriverEnum.UNDEFINED, } ]), (OpenConfigDriver, [ diff --git a/src/device/service/drivers/emulated/EmulatedDriver.py b/src/device/service/drivers/emulated/EmulatedDriver.py index 3328a29dec4b56512d66860d2578a93aa2447f34..1258f20d888a1c7fdb61764892c08517b26e6e66 100644 --- a/src/device/service/drivers/emulated/EmulatedDriver.py +++ b/src/device/service/drivers/emulated/EmulatedDriver.py @@ -7,7 +7,7 @@ from apscheduler.jobstores.memory import MemoryJobStore from apscheduler.schedulers.background import BackgroundScheduler from common.type_checkers.Checkers import chk_float, chk_length, chk_string, chk_type from device.service.driver_api._Driver import _Driver -from .AnyTreeTools import TreeNode, dump_subtree, get_subnode, set_subnode_value +from device.service.driver_api.AnyTreeTools import TreeNode, dump_subtree, get_subnode, set_subnode_value LOGGER = logging.getLogger(__name__) diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py index d76962a06de02668ec305191935bb9f8a41b66cd..53bfc98340fe42e5829ffe4d42226b3d204b43f2 100644 --- a/src/device/service/drivers/openconfig/OpenConfigDriver.py +++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py @@ -1,16 +1,19 @@ -import logging, ncclient.manager, pytz, queue, threading -import xml.dom.minidom -from typing import Any, Iterator, List, Optional, Tuple, Union -#import anytree, random +import anytree, logging, pytz, queue, threading +from anytree.importer import DictImporter #from datetime import datetime, timedelta +import lxml.etree as ET +from typing import Any, Iterator, List, Optional, Tuple, Union #from apscheduler.executors.pool import ThreadPoolExecutor #from apscheduler.job import Job #from apscheduler.jobstores.memory import MemoryJobStore #from apscheduler.schedulers.background import BackgroundScheduler -from common.type_checkers.Checkers import chk_float, chk_length, chk_string, chk_type +from netconf_client.connect import connect_ssh +from netconf_client.ncclient import Manager +from common.type_checkers.Checkers import chk_float, chk_integer, chk_length, chk_string, chk_type from device.service.driver_api._Driver import _Driver -from device.service.drivers.openconfig.handlers import DEFAULT_HANDLER, HANDLERS -#from .AnyTreeTools import TreeNode, dump_subtree, get_subnode, set_subnode_value +from device.service.driver_api.AnyTreeTools import TreeNode, dump_subtree, get_subnode, set_subnode_value +from device.service.drivers.openconfig.Tools import xml_pretty_print, xml_to_dict, xml_to_file +from device.service.drivers.openconfig.templates import ALL_RESOURCES, get_filter, extract_data logging.getLogger('ncclient.transport.ssh').setLevel(logging.WARNING) @@ -22,14 +25,14 @@ LOGGER = logging.getLogger(__name__) class OpenConfigDriver(_Driver): def __init__(self, address : str, port : int, **settings) -> None: # pylint: disable=super-init-not-called self.__address = address - self.__port = port + self.__port = int(port) self.__settings = settings self.__lock = threading.Lock() #self.__initial = TreeNode('.') #self.__running = TreeNode('.') self.__started = threading.Event() self.__terminate = threading.Event() - self.__netconf_manager : ncclient.manager.Manager = None + self.__netconf_manager : Manager = None #self.__scheduler = BackgroundScheduler(daemon=True) # scheduler used to emulate sampling events #self.__scheduler.configure( # jobstores = {'default': MemoryJobStore()}, @@ -43,11 +46,10 @@ class OpenConfigDriver(_Driver): if self.__started.is_set(): return True username = self.__settings.get('username') password = self.__settings.get('password') - handler_name = self.__settings.get('handler') - handler = HANDLERS.get(handler_name, DEFAULT_HANDLER) - self.__netconf_manager = ncclient.manager.connect_ssh( - host=self.__address, port=self.__port, username=username, password=password, hostkey_verify=False, - device_params=handler) + timeout = int(self.__settings.get('timeout', 120)) + session = connect_ssh( + host=self.__address, port=self.__port, username=username, password=password) + self.__netconf_manager = Manager(session, timeout=timeout) # Connect triggers activation of sampling events that will be scheduled based on subscriptions #self.__scheduler.start() self.__started.set() @@ -75,29 +77,21 @@ class OpenConfigDriver(_Driver): results = [] with self.__lock: - if len(resource_keys) == 0: - config = self.__netconf_manager.get_config(source='running').data_xml - with open('../data/drx30-01.xml', mode='w', encoding='UTF-8') as f: - dom = xml.dom.minidom.parseString(config) - f.write(dom.toprettyxml()) + if len(resource_keys) == 0: resource_keys = ALL_RESOURCES + for i,resource_key in enumerate(resource_keys): + str_resource_name = 'resource_key[#{:d}]'.format(i) + try: + str_filter = get_filter(resource_key) + if str_filter is None: str_filter = resource_key + xml_data = self.__netconf_manager.get(filter=str_filter).data_ele + if isinstance(xml_data, Exception): raise xml_data + results.extend(extract_data(resource_key, xml_data)) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Exception retrieving {:s}: {:s}'.format(str_resource_name, str(resource_key))) + results.append((resource_key, e)) # if validation fails, store the exception + continue return results -# resolver = anytree.Resolver(pathattr='name') -# for i,resource_key in enumerate(resource_keys): -# str_resource_name = 'resource_key[#{:d}]'.format(i) -# try: -# resource_path = resource_key.split('/') -# except Exception as e: # pylint: disable=broad-except -# LOGGER.exception('Exception validating {:s}: {:s}'.format(str_resource_name, str(resource_key))) -# results.append((resource_key, e)) # if validation fails, store the exception -# continue -# -# resource_node = get_subnode(resolver, self.__running, resource_path, default=None) -# # if not found, resource_node is None -# if resource_node is None: continue -# results.extend(dump_subtree(resource_node)) -# return results -# # def GetResource(self, endpoint_uuid : str) -> Optional[str]: # chk_string('endpoint_uuid', endpoint_uuid) # return { diff --git a/src/device/service/drivers/openconfig/Tools.py b/src/device/service/drivers/openconfig/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..230a6e3c33586c5d1771a6da2fd9dabc533e9538 --- /dev/null +++ b/src/device/service/drivers/openconfig/Tools.py @@ -0,0 +1,11 @@ +import xml.dom.minidom, xmltodict + +def xml_pretty_print(data : str): + return xml.dom.minidom.parseString(data).toprettyxml() + +def xml_to_file(data : str, file_path : str) -> None: + with open(file_path, mode='w', encoding='UTF-8') as f: + f.write(xml_pretty_print(data)) + +def xml_to_dict(data : str): + return xmltodict.parse(data) diff --git a/src/device/service/drivers/openconfig/handlers/InfineraDeviceHandler.py b/src/device/service/drivers/openconfig/handlers/InfineraDeviceHandler.py deleted file mode 100644 index ebf0af2e41c0385591f05c67461262a1948dfb9c..0000000000000000000000000000000000000000 --- a/src/device/service/drivers/openconfig/handlers/InfineraDeviceHandler.py +++ /dev/null @@ -1,18 +0,0 @@ -# Handler for Infinera device specific information through YANG. -from ncclient.xml_ import BASE_NS_1_0 -from ncclient.devices.default import DefaultDeviceHandler - -class InfineraDeviceHandler(DefaultDeviceHandler): - _EXEMPT_ERRORS = [] - - def get_capabilities(self): - return [ - 'urn:ietf:params:netconf:base:1.0', - 'urn:ietf:params:netconf:base:1.1', - ] - - def get_xml_base_namespace_dict(self): - return {None: BASE_NS_1_0} - - def get_xml_extra_prefix_kwargs(self): - return {'nsmap': self.get_xml_base_namespace_dict()} diff --git a/src/device/service/drivers/openconfig/handlers/__init__.py b/src/device/service/drivers/openconfig/handlers/__init__.py deleted file mode 100644 index 109853e3b8ab30f6d84075699dacbef11acac6f6..0000000000000000000000000000000000000000 --- a/src/device/service/drivers/openconfig/handlers/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .InfineraDeviceHandler import InfineraDeviceHandler - -HANDLERS = { - 'infinera': {'handler': InfineraDeviceHandler}, -} - -DEFAULT_HANDLER = {'name': 'default'} diff --git a/src/device/service/drivers/openconfig/templates/Namespace.py b/src/device/service/drivers/openconfig/templates/Namespace.py new file mode 100644 index 0000000000000000000000000000000000000000..ff4b7c9c747b4efa872ba177e6e6a4fd7f6203b4 --- /dev/null +++ b/src/device/service/drivers/openconfig/templates/Namespace.py @@ -0,0 +1,23 @@ + +NAMESPACE_NETCONF = 'urn:ietf:params:xml:ns:netconf:base:1.0' + +NAMESPACE_INTERFACES = 'http://openconfig.net/yang/interfaces' +NAMESPACE_INTERFACES_IP = 'http://openconfig.net/yang/interfaces/ip' +NAMESPACE_NETWORK_INSTANCE = 'http://openconfig.net/yang/network-instance' +NAMESPACE_NETWORK_INSTANCE_TYPES = 'http://openconfig.net/yang/network-instance-types' +NAMESPACE_OPENCONFIG_TYPES = 'http://openconfig.net/yang/openconfig-types' +NAMESPACE_PLATFORM = 'http://openconfig.net/yang/platform' +NAMESPACE_PLATFORM_PORT = 'http://openconfig.net/yang/platform/port' +NAMESPACE_VLAN = 'http://openconfig.net/yang/vlan' + +NAMESPACES = { + 'nc' : NAMESPACE_NETCONF, + 'oci' : NAMESPACE_INTERFACES, + 'ociip': NAMESPACE_INTERFACES_IP, + 'ocni' : NAMESPACE_NETWORK_INSTANCE, + 'ocnit': NAMESPACE_NETWORK_INSTANCE_TYPES, + 'ococt': NAMESPACE_OPENCONFIG_TYPES, + 'ocp' : NAMESPACE_PLATFORM, + 'ocpp' : NAMESPACE_PLATFORM_PORT, + 'ocv' : NAMESPACE_VLAN, +} diff --git a/src/device/service/drivers/openconfig/templates/Tools.py b/src/device/service/drivers/openconfig/templates/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..01e4e666ff6c72e65d5742a4a5be437ad06f824d --- /dev/null +++ b/src/device/service/drivers/openconfig/templates/Tools.py @@ -0,0 +1,12 @@ +import lxml.etree as ET +from typing import Collection, Dict + +def add_value_from_tag(container : Dict, name: str, value : ET.Element, cast=None) -> None: + if value is None or value.text is None: return + value = value.text + if cast is not None: value = cast(value) + container[name] = value + +def add_value_from_collection(container : Dict, name: str, value : Collection) -> None: + if value is None or len(value) == 0: return + container[name] = value diff --git a/src/device/service/drivers/openconfig/templates/__init__.py b/src/device/service/drivers/openconfig/templates/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..65d9f54e35f204f274cb9fe9c42bc41f8bfd325f --- /dev/null +++ b/src/device/service/drivers/openconfig/templates/__init__.py @@ -0,0 +1,25 @@ +import lxml.etree as ET +from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES +from .Components import get_filter_components, extract_port_data +from .Interfaces import get_filter_interfaces, extract_interface_data +from .NetworkInstance import get_filter_network_instances, extract_network_instance_data + +RESOURCE_FUNCTIONS = { + RESOURCE_ENDPOINTS : (get_filter_components, extract_port_data), + RESOURCE_INTERFACES : (get_filter_interfaces, extract_interface_data), + RESOURCE_NETWORK_INSTANCES : (get_filter_network_instances, extract_network_instance_data), +} + +ALL_RESOURCES = [ + RESOURCE_ENDPOINTS, + RESOURCE_INTERFACES, + RESOURCE_NETWORK_INSTANCES, +] + +def get_filter(resource_key : str, *args, **kwargs): + filter_function = RESOURCE_FUNCTIONS.get(resource_key) + return None if filter_function is None else filter_function[0](*args, **kwargs) + +def extract_data(resource_key : str, xml_data : ET.Element, *args, **kwargs): + extract_function = RESOURCE_FUNCTIONS.get(resource_key) + return [(resource_key, xml_data)] if extract_function is None else extract_function[1](xml_data, *args, **kwargs) diff --git a/src/device/tests/Tools.py b/src/device/tests/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..6c1344fb602bb523d5849227c5f052efdbba64c2 --- /dev/null +++ b/src/device/tests/Tools.py @@ -0,0 +1,11 @@ +from copy import deepcopy + +def config_rule(action, resource_key, resource_value): + return {'action': action, 'resource_key': resource_key, 'resource_value': resource_value} + +def endpoint_id(topology_id, device_id, endpoint_uuid): + return {'topology_id': deepcopy(topology_id), 'device_id': deepcopy(device_id), + 'endpoint_uuid': {'uuid': endpoint_uuid}} + +def endpoint(topology_id, device_id, endpoint_uuid, endpoint_type): + return {'endpoint_id': endpoint_id(topology_id, device_id, endpoint_uuid), 'endpoint_type': endpoint_type} diff --git a/src/device/tests/example_objects.py b/src/device/tests/example_objects.py index b84df119fbb5de869010e0051b70fe1e50cf4376..ba382695965b28dc5fddec77775e795bb4b061c9 100644 --- a/src/device/tests/example_objects.py +++ b/src/device/tests/example_objects.py @@ -1,33 +1,27 @@ from copy import deepcopy from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID from context.proto.context_pb2 import ConfigActionEnum, DeviceDriverEnum, DeviceOperationalStatusEnum +from .Tools import config_rule + +DEVICE_EMU_UUID = 'EMULARED' +DEVICE_EMU_TYPE = 'emulated' +DEVICE_EMU_ADDRESS = '127.0.0.1' +DEVICE_EMU_PORT = '0' +DEVICE_EMU_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_UNDEFINED] try: from ._device_credentials import ( - DEVICE1_UUID, DEVICE1_TYPE, DEVICE1_ADDRESS, DEVICE1_PORT, DEVICE1_USERNAME, DEVICE1_PASSWORD, DEVICE1_HANDLER, - DEVICE1_DRIVERS) + DEVICE_INF_UUID, DEVICE_INF_TYPE, DEVICE_INF_ADDRESS, DEVICE_INF_PORT, DEVICE_INF_USERNAME, + DEVICE_INF_PASSWORD, DEVICE_INF_DRIVERS, DEVICE_INF_CONFIG_RULES) except ImportError: - DEVICE1_UUID = 'DEV1' - DEVICE1_TYPE = 'packet-router' - DEVICE1_ADDRESS = '127.0.0.1' - DEVICE1_PORT = '830' - DEVICE1_USERNAME = 'username' - DEVICE1_PASSWORD = 'password' - DEVICE1_HANDLER = 'default' - DEVICE1_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG] - -# Some example objects to be used by the tests - -# Helper methods -def config_rule(action, resource_key, resource_value): - return {'action': action, 'resource_key': resource_key, 'resource_value': resource_value} - -def endpoint_id(topology_id, device_id, endpoint_uuid): - return {'topology_id': deepcopy(topology_id), 'device_id': deepcopy(device_id), - 'endpoint_uuid': {'uuid': endpoint_uuid}} - -def endpoint(topology_id, device_id, endpoint_uuid, endpoint_type): - return {'endpoint_id': endpoint_id(topology_id, device_id, endpoint_uuid), 'endpoint_type': endpoint_type} + DEVICE_INF_UUID = 'DEV2' + DEVICE_INF_TYPE = 'packet-router' + DEVICE_INF_ADDRESS = '127.0.0.1' + DEVICE_INF_PORT = '830' + DEVICE_INF_USERNAME = 'username' + DEVICE_INF_PASSWORD = 'password' + DEVICE_INF_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG] + DEVICE_INF_CONFIG_RULES = [] ## use "deepcopy" to prevent propagating forced changes during tests CONTEXT_ID = {'context_uuid': {'uuid': DEFAULT_CONTEXT_UUID}} @@ -47,26 +41,41 @@ TOPOLOGY = { 'link_ids': [], } -DEVICE1_ID = {'device_uuid': {'uuid': DEVICE1_UUID}} -DEVICE1 = { - 'device_id': deepcopy(DEVICE1_ID), - 'device_type': DEVICE1_TYPE, +DEVICE_EMU_ID = {'device_uuid': {'uuid': DEVICE_EMU_UUID}} +DEVICE_EMU = { + 'device_id': deepcopy(DEVICE_EMU_ID), + 'device_type': DEVICE_EMU_TYPE, 'device_config': {'config_rules': []}, 'device_operational_status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED, - 'device_drivers': DEVICE1_DRIVERS, + 'device_drivers': DEVICE_EMU_DRIVERS, 'device_endpoints': [], } -DEVICE1_CONNECT_RULES = [ - config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/address', DEVICE1_ADDRESS ), - config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/port', DEVICE1_PORT ), - config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/username', DEVICE1_USERNAME), - config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/password', DEVICE1_PASSWORD), - config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/handler', DEVICE1_HANDLER ), +DEVICE_EMU_CONNECT_RULES = [ + config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/address', DEVICE_EMU_ADDRESS ), + config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/port', DEVICE_EMU_PORT ), ] -DEVICE1_CONFIG_RULES = [ +DEVICE_EMU_CONFIG_RULES = [ config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc1/value', 'value1'), config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc2/value', 'value2'), config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc3/value', 'value3'), ] + + +DEVICE_INF_ID = {'device_uuid': {'uuid': DEVICE_INF_UUID}} +DEVICE_INF = { + 'device_id': deepcopy(DEVICE_INF_ID), + 'device_type': DEVICE_INF_TYPE, + 'device_config': {'config_rules': []}, + 'device_operational_status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED, + 'device_drivers': DEVICE_INF_DRIVERS, + 'device_endpoints': [], +} + +DEVICE_INF_CONNECT_RULES = [ + config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/address', DEVICE_INF_ADDRESS ), + config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/port', DEVICE_INF_PORT ), + config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/username', DEVICE_INF_USERNAME), + config_rule(ConfigActionEnum.CONFIGACTION_SET, '_connect/password', DEVICE_INF_PASSWORD), +] diff --git a/src/device/tests/test_unitary.py b/src/device/tests/test_unitary.py index e4132171cfa1303246570fdb7e1db4d5d04f5682..ee4a0784eea01d490dbfd8ce3b2041f2214d4519 100644 --- a/src/device/tests/test_unitary.py +++ b/src/device/tests/test_unitary.py @@ -24,7 +24,9 @@ from device.service.driver_api.DriverInstanceCache import DriverInstanceCache from device.service.drivers import DRIVERS from monitoring.client.monitoring_client import MonitoringClient from .example_objects import ( - CONTEXT, DEVICE1, DEVICE1_CONFIG_RULES, DEVICE1_CONNECT_RULES, DEVICE1_ID, DEVICE1_UUID, TOPOLOGY, config_rule) + CONTEXT, TOPOLOGY, config_rule, + DEVICE_EMU, DEVICE_EMU_CONFIG_RULES, DEVICE_EMU_CONNECT_RULES, DEVICE_EMU_ID, DEVICE_EMU_UUID, + DEVICE_INF, DEVICE_INF_CONFIG_RULES, DEVICE_INF_CONNECT_RULES, DEVICE_INF_ID, DEVICE_INF_UUID) LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) @@ -96,7 +98,8 @@ def grpc_message_to_json_string(message): return str(MessageToDict( message, including_default_value_fields=True, preserving_proto_field_name=True, use_integers_for_enums=False)) -def test_device_add_configure( + +def test_prepare_environment( context_client : ContextClient, # pylint: disable=redefined-outer-name device_client : DeviceClient, # pylint: disable=redefined-outer-name device_service : DeviceService): # pylint: disable=redefined-outer-name @@ -104,11 +107,17 @@ def test_device_add_configure( context_client.SetContext(Context(**CONTEXT)) context_client.SetTopology(Topology(**TOPOLOGY)) + +def test_device_add_emulated_error_cases( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService): # pylint: disable=redefined-outer-name + with pytest.raises(grpc.RpcError) as e: - DEVICE1_WITH_EXTRA_RULES = copy.deepcopy(DEVICE1) - DEVICE1_WITH_EXTRA_RULES['device_config']['config_rules'].extend(DEVICE1_CONNECT_RULES) - DEVICE1_WITH_EXTRA_RULES['device_config']['config_rules'].extend(DEVICE1_CONFIG_RULES) - device_client.AddDevice(Device(**DEVICE1_WITH_EXTRA_RULES)) + DEVICE_EMU_WITH_EXTRA_RULES = copy.deepcopy(DEVICE_EMU) + DEVICE_EMU_WITH_EXTRA_RULES['device_config']['config_rules'].extend(DEVICE_EMU_CONNECT_RULES) + DEVICE_EMU_WITH_EXTRA_RULES['device_config']['config_rules'].extend(DEVICE_EMU_CONFIG_RULES) + device_client.AddDevice(Device(**DEVICE_EMU_WITH_EXTRA_RULES)) assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT msg_head = 'device.device_config.config_rules([' msg_tail = ']) is invalid; RPC method AddDevice only accepts connection Config Rules that should start '\ @@ -116,23 +125,46 @@ def test_device_add_configure( except_msg = str(e.value.details()) assert except_msg.startswith(msg_head) and except_msg.endswith(msg_tail) - DEVICE1_WITH_CONNECT_RULES = copy.deepcopy(DEVICE1) - DEVICE1_WITH_CONNECT_RULES['device_config']['config_rules'].extend(DEVICE1_CONNECT_RULES) - device_client.AddDevice(Device(**DEVICE1_WITH_CONNECT_RULES)) - driver : _Driver = device_service.driver_instance_cache.get(DEVICE1_UUID) # we know the driver exists now + +def test_device_add_emulated_correct( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService): # pylint: disable=redefined-outer-name + + DEVICE_EMU_WITH_CONNECT_RULES = copy.deepcopy(DEVICE_EMU) + DEVICE_EMU_WITH_CONNECT_RULES['device_config']['config_rules'].extend(DEVICE_EMU_CONNECT_RULES) + device_client.AddDevice(Device(**DEVICE_EMU_WITH_CONNECT_RULES)) + driver : _Driver = device_service.driver_instance_cache.get(DEVICE_EMU_UUID) # we know the driver exists now assert driver is not None - initial_config = device_client.GetInitialConfig(DeviceId(**DEVICE1_ID)) + +def test_device_get_emulated( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService): # pylint: disable=redefined-outer-name + + initial_config = device_client.GetInitialConfig(DeviceId(**DEVICE_EMU_ID)) LOGGER.info('initial_config = {:s}'.format(grpc_message_to_json_string(initial_config))) - device_data = context_client.GetDevice(DeviceId(**DEVICE1_ID)) + device_data = context_client.GetDevice(DeviceId(**DEVICE_EMU_ID)) LOGGER.info('device_data = {:s}'.format(grpc_message_to_json_string(device_data))) + +def test_device_configure_emulated( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService): # pylint: disable=redefined-outer-name + + driver : _Driver = device_service.driver_instance_cache.get(DEVICE_EMU_UUID) # we know the driver exists now + assert driver is not None + driver_config = driver.GetConfig() LOGGER.info('driver_config = {:s}'.format(str(driver_config))) assert len(driver_config) == 0 - device_client.ConfigureDevice(Device(**DEVICE1)) + DEVICE_EMU_WITH_CONFIG_RULES = copy.deepcopy(DEVICE_EMU) + DEVICE_EMU_WITH_CONFIG_RULES['device_config']['config_rules'].extend(DEVICE_EMU_CONFIG_RULES) + device_client.ConfigureDevice(Device(**DEVICE_EMU_WITH_CONFIG_RULES)) driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0)) LOGGER.info('driver_config = {:s}'.format(str(driver_config))) @@ -141,14 +173,14 @@ def test_device_add_configure( assert driver_config[1] == ('/dev/rsrc2/value', 'value2') assert driver_config[2] == ('/dev/rsrc3/value', 'value3') - DEVICE1_WITH = copy.deepcopy(DEVICE1) - CONFIG_RULES : List[Dict[str, Any]] = DEVICE1_WITH['device_config']['config_rules'] - CONFIG_RULES.clear() - CONFIG_RULES.append(config_rule(ConfigActionEnum.CONFIGACTION_DELETE, 'dev/rsrc1/value', '')) - CONFIG_RULES.append(config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc10/value', 'value10')) - CONFIG_RULES.append(config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc11/value', 'value11')) - CONFIG_RULES.append(config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc12/value', 'value12')) - device_client.ConfigureDevice(Device(**DEVICE1_WITH)) + DEVICE_EMU_WITH_CONFIG_RULES_2 = copy.deepcopy(DEVICE_EMU) + DEVICE_EMU_WITH_CONFIG_RULES_2['device_config']['config_rules'].extend([ + config_rule(ConfigActionEnum.CONFIGACTION_DELETE, 'dev/rsrc1/value', ''), + config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc10/value', 'value10'), + config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc11/value', 'value11'), + config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc12/value', 'value12'), + ]) + device_client.ConfigureDevice(Device(**DEVICE_EMU_WITH_CONFIG_RULES_2)) driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0)) LOGGER.info('driver_config = {:s}'.format(str(driver_config))) @@ -159,8 +191,7 @@ def test_device_add_configure( assert driver_config[3] == ('/dev/rsrc2/value', 'value2') assert driver_config[4] == ('/dev/rsrc3/value', 'value3') - device_data = context_client.GetDevice(DeviceId(**DEVICE1_ID)) - #LOGGER.info('device_data = {:s}'.format(grpc_message_to_json_string(device_data))) + device_data = context_client.GetDevice(DeviceId(**DEVICE_EMU_ID)) LOGGER.info('device_data.device_config.config_rules = \n{:s}'.format( '\n'.join([ '{:s} {:s} = {:s}'.format( @@ -168,219 +199,100 @@ def test_device_add_configure( for config_rule in device_data.device_config.config_rules ]))) - device_client.DeleteDevice(DeviceId(**DEVICE1_ID)) - driver : _Driver = device_service.driver_instance_cache.get(DEVICE1_UUID, {}) # we know the driver exists now + +def test_device_delete_emulated( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService): # pylint: disable=redefined-outer-name + + device_client.DeleteDevice(DeviceId(**DEVICE_EMU_ID)) + driver : _Driver = device_service.driver_instance_cache.get(DEVICE_EMU_UUID, {}) assert driver is None - raise Exception() - -#def test_add_device_wrong_attributes(device_client : DeviceClient): -# # should fail with device uuid is empty -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['device_id']['device_id']['uuid'] = '' -# device_client.AddDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'device.device_id.device_id.uuid() is out of range: '\ -# 'allow_empty(False) min_length(None) max_length(None) allowed_lengths(None).' -# assert e.value.details() == msg -# -# # should fail with device type is empty -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['device_type'] = '' -# device_client.AddDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'device.device_type() is out of range: '\ -# 'allow_empty(False) min_length(None) max_length(None) allowed_lengths(None).' -# assert e.value.details() == msg -# -# # should fail with wrong device operational status -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['devOperationalStatus'] = OperationalStatus.KEEP_STATE.value -# device_client.AddDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'Method(AddDevice) does not accept OperationalStatus(KEEP_STATE). '\ -# 'Permitted values for Method(AddDevice) are OperationalStatus([\'DISABLED\', \'ENABLED\']).' -# assert e.value.details() == msg -# -#def test_add_device_wrong_endpoint(device_client : DeviceClient): -# # should fail with unsupported endpoint context -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['endpointList'][0]['port_id']['topoId']['contextId']['contextUuid']['uuid'] = 'wrong-context' -# request = Device(**copy_device) -# device_client.AddDevice(request) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'Context(wrong-context) in Endpoint(#0) of '\ -# 'Context(admin)/Topology(admin)/Device(DEV1) mismatches acceptable Contexts({\'admin\'}). '\ -# 'Optionally, leave field empty to use predefined Context(admin).' -# assert e.value.details() == msg -# -# # should fail with unsupported endpoint topology -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['endpointList'][0]['port_id']['topoId']['topoId']['uuid'] = 'wrong-topo' -# device_client.AddDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'Context(admin)/Topology(wrong-topo) in Endpoint(#0) of '\ -# 'Context(admin)/Topology(admin)/Device(DEV1) mismatches acceptable Topologies({\'admin\'}). '\ -# 'Optionally, leave field empty to use predefined Topology(admin).' -# assert e.value.details() == msg -# -# # should fail with wrong endpoint device -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['endpointList'][0]['port_id']['dev_id']['device_id']['uuid'] = 'wrong-device' -# device_client.AddDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'Context(admin)/Topology(admin)/Device(wrong-device) in Endpoint(#0) of '\ -# 'Context(admin)/Topology(admin)/Device(DEV1) mismatches acceptable Devices({\'DEV1\'}). '\ -# 'Optionally, leave field empty to use predefined Device(DEV1).' -# assert e.value.details() == msg -# -# # should fail with endpoint port uuid is empty -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['endpointList'][0]['port_id']['port_id']['uuid'] = '' -# device_client.AddDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'endpoint_id[#0].port_id.uuid() is out of range: '\ -# 'allow_empty(False) min_length(None) max_length(None) allowed_lengths(None).' -# assert e.value.details() == msg -# -# # should fail with endpoint port type is empty -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['endpointList'][0]['port_type'] = '' -# device_client.AddDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'endpoint[#0].port_type() is out of range: '\ -# 'allow_empty(False) min_length(None) max_length(None) allowed_lengths(None).' -# assert e.value.details() == msg -# -# # should fail with duplicate port in device -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['endpointList'][1]['port_id']['port_id']['uuid'] = 'EP2' -# device_client.AddDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'Duplicated Context(admin)/Topology(admin)/Device(DEV1)/Port(EP2) in Endpoint(#1) of '\ -# 'Context(admin)/Topology(admin)/Device(DEV1).' -# assert e.value.details() == msg -# -#def test_add_device(device_client : DeviceClient): -# # should work -# validate_device_id(MessageToDict( -# device_client.AddDevice(Device(**DEVICE)), -# including_default_value_fields=True, preserving_proto_field_name=True, -# use_integers_for_enums=False)) -# -#def test_add_device_duplicate(device_client : DeviceClient): -# # should fail with device already exists -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# device_client.AddDevice(Device(**DEVICE)) -# assert e.value.code() == grpc.StatusCode.ALREADY_EXISTS -# msg = 'Context(admin)/Topology(admin)/Device(DEV1) already exists in the database.' -# assert e.value.details() == msg -# -#def test_delete_device_empty_uuid(device_client : DeviceClient): -# # should fail with device uuid is empty -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device_id = copy.deepcopy(DEVICE_ID) -# copy_device_id['device_id']['uuid'] = '' -# device_client.DeleteDevice(DeviceId(**copy_device_id)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'device_id.device_id.uuid() is out of range: '\ -# 'allow_empty(False) min_length(None) max_length(None) allowed_lengths(None).' -# assert e.value.details() == msg -# -#def test_delete_device_not_found(device_client : DeviceClient): -# # should fail with device not found -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device_id = copy.deepcopy(DEVICE_ID) -# copy_device_id['device_id']['uuid'] = 'wrong-device-id' -# device_client.DeleteDevice(DeviceId(**copy_device_id)) -# assert e.value.code() == grpc.StatusCode.NOT_FOUND -# msg = 'Context(admin)/Topology(admin)/Device(wrong-device-id) does not exist in the database.' -# assert e.value.details() == msg -# -#def test_delete_device(device_client : DeviceClient): -# # should work -# validate_empty(MessageToDict( -# device_client.DeleteDevice(DeviceId(**DEVICE_ID)), -# including_default_value_fields=True, preserving_proto_field_name=True, -# use_integers_for_enums=False)) -# -#def test_configure_device_empty_device_uuid(device_client : DeviceClient): -# # should fail with device uuid is empty -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['device_id']['device_id']['uuid'] = '' -# device_client.ConfigureDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'device.device_id.device_id.uuid() is out of range: '\ -# 'allow_empty(False) min_length(None) max_length(None) allowed_lengths(None).' -# assert e.value.details() == msg -# -#def test_configure_device_not_found(device_client : DeviceClient): -# # should fail with device not found -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['device_id']['device_id']['uuid'] = 'wrong-device-id' -# device_client.ConfigureDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.NOT_FOUND -# msg = 'Context(admin)/Topology(admin)/Device(wrong-device-id) does not exist in the database.' -# assert e.value.details() == msg -# -#def test_add_device_default_endpoint_context_topology_device(device_client : DeviceClient): -# # should work -# copy_device = copy.deepcopy(DEVICE) -# copy_device['endpointList'][0]['port_id']['topoId']['contextId']['contextUuid']['uuid'] = '' -# copy_device['endpointList'][0]['port_id']['topoId']['topoId']['uuid'] = '' -# copy_device['endpointList'][0]['port_id']['dev_id']['device_id']['uuid'] = '' -# validate_device_id(MessageToDict( -# device_client.AddDevice(Device(**copy_device)), -# including_default_value_fields=True, preserving_proto_field_name=True, -# use_integers_for_enums=False)) -# -#def test_configure_device_wrong_attributes(device_client : DeviceClient): -# # should fail with device type is wrong -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['device_type'] = 'wrong-type' -# device_client.ConfigureDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# msg = 'Device(DEV1) has Type(ROADM) in the database. Cannot be changed to Type(wrong-type).' -# assert e.value.details() == msg -# -# # should fail with endpoints cannot be modified -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# device_client.ConfigureDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT -# assert e.value.details() == 'Endpoints belonging to Device(DEV1) cannot be modified.' -# -# # should fail with any change detected -# with pytest.raises(grpc._channel._InactiveRpcError) as e: -# copy_device = copy.deepcopy(DEVICE) -# copy_device['device_config']['device_config'] = '' -# copy_device['devOperationalStatus'] = OperationalStatus.KEEP_STATE.value -# copy_device['endpointList'].clear() -# device_client.ConfigureDevice(Device(**copy_device)) -# assert e.value.code() == grpc.StatusCode.ABORTED -# msg = 'Any change has been requested for Device(DEV1). '\ -# 'Either specify a new configuration or a new device operational status.' -# assert e.value.details() == msg -# -#def test_configure_device(device_client : DeviceClient): -# # should work -# copy_device = copy.deepcopy(DEVICE) -# copy_device['device_config']['device_config'] = '<new_config/>' -# copy_device['devOperationalStatus'] = OperationalStatus.DISABLED.value -# copy_device['endpointList'].clear() -# validate_device_id(MessageToDict( -# device_client.ConfigureDevice(Device(**copy_device)), -# including_default_value_fields=True, preserving_proto_field_name=True, -# use_integers_for_enums=False)) + +def test_device_add_openconfig_error_cases( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService): # pylint: disable=redefined-outer-name + + with pytest.raises(grpc.RpcError) as e: + DEVICE_INF_WITH_EXTRA_RULES = copy.deepcopy(DEVICE_INF) + DEVICE_INF_WITH_EXTRA_RULES['device_config']['config_rules'].extend(DEVICE_INF_CONNECT_RULES) + DEVICE_INF_WITH_EXTRA_RULES['device_config']['config_rules'].extend(DEVICE_INF_CONFIG_RULES) + device_client.AddDevice(Device(**DEVICE_INF_WITH_EXTRA_RULES)) + assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT + msg_head = 'device.device_config.config_rules([' + msg_tail = ']) is invalid; RPC method AddDevice only accepts connection Config Rules that should start '\ + 'with "_connect/" tag. Others should be configured after adding the device.' + except_msg = str(e.value.details()) + assert except_msg.startswith(msg_head) and except_msg.endswith(msg_tail) + + +def test_device_add_openconfig_correct( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService): # pylint: disable=redefined-outer-name + + DEVICE_INF_WITH_CONNECT_RULES = copy.deepcopy(DEVICE_INF) + DEVICE_INF_WITH_CONNECT_RULES['device_config']['config_rules'].extend(DEVICE_INF_CONNECT_RULES) + device_client.AddDevice(Device(**DEVICE_INF_WITH_CONNECT_RULES)) + driver : _Driver = device_service.driver_instance_cache.get(DEVICE_INF_UUID) # we know the driver exists now + assert driver is not None + + +def test_device_get_openconfig( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService): # pylint: disable=redefined-outer-name + + initial_config = device_client.GetInitialConfig(DeviceId(**DEVICE_INF_ID)) + LOGGER.info('initial_config = {:s}'.format(grpc_message_to_json_string(initial_config))) + + device_data = context_client.GetDevice(DeviceId(**DEVICE_INF_ID)) + LOGGER.info('device_data = {:s}'.format(grpc_message_to_json_string(device_data))) + + +def test_device_configure_openconfig( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService): # pylint: disable=redefined-outer-name + + driver : _Driver = device_service.driver_instance_cache.get(DEVICE_INF_UUID) # we know the driver exists now + assert driver is not None + + raise NotImplementedError() + + #driver_config = driver.GetConfig() + #LOGGER.info('driver_config = {:s}'.format(str(driver_config))) + #assert len(driver_config) == 0 + + #DEVICE_INF_WITH_CONFIG_RULES = copy.deepcopy(DEVICE_INF) + #DEVICE_INF_WITH_CONFIG_RULES['device_config']['config_rules'].extend(DEVICE_INF_CONFIG_RULES) + #device_client.ConfigureDevice(Device(**DEVICE_INF_WITH_CONFIG_RULES)) + + #driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0)) + #LOGGER.info('driver_config = {:s}'.format(str(driver_config))) + #assert len(driver_config) == 5 + #assert driver_config[0] == ('/dev/rsrc10/value', 'value10') + #assert driver_config[1] == ('/dev/rsrc11/value', 'value11') + #assert driver_config[2] == ('/dev/rsrc12/value', 'value12') + #assert driver_config[3] == ('/dev/rsrc2/value', 'value2') + #assert driver_config[4] == ('/dev/rsrc3/value', 'value3') + + #device_data = context_client.GetDevice(DeviceId(**DEVICE_INF_ID)) + #LOGGER.info('device_data.device_config.config_rules = \n{:s}'.format( + # '\n'.join([ + # '{:s} {:s} = {:s}'.format( + # ConfigActionEnum.Name(config_rule.action), config_rule.resource_key, config_rule.resource_value) + # for config_rule in device_data.device_config.config_rules + # ]))) + + +def test_device_delete_openconfig( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService): # pylint: disable=redefined-outer-name + + device_client.DeleteDevice(DeviceId(**DEVICE_INF_ID)) + driver : _Driver = device_service.driver_instance_cache.get(DEVICE_INF_UUID, {}) + assert driver is None