|
|
|
|
@ -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()
|
|
|
|
|
|
|
|
|
|
|