Add rollback option to create and update stacks

This patch adds a rollback option to create and update stacks.
If "CONF.v2_vnfm.enable_rollback_stack" is True,
when a resource creation fails in the create and update stack,
rollback stack is executed and the create resource is deleted.
This feature is implemented to successful AZ reselection at
the VDU using volume.

Closes-Bug: #2034886
Change-Id: Icdc70f299c65a137672935338dd6d795a3dbea73
This commit is contained in:
Ken Fujimoto 2023-08-06 11:40:01 +00:00
parent 4f857d3276
commit 83a109f765
6 changed files with 88 additions and 21 deletions

View File

@ -653,8 +653,7 @@
test_grant_zone_list: az-1
v2_vnfm:
placement_fallback_best_effort: true
server_notification:
server_notification: true
enable_rollback_stack: true
devstack_services:
n-cpu: true
placement-client: true

View File

@ -93,6 +93,10 @@ VNFM_OPTS = [
help=_('Error message for Availability Zone reselection. '
'These configs are regular expressions to detect '
'error messages from OpenStack Heat.')),
cfg.BoolOpt('enable_rollback_stack',
default=False,
help=_('If True, enable rollback stack on resource create '
'failure.')),
# NOTE: This is for test use since it is convenient to be able to delete
# under development.
cfg.BoolOpt('test_enable_lcm_op_occ_delete',

View File

@ -60,6 +60,8 @@ class HeatClient(object):
base_url=base_url)
def create_stack(self, fields, wait=True):
if CONF.v2_vnfm.enable_rollback_stack:
fields['disable_rollback'] = False
path = "stacks"
resp, body = self.client.do_request(path, "POST",
expected_status=[201], body=fields)
@ -71,6 +73,8 @@ class HeatClient(object):
return body['stack']['id']
def update_stack(self, stack_name, fields, wait=True):
if CONF.v2_vnfm.enable_rollback_stack:
fields['disable_rollback'] = False
path = f"stacks/{stack_name}"
# It was assumed that PATCH is used and therefore 'fields'
# contains only update parts and 'existing' is not used.
@ -132,6 +136,9 @@ class HeatClient(object):
LOG.info("%s %s done.", operation, stack_name.split('/')[0])
raise loopingcall.LoopingCallDone()
elif status in failed_status:
if (status == "ROLLBACK_COMPLETE"
or status == "ROLLBACK_FAILED"):
status_reason = self.get_original_failed_reason(stack_name)
LOG.error("%s %s failed.", operation, stack_name.split('/')[0])
sol_title = "%s failed" % operation
raise sol_ex.StackOperationFailed(sol_title=sol_title,
@ -149,12 +156,28 @@ class HeatClient(object):
timer.start(interval=CHECK_INTERVAL).wait()
def wait_stack_create(self, stack_name):
self._wait_completion(stack_name, "Stack create",
["CREATE_COMPLETE"], ["CREATE_IN_PROGRESS"], ["CREATE_FAILED"])
if CONF.v2_vnfm.enable_rollback_stack:
self._wait_completion(stack_name, "Stack create",
["CREATE_COMPLETE"],
["CREATE_IN_PROGRESS", "CREATE_FAILED",
"ROLLBACK_IN_PROGRESS"],
["ROLLBACK_COMPLETE", "ROLLBACK_FAILED"])
else:
self._wait_completion(stack_name, "Stack create",
["CREATE_COMPLETE"], ["CREATE_IN_PROGRESS"],
["CREATE_FAILED"])
def wait_stack_update(self, stack_name):
self._wait_completion(stack_name, "Stack update",
["UPDATE_COMPLETE"], ["UPDATE_IN_PROGRESS"], ["UPDATE_FAILED"])
if CONF.v2_vnfm.enable_rollback_stack:
self._wait_completion(stack_name, "Stack update",
["UPDATE_COMPLETE"],
["UPDATE_IN_PROGRESS", "UPDATE_FAILED",
"ROLLBACK_IN_PROGRESS"],
["ROLLBACK_COMPLETE", "ROLLBACK_FAILED"])
else:
self._wait_completion(stack_name, "Stack update",
["UPDATE_COMPLETE"], ["UPDATE_IN_PROGRESS"],
["UPDATE_FAILED"])
def wait_stack_delete(self, stack_name):
# NOTE: wait until stack is deleted in the DB since it is necessary
@ -203,6 +226,21 @@ class HeatClient(object):
return body
def get_original_failed_reason(self, stack_name):
# This method gets the reason for stack failure from the stack event
# if the rollback option is enabled.
resource_name = stack_name.split('/')[0]
path = (f"stacks/{stack_name}/resources/{resource_name}/"
"events?resource_action=CREATE&resource_action=UPDATE"
"&resource_status=FAILED&sort_dir=desc&limit=1")
resp, body = self.client.do_request(path, "GET",
expected_status=[200, 404])
if resp.status_code == 404:
return None
return body["events"][0]["resource_status_reason"]
def get_reses_by_types(heat_reses, types):
return [res for res in heat_reses if res['resource_type'] in types]

View File

@ -4,7 +4,7 @@ description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image-VDU1:
image-VDU1-VirtualStorage:
type: string
zone:
type: string
@ -21,7 +21,7 @@ resources:
properties:
flavor: { get_param: flavor }
name: VDU1
image: { get_param: image-VDU1 }
block_device_mapping_v2: [{"volume_id": { get_resource: VDU1-VirtualStorage }}]
networks:
- port:
get_resource: VDU1_CP1
@ -33,6 +33,19 @@ resources:
get_resource: VDU1_CP3
availability_zone: { get_param: zone }
VDU1-VirtualStorage:
type: OS::Cinder::Volume
properties:
image: { get_param: image-VDU1-VirtualStorage }
size: 1
volume_type: { get_resource: multi }
multi:
type: OS::Cinder::VolumeType
properties:
# making unique name to avoid conflicting with other packages
name: { list_join: ['-', [get_param: OS::stack_name, 'VDU1-multi']] }
metadata: { multiattach: "<is> True" }
# extVL without FixedIP or with numDynamicAddresses
VDU1_CP1:
type: OS::Neutron::Port

View File

@ -10,7 +10,7 @@ resources:
type: VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image-VDU1: { get_param: [ nfv, VDU, VDU1, vcImageId ] }
image-VDU1-VirtualStorage: { get_param: [ nfv, VDU, VDU1-VirtualStorage, vcImageId ] }
zone: { get_param: [ nfv, VDU, VDU1, locationConstraints] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network ] }
net2: { get_resource: internalVL1 }

View File

@ -84,17 +84,6 @@ topology_template:
# in other zones in the test_update_stack_retry.
# If there is a change in the Zuul environment in the future,
# it must be reviewed along with the flavor.
sw_image_data:
name: cirros-0.5.2-x86_64-disk
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 12 GB
capabilities:
virtual_compute:
properties:
@ -110,6 +99,30 @@ topology_template:
num_virtual_cpu: 2
virtual_local_storage:
- size_of_storage: 4 GB
requirements:
- virtual_storage: VDU1-VirtualStorage
VDU1-VirtualStorage:
type: tosca.nodes.nfv.Vdu.VirtualBlockStorage
properties:
virtual_block_storage_data:
size_of_storage: 1 GB
rdma_enabled: true
sw_image_data:
name: VDU1-VirtualStorage-image
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 12 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.5.2-x86_64-disk.img
VDU1_CP1:
type: tosca.nodes.nfv.VduCp
@ -188,7 +201,7 @@ topology_template:
VDU1_scale:
name: VDU1_scale
description: VDU1 scaling aspect
max_scale_level: 2
max_scale_level: 4
step_deltas:
- delta_1