Merge "Support TOSCA route for ChgExternalConnectivity"

This commit is contained in:
Zuul 2021-09-17 09:45:20 +00:00 committed by Gerrit Code Review
commit c6c8c7b6ba
9 changed files with 907 additions and 87 deletions

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import time
import yaml
@ -447,3 +448,31 @@ class BaseTackerTest(base.BaseTestCase):
self.validate_vnf_instance(vnfd_instance, vnf_instance)
return vnf_instance, tosca_dict
def _list_op_occs(self, filter_string=''):
show_url = os.path.join(
self.base_vnf_lcm_op_occs_url)
resp, response_body = self.http_client.do_request(
show_url + filter_string, "GET")
return resp, response_body
def _assert_occ_list(self, resp, op_occs_list):
self.assertEqual(200, resp.status_code)
# Only check required parameters.
for op_occs_info in op_occs_list:
self.assertIsNotNone(op_occs_info.get('id'))
self.assertIsNotNone(op_occs_info.get('operationState'))
self.assertIsNotNone(op_occs_info.get('stateEnteredTime'))
self.assertIsNotNone(op_occs_info.get('vnfInstanceId'))
self.assertIsNotNone(op_occs_info.get('operation'))
self.assertIsNotNone(op_occs_info.get('isAutomaticInvocation'))
self.assertIsNotNone(op_occs_info.get('isCancelPending'))
_links = op_occs_info.get('_links')
self.assertIsNotNone(_links.get('self'))
self.assertIsNotNone(_links.get('self').get('href'))
self.assertIsNotNone(_links.get('vnfInstance'))
self.assertIsNotNone(_links.get('vnfInstance').get('href'))
self.assertIsNotNone(_links.get('grant'))
self.assertIsNotNone(_links.get('grant').get('href'))

View File

@ -527,14 +527,6 @@ class BaseVnfLcmTest(base.BaseTackerTest):
return resp, response_body
def _list_op_occs(self, filter_string=''):
show_url = os.path.join(
self.base_vnf_lcm_op_occs_url)
resp, response_body = self.http_client.do_request(
show_url + filter_string, "GET")
return resp, response_body
def _wait_terminate_vnf_instance(self, id, timeout=None):
start_time = int(time.time())

View File

