#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright (c) 2016 Red Hat, Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # try: import ovirtsdk4 as sdk import ovirtsdk4.types as otypes except ImportError: pass from ansible.module_utils.ovirt import * ANSIBLE_METADATA = {'status': ['preview'], 'supported_by': 'community', 'version': '1.0'} DOCUMENTATION = ''' --- module: ovirt_disks short_description: "Module to manage Virtual Machine and floating disks in oVirt." version_added: "2.2" author: "Ondra Machacek (@machacekondra)" description: - "Module to manage Virtual Machine and floating disks in oVirt." options: id: description: - "ID of the disk to manage. Either C(id) or C(name) is required." name: description: - "Name of the disk to manage. Either C(id) or C(name)/C(alias) is required." aliases: ['alias'] vm_name: description: - "Name of the Virtual Machine to manage. Either C(vm_id) or C(vm_name) is required if C(state) is I(attached) or I(detached)." vm_id: description: - "ID of the Virtual Machine to manage. Either C(vm_id) or C(vm_name) is required if C(state) is I(attached) or I(detached)." state: description: - "Should the Virtual Machine disk be present/absent/attached/detached." choices: ['present', 'absent', 'attached', 'detached'] default: 'present' size: description: - "Size of the disk. Size should be specified using IEC standard units. For example 10GiB, 1024MiB, etc." interface: description: - "Driver of the storage interface." choices: ['virtio', 'ide', 'virtio_scsi'] default: 'virtio' format: description: - Specify format of the disk. - If (cow) format is used, disk will by created as sparse, so space will be allocated for the volume as needed, also known as I(thin provision). - If (raw) format is used, disk storage will be allocated right away, also known as I(preallocated). - Note that this option isn't idempotent as it's not currently possible to change format of the disk via API. choices: ['raw', 'cow'] storage_domain: description: - "Storage domain name where disk should be created. By default storage is chosen by oVirt engine." profile: description: - "Disk profile name to be attached to disk. By default profile is chosen by oVirt engine." bootable: description: - "I(True) if the disk should be bootable. By default when disk is created it isn't bootable." shareable: description: - "I(True) if the disk should be shareable. By default when disk is created it isn't shareable." logical_unit: description: - "Dictionary which describes LUN to be directly attached to VM:" - "C(address) - Address of the storage server. Used by iSCSI." - "C(port) - Port of the storage server. Used by iSCSI." - "C(target) - iSCSI target." - "C(lun_id) - LUN id." - "C(username) - CHAP Username to be used to access storage server. Used by iSCSI." - "C(password) - CHAP Password of the user to be used to access storage server. Used by iSCSI." - "C(storage_type) - Storage type either I(fcp) or I(iscsi)." extends_documentation_fragment: ovirt ''' EXAMPLES = ''' # Examples don't contain auth parameter for simplicity, # look at ovirt_auth module to see how to reuse authentication: # Create and attach new disk to VM - ovirt_disks: name: myvm_disk vm_name: rhel7 size: 10GiB format: cow interface: virtio # Attach logical unit to VM rhel7 - ovirt_disks: vm_name: rhel7 logical_unit: target: iqn.2016-08-09.brq.str-01:omachace id: 1IET_000d0001 address: 10.34.63.204 interface: virtio # Detach disk from VM - ovirt_disks: state: detached name: myvm_disk vm_name: rhel7 size: 10GiB format: cow interface: virtio ''' RETURN = ''' id: description: "ID of the managed disk" returned: "On success if disk is found." type: str sample: 7de90f31-222c-436c-a1ca-7e655bd5b60c disk: description: "Dictionary of all the disk attributes. Disk attributes can be found on your oVirt instance at following url: https://ovirt.example.com/ovirt-engine/api/model#types/disk." returned: "On success if disk is found and C(vm_id) or C(vm_name) wasn't passed." disk_attachment: description: "Dictionary of all the disk attachment attributes. Disk attachment attributes can be found on your oVirt instance at following url: https://ovirt.example.com/ovirt-engine/api/model#types/disk_attachment." returned: "On success if disk is found and C(vm_id) or C(vm_name) was passed and VM was found." ''' def _search_by_lun(disks_service, lun_id): """ Find disk by LUN ID. """ res = [ disk for disk in disks_service.list(search='disk_type=lun') if ( disk.lun_storage.id == lun_id ) ] return res[0] if res else None class DisksModule(BaseModule): def build_entity(self): logical_unit = self._module.params.get('logical_unit') return otypes.Disk( id=self._module.params.get('id'), name=self._module.params.get('name'), description=self._module.params.get('description'), format=otypes.DiskFormat( self._module.params.get('format') ) if self._module.params.get('format') else None, sparse=False if self._module.params.get('format') == 'raw' else True, provisioned_size=convert_to_bytes( self._module.params.get('size') ), storage_domains=[ otypes.StorageDomain( name=self._module.params.get('storage_domain'), ), ], shareable=self._module.params.get('shareable'), lun_storage=otypes.HostStorage( type=otypes.StorageType( logical_unit.get('storage_type', 'iscsi') ), logical_units=[ otypes.LogicalUnit( address=logical_unit.get('address'), port=logical_unit.get('port', 3260), target=logical_unit.get('target'), id=logical_unit.get('id'), username=logical_unit.get('username'), password=logical_unit.get('password'), ) ], ) if logical_unit else None, ) def update_check(self, entity): return ( equal(self._module.params.get('description'), entity.description) and equal(convert_to_bytes(self._module.params.get('size')), entity.provisioned_size) and equal(self._module.params.get('shareable'), entity.shareable) ) class DiskAttachmentsModule(DisksModule): def build_entity(self): return otypes.DiskAttachment( disk=super(DiskAttachmentsModule, self).build_entity(), interface=otypes.DiskInterface( self._module.params.get('interface') ) if self._module.params.get('interface') else None, bootable=self._module.params.get('bootable'), active=True, ) def update_check(self, entity): return ( equal(self._module.params.get('interface'), str(entity.interface)) and equal(self._module.params.get('bootable'), entity.bootable) ) def main(): argument_spec = ovirt_full_argument_spec( state=dict( choices=['present', 'absent', 'attached', 'detached'], default='present' ), id=dict(default=None), name=dict(default=None, aliases=['alias']), vm_name=dict(default=None), vm_id=dict(default=None), size=dict(default=None), interface=dict(default=None,), storage_domain=dict(default=None), profile=dict(default=None), format=dict(default=None, choices=['raw', 'cow']), bootable=dict(default=None, type='bool'), shareable=dict(default=None, type='bool'), logical_unit=dict(default=None, type='dict'), ) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, ) check_sdk(module) check_params(module) try: disk = None state = module.params['state'] connection = create_connection(module.params.pop('auth')) disks_service = connection.system_service().disks_service() disks_module = DisksModule( connection=connection, module=module, service=disks_service, ) lun = module.params.get('logical_unit') if lun: disk = _search_by_lun(disks_service, lun.get('id')) ret = None # First take care of creating the VM, if needed: if state == 'present' or state == 'detached' or state == 'attached': ret = disks_module.create( entity=disk, result_state=otypes.DiskStatus.OK if lun is None else None, ) # We need to pass ID to the module, so in case we want detach/attach disk # we have this ID specified to attach/detach method: module.params['id'] = ret['id'] if disk is None else disk.id elif state == 'absent': ret = disks_module.remove() # If VM was passed attach/detach disks to/from the VM: if module.params.get('vm_id') is not None or module.params.get('vm_name') is not None and state != 'absent': vms_service = connection.system_service().vms_service() # If `vm_id` isn't specified, find VM by name: vm_id = module.params['vm_id'] if vm_id is None: vm_id = getattr(search_by_name(vms_service, module.params['vm_name']), 'id', None) if vm_id is None: module.fail_json( msg="VM don't exists, please create it first." ) disk_attachments_service = vms_service.vm_service(vm_id).disk_attachments_service() disk_attachments_module = DiskAttachmentsModule( connection=connection, module=module, service=disk_attachments_service, changed=ret['changed'] if ret else False, ) if state == 'present' or state == 'attached': ret = disk_attachments_module.create() elif state == 'detached': ret = disk_attachments_module.remove() module.exit_json(**ret) except Exception as e: module.fail_json(msg=str(e)) finally: connection.close(logout=False) from ansible.module_utils.basic import * if __name__ == "__main__": main()