diff --git a/.zuul.yaml b/.zuul.yaml index 03596cc5..1479e9ba 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -103,6 +103,7 @@ security_group security_group_rule server + server_volume stack subnet subnet_pool diff --git a/ci/roles/server_volume/defaults/main.yml b/ci/roles/server_volume/defaults/main.yml new file mode 100644 index 00000000..819b6274 --- /dev/null +++ b/ci/roles/server_volume/defaults/main.yml @@ -0,0 +1,32 @@ +expected_fields: + - attachments + - availability_zone + - consistency_group_id + - created_at + - description + - extended_replication_status + - group_id + - host + - id + - image_id + - is_bootable + - is_encrypted + - metadata + - migration_id + - migration_status + - name + - project_id + - replication_driver_data + - replication_status + - scheduler_hints + - size + - snapshot_id + - source_volume_id + - status + - updated_at + - user_id + - volume_image_metadata + - volume_type +flavor: m1.tiny +server_name: ansible_server +server_network: private diff --git a/ci/roles/server_volume/tasks/main.yml b/ci/roles/server_volume/tasks/main.yml new file mode 100644 index 00000000..bf165384 --- /dev/null +++ b/ci/roles/server_volume/tasks/main.yml @@ -0,0 +1,90 @@ +- name: Create server + openstack.cloud.server: + cloud: "{{ cloud }}" + state: present + name: "{{ server_name }}" + image: "cirros-0.5.2-x86_64-disk" + flavor: "{{ flavor }}" + network: "{{ server_network }}" + auto_ip: false + wait: true + register: server + +- name: Create volume + openstack.cloud.volume: + cloud: "{{ cloud }}" + state: present + size: 1 + name: ansible_volume + wait: true + register: volume + +- name: Attach volume to server + openstack.cloud.server_volume: + cloud: "{{ cloud }}" + server: "{{ server.server.id }}" + volume: "{{ volume.volume.id }}" + wait: true + register: server_volume + +- name: Assert changed + assert: + that: server_volume is changed + +- name: Assert return values of server_volume module + assert: + that: + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(server_volume.volume.keys())|length == 0 + +- name: Attach volume to server again + openstack.cloud.server_volume: + cloud: "{{ cloud }}" + server: "{{ server.server.id }}" + volume: "{{ volume.volume.id }}" + wait: true + register: server_volume + +- name: Assert not changed + assert: + that: server_volume is not changed + +- name: Detach volume to server + openstack.cloud.server_volume: + cloud: "{{ cloud }}" + state: absent + server: "{{ server.server.id }}" + volume: "{{ volume.volume.id }}" + wait: true + register: server_volume + +- name: Assert changed + assert: + that: server_volume is changed + +- name: Detach volume to server again + openstack.cloud.server_volume: + cloud: "{{ cloud }}" + state: absent + server: "{{ server.server.id }}" + volume: "{{ volume.volume.id }}" + wait: true + register: server_volume + +- name: Assert not changed + assert: + that: server_volume is not changed + +- name: Delete volume + openstack.cloud.volume: + cloud: "{{ cloud }}" + state: absent + name: ansible_volume + wait: true + +- name: Delete server + openstack.cloud.server: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_name }}" + wait: true diff --git a/ci/run-collection.yml b/ci/run-collection.yml index 7f0e35b0..31b8e99a 100644 --- a/ci/run-collection.yml +++ b/ci/run-collection.yml @@ -58,6 +58,7 @@ - { role: security_group, tags: security_group } - { role: security_group_rule, tags: security_group_rule } - { role: server, tags: server } + - { role: server_volume, tags: server_volume } - { role: stack, tags: stack } - { role: subnet, tags: subnet } - { role: subnet_pool, tags: subnet_pool } diff --git a/plugins/modules/server_volume.py b/plugins/modules/server_volume.py index 325fa932..892c2c1b 100644 --- a/plugins/modules/server_volume.py +++ b/plugins/modules/server_volume.py @@ -4,7 +4,7 @@ # Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -DOCUMENTATION = ''' +DOCUMENTATION = r''' --- module: server_volume short_description: Attach/Detach Volumes from OpenStack VM's @@ -12,27 +12,26 @@ author: OpenStack Ansible SIG description: - Attach or Detach volumes from OpenStack VM's options: - state: + device: description: - - Should the resource be present or absent. - choices: [present, absent] - default: present - required: false + - Device you want to attach. Defaults to auto finding a device name. type: str server: description: - Name or ID of server you want to attach a volume to required: true type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str volume: description: - Name or id of volume you want to attach to a server required: true type: str - device: - description: - - Device you want to attach. Defaults to auto finding a device name. - type: str requirements: - "python >= 3.6" - "openstacksdk" @@ -41,18 +40,120 @@ extends_documentation_fragment: - openstack.cloud.openstack ''' -EXAMPLES = ''' -# Attaches a volume to a compute host -- name: attach a volume - hosts: localhost - tasks: - - name: attach volume to host - openstack.cloud.server_volume: - state: present - cloud: mordred - server: Mysql-server - volume: mysql-data - device: /dev/vdb +RETURN = r''' +volume: + type: dict + description: Volume that was just attached + returned: On success when I(state) is present + contains: + attachments: + description: Instance attachment information. If this volume is attached + to a server instance, the attachments list includes the UUID + of the attached server, an attachment UUID, the name of the + attached host, if any, the volume UUID, the device, and the + device UUID. Otherwise, this list is 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 + 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: Attaches a volume to a compute host + openstack.cloud.server_volume: + state: present + cloud: mordred + server: Mysql-server + volume: mysql-data + device: /dev/vdb ''' from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule @@ -60,15 +161,8 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O def _system_state_change(state, device): """Check if system state would change.""" - if state == 'present': - if device: - return False - return True - if state == 'absent': - if device: - return True - return False - return False + return (state == 'present' and not device) \ + or (state == 'absent' and device) class ServerVolumeModule(OpenStackModule): @@ -81,53 +175,40 @@ class ServerVolumeModule(OpenStackModule): ) def run(self): - state = self.params['state'] wait = self.params['wait'] timeout = self.params['timeout'] - server = self.conn.get_server(self.params['server']) - volume = self.conn.get_volume(self.params['volume']) - - if not server: - self.fail(msg='server %s is not found' % self.params['server']) - - if not volume: - self.fail(msg='volume %s is not found' % self.params['volume']) + server = self.conn.compute.find_server(self.params['server'], + ignore_missing=False) + volume = self.conn.block_storage.find_volume(self.params['volume'], + ignore_missing=False) dev = self.conn.get_volume_attach_device(volume, server.id) if self.ansible.check_mode: - self.exit(changed=_system_state_change(state, dev)) + self.exit_json(changed=_system_state_change(state, dev)) if state == 'present': changed = False if not dev: changed = True - self.conn.attach_volume(server, volume, self.params['device'], + self.conn.attach_volume(server, volume, + device=self.params['device'], wait=wait, timeout=timeout) + # refresh volume object + volume = self.conn.block_storage.get_volume(volume.id) - server = self.conn.get_server(self.params['server']) # refresh - volume = self.conn.get_volume(self.params['volume']) # refresh - hostvars = self.conn.get_openstack_vars(server) - - self.exit( - changed=changed, - id=volume['id'], - attachments=volume['attachments'], - openstack=hostvars - ) + self.exit_json(changed=changed, + volume=volume.to_dict(computed=False)) elif state == 'absent': if not dev: # Volume is not attached to this server - self.exit(changed=False) + self.exit_json(changed=False) self.conn.detach_volume(server, volume, wait=wait, timeout=timeout) - self.exit( - changed=True, - result='Detached volume from server' - ) + self.exit_json(changed=True) def main():