diff --git a/lib/ansible/module_utils/ovirt.py b/lib/ansible/module_utils/ovirt.py index ef7a45dc76..54678f3ccd 100644 --- a/lib/ansible/module_utils/ovirt.py +++ b/lib/ansible/module_utils/ovirt.py @@ -135,7 +135,7 @@ def create_connection(auth): :return: Python SDK connection """ - connection = sdk.Connection( + return sdk.Connection( url=auth.get('url'), username=auth.get('username'), password=auth.get('password'), @@ -144,15 +144,6 @@ def create_connection(auth): token=auth.get('token', None), kerberos=auth.get('kerberos', None), ) - api_version = LooseVersion(engine_version(connection)) - python_sdk_version = LooseVersion(sdk_version.VERSION) - if python_sdk_version < api_version: - raise Exception( - "Your SDK version is lower than engine version, please use same " - "version of the SDK as engine, or unexpected errors may appear." - ) - - return connection def convert_to_bytes(param): diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_disks.py b/lib/ansible/modules/cloud/ovirt/ovirt_disks.py index ef45c0456f..5c90ead99d 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_disks.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_disks.py @@ -50,7 +50,15 @@ options: - "Should the Virtual Machine disk be present/absent/attached/detached." choices: ['present', 'absent', 'attached', 'detached'] default: 'present' - image_path: + download_image_path: + description: + - "Path on a file system where disk should be downloaded." + - "Note that you must have an valid oVirt engine CA in your system trust store + or you must provide it in C(ca_file) parameter." + - "Note that the disk is not downloaded when the file already exists, + but you can forcibly download the disk when using C(force) I (true)." + version_added: "2.3" + upload_image_path: description: - "Path to disk image, which should be uploaded." - "Note that currently we support only compability version 0.10 of the qcow disk." @@ -159,6 +167,12 @@ EXAMPLES = ''' format: cow image_path: /path/to/mydisk.qcow2 storage_domain: data + +# Download disk to local file system: +# Since Ansible 2.3 +- ovirt_disks: + id: 7de90f31-222c-436c-a1ca-7e655bd5b60c + download_image_path: /home/user/mydisk.qcow2 ''' @@ -207,6 +221,7 @@ from ansible.module_utils.ovirt import ( convert_to_bytes, equal, follow_link, + get_id_by_name, ovirt_full_argument_spec, search_by_name, wait, @@ -225,14 +240,14 @@ def _search_by_lun(disks_service, lun_id): return res[0] if res else None -def upload_disk_image(connection, module): - size = os.path.getsize(module.params['image_path']) +def transfer(connection, module, direction, transfer_func): transfers_service = connection.system_service().image_transfers_service() transfer = transfers_service.add( otypes.ImageTransfer( image=otypes.Image( id=module.params['id'], - ) + ), + direction=direction, ) ) transfer_service = transfers_service.image_transfer_service(transfer.id) @@ -244,11 +259,6 @@ def upload_disk_image(connection, module): time.sleep(module.params['poll_interval']) transfer = transfer_service.get() - # Set needed headers for uploading: - upload_headers = { - 'Authorization': transfer.signed_ticket, - } - proxy_url = urlparse(transfer.proxy_url) context = ssl.create_default_context() auth = module.params['auth'] @@ -264,22 +274,13 @@ def upload_disk_image(connection, module): context=context, ) - with open(module.params['image_path'], "rb") as disk: - chunk_size = 1024 * 1024 * 8 - pos = 0 - while pos < size: - transfer_service.extend() - upload_headers['Content-Range'] = "bytes %d-%d/%d" % (pos, min(pos + chunk_size, size) - 1, size) - proxy_connection.request( - 'PUT', - proxy_url.path, - disk.read(chunk_size), - headers=upload_headers, - ) - r = proxy_connection.getresponse() - if r.status >= 400: - raise Exception("Failed to upload disk image.") - pos += chunk_size + transfer_func( + transfer_service, + proxy_connection, + proxy_url, + transfer.signed_ticket + ) + return True finally: transfer_service.finalize() while transfer.phase in [ @@ -306,14 +307,78 @@ def upload_disk_image(connection, module): timeout=module.params['timeout'], ) - return True + +def download_disk_image(connection, module): + def _transfer(transfer_service, proxy_connection, proxy_url, transfer_ticket): + disks_service = connection.system_service().disks_service() + disk = disks_service.disk_service(module.params['id']).get() + size = disk.provisioned_size + transfer_headers = { + 'Authorization': transfer_ticket, + } + with open(module.params['download_image_path'], "wb") as mydisk: + pos = 0 + MiB_per_request = 8 + chunk_size = 1024 * 1024 * MiB_per_request + while pos < size: + transfer_service.extend() + transfer_headers['Range'] = 'bytes=%d-%d' % (pos, min(size, pos + chunk_size) - 1) + proxy_connection.request( + 'GET', + proxy_url.path, + headers=transfer_headers, + ) + r = proxy_connection.getresponse() + if r.status >= 300: + raise Exception("Error: %s" % r.read()) + + mydisk.write(r.read()) + pos += chunk_size + return transfer( + connection, + module, + otypes.ImageTransferDirection.DOWNLOAD, + transfer_func=_transfer, + ) + + +def upload_disk_image(connection, module): + def _transfer(transfer_service, proxy_connection, proxy_url, transfer_ticket): + path = module.params['upload_image_path'] + transfer_headers = { + 'Authorization': transfer_ticket, + } + with open(path, "rb") as disk: + pos = 0 + MiB_per_request = 8 + size = os.path.getsize(path) + chunk_size = 1024 * 1024 * MiB_per_request + while pos < size: + transfer_service.extend() + transfer_headers['Content-Range'] = "bytes %d-%d/%d" % (pos, min(pos + chunk_size, size) - 1, size) + proxy_connection.request( + 'PUT', + proxy_url.path, + disk.read(chunk_size), + headers=transfer_headers, + ) + r = proxy_connection.getresponse() + if r.status >= 400: + raise Exception("Failed to upload disk image.") + pos += chunk_size + return transfer( + connection, + module, + otypes.ImageTransferDirection.UPLOAD, + transfer_func=_transfer, + ) class DisksModule(BaseModule): def build_entity(self): logical_unit = self._module.params.get('logical_unit') - return otypes.Disk( + disk = otypes.Disk( id=self._module.params.get('id'), name=self._module.params.get('name'), description=self._module.params.get('description'), @@ -346,6 +411,12 @@ class DisksModule(BaseModule): ], ) if logical_unit else None, ) + if hasattr(disk, 'initial_size'): + disk.initial_size = convert_to_bytes( + self._module.params.get('size') + ) + + return disk def update_storage_domains(self, disk_id): changed = False @@ -359,14 +430,14 @@ class DisksModule(BaseModule): # Initiate move: if self._module.params['storage_domain']: - new_disk_storage = search_by_name(sds_service, self._module.params['storage_domain']) + new_disk_storage_id = get_id_by_name(sds_service, self._module.params['storage_domain']) changed = self.action( action='move', entity=disk, - action_condition=lambda d: new_disk_storage.id != d.storage_domains[0].id, + action_condition=lambda d: new_disk_storage_id != d.storage_domains[0].id, wait_condition=lambda d: d.status == otypes.DiskStatus.OK, storage_domain=otypes.StorageDomain( - id=new_disk_storage.id, + id=new_disk_storage_id, ), post_action=lambda _: time.sleep(self._module.params['poll_interval']), )['changed'] @@ -435,7 +506,8 @@ def main(): bootable=dict(default=None, type='bool'), shareable=dict(default=None, type='bool'), logical_unit=dict(default=None, type='dict'), - image_path=dict(default=None), + download_image_path=dict(default=None), + upload_image_path=dict(default=None, aliases=['image_path']), force=dict(default=False, type='bool'), ) module = AnsibleModule( @@ -475,9 +547,16 @@ def main(): module.params['id'] = ret['id'] if disk is None else disk.id # Upload disk image in case it's new disk or force parameter is passed: - if module.params['image_path'] and (is_new_disk or module.params['force']): + if module.params['upload_image_path'] and (is_new_disk or module.params['force']): uploaded = upload_disk_image(connection, module) ret['changed'] = ret['changed'] or uploaded + # Download disk image in case it's file don't exist or force parameter is passed: + if ( + module.params['download_image_path'] + and (not os.path.isfile(module.params['download_image_path']) or module.params['force']) + ): + downloaded = download_disk_image(connection, module) + ret['changed'] = ret['changed'] or downloaded elif state == 'absent': ret = disks_module.remove()