VNF scaling: Functional test
implements blueprint: #vnf-scaling Change-Id: I29269369faf57766ac51c2d12e10fecc1331e474
This commit is contained in:
parent
cf7f677fc7
commit
f291eda290
|
@ -71,6 +71,10 @@ class VNFCreateWaitFailed(exceptions.TackerException):
|
|||
message = _('%(reason)s')
|
||||
|
||||
|
||||
class VNFScaleWaitFailed(exceptions.TackerException):
|
||||
message = _('%(reason)s')
|
||||
|
||||
|
||||
class VNFDeleteFailed(exceptions.TackerException):
|
||||
message = _('deleting VNF %(vnf_id)s failed')
|
||||
|
||||
|
|
|
@ -3,3 +3,4 @@ VNF_CIRROS_DELETE_TIMEOUT = 300
|
|||
VNF_CIRROS_DEAD_TIMEOUT = 250
|
||||
ACTIVE_SLEEP_TIME = 3
|
||||
DEAD_SLEEP_TIME = 1
|
||||
SCALE_WINDOW_SLEEP_TIME = 120
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
|
||||
|
||||
description: sample-tosca-vnfd-scaling
|
||||
|
||||
metadata:
|
||||
template_name: sample-tosca-vnfd-scaling
|
||||
|
||||
topology_template:
|
||||
node_templates:
|
||||
VDU1:
|
||||
type: tosca.nodes.nfv.VDU.Tacker
|
||||
capabilities:
|
||||
nfv_compute:
|
||||
properties:
|
||||
num_cpus: 1
|
||||
mem_size: 512 MB
|
||||
disk_size: 1 GB
|
||||
properties:
|
||||
image: cirros-0.3.4-x86_64-uec
|
||||
mgmt_driver: noop
|
||||
availability_zone: nova
|
||||
|
||||
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: net_mgmt
|
||||
vendor: Tacker
|
||||
|
||||
policies:
|
||||
- SP1:
|
||||
type: tosca.policy.tacker.Scaling
|
||||
properties:
|
||||
increment: 1
|
||||
cooldown: 60
|
||||
min_instances: 1
|
||||
max_instances: 3
|
||||
default_instances: 2
|
||||
targets: [VDU1]
|
|
@ -0,0 +1,86 @@
|
|||
#
|
||||
# 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 json
|
||||
import time
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from tacker.tests import constants
|
||||
from tacker.tests.functional import base
|
||||
from tacker.tests.utils import read_file
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class VnfTestToscaScale(base.BaseTackerTest):
|
||||
def test_vnf_tosca_scale(self):
|
||||
data = dict()
|
||||
data['tosca'] = read_file('sample-tosca-scale-all.yaml')
|
||||
vnfd_name = 'test_tosca_vnf_scale_all'
|
||||
toscal = data['tosca']
|
||||
tosca_arg = {'vnfd': {'name': vnfd_name,
|
||||
'attributes': {'vnfd': toscal}}}
|
||||
|
||||
# Create vnfd with tosca template
|
||||
vnfd_instance = self.client.create_vnfd(body=tosca_arg)
|
||||
self.assertIsNotNone(vnfd_instance)
|
||||
|
||||
# Create vnf with vnfd_id
|
||||
vnfd_id = vnfd_instance['vnfd']['id']
|
||||
vnf_name = 'test_tosca_vnf_scale_all'
|
||||
vnf_arg = {'vnf': {'vnfd_id': vnfd_id, 'name': vnf_name}}
|
||||
vnf_instance = self.client.create_vnf(body=vnf_arg)
|
||||
|
||||
self.validate_vnf_instance(vnfd_instance, vnf_instance)
|
||||
|
||||
vnf_id = vnf_instance['vnf']['id']
|
||||
|
||||
# TODO(kanagaraj-manickam) once load-balancer support is enabled,
|
||||
# update this logic to validate the scaling
|
||||
def _wait(count):
|
||||
self.wait_until_vnf_active(
|
||||
vnf_id,
|
||||
constants.VNF_CIRROS_CREATE_TIMEOUT,
|
||||
constants.ACTIVE_SLEEP_TIME)
|
||||
vnf = self.client.show_vnf(vnf_id)['vnf']
|
||||
|
||||
# {"VDU1": ["10.0.0.14", "10.0.0.5"]}
|
||||
self.assertEqual(count, len(json.loads(vnf['mgmt_url'])['VDU1']))
|
||||
|
||||
_wait(2)
|
||||
|
||||
def _scale(type, count):
|
||||
body = {"scale": {'type': type, 'policy': 'SP1'}}
|
||||
self.client.scale_vnf(vnf_id, body)
|
||||
_wait(count)
|
||||
|
||||
# scale out
|
||||
time.sleep(constants.SCALE_WINDOW_SLEEP_TIME)
|
||||
_scale('out', 3)
|
||||
|
||||
# scale in
|
||||
time.sleep(constants.SCALE_WINDOW_SLEEP_TIME)
|
||||
_scale('in', 2)
|
||||
|
||||
# Delete vnf_instance with vnf_id
|
||||
try:
|
||||
self.client.delete_vnf(vnf_id)
|
||||
except Exception:
|
||||
assert False, "vnf Delete failed"
|
||||
|
||||
# Delete vnfd_instance
|
||||
self.addCleanup(self.client.delete_vnfd, vnfd_id)
|
||||
self.addCleanup(self.wait_until_vnf_delete, vnf_id,
|
||||
constants.VNF_CIRROS_DELETE_TIMEOUT)
|
|
@ -469,7 +469,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
|||
policy=policy['id']
|
||||
)
|
||||
|
||||
LOG.debug(_("Policy %s is validated successfully") % policy)
|
||||
LOG.debug(_("Policy %s is validated successfully"), policy['id'])
|
||||
|
||||
def _get_status():
|
||||
if policy['action'] == constants.ACTION_SCALE_IN:
|
||||
|
@ -487,7 +487,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
|||
[constants.ACTIVE],
|
||||
status)
|
||||
LOG.debug(_("Policy %(policy)s vnf is at %(status)s"),
|
||||
{'policy': policy,
|
||||
{'policy': policy['id'],
|
||||
'status': status})
|
||||
return result
|
||||
|
||||
|
@ -500,14 +500,14 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
|||
new_status,
|
||||
mgmt_url)
|
||||
LOG.debug(_("Policy %(policy)s vnf is at %(status)s"),
|
||||
{'policy': policy,
|
||||
{'policy': policy['id'],
|
||||
'status': new_status})
|
||||
return result
|
||||
|
||||
# action
|
||||
def _vnf_policy_action():
|
||||
try:
|
||||
self._vnf_manager.invoke(
|
||||
last_event_id = self._vnf_manager.invoke(
|
||||
infra_driver,
|
||||
'scale',
|
||||
plugin=self,
|
||||
|
@ -516,24 +516,25 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
|||
policy=policy,
|
||||
region_name=region_name
|
||||
)
|
||||
LOG.debug(_("Policy %s action is started successfully") %
|
||||
policy)
|
||||
LOG.debug(_("Policy %s action is started successfully"),
|
||||
policy['id'])
|
||||
return last_event_id
|
||||
except Exception as e:
|
||||
LOG.error(_("Policy %s action is failed to start") %
|
||||
LOG.error(_("Policy %s action is failed to start"),
|
||||
policy)
|
||||
with excutils.save_and_reraise_exception():
|
||||
vnf['status'] = constants.ERROR
|
||||
self.set_vnf_error_status_reason(
|
||||
context,
|
||||
policy['vnf_id'],
|
||||
policy['vnf']['id'],
|
||||
six.text_type(e))
|
||||
_handle_vnf_scaling_post(constants.ERROR)
|
||||
|
||||
# wait
|
||||
def _vnf_policy_action_wait():
|
||||
try:
|
||||
LOG.debug(_("Policy %s action is in progress") %
|
||||
policy)
|
||||
LOG.debug(_("Policy %s action is in progress"),
|
||||
policy['id'])
|
||||
mgmt_url = self._vnf_manager.invoke(
|
||||
infra_driver,
|
||||
'scale_wait',
|
||||
|
@ -541,19 +542,20 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
|||
context=context,
|
||||
auth_attr=vim_auth,
|
||||
policy=policy,
|
||||
region_name=region_name
|
||||
region_name=region_name,
|
||||
last_event_id=last_event_id
|
||||
)
|
||||
LOG.debug(_("Policy %s action is completed successfully") %
|
||||
policy)
|
||||
LOG.debug(_("Policy %s action is completed successfully"),
|
||||
policy['id'])
|
||||
_handle_vnf_scaling_post(constants.ACTIVE, mgmt_url)
|
||||
# TODO(kanagaraj-manickam): Add support for config and mgmt
|
||||
except Exception as e:
|
||||
LOG.error(_("Policy %s action is failed to complete") %
|
||||
policy)
|
||||
policy['id'])
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.set_vnf_error_status_reason(
|
||||
context,
|
||||
policy['vnf_id'],
|
||||
policy['vnf']['id'],
|
||||
six.text_type(e))
|
||||
_handle_vnf_scaling_post(constants.ERROR)
|
||||
|
||||
|
@ -565,7 +567,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
|||
infra_driver = self._infra_driver_name(vnf)
|
||||
vim_auth = self.get_vim(context, vnf)
|
||||
region_name = vnf.get('placement_attr', {}).get('region_name', None)
|
||||
_vnf_policy_action()
|
||||
last_event_id = _vnf_policy_action()
|
||||
self.spawn_n(_vnf_policy_action_wait)
|
||||
|
||||
return policy
|
||||
|
|
|
@ -777,12 +777,12 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver,
|
|||
|
||||
mgmt_ips = {}
|
||||
for group_name in group_names:
|
||||
# Get scale group
|
||||
grp = heat_client.resource_get(instance_id,
|
||||
group_name)
|
||||
# Get scale group
|
||||
for rsc in heat_client.resource_get_list(
|
||||
grp.physical_resource_id):
|
||||
# Get list of resoruces in scale group
|
||||
# Get list of resources in scale group
|
||||
scale_rsc = heat_client.resource_get(
|
||||
grp.physical_resource_id,
|
||||
rsc.resource_name)
|
||||
|
@ -804,11 +804,21 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver,
|
|||
policy,
|
||||
region_name):
|
||||
heatclient_ = HeatClient(auth_attr, region_name)
|
||||
return heatclient_.resource_signal(policy['instance_id'],
|
||||
get_scaling_policy_name(
|
||||
policy_rsc = get_scaling_policy_name(
|
||||
policy_name=policy['id'],
|
||||
action=policy['action']
|
||||
))
|
||||
)
|
||||
events = heatclient_.resource_event_list(
|
||||
policy['instance_id'],
|
||||
policy_rsc,
|
||||
limit=1,
|
||||
sort_dir='desc',
|
||||
sort_keys='event_time'
|
||||
)
|
||||
|
||||
heatclient_.resource_signal(policy['instance_id'],
|
||||
policy_rsc)
|
||||
return events[0].id
|
||||
|
||||
@log.log
|
||||
def scale_wait(self,
|
||||
|
@ -816,29 +826,59 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver,
|
|||
plugin,
|
||||
auth_attr,
|
||||
policy,
|
||||
region_name):
|
||||
region_name,
|
||||
last_event_id):
|
||||
heatclient_ = HeatClient(auth_attr, region_name)
|
||||
|
||||
# TODO(kanagaraj-manickam) make wait logic into separate utility method
|
||||
# and make use of it here and other actions like create and delete
|
||||
stack_retries = STACK_RETRIES
|
||||
while (True):
|
||||
time.sleep(STACK_RETRY_WAIT)
|
||||
try:
|
||||
rsc = heatclient_.resource_get(
|
||||
policy['instance_id'],
|
||||
get_scaling_policy_name(policy_name=policy['id'],
|
||||
action=policy['action']))
|
||||
except Exception:
|
||||
LOG.exception(_("VNF scaling may not have "
|
||||
"happened because Heat API request failed "
|
||||
time.sleep(STACK_RETRY_WAIT)
|
||||
stack_id = policy['instance_id']
|
||||
policy_name = get_scaling_policy_name(
|
||||
policy_name=policy['id'],
|
||||
action=policy['action'])
|
||||
events = heatclient_.resource_event_list(
|
||||
stack_id,
|
||||
policy_name,
|
||||
limit=1,
|
||||
sort_dir='desc',
|
||||
sort_keys='event_time')
|
||||
|
||||
if events[0].id != last_event_id:
|
||||
if events[0].resource_status == 'SIGNAL_COMPLETE':
|
||||
break
|
||||
except Exception as e:
|
||||
error_reason = _("VNF scaling failed for stack %(stack)s with "
|
||||
"error %(error)s") % {
|
||||
'stack': policy['instance_id'],
|
||||
'error': e.message
|
||||
}
|
||||
LOG.warning(error_reason)
|
||||
raise vnfm.VNFScaleWaitFailed(
|
||||
vnf_id=policy['vnf']['id'],
|
||||
reason=error_reason)
|
||||
|
||||
if stack_retries == 0:
|
||||
metadata = heatclient_.resource_metadata(stack_id, policy_name)
|
||||
if not metadata['scaling_in_progress']:
|
||||
error_reason = _('when signal occurred within cool down '
|
||||
'window, no events generated from heat, '
|
||||
'so ignore it')
|
||||
LOG.warning(error_reason)
|
||||
break
|
||||
error_reason = _(
|
||||
"VNF scaling failed to complete within %{wait}s seconds "
|
||||
"while waiting for the stack %(stack)s to be "
|
||||
"scaled"), {'stack': policy['instance_id']})
|
||||
break
|
||||
|
||||
if rsc.resource_status == 'SIGNAL_IN_PROGRESS':
|
||||
continue
|
||||
|
||||
break
|
||||
"scaled.") % {'stack': stack_id,
|
||||
'wait': STACK_RETRIES * STACK_RETRY_WAIT}
|
||||
LOG.warning(error_reason)
|
||||
raise vnfm.VNFScaleWaitFailed(
|
||||
vnf_id=policy['vnf']['id'],
|
||||
reason=error_reason)
|
||||
stack_retries -= 1
|
||||
|
||||
def _fill_scaling_group_name():
|
||||
vnf = policy['vnf']
|
||||
|
@ -916,3 +956,9 @@ class HeatClient(object):
|
|||
|
||||
def resource_get(self, stack_id, rsc_name):
|
||||
return self.heat.resources.get(stack_id, rsc_name)
|
||||
|
||||
def resource_event_list(self, stack_id, rsc_name, **kwargs):
|
||||
return self.heat.events.list(stack_id, rsc_name, **kwargs)
|
||||
|
||||
def resource_metadata(self, stack_id, rsc_name):
|
||||
return self.heat.resources.metadata(stack_id, rsc_name)
|
||||
|
|
Loading…
Reference in New Issue