@ -30,6 +30,7 @@ VNF_PACKAGE_UPLOAD_TIMEOUT = 300
VNF_INSTANTIATE_TIMEOUT = 600
VNF_TERMINATE_TIMEOUT = 600
VNF_HEAL_TIMEOUT = 600
VNF_CHANGE_EXT_CONN_TIMEOUT = 600
RETRY_WAIT_TIME = 5
@ -47,26 +48,24 @@ def generate_mac_address():
return ':'.join(map(lambda x: "%02x" % x, mac))
def get_external_virtual_links(net_0_resource_id, net_mgmt_resource_id,
port_uuid):
return [
{
"id": "net0",
"resourceId": net_0_resource_id,
"extCps": [{
"cpdId": "CP1",
"cpConfig": [{
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"macAddress": generate_mac_address()
def generate_ip_addresses(
type_='IPV4',
fixed_addresses=None,
subnet_id=None):
if fixed_addresses:
ip_addr = {
'type': type_,
'fixedAddresses': fixed_addresses
}
}]
}]
}]},
{
if subnet_id:
ip_addr.update({'subnetId': subnet_id})
return [ip_addr]
def get_ext_cp_with_external_link_port(nw_resource_id, port_uuid):
ext_cp = {
"id": "external_network",
"resourceId": net_mgmt_resource_id,
"resourceId": nw_resource_id,
"extCps": [{
"cpdId": "CP2",
"cpConfig": [{
@ -84,7 +83,58 @@ def get_external_virtual_links(net_0_resource_id, net_mgmt_resource_id,
}
}]
}
return ext_cp
def get_ext_cp_with_fixed_address(nw_resource_id, fixed_addresses, subnet_id):
ext_cp = {
"id": "external_network",
"resourceId": nw_resource_id,
"extCps": [{
"cpdId": "CP2",
"cpConfig": [{
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": generate_ip_addresses(
fixed_addresses=fixed_addresses,
subnet_id=subnet_id)
}
}]
}]
}]
}
return ext_cp
def get_external_virtual_links(net_0_resource_id, net_mgmt_resource_id,
port_uuid, fixed_addresses=None, subnet_id=None):
ext_vl = [
{
"id": "net0",
"resourceId": net_0_resource_id,
"extCps": [{
"cpdId": "CP1",
"cpConfig": [{
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"macAddress": generate_mac_address()
}
}]
}]
}]
}
]
if fixed_addresses:
ext_cp = get_ext_cp_with_fixed_address(
net_mgmt_resource_id, fixed_addresses, subnet_id)
else:
ext_cp = get_ext_cp_with_external_link_port(
net_mgmt_resource_id, port_uuid)
ext_vl.append(ext_cp)
return ext_vl
def _create_and_upload_vnf_package(tacker_client, csar_package_name,
@ -170,6 +220,7 @@ class VnfLcmTest(base.BaseTackerTest):
def setUp(self):
super(VnfLcmTest, self).setUp()
self.base_url = "/vnflcm/v1/vnf_instances"
self.base_vnf_lcm_op_occs_url = "/vnflcm/v1/vnf_lcm_op_occs"
vim_list = self.client.list_vims()
if not vim_list:
@ -254,8 +305,8 @@ class VnfLcmTest(base.BaseTackerTest):
self.assertEqual(200, resp.status_code)
return vnf_instances
def _stack_update_wait(self, stack_id, expected_status):
timeout = VNF_HEAL_TIMEOUT
def _stack_update_wait(self, stack_id, expected_status,
timeout=VNF_HEAL_TIMEOUT):
start_time = int(time.time())
while True:
stack = self.h_client.stacks.get(stack_id)
@ -403,6 +454,115 @@ class VnfLcmTest(base.BaseTackerTest):
# in nova.
self._get_server(vdu_resource_id_current)
def _change_ext_conn_vnf_request(self, vim_id=None, ext_vl=None):
request_body = {}
if ext_vl:
request_body["extVirtualLinks"] = ext_vl
if vim_id:
request_body["vimConnectionInfo"] = [
{"id": uuidutils.generate_uuid(),
"vimId": vim_id,
"vimType": "ETSINFV.OPENSTACK_KEYSTONE.v_2"}]
return request_body
def _change_ext_conn_vnf_instance(self, vnf_instance, request_body,
expected_stack_status=infra_cnst.STACK_UPDATE_COMPLETE):
url = os.path.join(self.base_url, vnf_instance['id'],
"change_ext_conn")
resp, body = self.http_client.do_request(url, "POST",
body=jsonutils.dumps(request_body))
self.assertEqual(202, resp.status_code)
stack = self.h_client.stacks.get(vnf_instance['vnfInstanceName'])
# Wait until tacker changes the stack resources as requested
# in the change_ext_conn request
self._stack_update_wait(stack.id, expected_stack_status,
VNF_CHANGE_EXT_CONN_TIMEOUT)
def _get_heat_stack(self, vnf_instance_id, stack_name):
heatclient = self.heatclient()
try:
stacks = heatclient.stacks.list()
except Exception:
return None
target_stakcs = list(
filter(
lambda x: x.stack_name == stack_name,
stacks))
if len(target_stakcs) == 0:
return None
return target_stakcs[0]
def _get_heat_resource_info(self, stack_id, nested_depth=0,
resource_name=None):
heatclient = self.heatclient()
try:
if resource_name is None:
resources = heatclient.resources.list(stack_id,
nested_depth=nested_depth)
else:
resources = heatclient.resources.get(stack_id,
resource_name)
except Exception:
return None
return resources
def _get_fixed_ips(self, vnf_instance, request_body):
vnf_instance_id = vnf_instance['id']
vnf_instance_name = vnf_instance['vnfInstanceName']
res_name = None
for extvirlink in request_body['extVirtualLinks']:
if 'extCps' not in extvirlink:
continue
for extcps in extvirlink['extCps']:
if 'cpdId' in extcps:
if res_name is None:
res_name = list()
res_name.append(extcps['cpdId'])
break
if res_name is None:
return []
stack = self._get_heat_stack(vnf_instance_id,
vnf_instance_name)
stack_id = stack.id
stack_resource = self._get_heat_resource_info(
stack_id, nested_depth=2)
releations = dict()
for elmt in stack_resource:
if elmt.resource_type != 'OS::Neutron::Port':
continue
if elmt.resource_name not in res_name:
continue
parent = getattr(elmt, 'parent_resource', None)
releations[parent] = elmt.resource_name
details = dict()
for (parent_name, resource_name) in releations.items():
for elmt in stack_resource:
if parent_name is None:
detail_stack = self._get_heat_resource_info(
stack_id, resource_name=resource_name)
elif parent_name != elmt.resource_name:
continue
else:
detail_stack = self._get_heat_resource_info(
elmt.physical_resource_id, resource_name=resource_name)
details[resource_name] = detail_stack
ans_list = list()
for detail in details.values():
ans_list.append(detail.attributes['fixed_ips'])
return ans_list
def test_create_show_delete_vnf_instance(self):
"""Create, show and delete a vnf instance."""
@ -881,3 +1041,100 @@ class VnfLcmTest(base.BaseTackerTest):
self._terminate_vnf_instance(vnf_instance['id'], terminate_req_body)
self._delete_vnf_instance(vnf_instance['id'])
def test_inst_chgextconn_term(self):
"""Test change external vnf connectivity.
This test will instantiate vnf with external virtual link and
change the IP address on virtual link.
"""
# Create vnf instance
vnf_instance_name = "vnf_with_ext_vl_and_ext_managed_vl-%s" % \
uuidutils.generate_uuid()
vnf_instance_description = "vnf_with_ext_vl_and_ext_managed_vl"
resp, vnf_instance = self._create_vnf_instance(self.vnfd_id_3,
vnf_instance_name=vnf_instance_name,
vnf_instance_description=vnf_instance_description)
self.assertIsNotNone(vnf_instance['id'])
self.assertEqual(201, resp.status_code)
neutron_client = self.neutronclient()
net = neutron_client.list_networks()
networks = {}
for network in net['networks']:
networks[network['name']] = network['id']
subnet_list = neutron_client.list_subnets()
subnets = {}
for subnet in subnet_list['subnets']:
subnets[subnet['name']] = subnet['id']
net1_id = networks.get('net1')
if not net1_id:
self.fail("net1 network is not available")
net0_id = networks.get('net0')
if not net0_id:
self.fail("net0 network is not available")
net_mgmt_id = networks.get('net_mgmt')
if not net_mgmt_id:
self.fail("net_mgmt network is not available")
subnet_mgmt_id = subnets.get('subnet_mgmt')
if not subnet_mgmt_id:
self.fail("subnet_mgmt subnet is not available")
ext_managed_vl = get_ext_managed_virtual_link("net1", "VL3",
net1_id)
network_uuid = self._create_network(neutron_client,
"external_network")
subnet_uuid = self._create_subnet(neutron_client, network_uuid)
# Instantiate vnf
ext_vl = get_external_virtual_links(
net0_id, net_mgmt_id, None,
fixed_addresses=['192.168.120.100'],
subnet_id=subnet_mgmt_id)
request_body = self._instantiate_vnf_request("simple",
vim_id=self.vim_id, ext_vl=ext_vl, ext_managed_vl=ext_managed_vl)
self._instantiate_vnf_instance(vnf_instance['id'], request_body)
vnf_instance = self._show_vnf_instance(vnf_instance['id'])
vdu_count = len(vnf_instance['instantiatedVnfInfo']
['vnfcResourceInfo'])
self.assertEqual(1, vdu_count)
# Change external vnf connectivity
changed_ext_vl = get_external_virtual_links(
net0_id, network_uuid, None,
fixed_addresses=['22.22.0.100'],
subnet_id=subnet_uuid)
change_ext_conn_req_body = self._change_ext_conn_vnf_request(
vim_id=self.vim_id, ext_vl=changed_ext_vl)
before_fixed_ips = self._get_fixed_ips(vnf_instance, request_body)
self._change_ext_conn_vnf_instance(
vnf_instance, change_ext_conn_req_body)
after_fixed_ips = self._get_fixed_ips(vnf_instance, request_body)
self.assertNotEqual(before_fixed_ips, after_fixed_ips)
# Get op-occs
resp, op_occs_info = self._list_op_occs()
self._assert_occ_list(resp, op_occs_info)
# Wait for operation state completed
time.sleep(10)
# Terminate vnf gracefully with graceful timeout set to 60
terminate_req_body = {
"terminationType": fields.VnfInstanceTerminationType.GRACEFUL,
'gracefulTerminationTimeout': 60
}
self._terminate_vnf_instance(vnf_instance['id'], terminate_req_body)
self._delete_vnf_instance(vnf_instance['id'])

View File

@ -1860,27 +1860,6 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
self.assertIsNotNone(_links.get('grant'))
self.assertIsNotNone(_links.get('grant').get('href'))
def _assert_occ_list(self, resp, op_occs_list):
self.assertEqual(200, resp.status_code)
# Only check required parameters.
for op_occs_info in op_occs_list:
self.assertIsNotNone(op_occs_info.get('id'))
self.assertIsNotNone(op_occs_info.get('operationState'))
self.assertIsNotNone(op_occs_info.get('stateEnteredTime'))
self.assertIsNotNone(op_occs_info.get('vnfInstanceId'))
self.assertIsNotNone(op_occs_info.get('operation'))
self.assertIsNotNone(op_occs_info.get('isAutomaticInvocation'))
self.assertIsNotNone(op_occs_info.get('isCancelPending'))
_links = op_occs_info.get('_links')
self.assertIsNotNone(_links.get('self'))
self.assertIsNotNone(_links.get('self').get('href'))
self.assertIsNotNone(_links.get('vnfInstance'))
self.assertIsNotNone(_links.get('vnfInstance').get('href'))
self.assertIsNotNone(_links.get('grant'))
self.assertIsNotNone(_links.get('grant').get('href'))
def _assert_fail_vnf_response(self, fail_response):
# Only check parameters with cardinality = 1

View File

@ -558,6 +558,134 @@ def get_vnf_attribute_dict():
return vnf_attribute_dict
def get_stack_template():
stack_template = {
'heat_template_version': '2013-05-23',
'description': 'Simple deployment flavour for Sample VNF',
'parameters': {},
'resources': {
'VDU1': {
'type': 'OS::Nova::Server',
'properties': {
'name': 'VDU1',
'flavor': 'm1.tiny',
'image': 'None',
'networks': [
{
'port': {'get_resource': 'VDU1_CP1'},
}, {
'port': {'get_resource': 'VDU1_CP2'},
}
],
},
},
'VDU1_CP1': {
'type': 'OS::Neutron::Port',
'properties': {
'network': "nw-resource-id-1",
'fixed_ips': [{
'ip_address': '10.10.0.1',
}],
},
},
'VDU1_CP2': {
'type': 'OS::Neutron::Port',
'properties': {
'network': "nw-resource-id-1",
'fixed_ips': [{
'ip_address': '10.10.0.2',
'subnet': 'subnet-id-2',
}],
},
},
},
}
return stack_template
def get_stack_nested_template():
stack_nested_template = {
'heat_template_version': '2013-05-23',
'description': 'Simple deployment flavour for Sample VNF',
'parameters': {},
'resources': {
'VDU2': {
'type': 'OS::Nova::Server',
'properties': {
'name': 'VDU2',
'flavor': 'm1.tiny',
'image': 'cirros-0.4.0-x86_64-disk',
'networks': [
{
'port': {'get_resource': 'VDU2_CP1'},
}, {
'port': {'get_resource': 'VDU2_CP2'},
}
],
},
},
'VDU2_CP1': {
'type': 'OS::Neutron::Port',
'properties': {
'network': 'nw-resource-id-2',
'fixed_ips': [{
'subnet': 'subnet-id-2',
}],
},
},
'VDU2_CP2': {
'type': 'OS::Neutron::Port',
'properties': {
'network': 'nw-resource-id-2',
},
},
},
}
return stack_nested_template
def get_expected_update_resource_property_calls():
calls = {
'VDU1_CP1': {
'resource_types': ['OS::Neutron::Port'],
'network': 'nw-resource-id-1',
'fixed_ips': [{
'ip_address': '20.0.0.1'
}],
},
'VDU1_CP2': {
'resource_types': ['OS::Neutron::Port'],
'network': 'nw-resource-id-1',
'fixed_ips': [{
'ip_address': '30.0.0.2',
'subnet': 'changed-subnet-id-1',
}],
},
'VDU1_CP3': {
'resource_types': ['OS::Neutron::Port'],
'network': 'nw-resource-id-1',
'fixed_ips': [{
'ip_address': '10.0.0.1'
}],
},
'VDU2_CP1': {
'resource_types': ['OS::Neutron::Port'],
'network': 'changed-nw-resource-id-2',
'fixed_ips': [{
'subnet': 'changed-subnet-id-2',
}],
},
'VDU2_CP2': {
'resource_types': ['OS::Neutron::Port'],
'network': 'changed-nw-resource-id-2',
'fixed_ips': None,
},
}
return calls
def get_lcm_op_occs_object(operation="INSTANTIATE",
error_point=0):
vnf_lcm_op_occs = objects.VnfLcmOpOcc(

View File

@ -2912,19 +2912,16 @@ class TestOpenStack(base.FixturedTestCase):
self.assertEqual('30435eb8-1472-4cbc-abbe-00b395165ce7', grp_id)
@mock.patch('tacker.common.clients.OpenstackClients')
@mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict')
def test_change_ext_conn_vnf(self,
mock_get_base_hot_dict,
mock_mock_OpenstackClients_heat):
@mock.patch('tacker.vnfm.infra_drivers.openstack.update_template.'
'HOTUpdater')
def test_change_ext_conn_vnf_with_userdata(self,
mock_hot_updater,
mock_OpenstackClients_heat):
inst_vnf_info = fd_utils.get_vnf_instantiated_info()
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
nested_hot_dict = {'parameters': {'vnf': 'test'}}
mock_get_base_hot_dict.return_value = \
self._read_file(), nested_hot_dict
vnf_dict['vnfd'] = fd_utils.get_vnfd_dict()
vnf_dict['attributes'] = fd_utils.get_vnf_attribute_dict()
@ -2939,6 +2936,38 @@ class TestOpenStack(base.FixturedTestCase):
str(fd_utils.get_expect_stack_param()),
vnf_dict['attributes']['stack_param'])
@mock.patch('tacker.common.clients.OpenstackClients')
@mock.patch('tacker.vnfm.infra_drivers.openstack.update_template'
'.HOTUpdater')
def test_change_ext_conn_vnf_without_userdata(self,
mock_hot_updater,
mock_OpenstackClients_heat):
inst_vnf_info = fd_utils.get_vnf_instantiated_info()
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
vnf_dict['vnfd'] = fd_utils.get_vnfd_dict()
vnf_dict['attributes'] = {}
vim_connection_info = fd_utils.get_vim_connection_info_object()
change_ext_conn_request = fd_utils.get_change_ext_conn_request()
hot_instance = mock_hot_updater.return_value
hot_instance.template = fd_utils.get_stack_template()
hot_instance.nested_templates = {
'VDU2.yaml': fd_utils.get_stack_nested_template(),
}
self.openstack.change_ext_conn_vnf(
self.context, vnf_instance, vnf_dict,
vim_connection_info, change_ext_conn_request)
hot_instance.get_templates_from_stack.assert_called_once()
for resource, args in (fd_utils.
get_expected_update_resource_property_calls().items()):
hot_instance.update_resource_property.assert_any_call(
resource, **args)
def test_change_ext_conn_vnf_wait(self):
inst_vnf_info = fd_utils.get_vnf_instantiated_info()

View File

@ -0,0 +1,227 @@
# 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 unittest import mock
from heatclient.v1 import resources
from tacker.tests.unit import base
from tacker.tests import uuidsentinel
from tacker.vnfm.infra_drivers.openstack.heat_client import HeatClient
from tacker.vnfm.infra_drivers.openstack.update_template import HOTUpdater
class TestUpdateTemplate(base.TestCase):
def setUp(self):
super(TestUpdateTemplate, self).setUp()
self.maxDiff = None
self._mock_heatclient()
self.stack_id = uuidsentinel.stack_id
self.hot_updater = HOTUpdater(self.heatclient)
def _mock_heatclient(self):
self.heatclient = mock.Mock(spec=HeatClient)
self.heatclient.stacks = mock.Mock()
self.heatclient.stacks.template.side_effect = [
self._get_template(),
self._get_intermediate_template1(),
self._get_nested_template1(),
self._get_intermediate_template2(),
self._get_nested_template2()
]
self.heatclient.resource_get_list.return_value = \
self._get_stack_resources()
def test_get_templates_from_stack(self):
self.hot_updater.get_templates_from_stack(self.stack_id)
self.assertDictEqual(self._get_template(), self.hot_updater.template)
self.assertDictEqual(
{'VDU1.yaml': self._get_nested_template1(),
'VDU2.yaml': self._get_nested_template2()},
self.hot_updater.nested_templates)
def test_update_resource_property(self):
self.hot_updater.get_templates_from_stack(self.stack_id)
# Test pattern 1: Change network and CP1(DHCP) address to fixed.
self.hot_updater.update_resource_property(
'CP1', ['OS::Neutron::Port'],
network=uuidsentinel.network_id_cp1_changed,
fixed_ips=[{'ip_address': '20.20.0.100'}])
expected = {
'network': uuidsentinel.network_id_cp1_changed,
'fixed_ips': [{'ip_address': '20.20.0.100'}]
}
self.assertEqual(
expected,
self.hot_updater.template['resources']['CP1']['properties'])
# Test pattern 2: Change network and fixed address.
self.hot_updater.update_resource_property(
'CP2', ['OS::Neutron::Port'],
network=uuidsentinel.network_id_cp2_changed,
fixed_ips=[{
'ip_address': '20.20.0.200',
'subnet': uuidsentinel.subnet_id_cp2_changed
}])
expected = {
'network': uuidsentinel.network_id_cp2_changed,
'fixed_ips': [{
'ip_address': '20.20.0.200',
'subnet': uuidsentinel.subnet_id_cp2_changed,
}]
}
self.assertEqual(
expected,
self.hot_updater.template['resources']['CP2']['properties'])
# Test Pattern 3: Delete fixed_ips property.
self.hot_updater.update_resource_property(
'CP3', ['OS::Neutron::Port'],
network=uuidsentinel.network_id_cp3_changed,
fixed_ips=None)
self.assertIsNone(self.hot_updater.nested_templates[
'VDU1.yaml']['resources']['CP3']['properties'].get('fixed_ips'))
def test_update_resource_property_not_found(self):
self.hot_updater.get_templates_from_stack(self.stack_id)
# Test Pattern 4: Resource does not exist.
self.hot_updater.update_resource_property(
'CPX', ['OS::Neutron::Port'],
network=uuidsentinel.network_id_cpx,
fixed_ips=[{'ip_address': '20.20.0.100'}])
self.assertEqual(self._get_template(), self.hot_updater.template)
# Test Pattern 5: Resource type does not exist.
self.hot_updater.update_resource_property(
'CP1', ['OS::Sahara::Cluster'],
network=uuidsentinel.network_id_cp1_changed,
fixed_ips=[{'ip_address': '20.20.0.100'}])
self.assertEqual(self._get_template(), self.hot_updater.template)
# Test Pattern 6: Resource doess not have properties.
self.hot_updater.update_resource_property(
'CP5', ['OS::Neutron::Port'],
network=uuidsentinel.network_id_cp5,
fixed_ips=[{'ip_address': '20.20.0.100'}])
self.assertEqual(self._get_template(), self.hot_updater.template)
def _get_template(self):
return {
'heat_template_version': '2013-05-23',
'description': 'Simple deployment flavour for Sample VNF',
'parameters': {},
'resources': {
'CP1': {
'type': 'OS::Neutron::Port',
'properties': {
'network': uuidsentinel.network_id_cp1,
},
},
'CP2': {
'type': 'OS::Neutron::Port',
'properties': {
'network': uuidsentinel.network_id_cp2,
'fixed_ips': [{
'ip_address': '10.10.0.200',
'subnet': uuidsentinel.subnet_id_cp2,
}],
},
},
'CP5': {
'type': 'OS::Neutron::Port',
},
},
}
def _get_intermediate_template1(self):
return {
'heat_template_version': '2013-05-23',
'description': 'Simple deployment flavour for Sample VNF',
'parameters': {},
'resources': {
'xxxxxxxx': {
'type': 'VDU1.yaml',
},
},
}
def _get_intermediate_template2(self):
return {
'heat_template_version': '2013-05-23',
'description': 'Simple deployment flavour for Sample VNF',
'parameters': {},
'resources': {
'yyyyyyyy': {
'type': 'VDU2.yaml',
},
},
}
def _get_nested_template1(self):
return {
'heat_template_version': '2013-05-23',
'description': 'Simple deployment flavour for Sample VNF',
'parameters': {},
'resources': {
'CP3': {
'type': 'OS::Neutron::Port',
'properties': {
'network': uuidsentinel.network_id_cp3,
'fixed_ips': [{
'subnet': uuidsentinel.subnet_id_cp3,
}],
},
},
},
}
def _get_nested_template2(self):
return {
'heat_template_version': '2013-05-23',
'description': 'Simple deployment flavour for Sample VNF',
'parameters': {},
'resources': {
'CP4': {
'type': 'OS::Neutron::Port',
'properties': {
'network': uuidsentinel.network_id_cp4,
'fixed_ips': [{
'subnet': uuidsentinel.subnet_id_cp4,
}],
},
},
},
}
def _get_stack_resources(self):
def _create_resource(resource_name, resource_type):
return resources.Resource(None, {
'resource_name': resource_name,
'resource_type': resource_type,
'resource_status': 'CREATE_COMPLETE',
'physical_resource_id': uuidsentinel.uuid,
})
data = [
('VDU1_scale_group', 'OS::Heat::AutoScalingGroup'),
('xxxxxxxx', 'VDU1.yaml'),
('VDU2_scale_group', 'OS::Heat::AutoScalingGroup'),
('yyyyyyyy', 'VDU2.yaml'),
('CP1', 'OS::Neutron::Port'),
('CP2', 'OS::Neutron::Port'),
('CP3', 'OS::Neutron::Port'),
('CP4', 'OS::Neutron::Port')
]
return [_create_resource(row[0], row[1]) for row in data]

View File

@ -49,6 +49,7 @@ from tacker.vnfm.infra_drivers.openstack import constants as infra_cnst
from tacker.vnfm.infra_drivers.openstack import glance_client as gc
from tacker.vnfm.infra_drivers.openstack import heat_client as hc
from tacker.vnfm.infra_drivers.openstack import translate_template
from tacker.vnfm.infra_drivers.openstack import update_template as ut
from tacker.vnfm.infra_drivers.openstack import vdu
from tacker.vnfm.infra_drivers import scale_driver
from tacker.vnfm.lcm_user_data.constants import USER_DATA_TIMEOUT
@ -58,6 +59,7 @@ eventlet.monkey_patch(time=True)
SCALING_GROUP_RESOURCE = "OS::Heat::AutoScalingGroup"
NOVA_SERVER_RESOURCE = "OS::Nova::Server"
NEUTRON_PORT_RESOURCE = "OS::Neutron::Port"
VNF_PACKAGE_HOT_DIR = 'Files'
@ -1725,6 +1727,7 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
ip_addr, vnfc_rsc.id)
raise exceptions.InvalidIpAddr(
id=vnfc_rsc.id)
ip_addr = fixed_ip.get('ip_address')
ip_addresses.addresses.append(ip_addr)
ip_addresses.subnet_id = fixed_ip.get(
'subnet_id')
@ -1743,7 +1746,7 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
resource.resource_id =\
rsc_info.physical_resource_id
resource.vim_level_resource_type =\
'OS::Neutron::Port'
NEUTRON_PORT_RESOURCE
if not vl.vnf_link_ports:
vl.vnf_link_ports = []
link_port_info = objects.\
@ -2264,14 +2267,61 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
def change_ext_conn_vnf(self, context, vnf_instance, vnf_dict,
vim_connection_info, change_ext_conn_req):
base_hot_dict, nested_hot_dict = \
vnflcm_utils.get_base_nest_hot_dict(
context,
vnf_instance.instantiated_vnf_info.flavour_id,
vnf_instance.vnfd_id)
stack_param = yaml.safe_load(
vnf_dict['attributes']['stack_param'])
access_info = vim_connection_info.access_info
heatclient = hc.HeatClient(access_info,
region_name=access_info.get('region'))
hot_updater = ut.HOTUpdater(heatclient)
hot_updater.get_templates_from_stack(
vnf_instance.instantiated_vnf_info.instance_id)
if 'stack_param' in vnf_dict['attributes']:
LOG.debug('Target VNF instantiated with userdata.')
self._change_ext_conn_vnf_with_userdata(
context,
hot_updater,
vnf_instance,
vnf_dict,
vim_connection_info,
change_ext_conn_req)
else:
LOG.debug('Target VNF instantiated with '
'translating heat-template.')
self._change_ext_conn_vnf_with_tosca(
context,
hot_updater,
vnf_instance,
vnf_dict,
vim_connection_info,
change_ext_conn_req)
def _get_fixed_ips_from_ip_addr(self, ip_addr):
fixed_ips = dict()
updated_fixed_ips = []
if ip_addr.fixed_addresses:
for address in ip_addr.fixed_addresses:
fixed_ips = dict(ip_address=address)
if ip_addr.subnet_id:
fixed_ips.update(dict(subnet=ip_addr.subnet_id))
updated_fixed_ips.append(fixed_ips)
elif ip_addr.num_dynamic_addresses > 0:
if ip_addr.subnet_id:
fixed_ips.update(dict(subnet=ip_addr.subnet_id))
updated_fixed_ips.append(fixed_ips)
else:
updated_fixed_ips = None
return updated_fixed_ips
def _change_ext_conn_vnf_with_userdata(
self,
context,
hot_updater,
vnf_instance,
vnf_dict,
vim_connection_info,
change_ext_conn_req):
stack_param = yaml.safe_load(vnf_dict['attributes']['stack_param'])
LOG.debug('before stack_param: {}'.format(stack_param))
cp_param = stack_param['nfv']['CP']
@ -2293,34 +2343,65 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
# and ip_addresses cannot get, do nothing
continue
fixed_ips = dict()
updated_fixed_ips = []
if ip_addr.fixed_addresses:
for address in ip_addr.fixed_addresses:
fixed_ips = dict(ip_address=address)
if ip_addr.subnet_id:
fixed_ips.update(dict(subnet=ip_addr.subnet_id))
updated_fixed_ips.append(fixed_ips)
elif ip_addr.num_dynamic_addresses > 0:
if ip_addr.subnet_id:
fixed_ips.update(dict(subnet=ip_addr.subnet_id))
updated_fixed_ips.append(fixed_ips)
updated_fixed_ips = self._get_fixed_ips_from_ip_addr(ip_addr)
if updated_fixed_ips:
cp_param[cpd_id].update(
dict(fixed_ips=updated_fixed_ips))
LOG.debug('after stack_param: {}'.format(stack_param))
access_info = vim_connection_info.access_info
heatclient = hc.HeatClient(access_info,
region_name=access_info.get('region'))
# Update heat-stack with BaseHOT and parameters
self._update_stack_with_user_data(
heatclient, vnf_instance, base_hot_dict, nested_hot_dict,
stack_param, vnf_instance.instantiated_vnf_info.instance_id)
hot_updater.heatclient,
vnf_instance,
hot_updater.template,
hot_updater.nested_templates,
stack_param,
vnf_instance.instantiated_vnf_info.instance_id)
vnf_dict['attributes'].update({'stack_param': str(stack_param)})
def _change_ext_conn_vnf_with_tosca(
self,
context,
hot_updater,
vnf_instance,
vnf_dict,
vim_connection_info,
change_ext_conn_req):
for ext_virtual_link in change_ext_conn_req.ext_virtual_links:
for ext_cp in ext_virtual_link.ext_cps:
cpd_id = ext_cp.cpd_id
try:
ip_addr = ext_cp.cp_config[0].cp_protocol_data[0].\
ip_over_ethernet.ip_addresses[0]
except IndexError:
# If the element under ext_cp does not exist,
# and ip_addresses cannot get, do nothing
continue
updated_fixed_ips = self._get_fixed_ips_from_ip_addr(ip_addr)
hot_updater.update_resource_property(
cpd_id,
resource_types=[NEUTRON_PORT_RESOURCE],
network=ext_virtual_link.resource_id,
fixed_ips=updated_fixed_ips)
# Set parameters to update heat-stack
update_parameters = {
'template': self._format_base_hot(hot_updater.template),
}
if hot_updater.nested_templates:
files_dict = dict()
for name, value in hot_updater.nested_templates.items():
files_dict[name] = self._format_base_hot(value)
update_parameters['files'] = files_dict
# Update heat-stack
self._update_stack(
hot_updater.heatclient,
vnf_instance.instantiated_vnf_info.instance_id,
update_parameters)
@log.log
def change_ext_conn_vnf_wait(self, context, vnf_instance,
vim_connection_info):

