diff --git a/.zuul.yaml b/.zuul.yaml
index 32d47c9e21a03f4685a7ba2d8725cfa634144e02..03596cc5d7254acfc06973b89406b9dd66177466 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -103,6 +103,7 @@
         security_group
         security_group_rule
         server
+        stack
         subnet
         subnet_pool
         user
@@ -111,7 +112,6 @@
         volume
       # failing tags
       # floating_ip
-      # orchestrate
       # neutron_rbac
 
 - job:
diff --git a/ci/roles/orchestration/tasks/main.yaml b/ci/roles/orchestration/tasks/main.yaml
deleted file mode 100644
index 8cbecf51771c8ed1f8e09d5651f718d5a9198b72..0000000000000000000000000000000000000000
--- a/ci/roles/orchestration/tasks/main.yaml
+++ /dev/null
@@ -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')
diff --git a/ci/roles/orchestration/defaults/main.yaml b/ci/roles/stack/defaults/main.yaml
similarity index 97%
rename from ci/roles/orchestration/defaults/main.yaml
rename to ci/roles/stack/defaults/main.yaml
index d419f1ab7df5041b1d13a9eeaaddfbeef40a499c..2caff41f771be59a79f838d5a92478d9b345dca0 100644
--- a/ci/roles/orchestration/defaults/main.yaml
+++ b/ci/roles/stack/defaults/main.yaml
@@ -21,7 +21,6 @@ expected_fields:
   - parameters
   - parent_id
   - replaced
-  - stack_name
   - status
   - status_reason
   - tags
diff --git a/ci/roles/orchestration/files/hello-world.yaml b/ci/roles/stack/files/hello-world.yaml
similarity index 100%
rename from ci/roles/orchestration/files/hello-world.yaml
rename to ci/roles/stack/files/hello-world.yaml
diff --git a/ci/roles/stack/tasks/main.yaml b/ci/roles/stack/tasks/main.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..537153b50d730427104e2ba358787f854005ad7c
--- /dev/null
+++ b/ci/roles/stack/tasks/main.yaml
@@ -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')
diff --git a/ci/run-collection.yml b/ci/run-collection.yml
index 0725441486a2093705e4cddad51bec5bf8e79933..7f0e35b02eb770e971b276d96f2d16932873971c 100644
--- a/ci/run-collection.yml
+++ b/ci/run-collection.yml
@@ -58,14 +58,12 @@
     - { role: security_group, tags: security_group }
     - { role: security_group_rule, tags: security_group_rule }
     - { role: server, tags: server }
+    - { role: stack, tags: stack }
     - { role: subnet, tags: subnet }
     - { role: subnet_pool, tags: subnet_pool }
     - { role: user_group, tags: user_group }
     - { role: user_role, tags: user_role }
     - { role: volume, tags: volume }
-    - role: orchestration
-      tags: orchestrate
-      when: sdk_version is version("0.53.0", '>=')
     - role: loadbalancer
       tags: loadbalancer
     - { role: floating_ip, tags: floating_ip }
diff --git a/plugins/modules/stack.py b/plugins/modules/stack.py
index b36ed308e7312cd3ef1d0906d8a8cf420bcbd349..1ce6d256d821a1c97c67374f9410f3f79f009108 100644
--- a/plugins/modules/stack.py
+++ b/plugins/modules/stack.py
@@ -13,30 +13,23 @@ author: OpenStack Ansible SIG
 description:
    - Add or Remove a Stack to an OpenStack Heat
 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:
       description:
         - List of environment files that should be used for the stack creation
       type: list
       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:
       description:
         - Dictionary of parameters for the stack creation
@@ -46,6 +39,23 @@ options:
         - Rollback stack creation
       type: bool
       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:
       description:
         - Maximum number of seconds to wait for the stack creation
@@ -177,9 +187,6 @@ stack:
         replaced:
             description: A list of resource objects that will be replaced.
             type: str
-        stack_name:
-            description: Name of the stack.
-            type: str
         status:
             description: stack status.
             type: str
@@ -226,14 +233,14 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
 
 class StackModule(OpenStackModule):
     argument_spec = dict(
-        name=dict(required=True),
-        tag=dict(),
-        template=dict(),
         environment=dict(type='list', elements='str'),
+        name=dict(required=True),
         parameters=dict(default={}, type='dict'),
         rollback=dict(default=False, type='bool'),
-        timeout=dict(default=3600, type='int'),
         state=dict(default='present', choices=['absent', 'present']),
+        tags=dict(aliases=['tag']),
+        template=dict(),
+        timeout=dict(default=3600, type='int'),
     )
 
     module_kwargs = dict(
@@ -242,39 +249,7 @@ class StackModule(OpenStackModule):
             ('state', 'present', ('template',), True)]
     )
 
-    def _create_stack(self, stack, parameters):
-        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']
+    def _system_state_change(self, stack, state):
         if state == 'present':
             # This method will always return True if state is present to
             # include the case of stack update as there is no simple way
@@ -288,27 +263,56 @@ class StackModule(OpenStackModule):
         state = self.params['state']
         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)
 
         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':
-            parameters = self.params['parameters']
-            if not stack:
-                stack = self._create_stack(stack, parameters)
+            # assume an existing stack always requires updates because there is
+            # no simple way to check if stack will indeed have to be updated
+            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:
-                stack = self._update_stack(stack, parameters)
-            self.exit_json(changed=True,
-                           stack=stack.to_dict(computed=False))
+                stack = self.conn.update_stack(name, **kwargs)
+
+            stack = self.conn.orchestration.get_stack(stack['id'])
+            self.exit_json(changed=True, stack=stack.to_dict(computed=False))
         elif state == 'absent':
             if not stack:
-                changed = False
+                self.exit_json(changed=False)
             else:
-                changed = True
-                if not self.conn.delete_stack(stack['id'], wait=self.params['wait']):
-                    self.fail_json(msg='delete stack failed for stack: %s' % name)
-            self.exit_json(changed=changed)
+                self.conn.delete_stack(name_or_id=stack['id'],
+                                       wait=self.params['wait'])
+                self.exit_json(changed=True)
 
 
 def main():