diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/Dockerfile b/src/tests/tools/mock_ietf_actn_sdn_ctrl/Dockerfile similarity index 86% rename from src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/Dockerfile rename to src/tests/tools/mock_ietf_actn_sdn_ctrl/Dockerfile index 70fc81e5408d49ae332132612d0f86c1d5901e38..05c785fb1a353db544d58d0953bc1cc99881a693 100644 --- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/Dockerfile +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/Dockerfile @@ -23,13 +23,15 @@ RUN python3 -m pip install --upgrade setuptools wheel RUN python3 -m pip install --upgrade pip-tools # Create component sub-folders, and copy content -RUN mkdir -p /var/teraflow/mock_mw_sdn_ctrl -WORKDIR /var/teraflow/mock_mw_sdn_ctrl +RUN mkdir -p /var/teraflow/mock_ietf_actn_sdn_ctrl +WORKDIR /var/teraflow/mock_ietf_actn_sdn_ctrl COPY . . # Get specific Python packages RUN pip-compile --quiet --output-file=requirements.txt requirements.in RUN python3 -m pip install -r requirements.txt +RUN python3 -m pip list + # Start the service -ENTRYPOINT ["python", "MockMWSdnCtrl.py"] +ENTRYPOINT ["python", "MockIetfActnSdnCtrl.py"] 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 c20dde1b9958fb92e4c6f026fbfe55f9ba348ba1..ad49b4a423b3560e1753d0de41f711f5492a9de9 100644 --- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py @@ -12,79 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Mock MicroWave SDN controller +# Mock IETF ACTN SDN controller # ----------------------------- # REST server implementing minimal support for: -# - IETF YANG data model for Network Topology -# Ref: https://www.rfc-editor.org/rfc/rfc8345.html -# - IETF YANG data model for Transport Network Client Signals -# Ref: https://www.ietf.org/archive/id/draft-ietf-ccamp-client-signal-yang-07.html +# - IETF YANG Data Model for Transport Network Client Signals +# Ref: https://www.ietf.org/archive/id/draft-ietf-ccamp-client-signal-yang-10.html +# - IETF YANG Data Model for Traffic Engineering Tunnels, Label Switched Paths and Interfaces +# Ref: https://www.ietf.org/archive/id/draft-ietf-teas-yang-te-34.html -# Ref: https://blog.miguelgrinberg.com/post/running-your-flask-application-over-https -# Ref: https://blog.miguelgrinberg.com/post/designing-a-restful-api-using-flask-restful - import functools, logging, sys, time -from flask import Flask, abort, jsonify, make_response, request +from flask import Flask, jsonify, make_response, request from flask_restful import Api, Resource +from ResourceEthServices import EthService, EthServices +from ResourceOsuTunnels import OsuTunnel, OsuTunnels BIND_ADDRESS = '0.0.0.0' BIND_PORT = 8443 -BASE_URL = '/nmswebs/restconf/data' +BASE_URL = '/restconf/data' STR_ENDPOINT = 'https://{:s}:{:s}{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT), str(BASE_URL)) LOG_LEVEL = logging.DEBUG -NETWORK_NODES = [ - {'node-id': '192.168.27.139', 'ietf-network-topology:termination-point': [ - {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }}, - ]}, - {'node-id': '192.168.27.140', 'ietf-network-topology:termination-point': [ - {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }}, - ]}, - {'node-id': '192.168.27.141', 'ietf-network-topology:termination-point': [ - {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }}, - ]}, - {'node-id': '192.168.27.142', 'ietf-network-topology:termination-point': [ - {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }}, - ]} -] -NETWORK_LINKS = [ - { 'link-id' : '192.168.27.139:10--192.168.27.140:10', - 'source' : {'source-node': '192.168.27.139', 'source-tp': '10'}, - 'destination': {'dest-node' : '192.168.27.140', 'dest-tp' : '10'}, - }, - { 'link-id' : '192.168.27.141:10--192.168.27.142:10', - 'source' : {'source-node': '192.168.27.141', 'source-tp': '10'}, - 'destination': {'dest-node' : '192.168.27.142', 'dest-tp' : '10'}, - } -] -NETWORK_SERVICES = {} - - logging.basicConfig(level=LOG_LEVEL, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") LOGGER = logging.getLogger(__name__) @@ -99,35 +47,6 @@ class Health(Resource): def get(self): return make_response(jsonify({}), 200) -class Network(Resource): - def get(self, network_uuid : str): - if network_uuid != 'SIAE-ETH-TOPOLOGY': abort(400) - network = {'node': NETWORK_NODES, 'ietf-network-topology:link': NETWORK_LINKS} - return make_response(jsonify({'ietf-network:network': network}), 200) - -class Services(Resource): - def get(self): - services = [service for service in NETWORK_SERVICES.values()] - return make_response(jsonify({'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': services}}), 200) - - def post(self): - json_request = request.get_json() - if not json_request: abort(400) - if not isinstance(json_request, dict): abort(400) - if 'etht-svc-instances' not in json_request: abort(400) - json_services = json_request['etht-svc-instances'] - if not isinstance(json_services, list): abort(400) - if len(json_services) != 1: abort(400) - svc_data = json_services[0] - etht_svc_name = svc_data['etht-svc-name'] - NETWORK_SERVICES[etht_svc_name] = svc_data - return make_response(jsonify({}), 201) - -class DelServices(Resource): - def delete(self, service_uuid : str): - NETWORK_SERVICES.pop(service_uuid, None) - return make_response(jsonify({}), 204) - def main(): LOGGER.info('Starting...') @@ -135,10 +54,21 @@ def main(): app.after_request(functools.partial(log_request, LOGGER)) api = Api(app, prefix=BASE_URL) - api.add_resource(Health, '/ietf-network:networks') - api.add_resource(Network, '/ietf-network:networks/network=<string:network_uuid>') - api.add_resource(Services, '/ietf-eth-tran-service:etht-svc') - api.add_resource(DelServices, '/ietf-eth-tran-service:etht-svc/etht-svc-instances=<string:service_uuid>') + api.add_resource( + Health, '/' + ) + api.add_resource( + OsuTunnels, '/ietf-te:tunnel' + ) + api.add_resource( + OsuTunnel, '/ietf-te:tunnel[name=<string: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>]' + ) LOGGER.info('Listening on {:s}...'.format(str(STR_ENDPOINT))) app.run(debug=True, host=BIND_ADDRESS, port=BIND_PORT, ssl_context='adhoc') diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceEthServices.py b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceEthServices.py new file mode 100644 index 0000000000000000000000000000000000000000..4fcc0ca713145f958482c75b8794c5abff16bcf2 --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceEthServices.py @@ -0,0 +1,53 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# REST-API resource implementing minimal support for "IETF YANG Data Model for Transport Network Client Signals". +# Ref: https://www.ietf.org/archive/id/draft-ietf-ccamp-client-signal-yang-10.html + +from flask import abort, jsonify, make_response, request +from flask_restful import Resource + +ETH_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}} + return make_response(jsonify(data), 200) + + def post(self): + json_request = request.get_json() + if not json_request: abort(400) + if not isinstance(json_request, dict): abort(400) + 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 + 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) + 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) + 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 new file mode 100644 index 0000000000000000000000000000000000000000..2fe2f319b45a2c396851d1ce5432d91a0004cdf3 --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ResourceOsuTunnels.py @@ -0,0 +1,51 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# REST-API resource implementing minimal support for "IETF YANG Data Model for Traffic Engineering Tunnels, +# Label Switched Paths and Interfaces". +# Ref: https://www.ietf.org/archive/id/draft-ietf-teas-yang-te-34.html + + +from flask import abort, jsonify, make_response, request +from flask_restful import Resource + +OSU_TUNNELS = {} + +class OsuTunnels(Resource): + def get(self): + osu_tunnels = [osu_tunnel for osu_tunnel in OSU_TUNNELS.values()] + data = {'ietf-te:tunnel': osu_tunnels} + return make_response(jsonify(data), 200) + + def post(self): + json_request = request.get_json() + if not json_request: abort(400) + if not isinstance(json_request, list): abort(400) + osu_tunnels = json_request['ietf-te:tunnel'] + if len(osu_tunnels) != 1: abort(400) + osu_tunnel = osu_tunnels[0] + name = osu_tunnel['name'] + OSU_TUNNELS[name] = osu_tunnel + return make_response(jsonify({}), 201) + +class OsuTunnel(Resource): + def get(self, osu_tunnel_name : str): + osu_tunnel = OSU_TUNNELS.get(osu_tunnel_name, None) + data,status = ({}, 404) if osu_tunnel is None else (osu_tunnel, 200) + return make_response(jsonify(data), status) + + def delete(self, osu_tunnel_name : str): + osu_tunnel = OSU_TUNNELS.pop(osu_tunnel_name, None) + data,status = ({}, 404) if osu_tunnel is None else (osu_tunnel, 204) + return make_response(jsonify(data), status) diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/__init__.py b/src/tests/tools/mock_ietf_actn_sdn_ctrl/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/build.sh b/src/tests/tools/mock_ietf_actn_sdn_ctrl/build.sh new file mode 100755 index 0000000000000000000000000000000000000000..8d125727bfdf0e0ebbf2106c6593fd38921fbdd9 --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +docker build -t mock-ietf-actn-sdn-ctrl:test -f Dockerfile . +docker tag mock-ietf-actn-sdn-ctrl:test localhost:32000/tfs/mock-ietf-actn-sdn-ctrl:test +docker push localhost:32000/tfs/mock-ietf-actn-sdn-ctrl:test diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/deploy.sh b/src/tests/tools/mock_ietf_actn_sdn_ctrl/deploy.sh new file mode 100755 index 0000000000000000000000000000000000000000..822ecc06ec81a7688bdaa682320026a31c6dfc88 --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/deploy.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +kubectl delete namespace mocks +kubectl --namespace mocks apply -f mock-ietf-actn-sdn-ctrl.yaml diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/mock-ietf-actn-sdn-ctrl.yaml b/src/tests/tools/mock_ietf_actn_sdn_ctrl/mock-ietf-actn-sdn-ctrl.yaml new file mode 100644 index 0000000000000000000000000000000000000000..32cfd922896f6e8860ae07049657b97b0da90c3a --- /dev/null +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/mock-ietf-actn-sdn-ctrl.yaml @@ -0,0 +1,64 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +kind: Namespace +apiVersion: v1 +metadata: + name: mocks +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mock-ietf-actn-sdn-ctrl +spec: + selector: + matchLabels: + app: mock-ietf-actn-sdn-ctrl + replicas: 1 + template: + metadata: + annotations: + config.linkerd.io/skip-inbound-ports: "8443" + labels: + app: mock-ietf-actn-sdn-ctrl + spec: + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: localhost:32000/tfs/mock-ietf-actn-sdn-ctrl:test + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8443 + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: 700m + memory: 1024Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: mock-ietf-actn-sdn-ctrl + labels: + app: mock-ietf-actn-sdn-ctrl +spec: + type: ClusterIP + selector: + app: mock-ietf-actn-sdn-ctrl + ports: + - name: https + port: 8443 + targetPort: 8443 diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/requirements.in b/src/tests/tools/mock_ietf_actn_sdn_ctrl/requirements.in similarity index 97% rename from src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/requirements.in rename to src/tests/tools/mock_ietf_actn_sdn_ctrl/requirements.in index f4bc191062c8385b2eeb003832b284313305c795..d91775403366e93a4612296faa6a7d5fa527249a 100644 --- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/requirements.in +++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/requirements.in @@ -19,3 +19,4 @@ Flask-HTTPAuth==4.5.0 Flask-RESTful==0.3.9 jsonschema==4.4.0 requests==2.27.1 +werkzeug==2.3.7 diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/build.sh b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/build.sh deleted file mode 100755 index 4df315cec178cef13eaa059a739bc22efc011d4d..0000000000000000000000000000000000000000 --- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/build.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -docker build -t mock-mw-sdn-ctrl:test -f Dockerfile . -docker tag mock-mw-sdn-ctrl:test localhost:32000/tfs/mock-mw-sdn-ctrl:test -docker push localhost:32000/tfs/mock-mw-sdn-ctrl:test diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/deploy.sh b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/deploy.sh deleted file mode 100755 index ded232e5c50f8cd5ed448ec0193f58c43626f4ad..0000000000000000000000000000000000000000 --- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/deploy.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -kubectl delete namespace mocks -kubectl --namespace mocks apply -f mock-mw-sdn-ctrl.yaml diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/mock-mw-sdn-ctrl.yaml b/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/mock-mw-sdn-ctrl.yaml deleted file mode 100644 index 05b89f949e940ae55ad592b9cc0e82a6eea2e343..0000000000000000000000000000000000000000 --- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/ssl_not_working/mock-mw-sdn-ctrl.yaml +++ /dev/null @@ -1,46 +0,0 @@ -kind: Namespace -apiVersion: v1 -metadata: - name: mocks ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mock-mw-sdn-ctrl -spec: - selector: - matchLabels: - app: mock-mw-sdn-ctrl - replicas: 1 - template: - metadata: - labels: - app: mock-mw-sdn-ctrl - spec: - terminationGracePeriodSeconds: 5 - containers: - - name: server - image: localhost:32000/tfs/mock-mw-sdn-ctrl:test - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8443 - resources: - requests: - cpu: 250m - memory: 512Mi - limits: - cpu: 700m - memory: 1024Mi ---- -apiVersion: v1 -kind: Service -metadata: - name: mock-mw-sdn-ctrl -spec: - type: ClusterIP - selector: - app: mock-mw-sdn-ctrl - ports: - - name: https - port: 8443 - targetPort: 8443