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, tags: volume }
|
||||||
- { role: volume_type, tags: volume_type }
|
- { role: volume_type, tags: volume_type }
|
||||||
- { role: volume_backup, tags: volume_backup }
|
- { role: volume_backup, tags: volume_backup }
|
||||||
|
- { role: volume_manage, tags: volume_manage }
|
||||||
- { role: volume_service, tags: volume_service }
|
- { role: volume_service, tags: volume_service }
|
||||||
- { role: volume_snapshot, tags: volume_snapshot }
|
- { role: volume_snapshot, tags: volume_snapshot }
|
||||||
- { role: volume_type_access, tags: volume_type_access }
|
- { role: volume_type_access, tags: volume_type_access }
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ action_groups:
|
|||||||
- subnets_info
|
- subnets_info
|
||||||
- trunk
|
- trunk
|
||||||
- volume
|
- volume
|
||||||
|
- volume_manage
|
||||||
- volume_backup
|
- volume_backup
|
||||||
- volume_backup_info
|
- volume_backup_info
|
||||||
- volume_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