Files
Simon Dodsley 556208fc3c 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
2025-05-31 09:46:52 -04:00

310 lines
8.9 KiB
Python

#!/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()