Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions examples/virtual_disk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
- name: Upload a virtual disk image from http link to HyperCore
hosts: localhost
connection: local
gather_facts: false
vars:
image_url: https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
# image_url: https://github.com/ddemlow/RestAPIExamples/blob/master/ubuntu18_04-cloud-init/ubuntu18cloudimage.qcow2
image_filename: "{{ image_url | split('/') | last }}"
image_remove_old: false
sc_host: https://10.100.20.38 # TODO
sc_username: admin
sc_password: admin

tasks:
# # ------------------------------------------------------
- name: Download Virtual Disk {{ image_filename }} from URL
ansible.builtin.get_url: # TODO: what if file doesn't download completely?
url: "{{ image_url }}"
dest: /tmp/{{ image_filename }}

- name: Get the Virtual Disk size
ansible.builtin.stat:
path: /tmp/{{ image_filename }}
register: disk_file_info

# TODO
# - name: (Optionally) remove existing Virtual Disk {{ image_filename }} from HyperCore
# scale_computing.hypercore.api:
# action: get
# cluster_instance:
# host: "{{ sc_host }}"
# username: "{{ sc_username }}"
# password: "{{ sc_password }}"
# endpoint: "/rest/v1/VirtualDisk"
# register: virtualDiskResult

# ------------------------------------------------------
- name: Upload Virtual Disk {{ image_filename }} to HyperCore
scale_computing.hypercore.api:
action: put
cluster_instance:
host: "{{ sc_host }}"
username: "{{ sc_username }}"
password: "{{ sc_password }}"
endpoint: /rest/v1/VirtualDisk/upload
data:
filename: "{{ image_filename }}"
filesize: "{{ disk_file_info.stat.size }}"
source: /tmp/{{ image_filename }}
register: uploadResult

# ------------------------------------------------------
- name: Get Information About the uploaded Virtual Disk in HyperCore
scale_computing.hypercore.api:
action: get
cluster_instance:
host: "{{ sc_host }}"
username: "{{ sc_username }}"
password: "{{ sc_password }}"
endpoint: /rest/v1/VirtualDisk/{{ uploadResult.record.createdUUID }}
register: result

- name: Show uploaded disk info
debug:
var: result.record[0]
11 changes: 7 additions & 4 deletions plugins/module_utils/rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,27 +84,30 @@ def put_record(
endpoint,
payload,
check_mode,
query=None,
timeout=None,
binary_data=None,
headers=None,
):
# Method put doesn't support check mode # IT ACTUALLY DOES
if check_mode:
return None
# Only /rest/v1/ISO/[uuid}/data is using put, which doesn't return anything.
# self.client.put on this endpoint returns None.
try:
response = self.client.put(
endpoint,
data=payload,
query=_query(),
query=query,
timeout=timeout,
binary_data=binary_data,
headers=headers,
)
except TimeoutError as e:
raise errors.ScaleComputingError(f"Request timed out: {e}")
return response

try:
return response.json
except errors.ScaleComputingError:
return response


class CachedRestClient(RestClient):
Expand Down
45 changes: 43 additions & 2 deletions plugins/modules/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
- Tjaž Eržen (@tjazsch)
short_description: API interaction with Scale Computing HyperCore
description:
- Perform a C(GET), C(POST), C(PATCH) or C(DELETE) request on resource(s) from the given endpoint.
- Perform a C(GET), C(POST), C(PATCH), C(DELETE), or C(PUT) request on resource(s) from the given endpoint.
The api module can be used to perform raw API calls whenever there is no
suitable concrete module or role implementation for a specific task.
version_added: 1.0.0
Expand All @@ -36,6 +36,7 @@
- delete
- get
- post_list
- put
data:
type: dict
description:
Expand All @@ -50,6 +51,11 @@
- The raw endpoint that we want to perform post, patch or delete operation on.
type: str
required: true
source:
description:
- Source of the file to upload.
type: str
version_added: 1.1.0
notes:
- C(check_mode) is not supported.

Expand Down Expand Up @@ -240,6 +246,36 @@ def delete_record(module, rest_client):
return False, dict()


"""
PUT_TIMEOUT_TIME was copied from the iso module for ISO data upload.
Currently, assume we have 4.7 GB ISO and speed 1 MB/s -> 4700 seconds.
Rounded to 3600.

TODO: compute it from expected min upload speed and file size.
Even better, try to detect stalled uploads and terminate if no data was transmitted for more than N seconds.
Yum/dnf complain with error "Operation too slow. Less than 1000 bytes/sec transferred the last 30 seconds"
in such case.
"""
PUT_TIMEOUT_TIME = 3600


def put_record(module, rest_client):
with open(module.params["source"], "rb") as source_file:
result = rest_client.put_record(
endpoint=module.params["endpoint"],
payload=None,
check_mode=module.check_mode,
query=module.params["data"],
timeout=PUT_TIMEOUT_TIME,
binary_data=source_file,
headers={
"Content-Type": "application/octet-stream",
"Accept": "application/json",
},
)
return True, result


def get_records(module, rest_client):
records = rest_client.list_records(
query=module.params["data"],
Expand All @@ -258,6 +294,8 @@ def run(module, rest_client):
return post_list_record(module, rest_client)
elif action == "get": # GET method
return get_records(module, rest_client)
elif action == "put": # PUT method
return put_record(module, rest_client)
return delete_record(module, rest_client) # DELETE methodx


Expand All @@ -271,13 +309,16 @@ def main():
),
action=dict(
type="str",
choices=["post", "patch", "delete", "get", "post_list"],
choices=["post", "patch", "delete", "get", "post_list", "put"],
required=True,
),
endpoint=dict(
type="str",
required=True,
),
source=dict(
type="str",
),
),
)

Expand Down
2 changes: 1 addition & 1 deletion tests/unit/plugins/module_utils/test_rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def test_normal_mode(self, client):
client.put.assert_called_with(
"my_table/id",
data=None,
query=dict(),
query=None,
timeout=None,
binary_data=None,
headers=None,
Expand Down
25 changes: 25 additions & 0 deletions tests/unit/plugins/modules/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,31 @@ def test_get_method_record_absent(self, create_module, rest_client):
assert result == (False, [])


class TestPutMethod:
def test_put_method(self, create_module, rest_client, mocker):
# TODO: Put method hasn't been implemented yet, so tests still have to be written.
# Harcoding value for now.
module = create_module(
params=dict(
cluster_instance=dict(
host="https://0.0.0.0",
username="admin",
password="admin",
),
action="put",
endpoint="/rest/v1/VirDomain",
unique_id="id",
source="this-source",
data=dict(),
)
)
mocker.patch("builtins.open", mocker.mock_open(read_data="this-data"))
rest_client.put_record.return_value = "this-value"
result = api.put_record(module, rest_client)
print(result)
assert result == (True, "this-value")


class TestDeleteRecord:
def test_delete_method_record_present(self, create_module, rest_client, task_wait):
module = create_module(
Expand Down