diff --git a/scripts/run_tests_locally-device-ietf-actn.sh b/scripts/run_tests_locally-device-ietf-actn.sh index c694b6424ae83500720002b5a471bbb773586ce1..8e602b31d9465821dfd798e8038de9b78f7dedc6 100755 --- a/scripts/run_tests_locally-device-ietf-actn.sh +++ b/scripts/run_tests_locally-device-ietf-actn.sh @@ -20,5 +20,6 @@ cd $PROJECTDIR/src RCFILE=$PROJECTDIR/coverage/.coveragerc # Run unitary tests and analyze coverage of code at same time +# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0 coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ device/tests/test_unitary_ietf_actn.py diff --git a/src/device/requirements.in b/src/device/requirements.in index ece761571ec2ff9c3376b1062787d76047d71e7c..46f4a7518ad73d04f0d5a512bf95eb42688d42d3 100644 --- a/src/device/requirements.in +++ b/src/device/requirements.in @@ -16,6 +16,8 @@ anytree==2.8.0 APScheduler==3.10.1 cryptography==36.0.2 +deepdiff==6.7.* +deepmerge==1.1.* #fastcache==1.1.0 Jinja2==3.0.3 ncclient==0.6.13 diff --git a/src/device/service/drivers/ietf_actn/IetfActnDriver.py b/src/device/service/drivers/ietf_actn/IetfActnDriver.py index a419a002b14de7c2ffadc73e4ab31ef006e0faca..a33c403f3202ca5ee3025a7b7808ad53a89ede4a 100644 --- a/src/device/service/drivers/ietf_actn/IetfActnDriver.py +++ b/src/device/service/drivers/ietf_actn/IetfActnDriver.py @@ -45,7 +45,7 @@ class IetfActnDriver(_Driver): with self.__lock: if self.__started.is_set(): return True try: - self._rest_api_client.get('Check Credentials', '', []) + self._rest_api_client.get('Check Credentials', '') except requests.exceptions.Timeout: LOGGER.exception('Timeout exception checking connectivity') return False @@ -75,16 +75,23 @@ class IetfActnDriver(_Driver): for i, resource_key in enumerate(resource_keys): chk_string('resource_key[#{:d}]'.format(i), resource_key, allow_empty=False) - if resource_key == RESOURCE_SERVICES: - get_osu_tunnels(self._handler_osu_tunnel, results) - get_etht_services(self._handler_etht_service, results) - else: - # check if resource key is for a specific OSU tunnel or ETHT service, and get them accordingly - osu_tunnel_name, etht_service_name = parse_resource_key(resource_key) - if osu_tunnel_name is not None: - get_osu_tunnels(self._handler_osu_tunnel, results, osu_tunnel_name=osu_tunnel_name) - if etht_service_name is not None: - get_etht_services(self._handler_etht_service, results, etht_service_name=etht_service_name) + try: + _results = list() + + if resource_key == RESOURCE_SERVICES: + get_osu_tunnels(self._handler_osu_tunnel, _results) + get_etht_services(self._handler_etht_service, _results) + else: + # check if resource key is for a specific OSU tunnel or ETHT service, and get them accordingly + osu_tunnel_name, etht_service_name = parse_resource_key(resource_key) + if osu_tunnel_name is not None: + get_osu_tunnels(self._handler_osu_tunnel, _results, osu_tunnel_name=osu_tunnel_name) + if etht_service_name is not None: + get_etht_services(self._handler_etht_service, _results, etht_service_name=etht_service_name) + + results.extend(_results) + except Exception as e: + results.append((resource_key, e)) return results @@ -95,16 +102,23 @@ class IetfActnDriver(_Driver): with self.__lock: for resource_key, resource_value in resources: LOGGER.info('resource: key({:s}) => value({:s})'.format(str(resource_key), str(resource_value))) - if isinstance(resource_value, str): resource_value = json.loads(resource_value) - osu_tunnel_name, etht_service_name = parse_resource_key(resource_key) + try: + _results = list() - if osu_tunnel_name is not None: - succeeded = self._handler_osu_tunnel.update(resource_value) - results.extend(succeeded) + if isinstance(resource_value, str): resource_value = json.loads(resource_value) + osu_tunnel_name, etht_service_name = parse_resource_key(resource_key) - if etht_service_name is not None: - succeeded = self._handler_etht_service.update(resource_value) - results.extend(succeeded) + if osu_tunnel_name is not None: + succeeded = self._handler_osu_tunnel.update(resource_value) + _results.append(succeeded) + + if etht_service_name is not None: + succeeded = self._handler_etht_service.update(resource_value) + _results.append(succeeded) + + results.extend(_results) + except Exception as e: + results.append(e) return results @@ -115,16 +129,23 @@ class IetfActnDriver(_Driver): with self.__lock: for resource_key, resource_value in resources: LOGGER.info('resource: key({:s}) => value({:s})'.format(str(resource_key), str(resource_value))) - if isinstance(resource_value, str): resource_value = json.loads(resource_value) - osu_tunnel_name, etht_service_name = parse_resource_key(resource_key) + try: + _results = list() + + if isinstance(resource_value, str): resource_value = json.loads(resource_value) + osu_tunnel_name, etht_service_name = parse_resource_key(resource_key) - if osu_tunnel_name is not None: - succeeded = self._handler_osu_tunnel.delete(osu_tunnel_name) - results.extend(succeeded) + if osu_tunnel_name is not None: + succeeded = self._handler_osu_tunnel.delete(osu_tunnel_name) + _results.append(succeeded) + + if etht_service_name is not None: + succeeded = self._handler_etht_service.delete(etht_service_name) + _results.append(succeeded) - if etht_service_name is not None: - succeeded = self._handler_etht_service.delete(etht_service_name) - results.extend(succeeded) + results.extend(_results) + except Exception as e: + results.append(e) return results diff --git a/src/device/service/drivers/ietf_actn/Tools.py b/src/device/service/drivers/ietf_actn/Tools.py index 736911fdd5db06976e2a3dd3c42c77a6d05db791..52f5b15c4d9f0c723b7e4eeeea4537d23c5bf758 100644 --- a/src/device/service/drivers/ietf_actn/Tools.py +++ b/src/device/service/drivers/ietf_actn/Tools.py @@ -39,7 +39,7 @@ def get_osu_tunnels( for osu_tunnel in osu_tunnels: osu_tunnel_name = osu_tunnel['name'] resource_key = '/osu_tunnels/osu_tunnel[{:s}]'.format(osu_tunnel_name) - results.extend((resource_key, osu_tunnel)) + results.append((resource_key, osu_tunnel)) def get_etht_services( handler_etht_service : EthtServiceHandler, results : List[Tuple[str, Union[Any, None, Exception]]], @@ -49,4 +49,4 @@ def get_etht_services( for etht_service in etht_services: etht_service_name = etht_service['name'] resource_key = '/etht_services/etht_service[{:s}]'.format(etht_service_name) - results.extend((resource_key, etht_service)) + results.append((resource_key, etht_service)) diff --git a/src/device/service/drivers/ietf_actn/examples/eth_svc_1.json b/src/device/service/drivers/ietf_actn/examples/eth_svc_1.json deleted file mode 100644 index 840092429c28c42077d502ebb3860e4c99113e72..0000000000000000000000000000000000000000 --- a/src/device/service/drivers/ietf_actn/examples/eth_svc_1.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "ietf-eth-tran-service:etht-svc": { - "etht-svc-instances": [ - { - "etht-svc-name": "etht_service_1", - "etht-svc-title": "ETHT_SVC_1", - "etht-svc-type": "op-mp2mp-svc?", - "source-endpoints": { - "source-endpoint": [ - { - "node-id": "10.0.10.1", - "tp-id": "200", - "protection-role": "work?", - "layer-specific": { - "access-type": "port" - }, - "is-extendable": false, - "is-terminal": true, - "static-route-list": [ - { - "destination": "128.32.10.5", - "destination-mask": 24, - "next-hop": "128.32.33.5" - }, - { - "destination": "128.32.20.5", - "destination-mask": 24, - "next-hop": "128.32.33.5" - } - ], - "outer-tag": { - "tag-type": "ietf-eth-tran-types:classify-c-vlan", - "vlan-value": 21 - }, - "service-classification-type": "ietf-eth-tran-type:vlan-classification", - "ingress-egress-bandwidth-profile" : { - "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", - "CIR": 10000000, - "EIR": 10000000 - } - } - ] - }, - "destination-endpoints": { - "destination-endpoint": [ - { - "node-id": "10.0.30.1", - "tp-id": "200", - "protection-role": "work?", - "layer-specific": { - "access-type": "port" - }, - "is-extendable": false, - "is-terminal": true, - "static-route-list": [ - { - "destination": "172.1.101.22", - "destination-mask": 24, - "next-hop": "172.10.33.5" - } - ], - "outer-tag": { - "tag-type": "ietf-eth-tran-types:classify-c-vlan", - "vlan-value": 101 - }, - "service-classification-type": "ietf-eth-tran-type:vlan-classification", - "ingress-egress-bandwidth-profile" : { - "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", - "CIR": 10000000, - "EIR": 10000000 - } - } - ] - }, - "svc-tunnel": [ - { - "tunnel-name": "osu_tunnel_1" - } - ], - "optimizations": { - "optimization-metric": [ - { - "metric-role": "work?", - "metric-type": "ietf-te-types:path-metric-te" - } - ] - } - } - ] - } -} \ No newline at end of file diff --git a/src/device/service/drivers/ietf_actn/examples/eth_svc_2.json b/src/device/service/drivers/ietf_actn/examples/eth_svc_2.json deleted file mode 100644 index 74ca61d0399e4a2912ac280afc2eef9a50a235cf..0000000000000000000000000000000000000000 --- a/src/device/service/drivers/ietf_actn/examples/eth_svc_2.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "ietf-eth-tran-service:etht-svc": { - "etht-svc-instances": [ - { - "etht-svc-name": "etht_service_2", - "etht-svc-title": "ETHT_SVC_2", - "etht-svc-type": "op-p2mp-svc?", - "source-endpoints": { - "source-endpoint": [ - { - "node-id": "10.0.10.1", - "tp-id": "200", - "protection-role": "work?", - "layer-specific": { - "access-type": "port" - }, - "is-extendable": false, - "is-terminal": true, - "static-route-list": [ - { - "destination": "128.32.10.5", - "destination-mask": 24, - "next-hop": "128.32.33.5" - }, - { - "destination": "128.32.20.5", - "destination-mask": 24, - "next-hop": "128.32.33.5" - } - ], - "outer-tag": { - "tag-type": "ietf-eth-tran-types:classify-c-vlan", - "vlan-value": 31 - }, - "service-classification-type": "ietf-eth-tran-type:vlan-classification", - "ingress-egress-bandwidth-profile" : { - "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", - "CIR": 10000000, - "EIR": 10000000 - } - } - ] - }, - "destination-endpoints": { - "destination-endpoint": [ - { - "node-id": "10.0.30.1", - "tp-id": "200", - "protection-role": "work?", - "layer-specific": { - "access-type": "port" - }, - "is-extendable": false, - "is-terminal": true, - "static-route-list": [ - { - "destination": "172.1.101.22", - "destination-mask": 24, - "next-hop": "172.10.33.5" - } - ], - "outer-tag": { - "tag-type": "ietf-eth-tran-types:classify-c-vlan", - "vlan-value": 201 - }, - "service-classification-type": "ietf-eth-tran-type:vlan-classification", - "ingress-egress-bandwidth-profile" : { - "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", - "CIR": 10000000, - "EIR": 10000000 - } - } - ] - }, - "svc-tunnel": [ - { - "tunnel-name": "osu_tunnel_2" - } - ], - "optimizations": { - "optimization-metric": [ - { - "metric-role": "work?", - "metric-type": "ietf-te-types:path-metric-te" - } - ] - } - } - ] - } -} \ No newline at end of file diff --git a/src/device/service/drivers/ietf_actn/examples/osu_tunnel_1.json b/src/device/service/drivers/ietf_actn/examples/osu_tunnel_1.json deleted file mode 100644 index 728450b92883ed13916847577bbd0948607ff943..0000000000000000000000000000000000000000 --- a/src/device/service/drivers/ietf_actn/examples/osu_tunnel_1.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "ietf-te:tunnel": [ - { - "name": "osu_tunnel_1", - "title": "OSU_TUNNEL_1", - "admin-state": "ietf-te-types:tunnel-admin-state-up", - "delay": 20, - "te-bandwidth": { - "layer": "odu", - "odu-type": "osuflex", - "number": 1 - }, - "bidirectional": true, - "destination-endpoints": { - "destination-endpoint": [ - { - "node-id": "10.0.30.1", - "tp-id": "200", - "ttp-channel-name": "och:1-odu2:1-oduflex:3-osuflex:1?", - "protection-role": "work" - } - ] - }, - "source-endpoints": { - "source-endpoint": [ - { - "node-id": "10.0.10.1", - "tp-id": "200", - "ttp-channel-name": "och:1-odu2:1-oduflex:1-osuflex:2?", - "protection-role": "work" - } - ] - }, - "restoration": { - "restoration-type": "ietf-te-types:lsp-restoration-not-applicable", - "restoration-lock": false - }, - "protection": { - "protection-type": "ietf-te-types:lsp-protection-unprotected", - "protection-reversion-disable": true - } - } - ] -} \ No newline at end of file diff --git a/src/device/service/drivers/ietf_actn/examples/osu_tunnel_2.json b/src/device/service/drivers/ietf_actn/examples/osu_tunnel_2.json deleted file mode 100644 index 4e6966b8f53ab9ef87f930e2c7f6d9db88365ff9..0000000000000000000000000000000000000000 --- a/src/device/service/drivers/ietf_actn/examples/osu_tunnel_2.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "ietf-te:tunnel": [ - { - "name": "osu_tunnel_2", - "title": "OSU_TUNNEL_2", - "admin-state": "ietf-te-types:tunnel-admin-state-up", - "delay": 20, - "te-bandwidth": { - "layer": "odu", - "odu-type": "osuflex", - "number": 1 - }, - "bidirectional": true, - "destination-endpoints": { - "destination-endpoint": [ - { - "node-id": "10.0.30.1", - "tp-id": "200", - "ttp-channel-name": "och:1-odu2:1-oduflex:3-osuflex:1?", - "protection-role": "work" - } - ] - }, - "source-endpoints": { - "source-endpoint": [ - { - "node-id": "10.0.10.1", - "tp-id": "200", - "ttp-channel-name": "och:1-odu2:1-oduflex:1-osuflex:2?", - "protection-role": "work" - } - ] - }, - "restoration": { - "restoration-type": "ietf-te-types:lsp-restoration-not-applicable", - "restoration-lock": false - }, - "protection": { - "protection-type": "ietf-te-types:lsp-protection-unprotected", - "protection-reversion-disable": true - } - } - ] -} \ No newline at end of file diff --git a/src/device/service/drivers/ietf_actn/handlers/EthtServiceHandler.py b/src/device/service/drivers/ietf_actn/handlers/EthtServiceHandler.py index ac9a966337c7d5d718f946bad1090113ee99f5b5..230d13797f224931f657aa32c83091f4eb1c1d63 100644 --- a/src/device/service/drivers/ietf_actn/handlers/EthtServiceHandler.py +++ b/src/device/service/drivers/ietf_actn/handlers/EthtServiceHandler.py @@ -109,37 +109,43 @@ def compose_etht_service( class EthtServiceHandler: def __init__(self, rest_api_client : RestApiClient) -> None: self._rest_api_client = rest_api_client - self._object_name = 'EthtService' - self._subpath_url = '/ietf-eth-tran-service:etht-svc' - - def _rest_api_get(self, filters : List[Tuple[str, str]]) -> Union[Dict, List]: + self._object_name = 'EthtService' + self._subpath_root = '/ietf-eth-tran-service:etht-svc' + self._subpath_item = self._subpath_root + '/etht-svc-instances="{etht_service_name:s}"' + + def _rest_api_get(self, etht_service_name : Optional[str] = None) -> Union[Dict, List]: + if etht_service_name is None: + subpath_url = self._subpath_root + else: + subpath_url = self._subpath_item.format(etht_service_name=etht_service_name) return self._rest_api_client.get( - self._object_name, self._subpath_url, filters, expected_http_status={HTTP_STATUS_OK} + self._object_name, subpath_url, expected_http_status={HTTP_STATUS_OK} ) def _rest_api_update(self, data : Dict) -> bool: return self._rest_api_client.update( - self._object_name, self._subpath_url, data, expected_http_status={HTTP_STATUS_CREATED} + self._object_name, self._subpath_root, data, expected_http_status={HTTP_STATUS_CREATED} ) - def _rest_api_delete(self, filters : List[Tuple[str, str]]) -> bool: + def _rest_api_delete(self, etht_service_name : str) -> bool: + if etht_service_name is None: raise Exception('etht_service_name is None') + subpath_url = self._subpath_item.format(etht_service_name=etht_service_name) return self._rest_api_client.delete( - self._object_name, self._subpath_url, filters, expected_http_status={HTTP_STATUS_NO_CONTENT} + self._object_name, subpath_url, expected_http_status={HTTP_STATUS_NO_CONTENT} ) def get(self, etht_service_name : Optional[str] = None) -> Union[Dict, List]: - filters = [] if etht_service_name is None else [('etht-svc-name', etht_service_name)] - data = self._rest_api_get(filters) + data = self._rest_api_get(etht_service_name=etht_service_name) - if not isinstance(data, dict): return ValueError('data should be a dict') + if not isinstance(data, dict): raise ValueError('data should be a dict') if 'ietf-eth-tran-service:etht-svc' not in data: - return ValueError('data does not contain key "ietf-eth-tran-service:etht-svc"') + raise ValueError('data does not contain key "ietf-eth-tran-service:etht-svc"') data = data['ietf-eth-tran-service:etht-svc'] if 'etht-svc-instances' not in data: - return ValueError('data["ietf-eth-tran-service:etht-svc"] does not contain key "etht-svc-instances"') + raise ValueError('data["ietf-eth-tran-service:etht-svc"] does not contain key "etht-svc-instances"') data = data['etht-svc-instances'] if not isinstance(data, list): - return ValueError('data["ietf-eth-tran-service:etht-svc"]["etht-svc-instances"] should be a list') + raise ValueError('data["ietf-eth-tran-service:etht-svc"]["etht-svc-instances"] should be a list') etht_services : List[Dict] = list() for item in data: @@ -170,7 +176,7 @@ class EthtServiceHandler: 'src_tp_id' : src_endpoint['tp-id'], 'src_vlan_tag' : src_endpoint['outer-tag']['vlan-value'], 'src_static_routes': [ - (static_route['destination'], static_route['destination-mask'], static_route['next-hop']) + [static_route['destination'], static_route['destination-mask'], static_route['next-hop']] for static_route in src_endpoint.get('static-route-list', list()) ], @@ -178,7 +184,7 @@ class EthtServiceHandler: 'dst_tp_id' : dst_endpoint['tp-id'], 'dst_vlan_tag' : dst_endpoint['outer-tag']['vlan-value'], 'dst_static_routes': [ - (static_route['destination'], static_route['destination-mask'], static_route['next-hop']) + [static_route['destination'], static_route['destination-mask'], static_route['next-hop']] for static_route in dst_endpoint.get('static-route-list', list()) ], } @@ -212,5 +218,4 @@ class EthtServiceHandler: return self._rest_api_update(data) def delete(self, etht_service_name : str) -> bool: - filters = [('etht-svc-name', etht_service_name)] - return self._rest_api_delete(filters) + return self._rest_api_delete(etht_service_name) diff --git a/src/device/service/drivers/ietf_actn/handlers/OsuTunnelHandler.py b/src/device/service/drivers/ietf_actn/handlers/OsuTunnelHandler.py index 960ad70d782db3b6b420067cf46491ea1870dae7..bcecdf89e41c1cbb7284dbc6f7f08e1f03329c91 100644 --- a/src/device/service/drivers/ietf_actn/handlers/OsuTunnelHandler.py +++ b/src/device/service/drivers/ietf_actn/handlers/OsuTunnelHandler.py @@ -13,7 +13,7 @@ # limitations under the License. import enum, logging -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Union from .RestApiClient import HTTP_STATUS_CREATED, HTTP_STATUS_NO_CONTENT, HTTP_STATUS_OK, RestApiClient LOGGER = logging.getLogger(__name__) @@ -83,32 +83,38 @@ def compose_osu_tunnel( class OsuTunnelHandler: def __init__(self, rest_api_client : RestApiClient) -> None: self._rest_api_client = rest_api_client - self._object_name = 'OsuTunnel' - self._subpath_url = '/ietf-te:te/tunnels' - - def _rest_api_get(self, filters : List[Tuple[str, str]]) -> Union[Dict, List]: + self._object_name = 'OsuTunnel' + self._subpath_root = '/ietf-te:te/tunnels' + self._subpath_item = self._subpath_root + '/tunnel="{osu_tunnel_name:s}"' + + def _rest_api_get(self, osu_tunnel_name : Optional[str] = None) -> Union[Dict, List]: + if osu_tunnel_name is None: + subpath_url = self._subpath_root + else: + subpath_url = self._subpath_item.format(osu_tunnel_name=osu_tunnel_name) return self._rest_api_client.get( - self._object_name, self._subpath_url, filters, expected_http_status={HTTP_STATUS_OK} + self._object_name, subpath_url, expected_http_status={HTTP_STATUS_OK} ) def _rest_api_update(self, data : Dict) -> bool: return self._rest_api_client.update( - self._object_name, self._subpath_url, data, expected_http_status={HTTP_STATUS_CREATED} + self._object_name, self._subpath_root, data, expected_http_status={HTTP_STATUS_CREATED} ) - def _rest_api_delete(self, filters : List[Tuple[str, str]]) -> bool: + def _rest_api_delete(self, osu_tunnel_name : str) -> bool: + if osu_tunnel_name is None: raise Exception('osu_tunnel_name is None') + subpath_url = self._subpath_item.format(osu_tunnel_name=osu_tunnel_name) return self._rest_api_client.delete( - self._object_name, self._subpath_url, filters, expected_http_status={HTTP_STATUS_NO_CONTENT} + self._object_name, subpath_url, expected_http_status={HTTP_STATUS_NO_CONTENT} ) def get(self, osu_tunnel_name : Optional[str] = None) -> Union[Dict, List]: - filters = [] if osu_tunnel_name is None else [('name', osu_tunnel_name)] - data = self._rest_api_get(filters) + data = self._rest_api_get(osu_tunnel_name=osu_tunnel_name) - if not isinstance(data, dict): return ValueError('data should be a dict') - if 'ietf-te:tunnel' not in data: return ValueError('data does not contain key "ietf-te:tunnel"') + if not isinstance(data, dict): raise ValueError('data should be a dict') + if 'ietf-te:tunnel' not in data: raise ValueError('data does not contain key "ietf-te:tunnel"') data = data['ietf-te:tunnel'] - if not isinstance(data, list): return ValueError('data[ietf-te:tunnel] should be a list') + if not isinstance(data, list): raise ValueError('data[ietf-te:tunnel] should be a list') osu_tunnels : List[Dict] = list() for item in data: @@ -167,5 +173,4 @@ class OsuTunnelHandler: return self._rest_api_update(data) def delete(self, osu_tunnel_name : str) -> bool: - filters = [('name', osu_tunnel_name)] - return self._rest_api_delete(filters) + return self._rest_api_delete(osu_tunnel_name) diff --git a/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py b/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py index 25bc9fc712968d76be0c702ac9fe4247a8eaf963..1eed066b90c90c7509674c92f8b13e8feefa3513 100644 --- a/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py +++ b/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py @@ -14,7 +14,7 @@ import copy, json, logging, requests from requests.auth import HTTPBasicAuth -from typing import Any, Dict, List, Set, Tuple, Union +from typing import Any, Dict, List, Set, Union LOGGER = logging.getLogger(__name__) @@ -49,70 +49,56 @@ class RestApiClient: self._verify = bool(settings.get('verify', DEFAULT_VERIFY)) def get( - self, object_name : str, url : str, filters : List[Tuple[str, str]], + self, object_name : str, url : str, expected_http_status : Set[int] = {HTTP_STATUS_OK} ) -> Union[Dict, List]: - str_filters = ''.join([ - '[{:s}={:s}]'.format(filter_field, filter_value) - for filter_field, filter_value in filters - ]) - MSG = 'Get {:s}({:s})' - LOGGER.info(MSG.format(str(object_name), str(str_filters))) + LOGGER.info(MSG.format(str(object_name), str(url))) response = requests.get( - self._base_url + url + str_filters, + self._base_url + url, timeout=self._timeout, verify=self._verify, auth=self._auth ) - LOGGER.info(' Response: {:s}'.format(str(response))) + LOGGER.info(' Response[{:s}]: {:s}'.format(str(response.status_code), str(response.content))) if response.status_code in expected_http_status: return json.loads(response.content) MSG = 'Could not get {:s}({:s}): status_code={:s} reply={:s}' - msg = MSG.format(str(object_name), str(str_filters), str(response.status_code), str(response)) - LOGGER.error(msg) - return Exception(msg) + raise Exception(MSG.format(str(object_name), str(url), str(response.status_code), str(response))) def update( self, object_name : str, url : str, data : Dict, headers : Dict[str, Any] = dict(), expected_http_status : Set[int] = HTTP_OK_CODES - ) -> bool: + ) -> None: headers = copy.deepcopy(headers) if 'content-type' not in {header_name.lower() for header_name in headers.keys()}: headers.update({'content-type': 'application/json'}) - MSG = 'Create/Update {:s}({:s})' - LOGGER.info(MSG.format(str(object_name), str(data))) + MSG = 'Create/Update {:s}({:s}, {:s})' + LOGGER.info(MSG.format(str(object_name), str(url), str(data))) response = requests.post( self._base_url + url, data=json.dumps(data), headers=headers, timeout=self._timeout, verify=self._verify, auth=self._auth ) - LOGGER.info(' Response: {:s}'.format(str(response))) + LOGGER.info(' Response[{:s}]: {:s}'.format(str(response.status_code), str(response.content))) - if response.status_code in expected_http_status: return True + if response.status_code in expected_http_status: return - MSG = 'Could not create/update {:s}({:s}): status_code={:s} reply={:s}' - LOGGER.error(MSG.format(str(object_name), str(data), str(response.status_code), str(response))) - return False + MSG = 'Could not create/update {:s}({:s}, {:s}): status_code={:s} reply={:s}' + raise Exception(MSG.format(str(object_name), str(url), str(data), str(response.status_code), str(response))) def delete( - self, object_name : str, url : str, filters : List[Tuple[str, str]], + self, object_name : str, url : str, expected_http_status : Set[int] = HTTP_OK_CODES - ) -> bool: - str_filters = ''.join([ - '[{:s}={:s}]'.format(filter_field, filter_value) - for filter_field, filter_value in filters - ]) - + ) -> None: MSG = 'Delete {:s}({:s})' - LOGGER.info(MSG.format(str(object_name), str(str_filters))) + LOGGER.info(MSG.format(str(object_name), str(url))) response = requests.delete( - self._base_url + url + str_filters, + self._base_url + url, timeout=self._timeout, verify=self._verify, auth=self._auth ) - LOGGER.info(' Response: {:s}'.format(str(response))) + LOGGER.info(' Response[{:s}]: {:s}'.format(str(response.status_code), str(response.content))) - if response.status_code in expected_http_status: return True + if response.status_code in expected_http_status: return MSG = 'Could not delete {:s}({:s}): status_code={:s} reply={:s}' - LOGGER.error(MSG.format(str(object_name), str(str_filters), str(response.status_code), str(response))) - return False + raise Exception(MSG.format(str(object_name), str(url), str(response.status_code), str(response))) diff --git a/src/device/tests/data/ietf_actn/config_rules.json b/src/device/tests/data/ietf_actn/config_rules.json new file mode 100644 index 0000000000000000000000000000000000000000..d73a68674731cf4af7321044d345470c03d68134 --- /dev/null +++ b/src/device/tests/data/ietf_actn/config_rules.json @@ -0,0 +1,32 @@ +[ + {"action": 1, "custom": {"resource_key": "/osu_tunnels/osu_tunnel[osu_tunnel_1]", "resource_value": { + "name": "osu_tunnel_1", "odu_type": "osuflex", "osuflex_number": 40, "bidirectional": true, "delay": 20, + "src_node_id": "10.0.10.1", "src_tp_id": "200", "src_ttp_channel_name": "och:1-odu2:1-oduflex:1-osuflex:2", + "dst_node_id": "10.0.30.1", "dst_tp_id": "200", "dst_ttp_channel_name": "och:1-odu2:1-oduflex:3-osuflex:1" + }}}, + {"action": 1, "custom": {"resource_key": "/osu_tunnels/osu_tunnel[osu_tunnel_2]", "resource_value": { + "name": "osu_tunnel_2", "odu_type": "osuflex", "osuflex_number": 40, "bidirectional": true, "delay": 20, + "src_node_id": "10.0.10.1", "src_tp_id": "200", "src_ttp_channel_name": "och:1-odu2:1-oduflex:1-osuflex:2", + "dst_node_id": "10.0.30.1", "dst_tp_id": "200", "dst_ttp_channel_name": "och:1-odu2:1-oduflex:3-osuflex:1" + }}}, + {"action": 1, "custom": {"resource_key": "/etht_services/etht_service[etht_service_1]", "resource_value": { + "name": "etht_service_1", "osu_tunnel_name": "osu_tunnel_1", "service_type": "op-mp2mp-svc", + "src_node_id": "10.0.10.1", "src_tp_id": "200", "src_vlan_tag": 21, "src_static_routes": [ + ["128.32.10.5", 24, "128.32.33.5"], + ["128.32.20.5", 24, "128.32.33.5"] + ], + "dst_node_id": "10.0.30.1", "dst_tp_id": "200", "dst_vlan_tag": 101, "dst_static_routes": [ + ["172.1.101.22", 24, "172.10.33.5"] + ] + }}}, + {"action": 1, "custom": {"resource_key": "/etht_services/etht_service[etht_service_2]", "resource_value": { + "name": "etht_service_2", "osu_tunnel_name": "osu_tunnel_2", "service_type": "op-mp2mp-svc", + "src_node_id": "10.0.10.1", "src_tp_id": "200", "src_vlan_tag": 31, "src_static_routes": [ + ["128.32.10.5", 24, "128.32.33.5"], + ["128.32.20.5", 24, "128.32.33.5"] + ], + "dst_node_id": "10.0.30.1", "dst_tp_id": "200", "dst_vlan_tag": 201, "dst_static_routes": [ + ["172.1.101.22", 24, "172.10.33.5"] + ] + }}} +] diff --git a/src/device/tests/data/ietf_actn/deconfig_rules.json b/src/device/tests/data/ietf_actn/deconfig_rules.json new file mode 100644 index 0000000000000000000000000000000000000000..f18e5fb2db8b4d077ea68a499d61577030ce7f48 --- /dev/null +++ b/src/device/tests/data/ietf_actn/deconfig_rules.json @@ -0,0 +1,14 @@ +[ + {"action": 2, "custom": {"resource_key": "/osu_tunnels/osu_tunnel[osu_tunnel_1]", "resource_value": { + "name": "osu_tunnel_1" + }}}, + {"action": 2, "custom": {"resource_key": "/osu_tunnels/osu_tunnel[osu_tunnel_2]", "resource_value": { + "name": "osu_tunnel_2" + }}}, + {"action": 2, "custom": {"resource_key": "/etht_services/etht_service[etht_service_1]", "resource_value": { + "name": "etht_service_1" + }}}, + {"action": 2, "custom": {"resource_key": "/etht_services/etht_service[etht_service_2]", "resource_value": { + "name": "etht_service_2" + }}} +] diff --git a/src/device/tests/data/ietf_actn/expected_etht_services.json b/src/device/tests/data/ietf_actn/expected_etht_services.json new file mode 100644 index 0000000000000000000000000000000000000000..d9f41052692936fd5fffd855b1ba3f3e0478a3b6 --- /dev/null +++ b/src/device/tests/data/ietf_actn/expected_etht_services.json @@ -0,0 +1,176 @@ +{ + "ietf-eth-tran-service:etht-svc": { + "etht-svc-instances": [ + { + "etht-svc-name": "etht_service_1", + "etht-svc-title": "ETHT_SERVICE_1", + "etht-svc-type": "op-mp2mp-svc", + "source-endpoints": { + "source-endpoint": [ + { + "node-id": "10.0.10.1", + "tp-id": "200", + "protection-role": "work", + "layer-specific": { + "access-type": "port" + }, + "is-extendable": false, + "is-terminal": true, + "static-route-list": [ + { + "destination": "128.32.10.5", + "destination-mask": 24, + "next-hop": "128.32.33.5" + }, + { + "destination": "128.32.20.5", + "destination-mask": 24, + "next-hop": "128.32.33.5" + } + ], + "outer-tag": { + "tag-type": "ietf-eth-tran-types:classify-c-vlan", + "vlan-value": 21 + }, + "service-classification-type": "ietf-eth-tran-type:vlan-classification", + "ingress-egress-bandwidth-profile" : { + "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", + "CIR": 10000000, + "EIR": 10000000 + } + } + ] + }, + "destination-endpoints": { + "destination-endpoint": [ + { + "node-id": "10.0.30.1", + "tp-id": "200", + "protection-role": "work", + "layer-specific": { + "access-type": "port" + }, + "is-extendable": false, + "is-terminal": true, + "static-route-list": [ + { + "destination": "172.1.101.22", + "destination-mask": 24, + "next-hop": "172.10.33.5" + } + ], + "outer-tag": { + "tag-type": "ietf-eth-tran-types:classify-c-vlan", + "vlan-value": 101 + }, + "service-classification-type": "ietf-eth-tran-type:vlan-classification", + "ingress-egress-bandwidth-profile" : { + "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", + "CIR": 10000000, + "EIR": 10000000 + } + } + ] + }, + "svc-tunnel": [ + { + "tunnel-name": "osu_tunnel_1" + } + ], + "optimizations": { + "optimization-metric": [ + { + "metric-role": "work", + "metric-type": "ietf-te-types:path-metric-te" + } + ] + } + }, + { + "etht-svc-name": "etht_service_2", + "etht-svc-title": "ETHT_SERVICE_2", + "etht-svc-type": "op-mp2mp-svc", + "source-endpoints": { + "source-endpoint": [ + { + "node-id": "10.0.10.1", + "tp-id": "200", + "protection-role": "work", + "layer-specific": { + "access-type": "port" + }, + "is-extendable": false, + "is-terminal": true, + "static-route-list": [ + { + "destination": "128.32.10.5", + "destination-mask": 24, + "next-hop": "128.32.33.5" + }, + { + "destination": "128.32.20.5", + "destination-mask": 24, + "next-hop": "128.32.33.5" + } + ], + "outer-tag": { + "tag-type": "ietf-eth-tran-types:classify-c-vlan", + "vlan-value": 31 + }, + "service-classification-type": "ietf-eth-tran-type:vlan-classification", + "ingress-egress-bandwidth-profile" : { + "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", + "CIR": 10000000, + "EIR": 10000000 + } + } + ] + }, + "destination-endpoints": { + "destination-endpoint": [ + { + "node-id": "10.0.30.1", + "tp-id": "200", + "protection-role": "work", + "layer-specific": { + "access-type": "port" + }, + "is-extendable": false, + "is-terminal": true, + "static-route-list": [ + { + "destination": "172.1.101.22", + "destination-mask": 24, + "next-hop": "172.10.33.5" + } + ], + "outer-tag": { + "tag-type": "ietf-eth-tran-types:classify-c-vlan", + "vlan-value": 201 + }, + "service-classification-type": "ietf-eth-tran-type:vlan-classification", + "ingress-egress-bandwidth-profile" : { + "bandwidth-profile-type": "ietf-eth-tran-types:mef-10-bwp", + "CIR": 10000000, + "EIR": 10000000 + } + } + ] + }, + "svc-tunnel": [ + { + "tunnel-name": "osu_tunnel_2" + } + ], + "optimizations": { + "optimization-metric": [ + { + "metric-role": "work", + "metric-type": "ietf-te-types:path-metric-te" + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/src/device/tests/data/ietf_actn/expected_osu_tunnels.json b/src/device/tests/data/ietf_actn/expected_osu_tunnels.json new file mode 100644 index 0000000000000000000000000000000000000000..1debf555b7219b1f62c4f04782aeaf0eefca62f9 --- /dev/null +++ b/src/device/tests/data/ietf_actn/expected_osu_tunnels.json @@ -0,0 +1,84 @@ +{ + "ietf-te:tunnel": [ + { + "name": "osu_tunnel_1", + "title": "OSU_TUNNEL_1", + "admin-state": "ietf-te-types:tunnel-admin-state-up", + "delay": 20, + "te-bandwidth": { + "layer": "odu", + "odu-type": "osuflex", + "number": 40 + }, + "bidirectional": true, + "destination-endpoints": { + "destination-endpoint": [ + { + "node-id": "10.0.30.1", + "tp-id": "200", + "ttp-channel-name": "och:1-odu2:1-oduflex:3-osuflex:1", + "protection-role": "work" + } + ] + }, + "source-endpoints": { + "source-endpoint": [ + { + "node-id": "10.0.10.1", + "tp-id": "200", + "ttp-channel-name": "och:1-odu2:1-oduflex:1-osuflex:2", + "protection-role": "work" + } + ] + }, + "restoration": { + "restoration-type": "ietf-te-types:lsp-restoration-not-applicable", + "restoration-lock": false + }, + "protection": { + "protection-type": "ietf-te-types:lsp-protection-unprotected", + "protection-reversion-disable": true + } + }, + { + "name": "osu_tunnel_2", + "title": "OSU_TUNNEL_2", + "admin-state": "ietf-te-types:tunnel-admin-state-up", + "delay": 20, + "te-bandwidth": { + "layer": "odu", + "odu-type": "osuflex", + "number": 40 + }, + "bidirectional": true, + "destination-endpoints": { + "destination-endpoint": [ + { + "node-id": "10.0.30.1", + "tp-id": "200", + "ttp-channel-name": "och:1-odu2:1-oduflex:3-osuflex:1", + "protection-role": "work" + } + ] + }, + "source-endpoints": { + "source-endpoint": [ + { + "node-id": "10.0.10.1", + "tp-id": "200", + "ttp-channel-name": "och:1-odu2:1-oduflex:1-osuflex:2", + "protection-role": "work" + } + ] + }, + "restoration": { + "restoration-type": "ietf-te-types:lsp-restoration-not-applicable", + "restoration-lock": false + }, + "protection": { + "protection-type": "ietf-te-types:lsp-protection-unprotected", + "protection-reversion-disable": true + } + } + ] +} diff --git a/src/device/tests/test_unitary_ietf_actn.py b/src/device/tests/test_unitary_ietf_actn.py index e6afd4fffee3a6885308b3e19ea3b19c610e1ce8..5f01a412d88bca142d2bd96ce238947844bc9087 100644 --- a/src/device/tests/test_unitary_ietf_actn.py +++ b/src/device/tests/test_unitary_ietf_actn.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import copy, logging, os, pytest, time -from flask import Flask +import copy, deepdiff, json, logging, operator, os, pytest, time +from flask import Flask, jsonify, make_response +from flask_restful import Resource from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceId +from common.tools.descriptor.Tools import format_custom_config_rules from common.tools.grpc.Tools import grpc_message_to_json_string -from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set from common.tools.object_factory.Device import ( json_device_connect_rules, json_device_id, json_device_ietf_actn_disabled ) @@ -31,7 +32,7 @@ from tests.tools.mock_ietf_actn_sdn_ctrl.ResourceOsuTunnels import OsuTunnel, Os os.environ['DEVICE_EMULATED_ONLY'] = 'TRUE' from .PrepareTestScenario import ( # pylint: disable=unused-import # be careful, order of symbols is important here! - mock_service, device_service, context_client, device_client, test_prepare_environment + mock_service, device_service, context_client, device_client, monitoring_client, test_prepare_environment ) DEVICE_UUID = 'DEVICE-IETF-ACTN' @@ -56,56 +57,34 @@ DEVICE_CONNECT_RULES = json_device_connect_rules(DEVICE_ADDRESS, DEVICE_PORT, { 'verify' : DEVICE_VERIFY, }) -DEVICE_CONFIG_RULES = [ - json_config_rule_set('/osu_tunnels/osu_tunnel[osu_tunnel_1]', { - 'name' : 'osu_tunnel_1', - 'src_node_id' : '10.0.10.1', - 'src_tp_id' : '200', - 'src_ttp_channel_name': 'och:1-odu2:1-oduflex:1-osuflex:2', - 'dst_node_id' : '10.0.30.1', - 'dst_tp_id' : '200', - 'dst_ttp_channel_name': 'och:1-odu2:1-oduflex:3-osuflex:1', - 'odu_type' : 'osuflex', - 'osuflex_number' : 40, - 'delay' : 20, - 'bidirectional' : True, - }), - json_config_rule_set('/etht_services/etht_service[etht_service_1]', { - 'name' : 'etht_service_1', - 'service_type' : 'op-mp2mp-svc', - 'osu_tunnel_name' : 'osu_tunnel_1', - 'src_node_id' : '10.0.10.1', - 'src_tp_id' : '200', - 'src_vlan_tag' : 21, - 'src_static_routes': [('128.32.10.5', 24, '128.32.33.5'), ('128.32.20.5', 24, '128.32.33.5')], - 'dst_node_id' : '10.0.30.1', - 'dst_tp_id' : '200', - 'dst_vlan_tag' : 101, - 'dst_static_routes': [('172.1.101.22', 24, '172.10.33.5')], - }), -] - -DEVICE_DECONFIG_RULES = [ - json_config_rule_delete('/osu_tunnels/osu_tunnel[osu_tunnel_1]', {'name': 'osu_tunnel_1'}), - json_config_rule_delete('/etht_services/etht_service[etht_service_1]', {'name': 'etht_service_1'}), -] +DATA_FILE_CONFIG_RULES = 'device/tests/data/ietf_actn/config_rules.json' +DATA_FILE_DECONFIG_RULES = 'device/tests/data/ietf_actn/deconfig_rules.json' +DATA_FILE_EXPECTED_OSU_TUNNELS = 'device/tests/data/ietf_actn/expected_osu_tunnels.json' +DATA_FILE_EXPECTED_ETHT_SERVICES = 'device/tests/data/ietf_actn/expected_etht_services.json' LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) @pytest.fixture(scope='session') def ietf_actn_sdn_ctrl( - device_service: DeviceService, # pylint: disable=redefined-outer-name + device_service : DeviceService, # pylint: disable=redefined-outer-name ) -> Flask: _rest_server = GenericRestServer(DEVICE_PORT, DEVICE_BASE_URL, bind_address=DEVICE_ADDRESS) - _rest_server.app.debug = True - _rest_server.app.env = 'development' - _rest_server.app.testing = True - - _rest_server.add_resource(OsuTunnels, '/ietf-te:tunnel') - _rest_server.add_resource(OsuTunnel, '/ietf-te:tunnel[name=<string:name>]') - _rest_server.add_resource(EthServices, '/ietf-eth-tran-service:etht-svc') - _rest_server.add_resource(EthService, '/ietf-eth-tran-service:etht-svc[etht-svc-name=<string:etht_svc_name>]') + _rest_server.app.config['DEBUG' ] = True + _rest_server.app.config['ENV' ] = 'development' + _rest_server.app.config['SERVER_NAME'] = '{:s}:{:d}'.format(DEVICE_ADDRESS, DEVICE_PORT) + _rest_server.app.config['TESTING' ] = True + + class Root(Resource): + def get(self): + return make_response(jsonify({}), 200) + + add_rsrc = _rest_server.add_resource + add_rsrc(Root, '/') + add_rsrc(OsuTunnels, '/ietf-te:te/tunnels') + add_rsrc(OsuTunnel, '/ietf-te:te/tunnels/tunnel="<string:osu_tunnel_name>"') + add_rsrc(EthServices, '/ietf-eth-tran-service:etht-svc') + add_rsrc(EthService, '/ietf-eth-tran-service:etht-svc/etht-svc-instances="<string:etht_service_name>"') _rest_server.start() time.sleep(1) # bring time for the server to start @@ -114,9 +93,10 @@ def ietf_actn_sdn_ctrl( _rest_server.join() -def test_device_ietf_actn_add_correct( - device_client: DeviceClient, # pylint: disable=redefined-outer-name - device_service: DeviceService, # pylint: disable=redefined-outer-name +def test_device_ietf_actn_add( + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService, # pylint: disable=redefined-outer-name + ietf_actn_sdn_ctrl : GenericRestServer, # pylint: disable=redefined-outer-name ) -> None: DEVICE_WITH_CONNECT_RULES = copy.deepcopy(DEVICE) DEVICE_WITH_CONNECT_RULES['device_config']['config_rules'].extend(DEVICE_CONNECT_RULES) @@ -127,8 +107,9 @@ def test_device_ietf_actn_add_correct( def test_device_ietf_actn_get( - context_client: ContextClient, # pylint: disable=redefined-outer-name - device_client: DeviceClient, # pylint: disable=redefined-outer-name + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + ietf_actn_sdn_ctrl : GenericRestServer, # pylint: disable=redefined-outer-name ) -> None: initial_config = device_client.GetInitialConfig(DeviceId(**DEVICE_ID)) @@ -139,83 +120,161 @@ def test_device_ietf_actn_get( def test_device_ietf_actn_configure( - 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 : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService, # pylint: disable=redefined-outer-name + ietf_actn_sdn_ctrl : GenericRestServer, # pylint: disable=redefined-outer-name ) -> None: + ietf_actn_client = ietf_actn_sdn_ctrl.app.test_client() driver_instance_cache = device_service.device_servicer.driver_instance_cache driver : _Driver = driver_instance_cache.get(DEVICE_UUID) assert driver is not None - # Requires to retrieve data from device; might be slow. Uncomment only when needed and test does not pass directly. - #driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0)) - #LOGGER.info('driver_config = {:s}'.format(str(driver_config))) + retrieved_osu_tunnels = ietf_actn_client.get('/restconf/v2/data/ietf-te:te/tunnels') + retrieved_osu_tunnels = retrieved_osu_tunnels.json + LOGGER.info('osu_tunnels = {:s}'.format(str(retrieved_osu_tunnels))) + expected_osu_tunnels = {'ietf-te:tunnel': []} + osu_tunnels_diff = deepdiff.DeepDiff(expected_osu_tunnels, retrieved_osu_tunnels) + if len(osu_tunnels_diff) > 0: + LOGGER.error('PRE OSU TUNNELS - Differences:\n{:s}'.format(str(osu_tunnels_diff.pretty()))) + assert len(osu_tunnels_diff) == 0 + + retrieved_etht_services = ietf_actn_client.get('/restconf/v2/data/ietf-eth-tran-service:etht-svc') + retrieved_etht_services = retrieved_etht_services.json + LOGGER.info('etht_services = {:s}'.format(str(retrieved_etht_services))) + expected_etht_services = {'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': []}} + etht_services_diff = deepdiff.DeepDiff(expected_etht_services, retrieved_etht_services) + if len(etht_services_diff) > 0: + LOGGER.error('PRE ETHT SERVICES - Differences:\n{:s}'.format(str(etht_services_diff.pretty()))) + assert len(etht_services_diff) == 0 + + retrieved_driver_config_rules = sorted(driver.GetConfig(), key=operator.itemgetter(0)) + LOGGER.info('driver_config = {:s}'.format(str(retrieved_driver_config_rules))) + assert isinstance(retrieved_driver_config_rules, list) + if len(retrieved_driver_config_rules) > 0: + LOGGER.error('PRE DRIVER CONFIG RULES - Differences:\n{:s}'.format(str(retrieved_driver_config_rules))) + assert len(retrieved_driver_config_rules) == 0 DEVICE_WITH_CONFIG_RULES = copy.deepcopy(DEVICE) - DEVICE_WITH_CONFIG_RULES['device_config']['config_rules'].extend(DEVICE_CONFIG_RULES) + with open(DATA_FILE_CONFIG_RULES, 'r', encoding='UTF-8') as f: + config_rules = format_custom_config_rules(json.load(f)) + DEVICE_WITH_CONFIG_RULES['device_config']['config_rules'].extend(config_rules) device_client.ConfigureDevice(Device(**DEVICE_WITH_CONFIG_RULES)) - # Requires to retrieve data from device; might be slow. Uncomment only when needed and test does not pass directly. - #driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0)) - #LOGGER.info('driver_config = {:s}'.format(str(driver_config))) - - device_data = context_client.GetDevice(DeviceId(**DEVICE_ID)) - config_rules = [ - (ConfigActionEnum.Name(config_rule.action), config_rule.custom.resource_key, config_rule.custom.resource_value) - for config_rule in device_data.device_config.config_rules - if config_rule.WhichOneof('config_rule') == 'custom' + retrieved_osu_tunnels = ietf_actn_client.get('/restconf/v2/data/ietf-te:te/tunnels') + retrieved_osu_tunnels = retrieved_osu_tunnels.json + LOGGER.info('osu_tunnels = {:s}'.format(str(retrieved_osu_tunnels))) + with open(DATA_FILE_EXPECTED_OSU_TUNNELS, 'r', encoding='UTF-8') as f: + expected_osu_tunnels = json.load(f) + osu_tunnels_diff = deepdiff.DeepDiff(expected_osu_tunnels, retrieved_osu_tunnels) + if len(osu_tunnels_diff) > 0: + LOGGER.error('POST OSU TUNNELS - Differences:\n{:s}'.format(str(osu_tunnels_diff.pretty()))) + assert len(osu_tunnels_diff) == 0 + + retrieved_etht_services = ietf_actn_client.get('/restconf/v2/data/ietf-eth-tran-service:etht-svc') + retrieved_etht_services = retrieved_etht_services.json + LOGGER.info('etht_services = {:s}'.format(str(retrieved_etht_services))) + with open(DATA_FILE_EXPECTED_ETHT_SERVICES, 'r', encoding='UTF-8') as f: + expected_etht_services = json.load(f) + etht_services_diff = deepdiff.DeepDiff(expected_etht_services, retrieved_etht_services) + if len(etht_services_diff) > 0: + LOGGER.error('POST ETHT SERVICES - Differences:\n{:s}'.format(str(etht_services_diff.pretty()))) + assert len(etht_services_diff) == 0 + + retrieved_driver_config_rules = sorted(driver.GetConfig(), key=operator.itemgetter(0)) + LOGGER.info('driver_config = {:s}'.format(str(retrieved_driver_config_rules))) + retrieved_driver_config_rules = [ + {'action': 1, 'custom': {'resource_key': resource_key, 'resource_value': resource_value}} + for resource_key, resource_value in retrieved_driver_config_rules ] - LOGGER.info('device_data.device_config.config_rules = \n{:s}'.format( - '\n'.join(['{:s} {:s} = {:s}'.format(*config_rule) for config_rule in config_rules]))) - for config_rule in DEVICE_CONFIG_RULES: - assert 'custom' in config_rule - config_rule = ( - ConfigActionEnum.Name(config_rule['action']), config_rule['custom']['resource_key'], - config_rule['custom']['resource_value']) - assert config_rule in config_rules + with open(DATA_FILE_CONFIG_RULES, 'r', encoding='UTF-8') as f: + expected_driver_config_rules = sorted(json.load(f), key=lambda cr: cr['custom']['resource_key']) + driver_config_rules_diff = deepdiff.DeepDiff(expected_driver_config_rules, retrieved_driver_config_rules) + if len(driver_config_rules_diff) > 0: + LOGGER.error('POST DRIVER CONFIG RULES - Differences:\n{:s}'.format(str(driver_config_rules_diff.pretty()))) + assert len(driver_config_rules_diff) == 0 def test_device_ietf_actn_deconfigure( - 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 : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService, # pylint: disable=redefined-outer-name + ietf_actn_sdn_ctrl : GenericRestServer, # pylint: disable=redefined-outer-name ) -> None: + ietf_actn_client = ietf_actn_sdn_ctrl.app.test_client() driver_instance_cache = device_service.device_servicer.driver_instance_cache - driver: _Driver = driver_instance_cache.get(DEVICE_UUID) + driver : _Driver = driver_instance_cache.get(DEVICE_UUID) assert driver is not None - # Requires to retrieve data from device; might be slow. Uncomment only when needed and test does not pass directly. - #driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0)) - #LOGGER.info('driver_config = {:s}'.format(str(driver_config))) + retrieved_osu_tunnels = ietf_actn_client.get('/restconf/v2/data/ietf-te:te/tunnels') + retrieved_osu_tunnels = retrieved_osu_tunnels.json + LOGGER.info('osu_tunnels = {:s}'.format(str(retrieved_osu_tunnels))) + with open(DATA_FILE_EXPECTED_OSU_TUNNELS, 'r', encoding='UTF-8') as f: + expected_osu_tunnels = json.load(f) + osu_tunnels_diff = deepdiff.DeepDiff(expected_osu_tunnels, retrieved_osu_tunnels) + if len(osu_tunnels_diff) > 0: + LOGGER.error('PRE OSU TUNNELS - Differences:\n{:s}'.format(str(osu_tunnels_diff.pretty()))) + assert len(osu_tunnels_diff) == 0 + + retrieved_etht_services = ietf_actn_client.get('/restconf/v2/data/ietf-eth-tran-service:etht-svc') + retrieved_etht_services = retrieved_etht_services.json + LOGGER.info('etht_services = {:s}'.format(str(retrieved_etht_services))) + with open(DATA_FILE_EXPECTED_ETHT_SERVICES, 'r', encoding='UTF-8') as f: + expected_etht_services = json.load(f) + etht_services_diff = deepdiff.DeepDiff(expected_etht_services, retrieved_etht_services) + if len(etht_services_diff) > 0: + LOGGER.error('PRE ETHT SERVICES - Differences:\n{:s}'.format(str(etht_services_diff.pretty()))) + assert len(etht_services_diff) == 0 + + retrieved_driver_config_rules = sorted(driver.GetConfig(), key=operator.itemgetter(0)) + LOGGER.info('driver_config = {:s}'.format(str(retrieved_driver_config_rules))) + retrieved_driver_config_rules = [ + {'action': 1, 'custom': {'resource_key': resource_key, 'resource_value': resource_value}} + for resource_key, resource_value in retrieved_driver_config_rules + ] + with open(DATA_FILE_CONFIG_RULES, 'r', encoding='UTF-8') as f: + expected_driver_config_rules = sorted(json.load(f), key=lambda cr: cr['custom']['resource_key']) + driver_config_rules_diff = deepdiff.DeepDiff(expected_driver_config_rules, retrieved_driver_config_rules) + if len(driver_config_rules_diff) > 0: + LOGGER.error('PRE DRIVER CONFIG RULES - Differences:\n{:s}'.format(str(driver_config_rules_diff.pretty()))) + assert len(driver_config_rules_diff) == 0 DEVICE_WITH_DECONFIG_RULES = copy.deepcopy(DEVICE) - DEVICE_WITH_DECONFIG_RULES['device_config']['config_rules'].extend(DEVICE_DECONFIG_RULES) + with open(DATA_FILE_DECONFIG_RULES, 'r', encoding='UTF-8') as f: + deconfig_rules = format_custom_config_rules(json.load(f)) + DEVICE_WITH_DECONFIG_RULES['device_config']['config_rules'].extend(deconfig_rules) device_client.ConfigureDevice(Device(**DEVICE_WITH_DECONFIG_RULES)) - # Requires to retrieve data from device; might be slow. Uncomment only when needed and test does not pass directly. - #driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0)) - #LOGGER.info('driver_config = {:s}'.format(str(driver_config))) - - device_data = context_client.GetDevice(DeviceId(**DEVICE_ID)) - config_rules = [ - (ConfigActionEnum.Name(config_rule.action), config_rule.custom.resource_key, config_rule.custom.resource_value) - for config_rule in device_data.device_config.config_rules - if config_rule.WhichOneof('config_rule') == 'custom' - ] - LOGGER.info('device_data.device_config.config_rules = \n{:s}'.format( - '\n'.join(['{:s} {:s} = {:s}'.format(*config_rule) for config_rule in config_rules]))) - for config_rule in DEVICE_DECONFIG_RULES: - assert 'custom' in config_rule - action_set = ConfigActionEnum.Name(ConfigActionEnum.CONFIGACTION_SET) - config_rule = (action_set, config_rule['custom']['resource_key'], config_rule['custom']['resource_value']) - assert config_rule not in config_rules + retrieved_osu_tunnels = ietf_actn_client.get('/restconf/v2/data/ietf-te:te/tunnels') + retrieved_osu_tunnels = retrieved_osu_tunnels.json + LOGGER.info('osu_tunnels = {:s}'.format(str(retrieved_osu_tunnels))) + expected_osu_tunnels = {'ietf-te:tunnel': []} + osu_tunnels_diff = deepdiff.DeepDiff(expected_osu_tunnels, retrieved_osu_tunnels) + if len(osu_tunnels_diff) > 0: + LOGGER.error('POST OSU TUNNELS - Differences:\n{:s}'.format(str(osu_tunnels_diff.pretty()))) + assert len(osu_tunnels_diff) == 0 + + retrieved_etht_services = ietf_actn_client.get('/restconf/v2/data/ietf-eth-tran-service:etht-svc') + retrieved_etht_services = retrieved_etht_services.json + LOGGER.info('etht_services = {:s}'.format(str(retrieved_etht_services))) + expected_etht_services = {'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': []}} + etht_services_diff = deepdiff.DeepDiff(expected_etht_services, retrieved_etht_services) + if len(etht_services_diff) > 0: + LOGGER.error('POST ETHT SERVICES - Differences:\n{:s}'.format(str(etht_services_diff.pretty()))) + assert len(etht_services_diff) == 0 + + retrieved_driver_config_rules = sorted(driver.GetConfig(), key=operator.itemgetter(0)) + LOGGER.info('retrieved_driver_config_rules = {:s}'.format(str(retrieved_driver_config_rules))) + assert isinstance(retrieved_driver_config_rules, list) + if len(retrieved_driver_config_rules) > 0: + LOGGER.error('POST DRIVER CONFIG RULES - Differences:\n{:s}'.format(str(retrieved_driver_config_rules))) + assert len(retrieved_driver_config_rules) == 0 def test_device_ietf_actn_delete( - device_client : DeviceClient, # pylint: disable=redefined-outer-name - device_service : DeviceService, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + device_service : DeviceService, # pylint: disable=redefined-outer-name + ietf_actn_sdn_ctrl : GenericRestServer, # pylint: disable=redefined-outer-name ) -> None: device_client.DeleteDevice(DeviceId(**DEVICE_ID)) driver_instance_cache = device_service.device_servicer.driver_instance_cache diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py b/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py index ad49b4a423b3560e1753d0de41f711f5492a9de9..c459c294ccb1c457c258a4e80018a90244702b8b 100644 --- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py @@ -58,16 +58,16 @@ def main(): Health, '/' ) api.add_resource( - OsuTunnels, '/ietf-te:tunnel' + OsuTunnels, '/ietf-te:te/tunnels' ) api.add_resource( - OsuTunnel, '/ietf-te:tunnel[name=<string:name>]' + OsuTunnel, '/ietf-te:te/tunnels/tunnel="<string:osu_tunnel_name>"' ) api.add_resource( EthServices, '/ietf-eth-tran-service:etht-svc' ) api.add_resource( - EthService, '/ietf-eth-tran-service:etht-svc/etht-svc-instances[etht-svc-name=<string:etht_svc_name>]' + EthService, '/ietf-eth-tran-service:etht-svc/etht-svc-instances="<string:etht_service_name>"' ) LOGGER.info('Listening on {:s}...'.format(str(STR_ENDPOINT))) diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceEthServices.py b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceEthServices.py index 4fcc0ca713145f958482c75b8794c5abff16bcf2..0e08bbdaa48df2ae7338a96d727fdc9f876784e6 100644 --- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceEthServices.py +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceEthServices.py @@ -18,12 +18,12 @@ from flask import abort, jsonify, make_response, request from flask_restful import Resource -ETH_SERVICES = {} +ETHT_SERVICES = {} class EthServices(Resource): def get(self): - eth_services = [eth_service for eth_service in ETH_SERVICES.values()] - data = {'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': eth_services}} + etht_services = [etht_service for etht_service in ETHT_SERVICES.values()] + data = {'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': etht_services}} return make_response(jsonify(data), 200) def post(self): @@ -33,21 +33,21 @@ class EthServices(Resource): if 'ietf-eth-tran-service:etht-svc' not in json_request: abort(400) json_request = json_request['ietf-eth-tran-service:etht-svc'] if 'etht-svc-instances' not in json_request: abort(400) - eth_services = json_request['etht-svc-instances'] - if not isinstance(eth_services, list): abort(400) - if len(eth_services) != 1: abort(400) - eth_service = eth_services[0] - etht_svc_name = eth_service['etht-svc-name'] - ETH_SERVICES[etht_svc_name] = eth_service + etht_services = json_request['etht-svc-instances'] + if not isinstance(etht_services, list): abort(400) + if len(etht_services) != 1: abort(400) + etht_service = etht_services[0] + etht_service_name = etht_service['etht-svc-name'] + ETHT_SERVICES[etht_service_name] = etht_service return make_response(jsonify({}), 201) class EthService(Resource): - def get(self, service_uuid : str): - eth_service = ETH_SERVICES.get(service_uuid, None) - data,status = ({}, 404) if eth_service is None else (eth_service, 200) + def get(self, etht_service_name : str): + etht_service = ETHT_SERVICES.get(etht_service_name, None) + data,status = ({}, 404) if etht_service is None else (etht_service, 200) return make_response(jsonify(data), status) - def delete(self, service_uuid : str): - eth_service = ETH_SERVICES.pop(service_uuid, None) - data,status = ({}, 404) if eth_service is None else (eth_service, 204) + def delete(self, etht_service_name : str): + etht_service = ETHT_SERVICES.pop(etht_service_name, None) + data,status = ({}, 404) if etht_service is None else (etht_service, 204) return make_response(jsonify(data), status) diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceOsuTunnels.py b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceOsuTunnels.py index 2fe2f319b45a2c396851d1ce5432d91a0004cdf3..914f7096da4101a138ac9b0cdd2911dfcf22bb61 100644 --- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceOsuTunnels.py +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceOsuTunnels.py @@ -31,12 +31,14 @@ class OsuTunnels(Resource): def post(self): json_request = request.get_json() if not json_request: abort(400) - if not isinstance(json_request, list): abort(400) + if not isinstance(json_request, dict): abort(400) + if 'ietf-te:tunnel' not in json_request: abort(400) osu_tunnels = json_request['ietf-te:tunnel'] + if not isinstance(osu_tunnels, list): abort(400) if len(osu_tunnels) != 1: abort(400) osu_tunnel = osu_tunnels[0] - name = osu_tunnel['name'] - OSU_TUNNELS[name] = osu_tunnel + osu_tunnel_name = osu_tunnel['name'] + OSU_TUNNELS[osu_tunnel_name] = osu_tunnel return make_response(jsonify({}), 201) class OsuTunnel(Resource):