diff options
Diffstat (limited to 'mock-so')
-rw-r--r-- | mock-so/Dockerfile | 11 | ||||
-rw-r--r-- | mock-so/README.md | 2 | ||||
-rw-r--r-- | mock-so/app.py | 126 | ||||
-rw-r--r-- | mock-so/requirements.txt | 2 | ||||
-rw-r--r-- | mock-so/resources/__init__.py | 16 | ||||
-rw-r--r-- | mock-so/resources/orchestration_request.py | 93 | ||||
-rw-r--r-- | mock-so/resources/service_instance.py | 344 |
7 files changed, 594 insertions, 0 deletions
diff --git a/mock-so/Dockerfile b/mock-so/Dockerfile new file mode 100644 index 0000000..d558a0d --- /dev/null +++ b/mock-so/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.8-alpine + +COPY . /app + +WORKDIR /app + +RUN pip install -r requirements.txt + +ENTRYPOINT ["python"] + +CMD ["app.py"] diff --git a/mock-so/README.md b/mock-so/README.md new file mode 100644 index 0000000..539b890 --- /dev/null +++ b/mock-so/README.md @@ -0,0 +1,2 @@ +# mock-so + diff --git a/mock-so/app.py b/mock-so/app.py new file mode 100644 index 0000000..b1d6db2 --- /dev/null +++ b/mock-so/app.py @@ -0,0 +1,126 @@ +"""SO mock application.""" +""" + Copyright 2023 Deutsche Telekom AG, Orange + + 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. +""" +import json + +from flask import Flask, request +from flask_restful import Api + +from resources.orchestration_request import OrchestrationRequest +from resources.service_instance import ( + NetworkInstance, + NetworkInstanceList, + ServiceInstance, + ServiceInstanceList, + VnfInstance, + VnfInstanceList, + VfModuleInstance, + VfModuleInstanceList, +) + + +app = Flask(__name__) +api = Api(app) + + +@app.route("/reset") +def reset(): + """Reset endpoint. + + Reset all resources. + + Returns: + str: Empty string, it has to returns anything + + """ + ServiceInstanceList.reset() + OrchestrationRequest.reset() + return "" + + +@app.route("/set_aai_mock", methods=["POST"]) +def set_aai_mock(): + """Set A&AI mock url address. + + Set it for all resources which connects with A&AI mock. + + Returns: + str: Empty string, it has to returns anything + + """ + aai_mock_url = json.loads(request.data)["AAI_MOCK"] + ServiceInstance.set_aai_mock(aai_mock_url) + ServiceInstanceList.set_aai_mock(aai_mock_url) + VnfInstance.set_aai_mock(aai_mock_url) + VfModuleInstance.set_aai_mock(aai_mock_url) + VnfInstanceList.set_aai_mock(aai_mock_url) + VfModuleInstanceList.set_aai_mock(aai_mock_url) + NetworkInstance.set_aai_mock(aai_mock_url) + NetworkInstanceList.set_aai_mock(aai_mock_url) + return "" + + +api.add_resource( + ServiceInstance, + "/onap/so/infra/serviceInstantiation/v7/serviceInstances/<service_instance_id>", +) +api.add_resource( + ServiceInstanceList, "/onap/so/infra/serviceInstantiation/v7/serviceInstances" +) +api.add_resource( + VnfInstance, + ( + "/onap/so/infra/serviceInstantiation/v7/serviceInstances/<service_instance_id>/" + "vnfs/<vnf_instance_id>" + ), +) +api.add_resource( + VnfInstanceList, + "/onap/so/infra/serviceInstantiation/v7/serviceInstances/<service_instance_id>/vnfs", +) +api.add_resource( + VfModuleInstance, + ( + "/onap/so/infra/serviceInstantiation/v7/serviceInstances/<service_instance_id>/vnfs/" + "<vnf_instance_id>/vfModules/<vf_module_instance_id>" + ), +) +api.add_resource( + VfModuleInstanceList, + ( + "/onap/so/infra/serviceInstantiation/v7/serviceInstances/<service_instance_id>/vnfs/" + "<vnf_instance_id>/vfModules" + ), +) +api.add_resource( + OrchestrationRequest, + "/onap/so/infra/orchestrationRequests/v7/<orchestration_request_id>", +) +api.add_resource( + NetworkInstanceList, + "/onap/so/infra/serviceInstantiation/v7/serviceInstances/<service_instance_id>/networks", +) +api.add_resource( + NetworkInstance, + ( + "/onap/so/infra/serviceInstantiation/v7/serviceInstances/<service_instance_id>/" + "networks/<network_instance_id>" + ), +) + + +if __name__ == "__main__": + app.run(host="0.0.0.0", debug=True) diff --git a/mock-so/requirements.txt b/mock-so/requirements.txt new file mode 100644 index 0000000..f733aba --- /dev/null +++ b/mock-so/requirements.txt @@ -0,0 +1,2 @@ +Flask-RESTful==0.3.8 +requests==2.23.0
\ No newline at end of file diff --git a/mock-so/resources/__init__.py b/mock-so/resources/__init__.py new file mode 100644 index 0000000..e189131 --- /dev/null +++ b/mock-so/resources/__init__.py @@ -0,0 +1,16 @@ +"""SO mock resources.""" +""" + Copyright 2023 Deutsche Telekom AG, Orange + + 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/mock-so/resources/orchestration_request.py b/mock-so/resources/orchestration_request.py new file mode 100644 index 0000000..e7df8e9 --- /dev/null +++ b/mock-so/resources/orchestration_request.py @@ -0,0 +1,93 @@ +"""Mock SO orchestration request resource.""" +""" + Copyright 2023 Deutsche Telekom AG, Orange + + 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. +""" +from dataclasses import dataclass, field +from datetime import datetime +from typing import Dict + +from flask_restful import Resource + + +@dataclass +class OrchestrationRequestData: + """Orchestration request dataclass.""" + + request_id: str + created_at: datetime = field(default_factory=datetime.now) + + +ORCHESTRATION_REQUESTS = {} + + +def time_diff(dt: datetime, diff: int = 1) -> bool: + """Check if given datetime has older (in seconds) than current datetime. + + Args: + dt (datetime): Datetime to check + diff (int, optional): Number of seconds to check. Defaults to 1. + + Returns: + bool: True if datetime is older, False otherwise + + """ + return (datetime.now() - dt).seconds > diff + + +class OrchestrationRequest(Resource): + """Orchestration request resource.""" + + @staticmethod + def reset() -> None: + """Reset orchestration request resource. + + Clean ORCHESTRATION_REQUESTS dictionary + + """ + global ORCHESTRATION_REQUESTS + ORCHESTRATION_REQUESTS = {} + + def get(self, orchestration_request_id: str) -> Dict[str, Dict[str, str]]: + """Get orchestration request data. + + Return orchestration request data from ORCHESTRATION_REQUESTS dictionary. + If it doesn't exist it creates that. + + Args: + orchestration_request_id (str): Orchestration request id key value + + Returns: + Dict[str, Dict[str, str]]: Orchestration request data + """ + try: + orchestration_request_data = ORCHESTRATION_REQUESTS[ + orchestration_request_id + ] + except KeyError: + orchestration_request_data = OrchestrationRequestData( + request_id=orchestration_request_id + ) + ORCHESTRATION_REQUESTS[ + orchestration_request_id + ] = orchestration_request_data + return { + "request": { + "requestStatus": { + "requestState": "COMPLETE" + if time_diff(orchestration_request_data.created_at) + else "IN_PROGRESS" + } + } + } diff --git a/mock-so/resources/service_instance.py b/mock-so/resources/service_instance.py new file mode 100644 index 0000000..60880c2 --- /dev/null +++ b/mock-so/resources/service_instance.py @@ -0,0 +1,344 @@ +"""SO mock instance resources.""" +""" + Copyright 2023 Deutsche Telekom AG, Orange + + 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. +""" +from typing import Callable, Dict, List +from uuid import uuid4 + +import requests +from flask_restful import Resource, request + + +SERVICE_INSTANCES = {} + + +class SoResource(Resource): + """Base SO resource class.""" + + AAI_MOCK_URL = "" + + @classmethod + def set_aai_mock(cls, aai_mock_url: str) -> None: + """Set A&AI mock address. + + Instance resources needs to communicate with A&AI mock + to update it's state. + + Args: + aai_mock_url (str): A&AI mock url + + """ + cls.AAI_MOCK_URL = aai_mock_url + + @classmethod + def reset(cls) -> None: + """Reset SO resource. + + Clean SERVICE_INSTANCES dictionary + and A&AI mock url address. + + """ + global SERVICE_INSTANCES + SERVICE_INSTANCES = {} + cls.AAI_MOCK_URL = "" + + def check_aai_mock_address(method: Callable) -> Callable: + """Decorate method to check if A&AI address is set. + + If A&AI mock address is not set it returns 500 HTTP + response for resource's method + + Args: + method (Callable): method to decorate + + """ + + def decorator(self, *args, **kwargs): + if not self.AAI_MOCK_URL: + return "A&AI mock address not set", 500 + return method(self, *args, **kwargs) + + return decorator + + +class ServiceInstance(SoResource): + """Service instance resource class.""" + + def delete(self, service_instance_id: str) -> Dict[str, Dict[str, str]]: + """Delete service instance. + + Remove service instance data from SERVICE_INSTANCES + dictionary. + + Args: + service_instance_id (str): Service instance id key value + + Returns: + Dict[str, Dict[str, str]]: Deletion request dictionary + + """ + service_instance = SERVICE_INSTANCES[service_instance_id] + requests.delete( + ( + f"{self.AAI_MOCK_URL}/aai/v16/business/customers/customer/" + f"{service_instance['customerId']}/service-subscriptions/service-subscription/" + f"{service_instance['serviceSubscriptionId']}/service-instances/service-instance/" + f"{service_instance_id}" + ) + ) + del SERVICE_INSTANCES[service_instance_id] + return {"requestReferences": {"requestId": str(uuid4())}} + + +class ServiceInstanceList(SoResource): + """Service instances list resource.""" + + @SoResource.check_aai_mock_address + def post(self) -> Dict[str, Dict[str, str]]: + """Create service instance. + + Create service instance data dictionary. + Call request to A&AI mock to create service instance there. + + Returns: + Dict[str, Dict[str, str]]: Creation request dictionary + + """ + instance_id = str(uuid4()) + request_data = request.get_json() + customer_id = request_data["requestDetails"]["subscriberInfo"][ + "globalSubscriberId" + ] + service_subscription_id = request_data["requestDetails"]["requestParameters"][ + "subscriptionServiceType" + ] + instance_name = request_data["requestDetails"]["requestInfo"]["instanceName"] + service_instance = { + "requestId": str(uuid4()), + "instanceId": instance_id, + "customerId": customer_id, + "serviceSubscriptionId": service_subscription_id, + "instanceName": instance_name, + } + requests.post( + ( + f"{self.AAI_MOCK_URL}/aai/v16/business/customers/customer/{customer_id}/" + f"service-subscriptions/service-subscription/{service_subscription_id}/" + "service-instances" + ), + json={ + "service-instance-name": instance_name, + "service-instance-id": instance_id, + }, + ) + SERVICE_INSTANCES[service_instance["instanceId"]] = service_instance + return {"requestReferences": service_instance} + + +class VnfInstance(SoResource): + """Vnf instance resource.""" + + @SoResource.check_aai_mock_address + def delete( + self, service_instance_id: str, vnf_instance_id: str + ) -> Dict[str, Dict[str, str]]: + """Delete vnf instance. + + Remove vnf instanca data from SERVICE_INSTANCES dictionary. + Call DELETE request to A&AI mock. + + Args: + service_instance_id (str): Service instance id key value + vnf_instance_id (str): Vnf instance id key value + + Returns: + Dict[str, Dict[str, str]]: Deletion request dictionary. + + """ + related_service = SERVICE_INSTANCES[service_instance_id] + requests.delete( + ( + f"{self.AAI_MOCK_URL}/aai/v16/business/customers/customer/" + f"{related_service['customerId']}/service-subscriptions/service-subscription/" + f"{related_service['serviceSubscriptionId']}/service-instances/service-instance/" + f"{service_instance_id}/relationship-list" + ) + ) + return {"requestReferences": {"requestId": str(uuid4())}} + + +class VnfInstanceList(SoResource): + """Vnf instances list resource.""" + + @SoResource.check_aai_mock_address + def post(self, service_instance_id: str) -> Dict[str, Dict[str, str]]: + """Create vnf instance. + + Create vnf instance data dictionary. + Call request to A&AI mock to create vnf instance there. + + Returns: + Dict[str, Dict[str, str]]: Creation request dictionary + + """ + instance_id = str(uuid4()) + request_data = request.get_json() + related_instance_id = request_data["requestDetails"]["relatedInstanceList"][0][ + "relatedInstance" + ]["instanceId"] + related_service = SERVICE_INSTANCES[related_instance_id] + requests.post( + ( + f"{self.AAI_MOCK_URL}/aai/v16/business/customers/customer/{related_service['customerId']}/" + f"service-subscriptions/service-subscription/{related_service['serviceSubscriptionId']}/" + f"service-instances/service-instance/{related_instance_id}/relationship-list" + ), + json={ + "related-to": "generic-vnf", + "related-link": f"/aai/v16/network/generic-vnfs/generic-vnf/{instance_id}", + }, + ) + return { + "requestReferences": {"requestId": str(uuid4()), "instanceId": instance_id} + } + + +class VfModuleInstance(SoResource): + """Vf module instance resource class.""" + + @SoResource.check_aai_mock_address + def delete( + self, service_instance_id: str, vnf_instance_id: str, vf_module_instance_id: str + ) -> Dict[str, Dict[str, str]]: + """Delete vf module instance. + + Call DELETE request to A&AI mock to delete vf module instance. + + Args: + service_instance_id (str): Service instance id key value. + vnf_instance_id (str): Vnf instance id key value. + vf_module_instance_id (str): Vf module instance id key value. + + Returns: + Dict[str, Dict[str, str]]: Deletion request dictionary + + """ + requests.delete( + ( + f"{self.AAI_MOCK_URL}/aai/v16/network/generic-vnfs/generic-vnf/" + f"{vnf_instance_id}/vf-modules/{vf_module_instance_id}" + ) + ) + return {"requestReferences": {"requestId": str(uuid4())}} + + +class VfModuleInstanceList(SoResource): + """Vf module instances list resource.""" + + @SoResource.check_aai_mock_address + def post( + self, service_instance_id: str, vnf_instance_id: str + ) -> Dict[str, Dict[str, str]]: + """Create vf module instance. + + Call POST request to A&AI mock to create vf module instance. + + Args: + service_instance_id (str): Service instance id key value + vnf_instance_id (str): Vnf instance id key value + + Returns: + Dict[str, Dict[str, str]]: Creation request dictionary + + """ + instance_id = str(uuid4()) + requests.post( + ( + f"{self.AAI_MOCK_URL}/aai/v16/network/generic-vnfs/generic-vnf/" + f"{vnf_instance_id}/vf-modules" + ), + json={"vf-module-id": instance_id}, + ) + return { + "requestReferences": {"requestId": str(uuid4()), "instanceId": instance_id} + } + + +class NetworkInstance(SoResource): + """Network instance resource.""" + + @SoResource.check_aai_mock_address + def delete( + self, service_instance_id: str, network_instance_id: str + ) -> Dict[str, Dict[str, str]]: + """Delete network instance. + + Remove network instanca data from SERVICE_INSTANCES dictionary. + Call DELETE request to A&AI mock. + + Args: + service_instance_id (str): Service instance id key value + network_instance_id (str): Network instance id key value + + Returns: + Dict[str, Dict[str, str]]: Deletion request dictionary. + + """ + related_service = SERVICE_INSTANCES[service_instance_id] + requests.delete( + ( + f"{self.AAI_MOCK_URL}/aai/v16/business/customers/customer/" + f"{related_service['customerId']}/service-subscriptions/service-subscription/" + f"{related_service['serviceSubscriptionId']}/service-instances/service-instance/" + f"{service_instance_id}/relationship-list" + ) + ) + return {"requestReferences": {"requestId": str(uuid4())}} + + +class NetworkInstanceList(SoResource): + """Network instances list resource.""" + + @SoResource.check_aai_mock_address + def post(self, service_instance_id: str) -> Dict[str, Dict[str, str]]: + """Create network instance. + + Create network instance data dictionary. + Call request to A&AI mock to create network instance there. + + Returns: + Dict[str, Dict[str, str]]: Creation request dictionary + + """ + instance_id = str(uuid4()) + request_data = request.get_json() + related_instance_id = request_data["requestDetails"]["relatedInstanceList"][0][ + "relatedInstance" + ]["instanceId"] + related_service = SERVICE_INSTANCES[related_instance_id] + requests.post( + ( + f"{self.AAI_MOCK_URL}/aai/v16/business/customers/customer/{related_service['customerId']}/" + f"service-subscriptions/service-subscription/{related_service['serviceSubscriptionId']}/" + f"service-instances/service-instance/{related_instance_id}/relationship-list" + ), + json={ + "related-to": "l3-network", + "related-link": f"/aai/v16/network/l3-networks/l3-network/{instance_id}", + }, + ) + return { + "requestReferences": {"requestId": str(uuid4()), "instanceId": instance_id} + } |