View File

@ -0,0 +1,98 @@
# 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 oslo_log import log as logging
from tacker.common import log
LOG = logging.getLogger(__name__)
class HOTUpdater(object):
"""Update HOT template."""
def __init__(self, heatclient):
self.heatclient = heatclient
self.template = {}
self.nested_templates = dict()
@log.log
def get_templates_from_stack(self, stack_id):
"""Get template information from the stack.
Get the template from stack specified by stack_id,
if stack has scalable resource, get the its child
template.
"""
def _get_resource(name, resources):
for resource in resources:
if resource.resource_name == name:
return resource
self.template = self.heatclient.stacks.template(stack_id)
LOG.debug('got main template for stack({}). template={}'.format(
stack_id, self.template))
stack_resources = self.heatclient.resource_get_list(stack_id,
nested_depth=2)
for resource in stack_resources:
if resource.resource_type == 'OS::Heat::AutoScalingGroup':
intermediate_template = self.heatclient.stacks.template(
resource.physical_resource_id)
for resource_id in intermediate_template['resources'].keys():
corresponding_resource = _get_resource(resource_id,
stack_resources)
nested_template = self.heatclient.stacks.template(
corresponding_resource.physical_resource_id)
LOG.debug('got nested template for stack({}). template={}'
.format(corresponding_resource.physical_resource_id,
nested_template))
if nested_template:
self.nested_templates[
corresponding_resource.resource_type] = nested_template
@log.log
def update_resource_property(self,
resource_id,
resource_types=[],
**kwargs):
"""Update attributes of resource properties.
Get the resource information from template's resources section,
and update properties using kwargs information.
If resource type does not include in resource_types, nothing to do.
"""
def _update(template, resource_id, resource_types, kwargs):
resource = template.get('resources', {}).get(resource_id)
if not resource:
return
if resource.get('type', {}) not in resource_types:
return
resource_properties = resource.get('properties', {})
if not resource_properties:
return
for key, value in kwargs.items():
if value is not None:
resource_properties.update({key: value})
elif resource_properties.get(key):
del resource_properties[key]
_update(self.template, resource_id, resource_types, kwargs)
for value in self.nested_templates.values():
nested_template = value
_update(nested_template, resource_id, resource_types, kwargs)