diff --git a/doc/source/user/vnfm_usage_guide.rst b/doc/source/user/vnfm_usage_guide.rst index 3835367a5..747cf8b47 100644 --- a/doc/source/user/vnfm_usage_guide.rst +++ b/doc/source/user/vnfm_usage_guide.rst @@ -19,8 +19,8 @@ VNF Manager User Guide ====================== Tacker VNF Manager (VNFM) component manages the life-cycle of a Virtual Network -Function (VNF). VNFM takes care of deployment, monitoring, scaling and removal -of VNFs on a Virtual Infrastructure Manager (VIM). +Function (VNF). VNFM takes care of deployment, monitoring, updating, scaling +and removal of VNFs on a Virtual Infrastructure Manager (VIM). Onboarding VNF @@ -122,6 +122,19 @@ Status of various VNFM resources can be checked by following commands. .. +Updating VNF +============ + +VNFs can be updated as shown below. +--config, --config-file and --param-file can not be specified together. + +.. code-block:: console + + openstack vnf set --config + openstack vnf set --config-file + openstack vnf set --param-file +.. + Deleting VNF and VNFD ===================== diff --git a/tacker/extensions/vnfm.py b/tacker/extensions/vnfm.py index 33a6e5d2a..73180a0d4 100644 --- a/tacker/extensions/vnfm.py +++ b/tacker/extensions/vnfm.py @@ -64,6 +64,10 @@ class VNFCreateFailed(exceptions.TackerException): message = _('creating VNF based on %(vnfd_id)s failed') +class VNFUpdateInvalidInput(exceptions.TackerException): + message = _('VNF Update Invalid Input %(reason)s') + + class VNFUpdateWaitFailed(exceptions.TackerException): message = _('VNF Update %(reason)s') diff --git a/tacker/tests/constants.py b/tacker/tests/constants.py index de615b560..1e8c3b02b 100644 --- a/tacker/tests/constants.py +++ b/tacker/tests/constants.py @@ -15,6 +15,7 @@ DEFAULT_ALARM_ACTIONS = ['respawn', 'log', 'log_and_kill', 'notify'] POLICY_RESERVATION = 'tosca.policies.tacker.Reservation' VNF_CIRROS_CREATE_TIMEOUT = 300 VNFC_CREATE_TIMEOUT = 600 +VNF_CIRROS_UPDATE_TIMEOUT = 300 VNF_CIRROS_DELETE_TIMEOUT = 300 VNF_CIRROS_DEAD_TIMEOUT = 500 ACTIVE_SLEEP_TIME = 3 diff --git a/tacker/tests/etc/samples/sample-tosca-vnf-update-values.yaml b/tacker/tests/etc/samples/sample-tosca-vnf-update-values.yaml new file mode 100644 index 000000000..b66848cdc --- /dev/null +++ b/tacker/tests/etc/samples/sample-tosca-vnf-update-values.yaml @@ -0,0 +1,3 @@ +{ +flavor: 'm1.small' +} diff --git a/tacker/tests/functional/vnfm/test_vnfm_param.py b/tacker/tests/functional/vnfm/test_vnfm_param.py index 502159d04..3ac21097b 100644 --- a/tacker/tests/functional/vnfm/test_vnfm_param.py +++ b/tacker/tests/functional/vnfm/test_vnfm_param.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import time import unittest import yaml @@ -85,6 +86,48 @@ class VnfmTestParam(base.BaseTackerTest): return vnf_instance, param_values_dict + def _test_vnf_update(self, vnf_instance, param_values): + # Update Vnf + vnf_id = vnf_instance['vnf']['id'] + new_param_values = {'vnf': {'attributes': { + 'param_values': param_values}}} + self.client.update_vnf(vnf_id, new_param_values) + self.wait_until_vnf_active( + vnf_id, + constants.VNF_CIRROS_UPDATE_TIMEOUT, + constants.ACTIVE_SLEEP_TIME) + + # Wait until the update on the heat side is completed, + # because vnf deletion will cause a conflict without waiting for this. + stack_id = self.client.show_vnf(vnf_id)['vnf']['instance_id'] + start_time = int(time.time()) + while True: + vdu_resource = self.get_vdu_resource(stack_id, "VDU1") + vdu_resource_dict = vdu_resource.to_dict() + vdu_resource_status = vdu_resource_dict['resource_status'] + if ((int(time.time()) - start_time > + constants.VNF_CIRROS_UPDATE_TIMEOUT) or + (vdu_resource_status == 'UPDATE_COMPLETE')): + break + time.sleep(constants.ACTIVE_SLEEP_TIME) + + self.verify_vnf_crud_events( + vnf_id, evt_constants.RES_EVT_UPDATE, evt_constants.PENDING_UPDATE) + self.verify_vnf_crud_events( + vnf_id, evt_constants.RES_EVT_UPDATE, evt_constants.ACTIVE) + + # Verify vnf_param_values_dict is same as param values from vnf_show + vnf_instance = self.client.show_vnf(vnf_id) + vnf_param_values = vnf_instance['vnf']['attributes']['param_values'] + vnf_param_values_dict = yaml.safe_load(vnf_param_values) + + # Verify stack_parameters is same as parameters from stack_show + instance_id = vnf_instance['vnf']['instance_id'] + stack_values = self.h_client.stacks.get(instance_id) + stack_parameters = stack_values.parameters + + return vnf_param_values_dict, stack_parameters + def _test_vnf_delete(self, vnf_instance): # Delete Vnf vnf_id = vnf_instance['vnf']['id'] @@ -126,11 +169,32 @@ class VnfmTestParam(base.BaseTackerTest): def _test_vnf_param_tosca_template(self, vnfd_file, vnfd_name, param_file, vnf_name): vnfd_instance = self._test_vnfd_create(vnfd_file, vnfd_name) + + # Get vnfd_id + vnfd_id = vnfd_instance['vnfd']['id'] + + # Add vnfd delete to cleanup job so that if vnf_instance fails to + # create or update then it will be cleaned-up automatically + # in tearDown() + self.addCleanup(self.client.delete_vnfd, vnfd_id) + + # Create vnf instance values_str = read_file(param_file) values_dict = yaml.safe_load(values_str) vnf_instance, param_values_dict = self._test_vnf_create( vnfd_instance, vnf_name, values_dict) self.assertEqual(values_dict, param_values_dict) + + new_values_str = read_file('sample-tosca-vnf-update-values.yaml') + new_values_dict = yaml.safe_load(new_values_str) + vnf_param_values_dict, stack_parameters = self._test_vnf_update( + vnf_instance, new_values_dict) + for key, value in new_values_dict.items(): + if vnf_param_values_dict.get(key): + self.assertEqual(value, vnf_param_values_dict[key]) + if stack_parameters.get(key): + self.assertEqual(value, stack_parameters[key]) + self._test_vnf_delete(vnf_instance) vnf_id = vnf_instance['vnf']['id'] self.verify_vnf_crud_events( @@ -142,4 +206,3 @@ class VnfmTestParam(base.BaseTackerTest): constants.VNF_CIRROS_DELETE_TIMEOUT) self.verify_vnf_crud_events(vnf_id, evt_constants.RES_EVT_DELETE, evt_constants.PENDING_DELETE, cnt=2) - self.addCleanup(self.client.delete_vnfd, vnfd_instance['vnfd']['id']) diff --git a/tacker/tests/unit/db/utils.py b/tacker/tests/unit/db/utils.py index 89f204f25..bbb6d9ac6 100644 --- a/tacker/tests/unit/db/utils.py +++ b/tacker/tests/unit/db/utils.py @@ -30,9 +30,15 @@ def _get_template(name): return f.read() 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') config_data = _get_template('config_data.yaml') update_config_data = _get_template('update_config_data.yaml') +hot_data = _get_template('hot_data.yaml') +param_data = _get_template('param_data.yaml') +update_param_data = _get_template('update_param_data.yaml') +update_invalid_param_data = _get_template('update_invalid_param_data.yaml') +update_new_param_data = _get_template('update_new_param_data.yaml') vnffg_params = _get_template('vnffg_params.yaml') vnffg_multi_params = _get_template('vnffg_multi_params.yaml') vnffgd_template = yaml.safe_load(_get_template('vnffgd_template.yaml')) @@ -129,6 +135,24 @@ def get_dummy_vnf_config_obj(): 'config': {'firewall': 'dummy_firewall_values'}}}}}}} +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': @@ -172,10 +196,45 @@ def get_dummy_vnf_config_attr(): 'description': u'OpenWRT with services'} +def get_dummy_vnf_param_attr(): + return {'status': 'PENDING_CREATE', 'instance_id': None, 'name': + u'test_openwrt', 'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437', + 'vnfd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e', + 'vnfd': {'service_types': [{'service_type': u'vnfd', + 'id': u'4a4c2d44-8a52-4895-9a75-9d1c76c3e738'}], + 'description': u'OpenWRT with services', + 'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437', + 'mgmt_driver': u'openwrt', + 'attributes': {u'vnfd': tosca_vnfd_openwrt_param}, + 'id': u'fb048660-dc1b-4f0f-bd89-b023666650ec', + 'name': u'openwrt_services'}, + 'mgmt_url': None, 'service_context': [], + 'attributes': {'heat_template': hot_data, + 'param_values': param_data}, + 'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123', + 'description': u'OpenWRT with services'} + + def get_dummy_vnf_update_config(): return {'vnf': {'attributes': {'config': update_config_data}}} +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/hot_data.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_data.yaml new file mode 100644 index 000000000..99b8aefb7 --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/hot_data.yaml @@ -0,0 +1,27 @@ +heat_template_version: 2013-05-23 +description: 'OpenWRT with services + +' +parameters: + flavor: {type: string, default: m1.tiny} + reservation_id: {type: string, default: 891cd152-3925-4c9e-9074-239a902b68d7} +resources: + VDU1: + type: OS::Nova::Server + properties: + networks: + - port: {get_resource: CP1} + image: OpenWRT + user_data_format: SOFTWARE_CONFIG + config_drive: false + flavor: {get_param: flavor} + scheduler_hints: + reservation: {get_param: reservation_id} + CP1: + type: OS::Neutron::Port + properties: {network: existing_network_1, port_security_enabled: false} + +outputs: + mgmt_ip-VDU1: + value: + get_attr: [CP1, fixed_ips, 0, ip_address] diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/param_data.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/param_data.yaml new file mode 100644 index 000000000..d6980cba9 --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/param_data.yaml @@ -0,0 +1,2 @@ +flavor: m1.tiny +reservation_id: 891cd152-3925-4c9e-9074-239a902b68d7 diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/test_tosca_openwrt_param.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/test_tosca_openwrt_param.yaml new file mode 100644 index 000000000..7060356e6 --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/test_tosca_openwrt_param.yaml @@ -0,0 +1,44 @@ +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 + +description: OpenWRT with services + +metadata: + template_name: OpenWRT + +topology_template: + inputs: + flavor: + type: string + reservation_id: + type: string + + node_templates: + + VDU1: + type: tosca.nodes.nfv.VDU.Tacker + properties: + image: OpenWRT + flavor: {get_input: flavor} + reservation: {get_input: reservation_id} + config: | + param0: key1 + param1: key2 + mgmt_driver: openwrt + + CP1: + type: tosca.nodes.nfv.CP.Tacker + properties: + management: true + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL1 + - virtualBinding: + node: VDU1 + + VL1: + type: tosca.nodes.nfv.VL + properties: + network_name: existing_network_1 + vendor: Tacker + diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/update_invalid_param_data.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/update_invalid_param_data.yaml new file mode 100644 index 000000000..d6980cba9 --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/update_invalid_param_data.yaml @@ -0,0 +1,2 @@ +flavor: m1.tiny +reservation_id: 891cd152-3925-4c9e-9074-239a902b68d7 diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/update_new_param_data.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/update_new_param_data.yaml new file mode 100644 index 000000000..5e1b392e4 --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/update_new_param_data.yaml @@ -0,0 +1,3 @@ +flavor: m1.tiny +reservation_id: 99999999-3925-4c9e-9074-239a902b68d7 +new_param_key: new_param_value diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/data/update_param_data.yaml b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/update_param_data.yaml new file mode 100644 index 000000000..e40bbdc34 --- /dev/null +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/data/update_param_data.yaml @@ -0,0 +1,2 @@ +flavor: m1.tiny +reservation_id: 99999999-3925-4c9e-9074-239a902b68d7 diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack.py b/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack.py index e1509c9fc..6c0c8d667 100644 --- a/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack.py +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack.py @@ -105,6 +105,7 @@ class TestOpenStack(base.TestCase): hot_param_template = _get_template('hot_openwrt_params.yaml') hot_ipparam_template = _get_template('hot_openwrt_ipparams.yaml') tosca_vnfd_openwrt = _get_template('test_tosca_openwrt.yaml') + tosca_vnfd_openwrt_param = _get_template('test_tosca_openwrt_param.yaml') config_data = _get_template('config_data.yaml') def setUp(self): @@ -196,6 +197,40 @@ class TestOpenStack(base.TestCase): 'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123', 'description': u'OpenWRT with services'} + def _get_expected_vnf_update_param_obj(self): + return {'status': 'PENDING_CREATE', 'instance_id': None, 'name': + u'test_openwrt', 'tenant_id': + u'ad7ebc56538745a08ef7c5e97f8bd437', 'vnfd_id': + u'eb094833-995e-49f0-a047-dfb56aaf7c4e', 'vnfd': { + 'service_types': [{'service_type': u'vnfd', 'id': + u'4a4c2d44-8a52-4895-9a75-9d1c76c3e738'}], 'description': + u'OpenWRT with services', 'tenant_id': + u'ad7ebc56538745a08ef7c5e97f8bd437', 'mgmt_driver': u'openwrt', + 'attributes': {u'vnfd': self.tosca_vnfd_openwrt_param}, + 'id': u'fb048660-dc1b-4f0f-bd89-b023666650ec', 'name': + u'openwrt_services'}, 'mgmt_url': None, 'service_context': [], + 'attributes': {'heat_template': utils.hot_data, + 'param_values': utils.update_param_data}, + 'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123', 'description': + u'OpenWRT with services'} + + def _get_expected_vnf_update_new_param_obj(self): + return {'status': 'PENDING_CREATE', 'instance_id': None, 'name': + u'test_openwrt', 'tenant_id': + u'ad7ebc56538745a08ef7c5e97f8bd437', 'vnfd_id': + u'eb094833-995e-49f0-a047-dfb56aaf7c4e', 'vnfd': { + 'service_types': [{'service_type': u'vnfd', 'id': + u'4a4c2d44-8a52-4895-9a75-9d1c76c3e738'}], 'description': + u'OpenWRT with services', 'tenant_id': + u'ad7ebc56538745a08ef7c5e97f8bd437', 'mgmt_driver': u'openwrt', + 'attributes': {u'vnfd': self.tosca_vnfd_openwrt_param}, + 'id': u'fb048660-dc1b-4f0f-bd89-b023666650ec', 'name': + u'openwrt_services'}, 'mgmt_url': None, 'service_context': [], + 'attributes': {'heat_template': utils.hot_data, + 'param_values': utils.update_new_param_data}, + 'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123', 'description': + u'OpenWRT with services'} + def _get_expected_active_vnf(self): return {'status': 'ACTIVE', 'instance_id': None, @@ -259,6 +294,51 @@ class TestOpenStack(base.TestCase): mock_log.error.assert_called_with( "VNF '%s' failed to heal", vnf_dict['id']) + def test_update_new_param(self): + vnf_obj = utils.get_dummy_vnf_param_attr() + vnf_param_obj = utils.get_dummy_vnf_update_new_param() + expected_vnf_update = self._get_expected_vnf_update_new_param_obj() + vnf_id = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738' + self.infra_driver.update(plugin=None, context=self.context, + vnf_id=vnf_id, vnf_dict=vnf_obj, + vnf=vnf_param_obj, + auth_attr=utils.get_vim_auth_obj()) + expected_vnf_update['attributes']['param_values'] = yaml.safe_load( + expected_vnf_update['attributes']['param_values']) + vnf_obj['attributes']['param_values'] = yaml.safe_load( + vnf_obj['attributes']['param_values']) + self.assertEqual(expected_vnf_update, vnf_obj) + + @mock.patch('tacker.vnfm.infra_drivers.openstack.openstack.LOG') + def test_update_invalid_param(self, mock_log): + vnf_obj = utils.get_dummy_vnf_param_attr() + vnf_param_obj = utils.get_dummy_vnf_update_invalid_param() + vnf_id = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738' + self.assertRaises(vnfm.VNFUpdateInvalidInput, + self.infra_driver.update, + plugin=None, context=self.context, + vnf_id=vnf_id, vnf_dict=vnf_obj, + vnf=vnf_param_obj, + auth_attr=utils.get_vim_auth_obj()) + log_msg = "at vnf_id {} because all parameters "\ + "match the existing one.".format(vnf_id) + mock_log.warning.assert_called_with(log_msg) + + @mock.patch('tacker.vnfm.infra_drivers.openstack.openstack.LOG') + def test_update_empty_param(self, mock_log): + vnf_obj = utils.get_dummy_vnf_param_attr() + vnf_param_obj = utils.get_dummy_vnf_update_empty_param() + vnf_id = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738' + self.assertRaises(vnfm.VNFUpdateInvalidInput, + self.infra_driver.update, + plugin=None, context=self.context, + vnf_id=vnf_id, vnf_dict=vnf_obj, + vnf=vnf_param_obj, + auth_attr=utils.get_vim_auth_obj()) + log_msg = "at vnf_id {} because the target "\ + "yaml is empty.".format(vnf_id) + mock_log.warning.assert_called_with(log_msg) + def _get_expected_fields_tosca(self, template): return {'stack_name': 'test_openwrt_eb84260e-5ff7-4332-b032-50a14d6c1123', diff --git a/tacker/tests/unit/vnfm/test_plugin.py b/tacker/tests/unit/vnfm/test_plugin.py index 6895fb9c7..caa77aae4 100644 --- a/tacker/tests/unit/vnfm/test_plugin.py +++ b/tacker/tests/unit/vnfm/test_plugin.py @@ -824,6 +824,57 @@ class TestVNFMPlugin(db_base.SqlTestCase): dummy_vnf_obj['id'], 'VNF Update failed') + def test_update_vnf_param(self): + self._insert_dummy_vnf_template() + dummy_device_obj = self._insert_dummy_vnf() + vnf_param_obj = utils.get_dummy_vnf_param_obj() + result = self.vnfm_plugin.update_vnf(self.context, + dummy_device_obj['id'], + vnf_param_obj) + self.assertIsNotNone(result) + self.assertEqual(dummy_device_obj['id'], result['id']) + self.assertIn('instance_id', result) + self.assertIn('status', result) + self.assertIn('attributes', result) + self.assertIn('mgmt_ip_address', result) + self.assertIn('updated_at', result) + self._cos_db_plugin.create_event.assert_called_with( + self.context, evt_type=constants.RES_EVT_UPDATE, res_id=mock.ANY, + res_state=mock.ANY, res_type=constants.RES_TYPE_VNF, + tstamp=mock.ANY) + + def test_update_vnf_invalid_config_type(self): + self._insert_dummy_vnf_template() + dummy_device_obj = self._insert_dummy_vnf() + vnf_param_obj = utils.get_dummy_vnf_invalid_config_type_obj() + self.assertRaises(vnfm.InvalidAPIAttributeType, + self.vnfm_plugin.update_vnf, + self.context, + dummy_device_obj['id'], + vnf_param_obj) + + def test_update_vnf_invalid_param_type(self): + self._insert_dummy_vnf_template() + dummy_device_obj = self._insert_dummy_vnf() + vnf_param_obj = utils.get_dummy_vnf_invalid_param_type_obj() + self.assertRaises(vnfm.InvalidAPIAttributeType, + self.vnfm_plugin.update_vnf, + self.context, + dummy_device_obj['id'], + vnf_param_obj) + + def test_update_vnf_invalid_param_content(self): + self.update.side_effect = vnfm.VNFUpdateInvalidInput( + reason='failed') + self._insert_dummy_vnf_template() + dummy_device_obj = self._insert_dummy_vnf() + vnf_param_obj = utils.get_dummy_vnf_invalid_param_content() + self.assertRaises(vnfm.VNFUpdateInvalidInput, + self.vnfm_plugin.update_vnf, + self.context, + dummy_device_obj['id'], + vnf_param_obj) + def _get_dummy_scaling_policy(self, type): vnf_scale = {} vnf_scale['scale'] = {} diff --git a/tacker/vnfm/infra_drivers/openstack/heat_client.py b/tacker/vnfm/infra_drivers/openstack/heat_client.py index 025a0efef..13e0fe3ab 100644 --- a/tacker/vnfm/infra_drivers/openstack/heat_client.py +++ b/tacker/vnfm/infra_drivers/openstack/heat_client.py @@ -54,7 +54,11 @@ class HeatClient(object): return self.stacks.get(stack_id) def update(self, stack_id, **kwargs): - return self.stacks.update(stack_id, **kwargs) + try: + return self.stacks.update(stack_id, **kwargs) + except heatException.HTTPException: + type_, value, tb = sys.exc_info() + raise vnfm.HeatClientException(msg=value) def resource_attr_support(self, resource_name, property_name): resource = self.resource_types.get(resource_name) diff --git a/tacker/vnfm/infra_drivers/openstack/openstack.py b/tacker/vnfm/infra_drivers/openstack/openstack.py index 4ddec9ecc..2fc10e554 100644 --- a/tacker/vnfm/infra_drivers/openstack/openstack.py +++ b/tacker/vnfm/infra_drivers/openstack/openstack.py @@ -202,11 +202,52 @@ class OpenStack(abstract_driver.VnfAbstractDriver, heatclient = hc.HeatClient(auth_attr, region_name) heatclient.get(vnf_id) + update_param_yaml = vnf['vnf'].get('attributes', {}).get( + 'param_values', '') + update_config_yaml = vnf['vnf'].get('attributes', {}).get( + 'config', '') + + if update_param_yaml: + # conversion param_values + param_yaml = vnf_dict.get('attributes', {}).get('param_values', '') + param_dict = yaml.safe_load(param_yaml) + update_param_dict = yaml.safe_load(update_param_yaml) + + # check update values + update_values = {} + for key, value in update_param_dict.items(): + if key not in param_dict or\ + update_param_dict[key] != param_dict[key]: + update_values[key] = value + + if not update_values: + error_reason = _("at vnf_id {} because all parameters " + "match the existing one.").format(vnf_id) + LOG.warning(error_reason) + raise vnfm.VNFUpdateInvalidInput(reason=error_reason) + + # update vnf_dict + utils.deep_update(param_dict, update_param_dict) + new_param_yaml = yaml.safe_dump(param_dict) + vnf_dict.setdefault( + 'attributes', {})['param_values'] = new_param_yaml + + # run stack update + stack_update_param = { + 'parameters': update_values, + 'existing': True} + heatclient.update(vnf_id, **stack_update_param) + + elif not update_param_yaml and not update_config_yaml: + error_reason = _("at vnf_id {} because the target " + "yaml is empty.").format(vnf_id) + LOG.warning(error_reason) + raise vnfm.VNFUpdateInvalidInput(reason=error_reason) + # update config attribute config_yaml = vnf_dict.get('attributes', {}).get('config', '') - update_yaml = vnf['vnf'].get('attributes', {}).get('config', '') LOG.debug('yaml orig %(orig)s update %(update)s', - {'orig': config_yaml, 'update': update_yaml}) + {'orig': config_yaml, 'update': update_config_yaml}) # If config_yaml is None, yaml.safe_load() will raise Attribute Error. # So set config_yaml to {}, if it is None. @@ -214,7 +255,7 @@ class OpenStack(abstract_driver.VnfAbstractDriver, config_dict = {} else: config_dict = yaml.safe_load(config_yaml) or {} - update_dict = yaml.safe_load(update_yaml) + update_dict = yaml.safe_load(update_config_yaml) if not update_dict: return diff --git a/tacker/vnfm/plugin.py b/tacker/vnfm/plugin.py index 17b57dc0e..83feb3469 100644 --- a/tacker/vnfm/plugin.py +++ b/tacker/vnfm/plugin.py @@ -115,6 +115,7 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin): Plugin which supports Tacker framework """ + OPTS_INFRA_DRIVER = [ cfg.ListOpt( 'infra_driver', default=['noop', 'openstack', 'kubernetes'], @@ -532,6 +533,17 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin): vnf_attributes['config'] = yaml.safe_dump(config) else: raise vnfm.InvalidAPIAttributeType(atype=type(config)) + + if vnf_attributes.get('param_values'): + param = vnf_attributes['param_values'] + if isinstance(param, dict): + # TODO(sripriya) remove this yaml dump once db supports storing + # json format of yaml files in a separate column instead of + # key value string pairs in vnf attributes table + vnf_attributes['param_values'] = yaml.safe_dump(param) + else: + raise vnfm.InvalidAPIAttributeType(atype=type(param)) + vnf_dict = self._update_vnf_pre(context, vnf_id, constants.PENDING_UPDATE) driver_name, vim_auth = self._get_infra_driver(context, vnf_dict) @@ -543,6 +555,13 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin): driver_name, 'update', plugin=self, context=context, vnf_id=instance_id, vnf_dict=vnf_dict, vnf=vnf, auth_attr=vim_auth) + except vnfm.VNFUpdateInvalidInput: + with excutils.save_and_reraise_exception(): + vnf_dict['status'] = constants.ACTIVE + self._update_vnf_post(context, vnf_id, + constants.ACTIVE, + vnf_dict, constants.PENDING_UPDATE, + constants.RES_EVT_UPDATE) except Exception as e: with excutils.save_and_reraise_exception(): vnf_dict['status'] = constants.ERROR