diff --git a/tacker/tests/unit/db/utils.py b/tacker/tests/unit/db/utils.py index ce370fee4..de84d08cc 100644 --- a/tacker/tests/unit/db/utils.py +++ b/tacker/tests/unit/db/utils.py @@ -29,6 +29,7 @@ def _get_template(name): f = codecs.open(filename, encoding='utf-8', errors='strict') return f.read() +tosca_cvnf_vnfd = _get_template('test_tosca_cvnf.yaml') tosca_vnfd_openwrt = _get_template('test_tosca_openwrt.yaml') tosca_vnfd_openwrt_param = _get_template('test_tosca_openwrt_param.yaml') tosca_invalid_vnfd = _get_template('test_tosca_parser_failure.yaml') @@ -119,6 +120,16 @@ def get_dummy_inline_vnf_obj(): 'vnfd_id': None}} +def get_dummy_inline_cvnf_obj(): + return {'vnf': {'description': 'dummy_inline_cvnf_description', + 'vnfd_template': yaml.safe_load(tosca_cvnf_vnfd), + 'vim_id': u'6261579e-d6f3-49ad-8bc3-a9cb974778ff', + 'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437', + 'name': 'dummy_cvnf', + 'attributes': {}, + 'vnfd_id': None}} + + def get_dummy_vnf_obj(): return {'vnf': {'description': 'dummy_vnf_description', 'vnfd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e', @@ -153,6 +164,24 @@ def get_dummy_vnf_invalid_param_type_obj(): return {'vnf': {u'attributes': {u'param_values': 'dummy_param'}}} +def get_dummy_vnf_invalid_config_type_obj(): + return {'vnf': {u'attributes': {u'config': 'dummy_config'}}} + + +def get_dummy_vnf_invalid_param_content(): + return {'vnf': {u'attributes': {u'param_values': {}}}} + + +def get_dummy_vnf_param_obj(): + return {'vnf': {u'attributes': {u'param_values': + {'flavor': 'm1.tiny', + 'reservation_id': '99999999-3925-4c9e-9074-239a902b68d7'}}}} + + +def get_dummy_vnf_invalid_param_type_obj(): + return {'vnf': {u'attributes': {u'param_values': 'dummy_param'}}} + + def get_dummy_vnf(status='PENDING_CREATE', scaling_group=False, instance_id=None): dummy_vnf = {'status': status, 'instance_id': instance_id, 'name': @@ -235,6 +264,22 @@ def get_dummy_vnf_update_empty_param(): return {'vnf': {'attributes': {'param_values': {}}}} +def get_dummy_vnf_update_param(): + return {'vnf': {'attributes': {'param_values': update_param_data}}} + + +def get_dummy_vnf_update_new_param(): + return {'vnf': {'attributes': {'param_values': update_new_param_data}}} + + +def get_dummy_vnf_update_invalid_param(): + return {'vnf': {'attributes': {'param_values': update_invalid_param_data}}} + + +def get_dummy_vnf_update_empty_param(): + return {'vnf': {'attributes': {'param_values': {}}}} + + def get_vim_obj(): return {'vim': {'type': 'openstack', 'auth_url': 'http://localhost/identity', diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/test_tosca_cvnf.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/test_tosca_cvnf.yaml new file mode 100644 index 000000000..fa3e60421 --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/test_tosca_cvnf.yaml @@ -0,0 +1,37 @@ +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 + +description: A sample containerized VNF with one container per VDU + +metadata: + template_name: sample-tosca-vnfd + +topology_template: + node_templates: + VDU1: + type: tosca.nodes.nfv.VDU.Tacker + properties: + mapping_ports: + - 80:80 + namespace: default + vnfcs: + web_server: + num_cpus: 0.2 + mem_size: 100 MB + image: ubuntu:16.04 + config: | + param0: key1 + param1: key2 + CP11: + type: tosca.nodes.nfv.CP.Tacker + properties: + management: true + requirements: + - virtualLink: + node: VL11 + - virtualBinding: + node: VDU1 + VL11: + type: tosca.nodes.nfv.VL + properties: + network_name: k8s-pod-subnet + vendor: Tacker diff --git a/tacker/tests/unit/vnfm/test_k8s_plugin.py b/tacker/tests/unit/vnfm/test_k8s_plugin.py new file mode 100644 index 000000000..a85cf8a4a --- /dev/null +++ b/tacker/tests/unit/vnfm/test_k8s_plugin.py @@ -0,0 +1,220 @@ +# Copyright 2015 Brocade Communications System, Inc. +# All Rights Reserved. +# +# 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 datetime import datetime + +import mock + +from oslo_utils import uuidutils +from tacker import context +from tacker.db.common_services import common_services_db_plugin +from tacker.db.nfvo import nfvo_db +from tacker.db.vnfm import vnfm_db +from tacker.plugins.common import constants +from tacker.tests.unit.db import base as db_base +from tacker.tests.unit.db import utils +from tacker.vnfm import plugin + + +class FakeCVNFMonitor(mock.Mock): + pass + + +class FakeK8SVimClient(mock.Mock): + pass + + +class TestCVNFMPlugin(db_base.SqlTestCase): + def setUp(self): + super(TestCVNFMPlugin, self).setUp() + self.addCleanup(mock.patch.stopall) + self.context = context.get_admin_context() + self._mock_vim_client() + self._stub_get_vim() + self._mock_vnf_monitor() + self._insert_dummy_vim() + self.vnfm_plugin = plugin.VNFMPlugin() + mock.patch('tacker.db.common_services.common_services_db_plugin.' + 'CommonServicesPluginDb.create_event' + ).start() + mock.patch('tacker.db.vnfm.vnfm_db.VNFMPluginDb._mgmt_driver_name', + return_value='noop').start() + self.create = mock.patch('tacker.vnfm.infra_drivers.kubernetes.' + 'kubernetes_driver.Kubernetes.create', + return_value=uuidutils. + generate_uuid()).start() + self.create_wait = mock.patch('tacker.vnfm.infra_drivers.kubernetes.' + 'kubernetes_driver.Kubernetes.' + 'create_wait').start() + self.update = mock.patch('tacker.vnfm.infra_drivers.kubernetes.' + 'kubernetes_driver.Kubernetes.update').start() + self.update_wait = mock.patch('tacker.vnfm.infra_drivers.kubernetes.' + 'kubernetes_driver.Kubernetes.' + 'update_wait').start() + self.delete = mock.patch('tacker.vnfm.infra_drivers.kubernetes.' + 'kubernetes_driver.Kubernetes.delete').start() + self.delete_wait = mock.patch('tacker.vnfm.infra_drivers.kubernetes.' + 'kubernetes_driver.Kubernetes.' + 'delete_wait').start() + self.scale = mock.patch('tacker.vnfm.infra_drivers.kubernetes.' + 'kubernetes_driver.Kubernetes.scale', + return_value=uuidutils.generate_uuid()).start() + self.scale_wait = mock.patch('tacker.vnfm.infra_drivers.kubernetes.' + 'kubernetes_driver.Kubernetes.scale_wait', + return_value=uuidutils. + generate_uuid()).start() + + def _fake_spawn(func, *args, **kwargs): + func(*args, **kwargs) + + mock.patch.object(self.vnfm_plugin, 'spawn_n', + _fake_spawn).start() + self._cos_db_plugin =\ + common_services_db_plugin.CommonServicesPluginDb() + + def _mock_vim_client(self): + self.vim_client = mock.Mock(wraps=FakeK8SVimClient()) + fake_vim_client = mock.Mock() + fake_vim_client.return_value = self.vim_client + self._mock( + 'tacker.vnfm.vim_client.VimClient', fake_vim_client) + + def _stub_get_vim(self): + vim_obj = {'vim_id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff', + 'vim_name': 'fake_vim', + 'vim_auth': {'auth_url': 'http://localhost:6443', + 'password': 'test_pw', 'username': 'test_user', + 'project_name': 'test_project', + 'ssl_ca_cert': None}, + 'vim_type': 'kubernetes'} + self.vim_client.get_vim.return_value = vim_obj + + def _mock_vnf_monitor(self): + self._vnf_monitor = mock.Mock(wraps=FakeCVNFMonitor()) + fake_vnf_monitor = mock.Mock() + fake_vnf_monitor.return_value = self._vnf_monitor + self._mock( + 'tacker.vnfm.monitor.VNFMonitor', fake_vnf_monitor) + + def _insert_dummy_vnf_template(self): + session = self.context.session + vnf_template = vnfm_db.VNFD( + id='eb094833-995e-49f0-a047-dfb56aaf7c4e', + tenant_id='ad7ebc56538745a08ef7c5e97f8bd437', + name='fake_template', + description='fake_template_description', + template_source='onboarded', + deleted_at=datetime.min) + session.add(vnf_template) + session.flush() + return vnf_template + + def _insert_dummy_vnf_template_inline(self): + session = self.context.session + vnf_template = vnfm_db.VNFD( + id='d58bcc4e-d0cf-11e6-bf26-cec0c932ce01', + tenant_id='ad7ebc56538745a08ef7c5e97f8bd437', + name='tmpl-koeak4tqgoqo8cr4-dummy_inline_vnf', + description='inline_fake_template_description', + deleted_at=datetime.min, + template_source='inline') + session.add(vnf_template) + session.flush() + return vnf_template + + def _insert_dummy_vim(self): + pass + session = self.context.session + vim_db = nfvo_db.Vim( + id='6261579e-d6f3-49ad-8bc3-a9cb974778ff', + tenant_id='ad7ebc56538745a08ef7c5e97f8bd437', + name='fake_vim', + description='fake_vim_description', + type='kubernetes', + status='Active', + deleted_at=datetime.min, + placement_attr={'regions': ['default', 'kube-public', + 'kube-system']}) + vim_auth_db = nfvo_db.VimAuth( + vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff', + password='encrypted_pw', + auth_url='http://localhost:6443', + vim_project={'name': 'test_project'}, + auth_cred={'auth_url': 'https://localhost:6443', + 'username': 'admin', + 'bearer_token': None, + 'ssl_ca_cert': 'test', + 'project_name': 'default', + 'type': 'kubernetes'}) + session.add(vim_db) + session.add(vim_auth_db) + session.flush() + + def test_create_cvnf_with_vnfd(self): + self._insert_dummy_vnf_template() + vnf_obj = utils.get_dummy_vnf_obj() + result = self.vnfm_plugin.create_vnf(self.context, vnf_obj) + self.assertIsNotNone(result) + self.assertIn('id', result) + self.assertIn('instance_id', result) + self.assertIn('status', result) + self.assertIn('attributes', result) + self.assertIn('mgmt_ip_address', result) + self.assertIn('created_at', result) + self.assertIn('updated_at', result) + self.assertEqual('ACTIVE', result['status']) + self._cos_db_plugin.create_event.assert_called_with( + self.context, evt_type=constants.RES_EVT_CREATE, res_id=mock.ANY, + res_state=mock.ANY, res_type=constants.RES_TYPE_VNF, + tstamp=mock.ANY, details=mock.ANY) + + @mock.patch('tacker.vnfm.plugin.VNFMPlugin.create_vnfd') + def test_create_cvnf_from_template(self, mock_create_vnfd): + self._insert_dummy_vnf_template_inline() + mock_create_vnfd.return_value = {'id': + 'd58bcc4e-d0cf-11e6-bf26' + '-cec0c932ce01'} + vnf_obj = utils.get_dummy_inline_cvnf_obj() + result = self.vnfm_plugin.create_vnf(self.context, vnf_obj) + self.assertIsNotNone(result) + self.assertIn('id', result) + self.assertIn('instance_id', result) + self.assertIn('status', result) + self.assertIn('attributes', result) + self.assertIn('mgmt_ip_address', result) + self.assertIn('created_at', result) + self.assertIn('updated_at', result) + self.assertEqual('ACTIVE', result['status']) + mock_create_vnfd.assert_called_once_with(mock.ANY, mock.ANY) + self._cos_db_plugin.create_event.assert_called_with( + self.context, evt_type=constants.RES_EVT_CREATE, + res_id=mock.ANY, + res_state=mock.ANY, res_type=constants.RES_TYPE_VNF, + tstamp=mock.ANY, details=mock.ANY) + + def test_delete_vnf(self): + pass + + def test_update_vnf(self): + pass + + def _test_scale_vnf(self, type): + pass + + def test_scale_vnf_out(self): + pass + + def test_scale_vnf_in(self): + pass diff --git a/tacker/vnfm/infra_drivers/kubernetes/k8s/tosca_kube_object.py b/tacker/vnfm/infra_drivers/kubernetes/k8s/tosca_kube_object.py index ef6aaf9e6..1c809e00c 100644 --- a/tacker/vnfm/infra_drivers/kubernetes/k8s/tosca_kube_object.py +++ b/tacker/vnfm/infra_drivers/kubernetes/k8s/tosca_kube_object.py @@ -24,7 +24,7 @@ class ToscaKubeObject(object): def __init__(self, name=None, namespace=None, mapping_ports=None, containers=None, network_name=None, mgmt_connection_point=False, scaling_object=None, - service_type=None, labels=None): + service_type=None, labels=None, annotations=None): self._name = name self._namespace = namespace self._mapping_ports = mapping_ports @@ -34,6 +34,7 @@ class ToscaKubeObject(object): self._scaling_object = scaling_object self._service_type = service_type self._labels = labels + self._annotations = annotations @property def name(self): @@ -107,6 +108,14 @@ class ToscaKubeObject(object): def labels(self, labels): self._labels = labels + @property + def annotations(self): + return self._annotations + + @annotations.setter + def annotations(self, annotations): + self._annotations = annotations + class Container(object): """Container holds the basic structs of a container""" diff --git a/tacker/vnfm/infra_drivers/kubernetes/k8s/translate_inputs.py b/tacker/vnfm/infra_drivers/kubernetes/k8s/translate_inputs.py index 6730cf40d..e9dd35b85 100644 --- a/tacker/vnfm/infra_drivers/kubernetes/k8s/translate_inputs.py +++ b/tacker/vnfm/infra_drivers/kubernetes/k8s/translate_inputs.py @@ -14,6 +14,9 @@ # under the License. from oslo_config import cfg + +import json + from oslo_log import log as logging from oslo_utils import uuidutils @@ -26,7 +29,6 @@ from toscaparser.functions import GetInput from toscaparser import tosca_template import toscaparser.utils.yamlparser - LOG = logging.getLogger(__name__) CONF = cfg.CONF @@ -66,6 +68,7 @@ class Parser(object): try: parserd_params = None toscautils.updateimports(self.vnfd_dict) + tosca = tosca_template.\ ToscaTemplate(parsed_params=parserd_params, a_file=False, @@ -82,8 +85,14 @@ class Parser(object): vdu_name = node_template.name tosca_kube_obj = self.tosca_to_kube_mapping(node_template) - # Find network name in which VDU is attached - tosca_kube_obj.network_name = self.find_networks(tosca, vdu_name) + # Find network name in which VDU is attached + network_names = self.find_networks(tosca, vdu_name) + if network_names: + annotations_pad = \ + json.dumps([{"name": "%s" % net} + for net in network_names]) + tosca_kube_obj.annotations =\ + {'k8s.v1.cni.cncf.io/networks': annotations_pad} # If connection_point is True, Tacker will manage its service ip tosca_kube_obj.mgmt_connection_point = \ @@ -94,6 +103,7 @@ class Parser(object): tosca_kube_obj.scaling_object = \ self.get_scaling_policy(tosca, vdu_name) tosca_kube_objects.append(tosca_kube_obj) + return tosca_kube_objects @log.log @@ -226,17 +236,8 @@ class Parser(object): for key, value in tosca_props.items(): if key == 'network_name': network_names.append(value) - - if len(network_names) > 1: - # Currently, Kubernetes doesn't support multiple networks. - # If user provides more than one network, the error will raise. - # TODO(anyone): support Multus or multiple networking - LOG.debug("Kubernetes feature only support one network") - raise vnfm.InvalidKubernetesNetworkNumber if network_names: - return network_names[0] - else: - return None + return network_names @log.log def check_mgmt_cp(self, tosca, vdu_name): diff --git a/tacker/vnfm/infra_drivers/kubernetes/k8s/translate_outputs.py b/tacker/vnfm/infra_drivers/kubernetes/k8s/translate_outputs.py index 77b539941..099f1afa7 100644 --- a/tacker/vnfm/infra_drivers/kubernetes/k8s/translate_outputs.py +++ b/tacker/vnfm/infra_drivers/kubernetes/k8s/translate_outputs.py @@ -87,7 +87,6 @@ class Transformer(object): a list name of services """ - deployment_names = list() namespace = kubernetes_objects.get('namespace') k8s_objects = kubernetes_objects.get('objects') @@ -219,7 +218,8 @@ class Transformer(object): # Create and configure a spec section pod_template = client.V1PodTemplateSpec( - metadata=client.V1ObjectMeta(labels=labels), + metadata=client.V1ObjectMeta( + labels=labels, annotations=tosca_kube_obj.annotations), spec=client.V1PodSpec(containers=containers)) # Create the specification of deployment diff --git a/tools/test-setup-default-vim.sh b/tools/test-setup-default-vim.sh old mode 100755 new mode 100644 index 1dcaa770e..5111041da --- a/tools/test-setup-default-vim.sh +++ b/tools/test-setup-default-vim.sh @@ -4,4 +4,3 @@ # for functional testing, which cannot be put # in devstack/plugin.sh because new zuul3 CI # cannot keep the devstack plugins order -