diff --git a/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py b/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py new file mode 100644 index 0000000000000000000000000000000000000000..52a85a00da442a2684877c9f753571db124eee79 --- /dev/null +++ b/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py @@ -0,0 +1,131 @@ +# 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. + +# Mock IPM controller (implements minimal support) + +import functools, json, logging, sys, time, uuid +from flask import Flask, jsonify, make_response, request +from flask_restful import Api, Resource + +BIND_ADDRESS = '0.0.0.0' +BIND_PORT = 8444 +IPM_USERNAME = 'xr-user-1' +IPM_PASSWORD = 'xr-user-1' +STR_ENDPOINT = 'https://{:s}:{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT)) +LOG_LEVEL = logging.DEBUG + +CONSTELLATION = { + 'id': 'ofc-constellation', + 'hubModule': {'state': { + 'module': {'moduleName': 'OFC HUB 1', 'trafficMode': 'L1Mode'}, + 'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}, {'moduleIf': {'clientIfAid': 'XR-T4'}}] + }}, + 'leafModules': [ + {'state': { + 'module': {'moduleName': 'OFC LEAF 1', 'trafficMode': 'L1Mode'}, + 'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}] + }}, + {'state': { + 'module': {'moduleName': 'OFC LEAF 2', 'trafficMode': 'L1Mode'}, + 'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}] + }} + ] +} + +logging.basicConfig(level=LOG_LEVEL, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") +LOGGER = logging.getLogger(__name__) + +logging.getLogger('werkzeug').setLevel(logging.WARNING) + +def log_request(logger : logging.Logger, response): + timestamp = time.strftime('[%Y-%b-%d %H:%M]') + logger.info('%s %s %s %s %s', timestamp, request.remote_addr, request.method, request.full_path, response.status) + return response + +#class Health(Resource): +# def get(self): +# return make_response(jsonify({}), 200) + +class OpenIdConnect(Resource): + ACCESS_TOKENS = {} + + def post(self): + if request.content_type != 'application/x-www-form-urlencoded': return make_response('bad content type', 400) + if request.content_length == 0: return make_response('bad content length', 400) + form_request = request.form + if form_request.get('client_id') != 'xr-web-client': return make_response('bad client_id', 403) + if form_request.get('client_secret') != 'xr-web-client': return make_response('bad client_secret', 403) + if form_request.get('grant_type') != 'password': return make_response('bad grant_type', 403) + if form_request.get('username') != IPM_USERNAME: return make_response('bad username', 403) + if form_request.get('password') != IPM_PASSWORD: return make_response('bad password', 403) + access_token = OpenIdConnect.ACCESS_TOKENS.setdefault(IPM_USERNAME, uuid.uuid4()) + reply = {'access_token': access_token, 'expires_in': 86400} + return make_response(jsonify(reply), 200) + +class XrNetworks(Resource): + def get(self): + print(str(request.args)) + content = request.args.get('content') + print('content', content) + query = json.loads(request.args.get('q')) + hub_module_name = query.get('hubModule.state.module.moduleName') + if hub_module_name != 'OFC HUB 1': return make_response('unexpected hub module', 404) + print('query', query) + return make_response(jsonify([CONSTELLATION]), 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...') + + app = Flask(__name__) + app.after_request(functools.partial(log_request, LOGGER)) + + api = Api(app) + #api.add_resource(Health, '/ietf-network:networks') + api.add_resource(OpenIdConnect, '/realms/xr-cm/protocol/openid-connect/token') + api.add_resource(XrNetworks, '/api/v1/xr-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>') + + LOGGER.info('Listening on {:s}...'.format(str(STR_ENDPOINT))) + app.run(debug=True, host=BIND_ADDRESS, port=BIND_PORT, ssl_context='adhoc') + + LOGGER.info('Bye') + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/tests/tools/mock_ipm_sdn_ctrl/run.sh b/src/tests/tools/mock_ipm_sdn_ctrl/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..2aa78712c58d8cc255b60202d1576de683798d2e --- /dev/null +++ b/src/tests/tools/mock_ipm_sdn_ctrl/run.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# 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. + +python MockIPMSdnCtrl.py