commit 2303b5d7a76e6df8859517078aa6fd6d99068491 Author: Aldinson Esto Date: Thu Aug 20 21:00:23 2020 +0900 Support a judgement of NFVO operation condition Supported judging NFVO operation condition. This is related to PKG information and Granting information. Implements: blueprint support-vnfm-operations Spec: https://specs.openstack.org/openstack/tacker-specs/specs/victoria/support-sol003-vnfm-operations.html Change-Id: Ic1da78cb9d63628f12a1f2900b07b66e0d235155 diff --git a/tacker/tests/unit/vnfm/test_nfvo_client.py b/tacker/tests/unit/vnfm/test_nfvo_client.py new file mode 100644 index 0000000..b6e2030 --- /dev/null +++ b/tacker/tests/unit/vnfm/test_nfvo_client.py @@ -0,0 +1,632 @@ +# 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 datetime +import hashlib +import io +import json +import os +import shutil +import tempfile +import uuid +import zipfile + +import ddt +from oslo_config import cfg +import requests + +from tacker.tests.unit import base +from tacker.tests.unit.vnfm.infra_drivers.openstack.fixture_data import client +from tacker.tests.unit.vnfpkgm import fakes +from tacker.tests import utils +from tacker.tests import uuidsentinel +import tacker.vnfm.nfvo_client as nfvo_client +from unittest import mock + + +def _count_mock_history(history, *url): + req_count = 0 + for mock_history in history: + actual_url = '{}://{}'.format(mock_history.scheme, + mock_history.hostname) + if actual_url in url: + req_count += 1 + return req_count + + +@ddt.ddt +class TestVnfPackageRequest(base.FixturedTestCase): + + client_fixture_class = client.ClientFixture + sdk_connection_fixure_class = client.SdkConnectionFixture + + def setUp(self): + super(TestVnfPackageRequest, self).setUp() + self.url = "http://nfvo.co.jp/vnfpkgm/v1/vnf_packages" + self.nfvo_url = "http://nfvo.co.jp" + self.test_package_dir = 'tacker/tests/unit/vnfm/' + self.headers = {'Content-Type': 'application/json'} + + def tearDown(self): + super(TestVnfPackageRequest, self).tearDown() + self.addCleanup(mock.patch.stopall) + + def assert_zipfile( + self, + actual_zip, + expected_zips, + expected_artifacts=None): + expected_artifacts = expected_artifacts if expected_artifacts else [] + + def check_zip(expected_zip): + self.assertIsInstance(expected_zip, zipfile.ZipFile) + for expected_name in expected_zip.namelist(): + expected_checksum = hashlib.sha256( + expected_zip.read(expected_name)).hexdigest() + actual_checksum = hashlib.sha256( + actual_zip.read(expected_name)).hexdigest() + self.assertEqual(expected_checksum, actual_checksum) + + try: + self.assertIsInstance(actual_zip, zipfile.ZipFile) + self.assertIsNone(actual_zip.testzip()) + + expected_elm_cnt = sum( + map(lambda x: len(x.namelist()), expected_zips)) + self.assertEqual(expected_elm_cnt + + len(expected_artifacts), len(actual_zip.namelist())) + + for expected_zip in expected_zips: + check_zip(expected_zip) + + for expected_artifact in expected_artifacts: + expected_checksum = hashlib.sha256( + open(expected_artifact, 'rb').read()).hexdigest() + actual_checksum = hashlib.sha256( + actual_zip.read(expected_artifact)).hexdigest() + self.assertEqual(expected_checksum, actual_checksum) + except Exception as e: + self.fail(e) + + def json_serial_date_to_dict(self, json_obj): + def json_serial(obj): + if isinstance(obj, datetime.datetime): + return obj.isoformat() + + raise TypeError("Type %s not serializable" % type(obj)) + + serial_json_str = json.dumps(json_obj, default=json_serial) + return json.loads(serial_json_str) + + def test_init(self): + self.assertEqual(None, cfg.CONF.connect_vnf_packages.base_url) + self.assertEqual(None, cfg.CONF.connect_vnf_packages.pipeline) + self.assertEqual(2, cfg.CONF.connect_vnf_packages.retry_num) + self.assertEqual(30, cfg.CONF.connect_vnf_packages.retry_wait) + self.assertEqual(20, cfg.CONF.connect_vnf_packages.timeout) + + def _make_zip_file_from_sample(self, dir_name, read_vnfd_only=False): + unique_name = str(uuid.uuid4()) + temp_dir = os.path.join('/tmp', unique_name) + utils.copy_csar_files(temp_dir, dir_name, read_vnfd_only) + tempfd, temp_filepath = tempfile.mkstemp(suffix=".zip", dir=temp_dir) + os.close(tempfd) + zipfile.ZipFile(temp_filepath, 'w') + self.addCleanup(shutil.rmtree, temp_dir) + return temp_filepath + + @ddt.data({'content': 'vnfpkgm1', + 'vnfd': None, + 'artifacts': None}, + {'content': None, + 'vnfd': 'vnfpkgm2', + 'artifacts': None}, + {'content': None, + 'vnfd': None, + 'artifacts': ["vnfd_lcm_user_data.yaml"]}, + {'content': 'vnfpkgm1', + 'vnfd': 'vnfpkgm2', + 'artifacts': ["vnfd_lcm_user_data.yaml"]}, + {'content': 'vnfpkgm1', + 'vnfd': None, + 'artifacts': None}, + {'content': None, + 'vnfd': 'vnfpkgm2', + 'artifacts': ["vnfd_lcm_user_data.yaml"]}, + {'content': 'vnfpkgm1', + 'vnfd': 'vnfpkgm2', + 'artifacts': ["vnfd_lcm_user_data.yaml"]}, + ) + @ddt.unpack + def test_download_vnf_packages(self, content, vnfd, artifacts): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + fetch_base_url = os.path.join(self.url, uuidsentinel.vnf_pkg_id) + expected_connect_cnt = 0 + pipelines = [] + + if content: + expected_connect_cnt += 1 + pipelines.append('package_content') + path = self._make_zip_file_from_sample(content) + with open(path, 'rb') as test_package_content_zip_obj: + expected_package_content_zip = zipfile.ZipFile( + io.BytesIO(test_package_content_zip_obj.read())) + test_package_content_zip_obj.seek(0) + self.requests_mock.register_uri( + 'GET', + os.path.join( + fetch_base_url, + 'package_content'), + content=test_package_content_zip_obj.read(), + headers={ + 'Content-Type': 'application/zip'}, + status_code=200) + + if vnfd: + expected_connect_cnt += 1 + pipelines.append('vnfd') + path = self._make_zip_file_from_sample(vnfd, read_vnfd_only=True) + with open(path, 'rb') as test_vnfd_zip_obj: + expected_vnfd_zip = zipfile.ZipFile( + io.BytesIO(test_vnfd_zip_obj.read())) + test_vnfd_zip_obj.seek(0) + self.requests_mock.register_uri( + 'GET', + os.path.join( + fetch_base_url, + 'vnfd'), + content=test_vnfd_zip_obj.read(), + headers={ + 'Content-Type': 'application/zip'}, + status_code=200) + + if artifacts: + pipelines.append('artifacts') + artifacts = [os.path.join("tacker/tests/etc/samples", p) + for p in artifacts] + for artifact_path in artifacts: + expected_connect_cnt += 1 + with open(artifact_path, 'rb') as artifact_path_obj: + self.requests_mock.register_uri( + 'GET', + os.path.join( + fetch_base_url, + 'artifacts', + artifact_path), + headers={ + 'Content-Type': 'application/octet-stream'}, + status_code=200, + content=artifact_path_obj.read()) + + cfg.CONF.set_default( + name='pipeline', + group='connect_vnf_packages', + default=pipelines) + + if artifacts: + res = nfvo_client.VnfPackageRequest.download_vnf_packages( + uuidsentinel.vnf_pkg_id, artifacts) + else: + res = nfvo_client.VnfPackageRequest.download_vnf_packages( + uuidsentinel.vnf_pkg_id) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + + self.assertIsInstance(res, io.BytesIO) + actual_zip = zipfile.ZipFile(res) + if content and vnfd: + self.assert_zipfile( + actual_zip, [ + expected_package_content_zip, + expected_vnfd_zip], + artifacts) + elif content: + self.assert_zipfile( + actual_zip, [expected_package_content_zip], artifacts) + elif vnfd: + self.assert_zipfile( + actual_zip, [expected_vnfd_zip], artifacts) + else: + self.assert_zipfile( + actual_zip, [], artifacts) + + self.assertEqual(expected_connect_cnt, req_count) + + def test_download_vnf_packages_content_disposition(self): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + fetch_base_url = os.path.join(self.url, uuidsentinel.vnf_pkg_id) + test_yaml_filepath = os.path.join( + 'tacker/tests/etc/samples', + 'vnfd_lcm_user_data.yaml') + with open(test_yaml_filepath, 'rb') as test_yaml_obj: + headers = { + 'Content-Type': 'application/octet-stream', + 'Content-Disposition': + 'filename={}'.format(test_yaml_filepath)} + self.requests_mock.register_uri( + 'GET', + os.path.join( + fetch_base_url, + 'vnfd'), + content=test_yaml_obj.read(), + headers=headers, + status_code=200) + + cfg.CONF.set_default( + name='pipeline', + group='connect_vnf_packages', + default=['vnfd']) + + res = nfvo_client.VnfPackageRequest.download_vnf_packages( + uuidsentinel.vnf_pkg_id) + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual(1, req_count) + + self.assertIsInstance(res, io.BytesIO) + actual_zip = zipfile.ZipFile(res) + self.assert_zipfile(actual_zip, [], [test_yaml_filepath]) + + def test_download_vnf_packages_non_content_disposition_raise_download( + self): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + fetch_base_url = os.path.join(self.url, uuidsentinel.vnf_pkg_id) + test_yaml_filepath = os.path.join( + 'tacker/tests/etc/samples', + 'vnfd_lcm_user_data.yaml') + with open(test_yaml_filepath, 'rb') as test_yaml_obj: + headers = {'Content-Type': 'application/octet-stream'} + self.requests_mock.register_uri( + 'GET', + os.path.join( + fetch_base_url, + 'vnfd'), + content=test_yaml_obj.read(), + headers=headers, + status_code=200) + + cfg.CONF.set_default( + name='pipeline', + group='connect_vnf_packages', + default=['vnfd']) + + self.assertRaises( + nfvo_client.FaliedDownloadContentException, + nfvo_client.VnfPackageRequest.download_vnf_packages, + uuidsentinel.vnf_pkg_id) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual(1, req_count) + + def test_download_vnf_packages_with_retry_raise_not_found(self): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + cfg.CONF.set_default( + name='pipeline', + group='connect_vnf_packages', + default=[ + "package_content", + "vnfd"]) + + fetch_base_url = os.path.join(self.url, uuidsentinel.vnf_pkg_id) + self.requests_mock.register_uri( + 'GET', + os.path.join( + fetch_base_url, + 'package_content'), + headers=self.headers, + status_code=404) + + try: + nfvo_client.VnfPackageRequest.download_vnf_packages( + uuidsentinel.vnf_pkg_id) + except requests.exceptions.RequestException as e: + self.assertEqual(404, e.response.status_code) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual( + cfg.CONF.connect_vnf_packages.retry_num + 1, req_count) + + def test_download_vnf_packages_with_retry_raise_timeout(self): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + cfg.CONF.set_default( + name='pipeline', + group='connect_vnf_packages', + default=[ + "package_content", + "vnfd"]) + + fetch_base_url = os.path.join(self.url, uuidsentinel.vnf_pkg_id) + self.requests_mock.register_uri( + 'GET', + os.path.join( + fetch_base_url, + 'package_content'), + exc=requests.exceptions.ConnectTimeout) + + try: + nfvo_client.VnfPackageRequest.download_vnf_packages( + uuidsentinel.vnf_pkg_id) + except requests.exceptions.RequestException as e: + self.assertIsNone(e.response) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual( + cfg.CONF.connect_vnf_packages.retry_num + 1, req_count) + + def test_download_vnf_packages_raise_failed_download_content(self): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + cfg.CONF.set_default( + name='pipeline', + group='connect_vnf_packages', + default=[ + "package_content", + "vnfd"]) + + fetch_base_url = os.path.join(self.url, uuidsentinel.vnf_pkg_id) + self.requests_mock.register_uri('GET', os.path.join( + fetch_base_url, 'package_content'), content=None) + + self.assertRaises( + nfvo_client.FaliedDownloadContentException, + nfvo_client.VnfPackageRequest.download_vnf_packages, + uuidsentinel.vnf_pkg_id) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual(1, req_count) + + @ddt.data(None, "", " ") + def test_download_vnf_packages_raise_non_baseurl(self, empty_val): + cfg.CONF.set_override("base_url", empty_val, + group='connect_vnf_packages') + + self.assertRaises( + nfvo_client.UndefinedExternalSettingException, + nfvo_client.VnfPackageRequest.download_vnf_packages, + uuidsentinel.vnf_pkg_id) + + @ddt.data(None, [], ["non"]) + def test_download_vnf_packages_raise_non_pipeline(self, empty_val): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + cfg.CONF.set_override('pipeline', empty_val, + group='connect_vnf_packages') + + self.assertRaises( + nfvo_client.UndefinedExternalSettingException, + nfvo_client.VnfPackageRequest.download_vnf_packages, + uuidsentinel.vnf_pkg_id) + + def test_index(self): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + + response_body = self.json_serial_date_to_dict( + [fakes.VNFPACKAGE_RESPONSE, fakes.VNFPACKAGE_RESPONSE]) + self.requests_mock.register_uri( + 'GET', self.url, headers=self.headers, json=response_body) + + res = nfvo_client.VnfPackageRequest.index() + self.assertEqual(200, res.status_code) + self.assertIsInstance(res.json(), list) + self.assertEqual(response_body, res.json()) + self.assertEqual(2, len(res.json())) + self.assertEqual(response_body, json.loads(res.text)) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual(1, req_count) + + def test_index_raise_not_found(self): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + + self.requests_mock.register_uri( + 'GET', self.url, headers=self.headers, status_code=404) + + try: + nfvo_client.VnfPackageRequest.index() + except requests.exceptions.RequestException as e: + self.assertEqual(404, e.response.status_code) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual( + cfg.CONF.connect_vnf_packages.retry_num + 1, req_count) + + def test_index_raise_non_baseurl(self): + cfg.CONF.set_override("base_url", None, + group='connect_vnf_packages') + + self.assertRaises(nfvo_client.UndefinedExternalSettingException, + nfvo_client.VnfPackageRequest.index) + + def test_show(self): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + + response_body = self.json_serial_date_to_dict( + fakes.VNFPACKAGE_RESPONSE) + self.requests_mock.register_uri( + 'GET', + os.path.join( + self.url, + uuidsentinel.vnf_pkg_id), + headers=self.headers, + json=response_body) + + res = nfvo_client.VnfPackageRequest.show(uuidsentinel.vnf_pkg_id) + self.assertEqual(200, res.status_code) + self.assertIsInstance(res.json(), dict) + self.assertEqual(response_body, res.json()) + self.assertEqual(response_body, json.loads(res.text)) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual(1, req_count) + + def test_show_raise_not_found(self): + cfg.CONF.set_override("base_url", self.url, + group='connect_vnf_packages') + + self.requests_mock.register_uri( + 'GET', + os.path.join( + self.url, + uuidsentinel.vnf_pkg_id), + headers=self.headers, + status_code=404) + + try: + nfvo_client.VnfPackageRequest.show(uuidsentinel.vnf_pkg_id) + except requests.exceptions.RequestException as e: + self.assertEqual(404, e.response.status_code) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual( + cfg.CONF.connect_vnf_packages.retry_num + 1, req_count) + + def test_show_raise_non_baseurl(self): + cfg.CONF.set_override("base_url", None, + group='connect_vnf_packages') + + self.assertRaises(nfvo_client.UndefinedExternalSettingException, + nfvo_client.VnfPackageRequest.show, + uuidsentinel.vnf_pkg_id) + + +@ddt.ddt +class TestGrantRequest(base.FixturedTestCase): + client_fixture_class = client.ClientFixture + sdk_connection_fixure_class = client.SdkConnectionFixture + + def setUp(self): + super(TestGrantRequest, self).setUp() + self.url = "http://nfvo.co.jp/grant/v1/grants" + self.nfvo_url = 'http://nfvo.co.jp' + self.headers = {'content-type': 'application/json'} + + def tearDown(self): + super(TestGrantRequest, self).tearDown() + self.addCleanup(mock.patch.stopall) + + def create_request_body(self): + return { + "vnfInstanceId": uuidsentinel.vnf_instance_id, + "vnfLcmOpOccId": uuidsentinel.vnf_lcm_op_occ_id, + "operation": "INST", + "isAutomaticInvocation": False, + "links": { + "vnfLcmOpOcc": { + "href": + "https://localost/vnfm/vnflcm/v1/vnf_lcm_op_occs/" + + uuidsentinel.vnf_lcm_op_occ_id}, + "vnfInstance": { + "href": "https://localost/vnfm/vnflcm/v1/vnf_instances/" + + uuidsentinel.vnf_instance_id}}} + + def fake_response_body(self): + return { + "id": uuidsentinel.grant_id, + "vnfInstanceId": uuidsentinel.vnf_instance_id, + "vnfLcmOpOccId": uuidsentinel.vnf_lcm_op_occ_id, + "additionalParams": {}, + "_links": { + "self": { + "href": + "http://nfvo.co.jp/grant/v1/grants/\ + 19533fd4-eacb-4e6f-acd9-b56210a180d7"}, + "vnfLcmOpOcc": { + "href": + "https://localost/vnfm/vnflcm/v1/vnf_lcm_op_occs/" + + uuidsentinel.vnf_lcm_op_occ_id}, + "vnfInstance": { + "href": "https://localost/vnfm/vnflcm/v1/vnf_instances/" + + uuidsentinel.vnf_instance_id}}} + + def test_init(self): + self.assertEqual(None, cfg.CONF.connect_grant.base_url) + self.assertEqual(2, cfg.CONF.connect_grant.retry_num) + self.assertEqual(30, cfg.CONF.connect_grant.retry_wait) + self.assertEqual(20, cfg.CONF.connect_grant.timeout) + + def test_grants(self): + cfg.CONF.set_override("base_url", self.url, group='connect_grant') + + response_body = self.fake_response_body() + self.requests_mock.register_uri( + 'POST', + self.url, + json=response_body, + headers=self.headers, + status_code=201) + + request_body = self.create_request_body() + res = nfvo_client.GrantRequest.grants(data=request_body) + self.assertEqual(response_body, json.loads(res.text)) + self.assertEqual(response_body, res.json()) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual(1, req_count) + + def test_grants_with_retry_raise_bad_request(self): + cfg.CONF.set_override("base_url", self.url, group='connect_grant') + + response_body = self.fake_response_body() + self.requests_mock.register_uri('POST', self.url, json=json.dumps( + response_body), headers=self.headers, status_code=400) + + request_body = self.create_request_body() + try: + nfvo_client.GrantRequest.grants(data=request_body) + except requests.exceptions.RequestException as e: + self.assertEqual(400, e.response.status_code) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual( + cfg.CONF.connect_grant.retry_num + 1, req_count) + + def test_grants_with_retry_raise_timeout(self): + cfg.CONF.set_override("base_url", self.url, group='connect_grant') + self.requests_mock.register_uri( + 'POST', self.url, exc=requests.exceptions.ConnectTimeout) + + request_body = self.create_request_body() + try: + nfvo_client.GrantRequest.grants(data=request_body) + except requests.exceptions.RequestException as e: + self.assertIsNone(e.response) + + history = self.requests_mock.request_history + req_count = _count_mock_history(history, self.nfvo_url) + self.assertEqual( + cfg.CONF.connect_grant.retry_num + 1, req_count) + + @ddt.data(None, "", " ") + def test_grants_raise_non_baseurl(self, empty_val): + cfg.CONF.set_override("base_url", empty_val, group='connect_grant') + self.assertRaises(nfvo_client.UndefinedExternalSettingException, + nfvo_client.GrantRequest.grants, + data={"test": "value1"}) diff --git a/tacker/vnfm/nfvo_client.py b/tacker/vnfm/nfvo_client.py new file mode 100644 index 0000000..65b82b9 --- /dev/null +++ b/tacker/vnfm/nfvo_client.py @@ -0,0 +1,342 @@ +# 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 io +import os +import requests +import time +import zipfile + +from oslo_config import cfg +from oslo_log import log as logging + +LOG = logging.getLogger(__name__) + + +class UndefinedExternalSettingException(Exception): + pass + + +class FaliedDownloadContentException(Exception): + pass + + +class _Connect: + + def __init__(self, retry_num=0, retry_wait=0, timeout=0): + self.retry_num = retry_num + self.retry_wait = retry_wait + self.timeout = timeout + + def replace_placeholder_url(self, base_url, path, *params): + replace_placeholder_url = os.path.join(base_url, path) + try: + return replace_placeholder_url.format(*params) + except Exception: + return replace_placeholder_url + + def request(self, *args, **kwargs): + return self.__request( + requests.Session().request, + *args, + timeout=self.timeout, + **kwargs) + + def __request(self, request_function, *args, **kwargs): + response = None + for retry_cnt in range(self.retry_num + 1): + LOG.info("Connecting to <{ip}:{port}>, count=<{count}>".format( + ip=args[0], port=args[1], count=retry_cnt)) + if 'headers' in kwargs: + LOG.info("[REQ] HEADERS={}".format(kwargs['headers'])) + + if 'data' in kwargs: + LOG.info("[REQ] BODY={}".format(kwargs['data'])) + + elif 'json' in kwargs: + LOG.info("[REQ] BODY={}".format(kwargs['json'])) + + try: + response = request_function(*args, **kwargs) + if 200 <= response.status_code <= 299: + return response + + LOG.error("Connected error. Failed http status=<{}>".format( + response.status_code)) + except requests.exceptions.ConnectTimeout as e: + LOG.error("Connected error. details=<{}>".format(e)) + + if retry_cnt < self.retry_num: + time.sleep(self.retry_wait) + + raise requests.exceptions.RequestException(response=response) + + +class VnfPackageRequest: + OPTS = [ + cfg.StrOpt('base_url', + default=None, + help="vnf_packages base_url"), + cfg.ListOpt('pipeline', + default=None, + help="Get vnf_packages api pipeline"), + cfg.IntOpt('retry_num', + default=2, + help="Number of vnf_packages retry count"), + cfg.IntOpt('retry_wait', + default=30, + help="Number of vnf_packages retry wait"), + cfg.IntOpt('timeout', + default=20, + help="Number of vnf_packages connect timeout") + ] + cfg.CONF.register_opts(OPTS, group='connect_vnf_packages') + _connector = _Connect( + cfg.CONF.connect_vnf_packages.retry_num, + cfg.CONF.connect_vnf_packages.retry_wait, + cfg.CONF.connect_vnf_packages.timeout) + + @classmethod + def validate(cls): + """Check config settings. + + Raises: + UndefinedExternalSettingException: tacker.conf undefined setting. + """ + if (not cfg.CONF.connect_vnf_packages.base_url or + cfg.CONF.connect_vnf_packages.base_url.strip() == ''): + raise UndefinedExternalSettingException( + "Vnf package the external setting to 'base_url' undefined.") + + @classmethod + def _write(cls, vnf_package_zip, response, filename=None): + def write_zip(): + with zipfile.ZipFile(io.BytesIO(response.content)) as fin: + for info in fin.infolist(): + vnf_package_zip.writestr( + info.filename, fin.read(info.filename)) + + def get_filename(): + content_disposition = response.headers.get('Content-Disposition') + if not content_disposition: + return None + + attribute = 'filename=' + return content_disposition[content_disposition.find( + attribute) + len(attribute):] + + if response.headers.get('Content-Type') == 'application/zip': + write_zip() + return + + filename = get_filename() if (not filename) else filename + if filename: + vnf_package_zip.writestr(filename, response.content) + return + + raise FaliedDownloadContentException( + "Failed response content, vnf_package_zip={}".format( + vnf_package_zip)) + + @classmethod + def download_vnf_packages(cls, vnf_package_id, artifact_paths=None): + """Get vnf packages from the nfvo. + + Args: + vnf_package_id (UUID): VNF Package ID + artifact_paths (list, optional): artifatcs paths. Defaults to []. + + Returns: + io.BytesIO: zip archive for vnf packages content. + + Raises: + takcer.nfvo.nfvo_client.UndefinedExternalSettingException: + tacker.conf undefined setting. + requests.exceptions.RequestException: + Failed connected, download vnf packages. + takcer.nfvo.nfvo_client.FaliedDownloadContentException: + Failed content, create vnf package zip file. + """ + cls.validate() + if not cfg.CONF.connect_vnf_packages.pipeline or len( + cfg.CONF.connect_vnf_packages.pipeline) == 0: + raise UndefinedExternalSettingException( + "Vnf package the external setting to 'pipeline' undefined.") + + if artifact_paths is None: + artifact_paths = [] + + def download_vnf_package(pipeline_type, vnf_package_zip): + if pipeline_type == 'package_content': + cls._download_package_content(vnf_package_zip, vnf_package_id) + elif pipeline_type == 'vnfd': + cls._download_vnfd( + vnf_package_zip, vnf_package_id) + elif pipeline_type == 'artifacts': + cls._download_artifacts(vnf_package_zip, vnf_package_id, + artifact_paths) + else: + raise UndefinedExternalSettingException( + "Vnf package the external setting to 'pipeline=<{}>' " + + "not supported.".format(pipeline_type)) + + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, + mode='w', + compression=zipfile.ZIP_DEFLATED) as vnf_package_zip: + for pipeline_type in cfg.CONF.connect_vnf_packages.pipeline: + download_vnf_package(pipeline_type, vnf_package_zip) + + zip_buffer.seek(0) + return zip_buffer + + @classmethod + def _download_package_content(cls, vnf_package_zip, vnf_package_id): + LOG.info("Processing: download vnf_package to package content.") + request_url = cls._connector.replace_placeholder_url( + cfg.CONF.connect_vnf_packages.base_url, + "{}/package_content", + vnf_package_id) + + headers = {'Accept': 'application/zip'} + response = cls._connector.request('GET', request_url, headers=headers) + cls._write(vnf_package_zip, response) + + @classmethod + def _download_vnfd(cls, vnf_package_zip, vnf_package_id): + LOG.info("Processing: download vnf_package to vnfd.") + request_url = cls._connector.replace_placeholder_url( + cfg.CONF.connect_vnf_packages.base_url, + "{}/vnfd", + vnf_package_id) + + # zip format only. + headers = {'Accept': 'application/zip'} + response = cls._connector.request('GET', request_url, headers=headers) + cls._write(vnf_package_zip, response) + + @classmethod + def _download_artifacts( + cls, + vnf_package_zip, + vnf_package_id, + artifact_paths): + LOG.info("Processing: download vnf_package to artifact.") + for artifact_path in artifact_paths: + request_url = cls._connector.replace_placeholder_url( + cfg.CONF.connect_vnf_packages.base_url, + "{}/artifacts/{}", + vnf_package_id, + artifact_path) + headers = {'Accept': 'application/zip'} + response = cls._connector.request( + 'GET', request_url, headers=headers) + cls._write(vnf_package_zip, response, artifact_path) + + @classmethod + def index(cls, **kwargs): + """List vnf package. + + Args: + kwargs: + any other parameter that can be passed + to requests.Session.request. + + Returns: + requests.Response: individual vnf package. + """ + cls.validate() + + LOG.info("Processing: index vnf_package.") + return cls._connector.request( + 'GET', cfg.CONF.connect_vnf_packages.base_url, **kwargs) + + @classmethod + def show(cls, vnf_package_id, **kwargs): + """Individual vnf package. + + Args: + vnf_package_id (UUID): VNF Package ID. + + kwargs: + any other parameter that can be passed + to requests.Session.request. + + Returns: + requests.Response: individual vnf package. + """ + cls.validate() + + LOG.info("Processing: show vnf_package.") + request_url = cls._connector.replace_placeholder_url( + cfg.CONF.connect_vnf_packages.base_url, vnf_package_id) + + return cls._connector.request('GET', request_url, **kwargs) + + +class GrantRequest: + OPTS = [ + cfg.StrOpt('base_url', + default=None, + help="grant of base_url"), + cfg.IntOpt('retry_num', + default=2, + help="Number of grant retry count"), + cfg.IntOpt('retry_wait', + default=30, + help="Number of grant retry wait"), + cfg.IntOpt('timeout', + default=20, + help="Number of grant connect timeout") + ] + cfg.CONF.register_opts(OPTS, group='connect_grant') + + _connector = _Connect( + cfg.CONF.connect_grant.retry_num, + cfg.CONF.connect_grant.retry_wait, + cfg.CONF.connect_grant.timeout) + + @classmethod + def validate(cls): + """Check config settings. + + Raises: + UndefinedExternalSettingException: tacker.conf undefined setting. + """ + if (not cfg.CONF.connect_grant.base_url or + cfg.CONF.connect_grant.base_url.strip() == ''): + raise UndefinedExternalSettingException( + "Grant the external setting to 'base_url' undefined.") + + @classmethod + def grants(cls, **kwargs): + """grants request. + + Args: + kwargs: + any other parameter that can be passed + to requests.Session.request. + + Returns: + io.BytesIO: zip archive for vnf packages content. + + Raises: + takcer.nfvo.nfvo_client.UndefinedExternalSettingException: + tacker.conf undefined setting. + requests.exceptions.RequestException: + Failed connected, download vnf packages. + takcer.nfvo.nfvo_client.FaliedDownloadContentException: + Failed content, create vnf package zip file. + """ + cls.validate() + return cls._connector.request( + 'POST', cfg.CONF.connect_grant.base_url, **kwargs)