aboutsummaryrefslogtreecommitdiffstats
path: root/onap-client/onap_client/client
diff options
context:
space:
mode:
authorstark, steven <steven.stark@att.com>2020-03-11 14:30:58 -0700
committerstark, steven <steven.stark@att.com>2020-03-12 14:41:09 -0700
commitc02d3ed39e53d4ce56406a43089fc4a336c43f17 (patch)
tree8354fe71b2b4d379f36042b76c0e29775692f03e /onap-client/onap_client/client
parentd7d8722ce27e308defb6764d8d76f85ce7d63927 (diff)
[VVP] Adding onap-client intial commit.
This is supplementary tooling to interact with various ONAP applications. It will be used by the OVP VNF Testcase, it's not deployed with the ONAP platform. Issue-ID: VVP-381 Change-Id: I2ff3952ba8f4b9448acb5a24717ccc3b1f0a92fe Signed-off-by: stark, steven <steven.stark@att.com>
Diffstat (limited to 'onap-client/onap_client/client')
-rw-r--r--onap-client/onap_client/client/__init__.py36
-rw-r--r--onap-client/onap_client/client/catalog.py156
-rw-r--r--onap-client/onap_client/client/clients.py85
-rw-r--r--onap-client/onap_client/client/request.py203
-rw-r--r--onap-client/onap_client/client/response.py115
5 files changed, 595 insertions, 0 deletions
diff --git a/onap-client/onap_client/client/__init__.py b/onap-client/onap_client/client/__init__.py
new file mode 100644
index 0000000..5519a84
--- /dev/null
+++ b/onap-client/onap_client/client/__init__.py
@@ -0,0 +1,36 @@
+# -*- coding: utf8 -*-
+# ============LICENSE_START=======================================================
+# org.onap.vvp/validation-scripts
+# ===================================================================
+# Copyright © 2020 AT&T Intellectual Property. All rights reserved.
+# ===================================================================
+#
+# Unless otherwise specified, all software contained herein is licensed
+# under the Apache License, Version 2.0 (the "License");
+# you may not use this software 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.
+#
+#
+#
+# Unless otherwise specified, all documentation contained herein is licensed
+# under the Creative Commons License, Attribution 4.0 Intl. (the "License");
+# you may not use this documentation except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://creativecommons.org/licenses/by/4.0/
+#
+# Unless required by applicable law or agreed to in writing, documentation
+# 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.
+#
+# ============LICENSE_END============================================
diff --git a/onap-client/onap_client/client/catalog.py b/onap-client/onap_client/client/catalog.py
new file mode 100644
index 0000000..419d00f
--- /dev/null
+++ b/onap-client/onap_client/client/catalog.py
@@ -0,0 +1,156 @@
+# -*- coding: utf8 -*-
+# ============LICENSE_START=======================================================
+# org.onap.vvp/validation-scripts
+# ===================================================================
+# Copyright © 2020 AT&T Intellectual Property. All rights reserved.
+# ===================================================================
+#
+# Unless otherwise specified, all software contained herein is licensed
+# under the Apache License, Version 2.0 (the "License");
+# you may not use this software 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.
+#
+#
+#
+# Unless otherwise specified, all documentation contained herein is licensed
+# under the Creative Commons License, Attribution 4.0 Intl. (the "License");
+# you may not use this documentation except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://creativecommons.org/licenses/by/4.0/
+#
+# Unless required by applicable law or agreed to in writing, documentation
+# 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.
+#
+# ============LICENSE_END============================================
+
+from abc import ABC, abstractmethod
+from onap_client.lib import make_request
+
+
+class Catalog(ABC):
+ """Abstract class for an ONAP client, automatically loads
+ child classes as attributes."""
+
+ class CallHandle:
+ """Attached as an attribute for each catalog entry in a catalog.
+ Used to make a request to ONAP."""
+
+ def __init__(self, catalog_resource):
+ self.resource = catalog_resource
+
+ def __call__(self, **kwargs):
+ return make_request(self.resource, **kwargs)
+
+ def __init__(self):
+ """Iterates through all child classes and attaches them as attributes, named
+ after the namespace property.
+
+ If the child Catalog class has items in the
+ catalog_resources property, they will be added as attributes to the child attribute
+ as a CallHandle object.
+ """
+ self.catalog_items = {}
+
+ for cls in self.__class__.__subclasses__():
+ subclass = cls()
+ namespace = subclass.namespace
+ catalog_resources = subclass.catalog_resources
+
+ for k, v in catalog_resources.items():
+ subclass.load(k, v)
+
+ setattr(self, namespace, subclass)
+
+ def load(self, item_name, resource_data):
+ """Consume a catalog resource entry as an APICatalogResource,
+ and set it as an attribute on this.class as a CallHandle object"""
+ resource = APICatalogResource(item_name, resource_data)
+
+ self.catalog_items[item_name] = resource
+ setattr(self, item_name.lower(), self.CallHandle(resource))
+
+ @property
+ @abstractmethod
+ def namespace(self):
+ raise NotImplementedError
+
+ @property
+ @abstractmethod
+ def catalog_resources(self):
+ raise NotImplementedError
+
+
+class APICatalogResource:
+ """Class representation of a single catalog entry"""
+
+ def __init__(self, catalog_resource_name, resource_data):
+ """
+ :catalog_resource_name: name of the catalog resource
+ :resource_data: dictionary containing catalog resource attributes
+ """
+ self.catalog_resource_name = catalog_resource_name
+ self.catalog_resource_data = resource_data
+
+ @property
+ def verb(self):
+ return self.catalog_resource_data.get("verb", None)
+
+ @property
+ def description(self):
+ return self.catalog_resource_data.get("description", None)
+
+ @property
+ def uri(self):
+ return self.catalog_resource_data.get("uri", None)
+
+ @property
+ def payload(self):
+ return self.catalog_resource_data.get("payload", None)
+
+ @property
+ def uri_parameters(self):
+ return self.catalog_resource_data.get("uri-parameters", [])
+
+ @property
+ def payload_parameters(self):
+ return self.catalog_resource_data.get("payload-parameters", [])
+
+ @property
+ def payload_path(self):
+ return self.catalog_resource_data.get("payload-path", [])
+
+ @property
+ def file_parameters(self):
+ return self.catalog_resource_data.get("files-parameters", [])
+
+ @property
+ def header_parameters(self):
+ return self.catalog_resource_data.get("header_parameters", [])
+
+ @property
+ def success_code(self):
+ return self.catalog_resource_data.get("success_code", None)
+
+ @property
+ def headers(self):
+ return self.catalog_resource_data.get("headers", None)
+
+ @property
+ def return_data(self):
+ return self.catalog_resource_data.get("return_data", {})
+
+ @property
+ def auth(self):
+ return self.catalog_resource_data.get("auth", None)
diff --git a/onap-client/onap_client/client/clients.py b/onap-client/onap_client/client/clients.py
new file mode 100644
index 0000000..0c4605f
--- /dev/null
+++ b/onap-client/onap_client/client/clients.py
@@ -0,0 +1,85 @@
+# -*- coding: utf8 -*-
+# ============LICENSE_START=======================================================
+# org.onap.vvp/validation-scripts
+# ===================================================================
+# Copyright © 2020 AT&T Intellectual Property. All rights reserved.
+# ===================================================================
+#
+# Unless otherwise specified, all software contained herein is licensed
+# under the Apache License, Version 2.0 (the "License");
+# you may not use this software 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.
+#
+#
+#
+# Unless otherwise specified, all documentation contained herein is licensed
+# under the Creative Commons License, Attribution 4.0 Intl. (the "License");
+# you may not use this documentation except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://creativecommons.org/licenses/by/4.0/
+#
+# Unless required by applicable law or agreed to in writing, documentation
+# 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.
+#
+# ============LICENSE_END============================================
+
+import importlib
+import onap_client
+import pkgutil
+import inspect
+
+from onap_client.client.catalog import Catalog
+
+
+class Client(Catalog):
+ """Base class for the ONAP client. Subclasses are dynamically
+ loaded and added as attributes. Instantiate and use this class
+ to interact with ONAP."""
+ def __init__(self):
+ self.modules = import_submodules(onap_client)
+ super().__init__()
+
+ @property
+ def namespace(self):
+ return "onap"
+
+ @property
+ def catalog_resources(self):
+ return {}
+
+ @property
+ def utility_functions(self):
+ utility_functions = {}
+ for module_name, module in self.modules.items():
+ all_functions = inspect.getmembers(module, inspect.isfunction)
+ for func in all_functions:
+ function = func[1]
+ if hasattr(function, "utility_function"):
+ utility_functions[func[0]] = func[1]
+ return utility_functions
+
+
+def import_submodules(package, recursive=True):
+ """Import all the modules in onap-client, except for those starting
+ with tests*. This is needed so that the Client object can register child classes"""
+ if isinstance(package, str):
+ package = importlib.import_module(package)
+ results = {}
+ for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
+ full_name = package.__name__ + "." + name
+ results[full_name] = importlib.import_module(full_name)
+ if recursive and is_pkg and full_name.find("tests") == -1:
+ results.update(import_submodules(full_name))
+ return results
diff --git a/onap-client/onap_client/client/request.py b/onap-client/onap_client/client/request.py
new file mode 100644
index 0000000..0e40591
--- /dev/null
+++ b/onap-client/onap_client/client/request.py
@@ -0,0 +1,203 @@
+# -*- coding: utf8 -*-
+# ============LICENSE_START=======================================================
+# org.onap.vvp/validation-scripts
+# ===================================================================
+# Copyright © 2020 AT&T Intellectual Property. All rights reserved.
+# ===================================================================
+#
+# Unless otherwise specified, all software contained herein is licensed
+# under the Apache License, Version 2.0 (the "License");
+# you may not use this software 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.
+#
+#
+#
+# Unless otherwise specified, all documentation contained herein is licensed
+# under the Creative Commons License, Attribution 4.0 Intl. (the "License");
+# you may not use this documentation except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://creativecommons.org/licenses/by/4.0/
+#
+# Unless required by applicable law or agreed to in writing, documentation
+# 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.
+#
+# ============LICENSE_END============================================
+
+import jinja2
+import requests
+import json
+import os
+import copy
+
+from onap_client.client.response import ResponseHandler
+from onap_client.config import LOG as logger
+from onap_client.exceptions import FilesRequestFailure
+from jinja2 import exceptions as jinja_exceptions
+
+
+class RequestHandler:
+ """Handles a APICatalogRequestObject to make a request
+ and returns a ResponseHandler object"""
+
+ def __init__(self, request_object):
+ """
+ :request_object: APICatalogRequestObject
+ """
+ self.request_object = request_object
+
+ def make_request(self):
+ r = Request(self.request_object)
+
+ logger.warning("Submitting request: {}".format(self.request_object.description))
+ # TODO
+ # Add verify to config file
+ return ResponseHandler(r.request(verify=False), self.request_object)
+
+
+class Request:
+ """Parses a APICatalogRequestObject to fill out the
+ kwargs to send to the requests library"""
+
+ def __init__(self, request_object):
+ """
+ :request_object: APICatalogRequestObject
+ """
+ self.request_object = request_object
+ self.kwargs = {}
+ self.response = None
+
+ self.build_request()
+
+ def build_request(self):
+ request_object = self.request_object
+
+ if request_object.verb:
+ self.kwargs["method"] = request_object.verb
+
+ if request_object.auth:
+ self.kwargs["auth"] = request_object.auth
+
+ if request_object.uri:
+ self.kwargs["url"] = request_object.uri
+
+ if request_object.headers:
+ self.kwargs["headers"] = request_object.headers
+
+ if request_object.payload:
+ self.kwargs["data"] = request_object.payload
+
+ if request_object.files:
+ self.kwargs["files"] = request_object.files
+
+ debug_request = copy.deepcopy(self.kwargs)
+ if "auth" in debug_request:
+ debug_request["auth"] = "***********"
+
+ try:
+ logger.info(json.dumps(debug_request, indent=4))
+ except TypeError:
+ logger.info(debug_request)
+
+ def request(self, verify=True):
+ return requests.request(**self.kwargs, verify=verify)
+
+
+class APICatalogRequestObject:
+ """Fills a APICatalogResource object with request-specific data"""
+
+ def __init__(self, api_catalog_resource, **kwargs):
+ """
+ :api_catalog_resource: APICatalogResource object
+ :kwargs: key/value to fill in APICatalogResource parameters
+ """
+ self.api_catalog_resource = api_catalog_resource
+ self.payload_parameters = kwargs.get("payload_parameters", {})
+ self.uri_parameters = kwargs.get("uri_parameters", {})
+ self.header_parameters = kwargs.get("header_parameters", {})
+ self.file_parameters = kwargs.get("file_parameters", {})
+ if api_catalog_resource.payload_path:
+ self.payload_path = kwargs.get("payload_path", {}).get(
+ api_catalog_resource.payload_path[0]
+ )
+
+ self.uri = ""
+ self.files = None
+ self.payload = None
+ self.verb = api_catalog_resource.verb
+ self.headers = api_catalog_resource.headers
+ self.success_code = api_catalog_resource.success_code
+ self.return_data = api_catalog_resource.return_data
+ self.auth = api_catalog_resource.auth
+ self.description = api_catalog_resource.description
+
+ if api_catalog_resource.payload or api_catalog_resource.payload_path:
+ self.resolve_payload()
+
+ if api_catalog_resource.file_parameters:
+ self.resolve_files()
+
+ if isinstance(self.headers, dict):
+ for k, v in self.header_parameters.items():
+ self.headers[k] = v
+
+ self.resolve_uri()
+
+ def resolve_files(self):
+ # TODO
+ # is there a better way to figure out waht params are needed?
+ # right now its hardcoded
+ file_type = self.file_parameters.get("file_type", "application/zip")
+ file_path = self.file_parameters.get("file_path")
+ if not file_path:
+ raise FilesRequestFailure("File path was not provided")
+
+ try:
+ with open(file_path, "rb") as f:
+ data = f.read()
+ except IOError:
+ logger.error("file {} was not found".format(file_path))
+ raise
+
+ file_name = os.path.basename(file_path)
+
+ self.files = {"upload": [file_name, data, file_type]}
+
+ def resolve_payload(self):
+ try:
+ if self.api_catalog_resource.payload_path:
+ with open(self.payload_path, "r") as f:
+ self.payload = f.read()
+ else:
+ with open(self.api_catalog_resource.payload, "r") as f:
+ self.payload = jinja2.Template(f.read()).render(
+ **self.payload_parameters
+ )
+ except jinja_exceptions.TemplateNotFound:
+ logger.error(
+ "{} file not found. Check payloads directory.".format(self.payload)
+ )
+ raise
+ except FileNotFoundError:
+ logger.error(
+ "{} file not found. Check payloads directory.".format(self.payload)
+ )
+ raise
+
+ def resolve_uri(self):
+ try:
+ self.uri = self.api_catalog_resource.uri(**self.uri_parameters)
+ except KeyError:
+ logger.error("invalid uri keys {}.".format(self.uri_parameters))
+ raise
diff --git a/onap-client/onap_client/client/response.py b/onap-client/onap_client/client/response.py
new file mode 100644
index 0000000..ef0aab5
--- /dev/null
+++ b/onap-client/onap_client/client/response.py
@@ -0,0 +1,115 @@
+# -*- coding: utf8 -*-
+# ============LICENSE_START=======================================================
+# org.onap.vvp/validation-scripts
+# ===================================================================
+# Copyright © 2020 AT&T Intellectual Property. All rights reserved.
+# ===================================================================
+#
+# Unless otherwise specified, all software contained herein is licensed
+# under the Apache License, Version 2.0 (the "License");
+# you may not use this software 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.
+#
+#
+#
+# Unless otherwise specified, all documentation contained herein is licensed
+# under the Creative Commons License, Attribution 4.0 Intl. (the "License");
+# you may not use this documentation except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://creativecommons.org/licenses/by/4.0/
+#
+# Unless required by applicable law or agreed to in writing, documentation
+# 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.
+#
+# ============LICENSE_END============================================
+
+import simplejson
+
+from onap_client.config import LOG as logger
+
+
+class ResponseHandler:
+ """Handles a response from the requests library,
+ and compares it to the APICatalogRequestObject that was used to make the request.
+ If the request object has return_data, then it will parse the response
+ object and add the return data as an attribute."""
+
+ def __init__(self, response, request_object):
+ """
+ :response: requests.response
+ :request_object: APICatalogRequestObject
+ """
+ self.response = response
+ self.request_object = request_object
+ self.response_data = {}
+ self.status_code = None
+ self.success = False
+
+ self.validate_response()
+
+ def validate_response(self):
+ response = self.response
+ if self.request_object.success_code != response.status_code:
+ response_data = response.text
+ logger.error(
+ "Request failed with code {} and data {}".format(
+ response.status_code, response_data
+ )
+ )
+ else:
+ logger.info("Request was successful")
+ self.success = True
+ try:
+ response_data = response.json()
+ for (
+ response_key,
+ response_items,
+ ) in self.request_object.return_data.items():
+ response_value = response_iterator(response_data, *response_items)
+ if not response_value:
+ logger.warning(
+ "Request was successful but value for {} was not present in response".format(
+ response_key
+ )
+ )
+ setattr(self, response_key, response_value)
+ except simplejson.errors.JSONDecodeError:
+ response_data = response.text
+
+ logger.debug("{}\n".format(response_data))
+
+ self.response_data = response_data
+ self.status_code = response.status_code
+
+
+def response_iterator(response_content, *keys):
+ """helper function to search a response for return_data keys"""
+ props = list(keys)
+
+ key = props.pop(0)
+ prop = response_content.get(key, None)
+
+ if isinstance(prop, str) or len(props) <= 0:
+ return prop
+ elif isinstance(prop, list):
+ if isinstance(key, int):
+ return response_iterator(prop[key], *props)
+ else:
+ for x in prop:
+ return response_iterator(x, *props)
+ elif isinstance(prop, dict):
+ return response_iterator(prop, *props)
+ else:
+ return None