Refactored stack module and use stack's tag(s) attribute
Dropped stack status checks for CREATE_COMPLETE and UPDATE_\ COMPLETE and instead pass wait=True to openstacksdk's create_stack() and update_stack() calls because those will call event_utils.\ poll_for_events() which garantees that stack has reached those states when they return [1],[2]. Check for duplicate keys in parameters which already have been defined by regular module attributes. Raise an error to warn users that they tried to overwrite parameters which they already specified with module attributes. Renamed stack's module attribute 'tag' to 'tags' to match both openstacksdk and OpenStack API and clarify that more than a single tag can be specified. Added 'tag' as an alias to keep backward compatibility. Actually pass the 'tags' aka 'tag' attribute to stack create and update functions of the openstacksdk which was lost in [3]. Added tags to integration tests. Sorted argument specs and documentation of the stack module and marked attributes which are not updatable. Dropped condition from self.conn.delete_stack() because the latter will only return False when a stack could not be found which we already. self.conn.delete_stack() will raise an exception when stack deletion fails. Renamed ci integration tests from 'orchestration' to 'stack' in order to match module name and adapted tags accordingly. Fixed and enabled ci integration tests for stack and stack_info modules. Dropped 'stack_name' from module return values in RETURN docstring and ci integration tests because openstacksdk not return this attribute. [1]9b1c433352/openstack/cloud/_orchestration.py (L92)
[2]9b1c433352/openstack/cloud/_orchestration.py (L148)
[3]af79857bfb
Change-Id: I4ace6012112bbcce9094353e27eb4066cf25f229
This commit is contained in:
parent
a660a35d86
commit
41299b9666
@ -103,6 +103,7 @@
|
|||||||
security_group
|
security_group
|
||||||
security_group_rule
|
security_group_rule
|
||||||
server
|
server
|
||||||
|
stack
|
||||||
subnet
|
subnet
|
||||||
subnet_pool
|
subnet_pool
|
||||||
user
|
user
|
||||||
@ -111,7 +112,6 @@
|
|||||||
volume
|
volume
|
||||||
# failing tags
|
# failing tags
|
||||||
# floating_ip
|
# floating_ip
|
||||||
# orchestrate
|
|
||||||
# neutron_rbac
|
# neutron_rbac
|
||||||
|
|
||||||
- job:
|
- job:
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Create minimal stack
|
|
||||||
openstack.cloud.stack:
|
|
||||||
cloud: "{{ cloud }}"
|
|
||||||
# template is searched related to playbook location or as absolute path
|
|
||||||
template: "roles/orchestration/files/hello-world.yaml"
|
|
||||||
name: "{{ stack_name }}"
|
|
||||||
register: minimal_stack
|
|
||||||
|
|
||||||
- name: Assert fields returned by create stack
|
|
||||||
assert:
|
|
||||||
that: item in minimal_stack.stack
|
|
||||||
loop: "{{ expected_fields }}"
|
|
||||||
|
|
||||||
- name: List stacks
|
|
||||||
openstack.cloud.stack_info:
|
|
||||||
cloud: "{{ cloud }}"
|
|
||||||
register: stacks
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- stacks['stacks']|length > 0
|
|
||||||
|
|
||||||
- name: Get Single stack
|
|
||||||
openstack.cloud.stack_info:
|
|
||||||
cloud: "{{ cloud }}"
|
|
||||||
name: "{{ stack_name }}"
|
|
||||||
register: test_stack
|
|
||||||
|
|
||||||
- name: Assert fields returned by stack info
|
|
||||||
assert:
|
|
||||||
that: item in test_stack.stack[0]
|
|
||||||
loop: "{{ expected_fields }}"
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- test_stack is defined
|
|
||||||
- test_stack['stacks'][0]['name'] == stack_name
|
|
||||||
|
|
||||||
- name: Delete stack
|
|
||||||
openstack.cloud.stack:
|
|
||||||
cloud: "{{ cloud }}"
|
|
||||||
name: "{{ stack_name }}"
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Get Single stack
|
|
||||||
openstack.cloud.stack_info:
|
|
||||||
cloud: "{{ cloud }}"
|
|
||||||
name: "{{ stack_name }}"
|
|
||||||
register: stacks
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- stacks is defined
|
|
||||||
- (stacks['stacks']|length == 0) or (stacks['stacks'][0]['status'] == 'DELETE_COMPLETE')
|
|
@ -21,7 +21,6 @@ expected_fields:
|
|||||||
- parameters
|
- parameters
|
||||||
- parent_id
|
- parent_id
|
||||||
- replaced
|
- replaced
|
||||||
- stack_name
|
|
||||||
- status
|
- status
|
||||||
- status_reason
|
- status_reason
|
||||||
- tags
|
- tags
|
89
ci/roles/stack/tasks/main.yaml
Normal file
89
ci/roles/stack/tasks/main.yaml
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
- name: Create minimal stack
|
||||||
|
openstack.cloud.stack:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
template: "roles/stack/files/hello-world.yaml"
|
||||||
|
name: "{{ stack_name }}"
|
||||||
|
tags: "tag1,tag2"
|
||||||
|
register: stack
|
||||||
|
|
||||||
|
- name: Assert fields returned by create stack
|
||||||
|
assert:
|
||||||
|
that: item in stack.stack
|
||||||
|
loop: "{{ expected_fields }}"
|
||||||
|
|
||||||
|
- name: List stacks
|
||||||
|
openstack.cloud.stack_info:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
register: stacks
|
||||||
|
|
||||||
|
- name: Assert stack_info module return values
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- stacks.stacks|length > 0
|
||||||
|
|
||||||
|
- name: Assert fields returned by stack info
|
||||||
|
assert:
|
||||||
|
that: item in stacks.stacks[0]
|
||||||
|
loop: "{{ expected_fields }}"
|
||||||
|
|
||||||
|
- name: Get single stack
|
||||||
|
openstack.cloud.stack_info:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
name: "{{ stack_name }}"
|
||||||
|
register: stacks
|
||||||
|
|
||||||
|
- name: Assert single stack
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- stacks.stacks|length == 1
|
||||||
|
- stacks.stacks.0.name == stack_name
|
||||||
|
- stacks.stacks.0.id == stack.stack.id
|
||||||
|
# Older openstacksdk releases use datatype list instead of str for tags
|
||||||
|
# Ref.: https://review.opendev.org/c/openstack/openstacksdk/+/860534
|
||||||
|
- stacks.stacks.0.tags|string in ["tag1,tag2", "['tag1', 'tag2']"]
|
||||||
|
|
||||||
|
- name: Update stack
|
||||||
|
openstack.cloud.stack:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
template: "roles/stack/files/hello-world.yaml"
|
||||||
|
name: "{{ stack_name }}"
|
||||||
|
tags: "tag1,tag2,tag3"
|
||||||
|
register: stack_updated
|
||||||
|
|
||||||
|
- name: Assert updated stack
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- stack_updated.stack.id == stack.stack.id
|
||||||
|
- stack_updated is changed
|
||||||
|
|
||||||
|
- name: Get updated stack
|
||||||
|
openstack.cloud.stack_info:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
name: "{{ stack_name }}"
|
||||||
|
register: stacks
|
||||||
|
|
||||||
|
- name: Assert updated stack
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- stacks.stacks|length == 1
|
||||||
|
- stacks.stacks.0.id == stack.stack.id
|
||||||
|
# Older openstacksdk releases use datatype list instead of str for tags
|
||||||
|
# Ref.: https://review.opendev.org/c/openstack/openstacksdk/+/860534
|
||||||
|
- stacks.stacks.0.tags|string in ["tag1,tag2,tag3", "['tag1', 'tag2', 'tag3']"]
|
||||||
|
|
||||||
|
- name: Delete stack
|
||||||
|
openstack.cloud.stack:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
name: "{{ stack_name }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Get single stack
|
||||||
|
openstack.cloud.stack_info:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
name: "{{ stack_name }}"
|
||||||
|
register: stacks
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- (stacks.stacks|length == 0) or (stacks.stacks.0.status == 'DELETE_COMPLETE')
|
@ -58,14 +58,12 @@
|
|||||||
- { role: security_group, tags: security_group }
|
- { role: security_group, tags: security_group }
|
||||||
- { role: security_group_rule, tags: security_group_rule }
|
- { role: security_group_rule, tags: security_group_rule }
|
||||||
- { role: server, tags: server }
|
- { role: server, tags: server }
|
||||||
|
- { role: stack, tags: stack }
|
||||||
- { role: subnet, tags: subnet }
|
- { role: subnet, tags: subnet }
|
||||||
- { role: subnet_pool, tags: subnet_pool }
|
- { role: subnet_pool, tags: subnet_pool }
|
||||||
- { role: user_group, tags: user_group }
|
- { role: user_group, tags: user_group }
|
||||||
- { role: user_role, tags: user_role }
|
- { role: user_role, tags: user_role }
|
||||||
- { role: volume, tags: volume }
|
- { role: volume, tags: volume }
|
||||||
- role: orchestration
|
|
||||||
tags: orchestrate
|
|
||||||
when: sdk_version is version("0.53.0", '>=')
|
|
||||||
- role: loadbalancer
|
- role: loadbalancer
|
||||||
tags: loadbalancer
|
tags: loadbalancer
|
||||||
- { role: floating_ip, tags: floating_ip }
|
- { role: floating_ip, tags: floating_ip }
|
||||||
|
@ -13,30 +13,23 @@ author: OpenStack Ansible SIG
|
|||||||
description:
|
description:
|
||||||
- Add or Remove a Stack to an OpenStack Heat
|
- Add or Remove a Stack to an OpenStack Heat
|
||||||
options:
|
options:
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Indicate desired state of the resource
|
|
||||||
choices: ['present', 'absent']
|
|
||||||
default: present
|
|
||||||
type: str
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Name of the stack that should be created, name could be char and digit, no space
|
|
||||||
required: true
|
|
||||||
type: str
|
|
||||||
tag:
|
|
||||||
description:
|
|
||||||
- Tag for the stack that should be created, name could be char and digit, no space
|
|
||||||
type: str
|
|
||||||
template:
|
|
||||||
description:
|
|
||||||
- Path of the template file to use for the stack creation
|
|
||||||
type: str
|
|
||||||
environment:
|
environment:
|
||||||
description:
|
description:
|
||||||
- List of environment files that should be used for the stack creation
|
- List of environment files that should be used for the stack creation
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- A name for the stack.
|
||||||
|
- The value must be unique within a project.
|
||||||
|
- The name must start with an ASCII letter and can contain ASCII
|
||||||
|
letters, digits, underscores, periods, and hyphens. Specifically,
|
||||||
|
the name must match the C(^[a-zA-Z][a-zA-Z0-9_.-]{0,254}$) regular
|
||||||
|
expression.
|
||||||
|
- When you delete or abandon a stack, its name will not become
|
||||||
|
available for reuse until the deletion completes successfully.
|
||||||
|
required: true
|
||||||
|
type: str
|
||||||
parameters:
|
parameters:
|
||||||
description:
|
description:
|
||||||
- Dictionary of parameters for the stack creation
|
- Dictionary of parameters for the stack creation
|
||||||
@ -46,6 +39,23 @@ options:
|
|||||||
- Rollback stack creation
|
- Rollback stack creation
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Indicate desired state of the resource
|
||||||
|
choices: ['present', 'absent']
|
||||||
|
default: present
|
||||||
|
type: str
|
||||||
|
tags:
|
||||||
|
description:
|
||||||
|
- One or more simple string tags to associate with the stack.
|
||||||
|
- To associate multiple tags with a stack, separate the tags with
|
||||||
|
commas. For example, C(tag1,tag2).
|
||||||
|
type: str
|
||||||
|
aliases: ['tag']
|
||||||
|
template:
|
||||||
|
description:
|
||||||
|
- Path of the template file to use for the stack creation
|
||||||
|
type: str
|
||||||
timeout:
|
timeout:
|
||||||
description:
|
description:
|
||||||
- Maximum number of seconds to wait for the stack creation
|
- Maximum number of seconds to wait for the stack creation
|
||||||
@ -177,9 +187,6 @@ stack:
|
|||||||
replaced:
|
replaced:
|
||||||
description: A list of resource objects that will be replaced.
|
description: A list of resource objects that will be replaced.
|
||||||
type: str
|
type: str
|
||||||
stack_name:
|
|
||||||
description: Name of the stack.
|
|
||||||
type: str
|
|
||||||
status:
|
status:
|
||||||
description: stack status.
|
description: stack status.
|
||||||
type: str
|
type: str
|
||||||
@ -226,14 +233,14 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
|
|||||||
|
|
||||||
class StackModule(OpenStackModule):
|
class StackModule(OpenStackModule):
|
||||||
argument_spec = dict(
|
argument_spec = dict(
|
||||||
name=dict(required=True),
|
|
||||||
tag=dict(),
|
|
||||||
template=dict(),
|
|
||||||
environment=dict(type='list', elements='str'),
|
environment=dict(type='list', elements='str'),
|
||||||
|
name=dict(required=True),
|
||||||
parameters=dict(default={}, type='dict'),
|
parameters=dict(default={}, type='dict'),
|
||||||
rollback=dict(default=False, type='bool'),
|
rollback=dict(default=False, type='bool'),
|
||||||
timeout=dict(default=3600, type='int'),
|
|
||||||
state=dict(default='present', choices=['absent', 'present']),
|
state=dict(default='present', choices=['absent', 'present']),
|
||||||
|
tags=dict(aliases=['tag']),
|
||||||
|
template=dict(),
|
||||||
|
timeout=dict(default=3600, type='int'),
|
||||||
)
|
)
|
||||||
|
|
||||||
module_kwargs = dict(
|
module_kwargs = dict(
|
||||||
@ -242,39 +249,7 @@ class StackModule(OpenStackModule):
|
|||||||
('state', 'present', ('template',), True)]
|
('state', 'present', ('template',), True)]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_stack(self, stack, parameters):
|
def _system_state_change(self, stack, state):
|
||||||
stack = self.conn.create_stack(
|
|
||||||
self.params['name'],
|
|
||||||
template_file=self.params['template'],
|
|
||||||
environment_files=self.params['environment'],
|
|
||||||
timeout=self.params['timeout'],
|
|
||||||
wait=True,
|
|
||||||
rollback=self.params['rollback'],
|
|
||||||
**parameters)
|
|
||||||
|
|
||||||
if stack.status == 'CREATE_COMPLETE':
|
|
||||||
return stack
|
|
||||||
else:
|
|
||||||
self.fail_json(msg="Failure in creating stack: {0}".format(stack))
|
|
||||||
|
|
||||||
def _update_stack(self, stack, parameters):
|
|
||||||
stack = self.conn.update_stack(
|
|
||||||
self.params['name'],
|
|
||||||
template_file=self.params['template'],
|
|
||||||
environment_files=self.params['environment'],
|
|
||||||
timeout=self.params['timeout'],
|
|
||||||
rollback=self.params['rollback'],
|
|
||||||
wait=self.params['wait'],
|
|
||||||
**parameters)
|
|
||||||
|
|
||||||
if stack['stack_status'] == 'UPDATE_COMPLETE':
|
|
||||||
return stack
|
|
||||||
else:
|
|
||||||
self.fail_json(msg="Failure in updating stack: %s" %
|
|
||||||
stack['stack_status_reason'])
|
|
||||||
|
|
||||||
def _system_state_change(self, stack):
|
|
||||||
state = self.params['state']
|
|
||||||
if state == 'present':
|
if state == 'present':
|
||||||
# This method will always return True if state is present to
|
# This method will always return True if state is present to
|
||||||
# include the case of stack update as there is no simple way
|
# include the case of stack update as there is no simple way
|
||||||
@ -288,27 +263,56 @@ class StackModule(OpenStackModule):
|
|||||||
state = self.params['state']
|
state = self.params['state']
|
||||||
name = self.params['name']
|
name = self.params['name']
|
||||||
|
|
||||||
|
# self.conn.get_stack() will not return stacks with status ==
|
||||||
|
# DELETE_COMPLETE while self.conn.orchestration.find_stack() will
|
||||||
|
# do so. A name of a stack which has been deleted completely can be
|
||||||
|
# reused to create a new stack, hence we want self.conn.get_stack()'s
|
||||||
|
# behaviour here.
|
||||||
stack = self.conn.get_stack(name)
|
stack = self.conn.get_stack(name)
|
||||||
|
|
||||||
if self.ansible.check_mode:
|
if self.ansible.check_mode:
|
||||||
self.exit_json(changed=self._system_state_change(stack))
|
self.exit_json(changed=self._system_state_change(stack, state))
|
||||||
|
|
||||||
if state == 'present':
|
if state == 'present':
|
||||||
parameters = self.params['parameters']
|
# assume an existing stack always requires updates because there is
|
||||||
if not stack:
|
# no simple way to check if stack will indeed have to be updated
|
||||||
stack = self._create_stack(stack, parameters)
|
is_update = bool(stack)
|
||||||
|
kwargs = dict(
|
||||||
|
template_file=self.params['template'],
|
||||||
|
environment_files=self.params['environment'],
|
||||||
|
timeout=self.params['timeout'],
|
||||||
|
rollback=self.params['rollback'],
|
||||||
|
#
|
||||||
|
# Always wait because we expect status to be
|
||||||
|
# CREATE_COMPLETE or UPDATE_COMPLETE
|
||||||
|
wait=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
tags = self.params['tags']
|
||||||
|
if tags is not None:
|
||||||
|
kwargs['tags'] = tags
|
||||||
|
|
||||||
|
extra_kwargs = self.params['parameters']
|
||||||
|
dup_kwargs = set(kwargs.keys()) & set(extra_kwargs.keys())
|
||||||
|
if dup_kwargs:
|
||||||
|
raise ValueError('Duplicate key(s) {0} in parameters'
|
||||||
|
.format(list(dup_kwargs)))
|
||||||
|
kwargs = dict(kwargs, **extra_kwargs)
|
||||||
|
|
||||||
|
if not is_update:
|
||||||
|
stack = self.conn.create_stack(name, **kwargs)
|
||||||
else:
|
else:
|
||||||
stack = self._update_stack(stack, parameters)
|
stack = self.conn.update_stack(name, **kwargs)
|
||||||
self.exit_json(changed=True,
|
|
||||||
stack=stack.to_dict(computed=False))
|
stack = self.conn.orchestration.get_stack(stack['id'])
|
||||||
|
self.exit_json(changed=True, stack=stack.to_dict(computed=False))
|
||||||
elif state == 'absent':
|
elif state == 'absent':
|
||||||
if not stack:
|
if not stack:
|
||||||
changed = False
|
self.exit_json(changed=False)
|
||||||
else:
|
else:
|
||||||
changed = True
|
self.conn.delete_stack(name_or_id=stack['id'],
|
||||||
if not self.conn.delete_stack(stack['id'], wait=self.params['wait']):
|
wait=self.params['wait'])
|
||||||
self.fail_json(msg='delete stack failed for stack: %s' % name)
|
self.exit_json(changed=True)
|
||||||
self.exit_json(changed=changed)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
Loading…
Reference in New Issue
Block a user