mirror of
https://opendev.org/openstack/ansible-collections-openstack.git
synced 2026-03-26 21:43:02 +00:00
Add volume_manage module
This module introduces the ability to use the cinder manage and unmanage of an existing volume on a cinder backend. Due to API limitations, when unmanaging a volume, only the volume ID can be provided. Change-Id: If969f198864e6bd65dbb9fce4923af1674da34bc
This commit is contained in:
32
ci/roles/volume_manage/defaults/main.yml
Normal file
32
ci/roles/volume_manage/defaults/main.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
test_volume: ansible_test_volume
|
||||
managed_volume: managed_test_volume
|
||||
expected_fields:
|
||||
- attachments
|
||||
- availability_zone
|
||||
- consistency_group_id
|
||||
- created_at
|
||||
- updated_at
|
||||
- description
|
||||
- extended_replication_status
|
||||
- group_id
|
||||
- host
|
||||
- image_id
|
||||
- is_bootable
|
||||
- is_encrypted
|
||||
- is_multiattach
|
||||
- migration_id
|
||||
- migration_status
|
||||
- project_id
|
||||
- replication_driver_data
|
||||
- replication_status
|
||||
- scheduler_hints
|
||||
- size
|
||||
- snapshot_id
|
||||
- source_volume_id
|
||||
- status
|
||||
- user_id
|
||||
- volume_image_metadata
|
||||
- volume_type
|
||||
- id
|
||||
- name
|
||||
- metadata
|
||||
65
ci/roles/volume_manage/tasks/main.yml
Normal file
65
ci/roles/volume_manage/tasks/main.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
- name: Create volume
|
||||
openstack.cloud.volume:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
size: 1
|
||||
name: "{{ test_volume }}"
|
||||
description: Test volume
|
||||
register: vol
|
||||
|
||||
- assert:
|
||||
that: item in vol.volume
|
||||
loop: "{{ expected_fields }}"
|
||||
|
||||
- name: Unmanage volume
|
||||
openstack.cloud.volume_manage:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ vol.volume.id }}"
|
||||
|
||||
- name: Unmanage volume again
|
||||
openstack.cloud.volume_manage:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ vol.volume.id }}"
|
||||
register: unmanage_idempotency
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- unmanage_idempotency is not changed
|
||||
|
||||
- name: Manage volume
|
||||
openstack.cloud.volume_manage:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
source_name: volume-{{ vol.volume.id }}
|
||||
host: "{{ vol.volume.host }}"
|
||||
name: "{{ managed_volume }}"
|
||||
register: new_vol
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- new_vol.volume.name == "{{ managed_volume }}"
|
||||
|
||||
- name: Manage volume again
|
||||
openstack.cloud.volume_manage:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
source_name: volume-{{ vol.volume.id }}
|
||||
host: "{{ vol.volume.host }}"
|
||||
name: "{{ managed_volume }}"
|
||||
register: vol_idempotency
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- vol_idempotency is not changed
|
||||
|
||||
- pause:
|
||||
seconds: 10
|
||||
|
||||
- name: Delete volume
|
||||
openstack.cloud.volume:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ managed_volume }}"
|
||||
@@ -59,6 +59,7 @@
|
||||
- { role: volume, tags: volume }
|
||||
- { role: volume_type, tags: volume_type }
|
||||
- { role: volume_backup, tags: volume_backup }
|
||||
- { role: volume_manage, tags: volume_manage }
|
||||
- { role: volume_service, tags: volume_service }
|
||||
- { role: volume_snapshot, tags: volume_snapshot }
|
||||
- { role: volume_type_access, tags: volume_type_access }
|
||||
|
||||
@@ -85,6 +85,7 @@ action_groups:
|
||||
- subnets_info
|
||||
- trunk
|
||||
- volume
|
||||
- volume_manage
|
||||
- volume_backup
|
||||
- volume_backup_info
|
||||
- volume_info
|
||||
|
||||
309
plugins/modules/volume_manage.py
Normal file
309
plugins/modules/volume_manage.py
Normal file
@@ -0,0 +1,309 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2025 by Pure Storage, Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: volume_manage
|
||||
short_description: Manage/Unmanage Volumes
|
||||
author: OpenStack Ansible SIG
|
||||
description:
|
||||
- Manage or Unmanage Volume in OpenStack.
|
||||
options:
|
||||
description:
|
||||
description:
|
||||
- String describing the volume
|
||||
type: str
|
||||
metadata:
|
||||
description: Metadata for the volume
|
||||
type: dict
|
||||
name:
|
||||
description:
|
||||
- Name of the volume to be unmanaged or
|
||||
the new name of a managed volume
|
||||
- When I(state) is C(absent) this must be
|
||||
the cinder volume ID
|
||||
required: true
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Should the resource be present or absent.
|
||||
choices: [present, absent]
|
||||
default: present
|
||||
type: str
|
||||
bootable:
|
||||
description:
|
||||
- Bootable flag for volume.
|
||||
type: bool
|
||||
default: False
|
||||
volume_type:
|
||||
description:
|
||||
- Volume type for volume
|
||||
type: str
|
||||
availability_zone:
|
||||
description:
|
||||
- The availability zone.
|
||||
type: str
|
||||
host:
|
||||
description:
|
||||
- Cinder host on which the existing volume resides
|
||||
- Takes the form "host@backend-name#pool"
|
||||
- Required when I(state) is C(present).
|
||||
type: str
|
||||
source_name:
|
||||
description:
|
||||
- Name of existing volume
|
||||
type: str
|
||||
source_id:
|
||||
description:
|
||||
- Identifier of existing volume
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- openstack.cloud.openstack
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
volume:
|
||||
description: Cinder's representation of the volume object
|
||||
returned: always
|
||||
type: dict
|
||||
contains:
|
||||
attachments:
|
||||
description: Instance attachment information. For a amanaged volume, this
|
||||
will always be empty.
|
||||
type: list
|
||||
availability_zone:
|
||||
description: The name of the availability zone.
|
||||
type: str
|
||||
consistency_group_id:
|
||||
description: The UUID of the consistency group.
|
||||
type: str
|
||||
created_at:
|
||||
description: The date and time when the resource was created.
|
||||
type: str
|
||||
description:
|
||||
description: The volume description.
|
||||
type: str
|
||||
extended_replication_status:
|
||||
description: Extended replication status on this volume.
|
||||
type: str
|
||||
group_id:
|
||||
description: The ID of the group.
|
||||
type: str
|
||||
host:
|
||||
description: The volume's current back-end.
|
||||
type: str
|
||||
id:
|
||||
description: The UUID of the volume.
|
||||
type: str
|
||||
image_id:
|
||||
description: Image on which the volume was based
|
||||
type: str
|
||||
is_bootable:
|
||||
description: Enables or disables the bootable attribute. You can boot an
|
||||
instance from a bootable volume.
|
||||
type: str
|
||||
is_encrypted:
|
||||
description: If true, this volume is encrypted.
|
||||
type: bool
|
||||
is_multiattach:
|
||||
description: Whether this volume can be attached to more than one
|
||||
server.
|
||||
type: bool
|
||||
metadata:
|
||||
description: A metadata object. Contains one or more metadata key and
|
||||
value pairs that are associated with the volume.
|
||||
type: dict
|
||||
migration_id:
|
||||
description: The volume ID that this volume name on the backend is
|
||||
based on.
|
||||
type: str
|
||||
migration_status:
|
||||
description: The status of this volume migration (None means that a
|
||||
migration is not currently in progress).
|
||||
type: str
|
||||
name:
|
||||
description: The volume name.
|
||||
type: str
|
||||
project_id:
|
||||
description: The project ID which the volume belongs to.
|
||||
type: str
|
||||
replication_driver_data:
|
||||
description: Data set by the replication driver
|
||||
type: str
|
||||
replication_status:
|
||||
description: The volume replication status.
|
||||
type: str
|
||||
scheduler_hints:
|
||||
description: Scheduler hints for the volume
|
||||
type: dict
|
||||
size:
|
||||
description: The size of the volume, in gibibytes (GiB).
|
||||
type: int
|
||||
snapshot_id:
|
||||
description: To create a volume from an existing snapshot, specify the
|
||||
UUID of the volume snapshot. The volume is created in same
|
||||
availability zone and with same size as the snapshot.
|
||||
type: str
|
||||
source_volume_id:
|
||||
description: The UUID of the source volume. The API creates a new volume
|
||||
with the same size as the source volume unless a larger size
|
||||
is requested.
|
||||
type: str
|
||||
status:
|
||||
description: The volume status.
|
||||
type: str
|
||||
updated_at:
|
||||
description: The date and time when the resource was updated.
|
||||
type: str
|
||||
user_id:
|
||||
description: The UUID of the user.
|
||||
type: str
|
||||
volume_image_metadata:
|
||||
description: List of image metadata entries. Only included for volumes
|
||||
that were created from an image, or from a snapshot of a
|
||||
volume originally created from an image.
|
||||
type: dict
|
||||
volume_type:
|
||||
description: The associated volume type name for the volume.
|
||||
type: str
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Manage volume
|
||||
openstack.cloud.volume_manage:
|
||||
name: newly-managed-vol
|
||||
source_name: manage-me
|
||||
host: host@backend-name#pool
|
||||
|
||||
- name: Unmanage volume
|
||||
openstack.cloud.volume_manage:
|
||||
name: "5c831866-3bb3-4d67-a7d3-1b90880c9d18"
|
||||
state: absent
|
||||
"""
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
|
||||
OpenStackModule,
|
||||
)
|
||||
|
||||
|
||||
class VolumeManageModule(OpenStackModule):
|
||||
|
||||
argument_spec = dict(
|
||||
description=dict(type="str"),
|
||||
metadata=dict(type="dict"),
|
||||
source_name=dict(type="str"),
|
||||
source_id=dict(type="str"),
|
||||
availability_zone=dict(type="str"),
|
||||
host=dict(type="str"),
|
||||
bootable=dict(default="false", type="bool"),
|
||||
volume_type=dict(type="str"),
|
||||
name=dict(required=True, type="str"),
|
||||
state=dict(
|
||||
default="present", choices=["absent", "present"], type="str"
|
||||
),
|
||||
)
|
||||
|
||||
module_kwargs = dict(
|
||||
required_if=[("state", "present", ["host"])],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
def run(self):
|
||||
name = self.params["name"]
|
||||
state = self.params["state"]
|
||||
changed = False
|
||||
|
||||
if state == "present":
|
||||
changed = True
|
||||
if not self.ansible.check_mode:
|
||||
volumes = self._manage_list()
|
||||
manageable = volumes["manageable-volumes"]
|
||||
safe_to_manage = self._is_safe_to_manage(
|
||||
manageable, self.params["source_name"]
|
||||
)
|
||||
if not safe_to_manage:
|
||||
self.exit_json(changed=False)
|
||||
volume = self._manage()
|
||||
if volume:
|
||||
self.exit_json(
|
||||
changed=changed, volume=volume.to_dict(computed=False)
|
||||
)
|
||||
else:
|
||||
self.exit_json(changed=False)
|
||||
else:
|
||||
self.exit_json(changed=changed)
|
||||
|
||||
else:
|
||||
volume = self.conn.block_storage.find_volume(name)
|
||||
if volume:
|
||||
changed = True
|
||||
if not self.ansible.check_mode:
|
||||
self._unmanage()
|
||||
self.exit_json(changed=changed)
|
||||
else:
|
||||
self.exit_json(changed=changed)
|
||||
|
||||
def _is_safe_to_manage(self, manageable_list, target_name):
|
||||
entry = next(
|
||||
(
|
||||
v
|
||||
for v in manageable_list
|
||||
if isinstance(v.get("reference"), dict)
|
||||
and (
|
||||
v["reference"].get("name") == target_name
|
||||
or v["reference"].get("source-name") == target_name
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if entry is None:
|
||||
return False
|
||||
return entry.get("safe_to_manage", False)
|
||||
|
||||
def _manage(self):
|
||||
kwargs = {
|
||||
key: self.params[key]
|
||||
for key in [
|
||||
"description",
|
||||
"bootable",
|
||||
"volume_type",
|
||||
"availability_zone",
|
||||
"host",
|
||||
"metadata",
|
||||
"name",
|
||||
]
|
||||
if self.params.get(key) is not None
|
||||
}
|
||||
kwargs["ref"] = {}
|
||||
if self.params["source_name"]:
|
||||
kwargs["ref"]["source-name"] = self.params["source_name"]
|
||||
if self.params["source_id"]:
|
||||
kwargs["ref"]["source-id"] = self.params["source_id"]
|
||||
|
||||
volume = self.conn.block_storage.manage_volume(**kwargs)
|
||||
|
||||
return volume
|
||||
|
||||
def _manage_list(self):
|
||||
response = self.conn.block_storage.get(
|
||||
"/manageable_volumes?host=" + self.params["host"],
|
||||
microversion="3.8",
|
||||
)
|
||||
response.raise_for_status()
|
||||
manageable_volumes = response.json()
|
||||
return manageable_volumes
|
||||
|
||||
def _unmanage(self):
|
||||
self.conn.block_storage.unmanage_volume(self.params["name"])
|
||||
|
||||
|
||||
def main():
|
||||
module = VolumeManageModule()
|
||||
module()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user