cloud: ovirt: Add download image support to disks module (#22101)
This PR fixes: https://github.com/ansible/ansible/issues/22088
This commit is contained in:
parent
ef79932d16
commit
7d397e7d3e
2 changed files with 112 additions and 42 deletions
|
@ -135,7 +135,7 @@ def create_connection(auth):
|
||||||
:return: Python SDK connection
|
:return: Python SDK connection
|
||||||
"""
|
"""
|
||||||
|
|
||||||
connection = sdk.Connection(
|
return sdk.Connection(
|
||||||
url=auth.get('url'),
|
url=auth.get('url'),
|
||||||
username=auth.get('username'),
|
username=auth.get('username'),
|
||||||
password=auth.get('password'),
|
password=auth.get('password'),
|
||||||
|
@ -144,15 +144,6 @@ def create_connection(auth):
|
||||||
token=auth.get('token', None),
|
token=auth.get('token', None),
|
||||||
kerberos=auth.get('kerberos', 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):
|
def convert_to_bytes(param):
|
||||||
|
|
|
@ -50,7 +50,15 @@ options:
|
||||||
- "Should the Virtual Machine disk be present/absent/attached/detached."
|
- "Should the Virtual Machine disk be present/absent/attached/detached."
|
||||||
choices: ['present', 'absent', 'attached', 'detached']
|
choices: ['present', 'absent', 'attached', 'detached']
|
||||||
default: 'present'
|
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:
|
description:
|
||||||
- "Path to disk image, which should be uploaded."
|
- "Path to disk image, which should be uploaded."
|
||||||
- "Note that currently we support only compability version 0.10 of the qcow disk."
|
- "Note that currently we support only compability version 0.10 of the qcow disk."
|
||||||
|
@ -159,6 +167,12 @@ EXAMPLES = '''
|
||||||
format: cow
|
format: cow
|
||||||
image_path: /path/to/mydisk.qcow2
|
image_path: /path/to/mydisk.qcow2
|
||||||
storage_domain: data
|
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,
|
convert_to_bytes,
|
||||||
equal,
|
equal,
|
||||||
follow_link,
|
follow_link,
|
||||||
|
get_id_by_name,
|
||||||
ovirt_full_argument_spec,
|
ovirt_full_argument_spec,
|
||||||
search_by_name,
|
search_by_name,
|
||||||
wait,
|
wait,
|
||||||
|
@ -225,14 +240,14 @@ def _search_by_lun(disks_service, lun_id):
|
||||||
return res[0] if res else None
|
return res[0] if res else None
|
||||||
|
|
||||||
|
|
||||||
def upload_disk_image(connection, module):
|
def transfer(connection, module, direction, transfer_func):
|
||||||
size = os.path.getsize(module.params['image_path'])
|
|
||||||
transfers_service = connection.system_service().image_transfers_service()
|
transfers_service = connection.system_service().image_transfers_service()
|
||||||
transfer = transfers_service.add(
|
transfer = transfers_service.add(
|
||||||
otypes.ImageTransfer(
|
otypes.ImageTransfer(
|
||||||
image=otypes.Image(
|
image=otypes.Image(
|
||||||
id=module.params['id'],
|
id=module.params['id'],
|
||||||
)
|
),
|
||||||
|
direction=direction,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
transfer_service = transfers_service.image_transfer_service(transfer.id)
|
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'])
|
time.sleep(module.params['poll_interval'])
|
||||||
transfer = transfer_service.get()
|
transfer = transfer_service.get()
|
||||||
|
|
||||||
# Set needed headers for uploading:
|
|
||||||
upload_headers = {
|
|
||||||
'Authorization': transfer.signed_ticket,
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_url = urlparse(transfer.proxy_url)
|
proxy_url = urlparse(transfer.proxy_url)
|
||||||
context = ssl.create_default_context()
|
context = ssl.create_default_context()
|
||||||
auth = module.params['auth']
|
auth = module.params['auth']
|
||||||
|
@ -264,22 +274,13 @@ def upload_disk_image(connection, module):
|
||||||
context=context,
|
context=context,
|
||||||
)
|
)
|
||||||
|
|
||||||
with open(module.params['image_path'], "rb") as disk:
|
transfer_func(
|
||||||
chunk_size = 1024 * 1024 * 8
|
transfer_service,
|
||||||
pos = 0
|
proxy_connection,
|
||||||
while pos < size:
|
proxy_url,
|
||||||
transfer_service.extend()
|
transfer.signed_ticket
|
||||||
upload_headers['Content-Range'] = "bytes %d-%d/%d" % (pos, min(pos + chunk_size, size) - 1, size)
|
)
|
||||||
proxy_connection.request(
|
return True
|
||||||
'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
|
|
||||||
finally:
|
finally:
|
||||||
transfer_service.finalize()
|
transfer_service.finalize()
|
||||||
while transfer.phase in [
|
while transfer.phase in [
|
||||||
|
@ -306,14 +307,78 @@ def upload_disk_image(connection, module):
|
||||||
timeout=module.params['timeout'],
|
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):
|
class DisksModule(BaseModule):
|
||||||
|
|
||||||
def build_entity(self):
|
def build_entity(self):
|
||||||
logical_unit = self._module.params.get('logical_unit')
|
logical_unit = self._module.params.get('logical_unit')
|
||||||
return otypes.Disk(
|
disk = otypes.Disk(
|
||||||
id=self._module.params.get('id'),
|
id=self._module.params.get('id'),
|
||||||
name=self._module.params.get('name'),
|
name=self._module.params.get('name'),
|
||||||
description=self._module.params.get('description'),
|
description=self._module.params.get('description'),
|
||||||
|
@ -346,6 +411,12 @@ class DisksModule(BaseModule):
|
||||||
],
|
],
|
||||||
) if logical_unit else None,
|
) 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):
|
def update_storage_domains(self, disk_id):
|
||||||
changed = False
|
changed = False
|
||||||
|
@ -359,14 +430,14 @@ class DisksModule(BaseModule):
|
||||||
|
|
||||||
# Initiate move:
|
# Initiate move:
|
||||||
if self._module.params['storage_domain']:
|
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(
|
changed = self.action(
|
||||||
action='move',
|
action='move',
|
||||||
entity=disk,
|
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,
|
wait_condition=lambda d: d.status == otypes.DiskStatus.OK,
|
||||||
storage_domain=otypes.StorageDomain(
|
storage_domain=otypes.StorageDomain(
|
||||||
id=new_disk_storage.id,
|
id=new_disk_storage_id,
|
||||||
),
|
),
|
||||||
post_action=lambda _: time.sleep(self._module.params['poll_interval']),
|
post_action=lambda _: time.sleep(self._module.params['poll_interval']),
|
||||||
)['changed']
|
)['changed']
|
||||||
|
@ -435,7 +506,8 @@ def main():
|
||||||
bootable=dict(default=None, type='bool'),
|
bootable=dict(default=None, type='bool'),
|
||||||
shareable=dict(default=None, type='bool'),
|
shareable=dict(default=None, type='bool'),
|
||||||
logical_unit=dict(default=None, type='dict'),
|
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'),
|
force=dict(default=False, type='bool'),
|
||||||
)
|
)
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
|
@ -475,9 +547,16 @@ def main():
|
||||||
module.params['id'] = ret['id'] if disk is None else disk.id
|
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:
|
# 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)
|
uploaded = upload_disk_image(connection, module)
|
||||||
ret['changed'] = ret['changed'] or uploaded
|
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':
|
elif state == 'absent':
|
||||||
ret = disks_module.remove()
|
ret = disks_module.remove()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue