-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tests: basic unit testing on incus_instance (#17)
- includes crude mocking of incuscli
- Loading branch information
Showing
12 changed files
with
716 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
|
||
.PHONY: clean | ||
clean: | ||
@echo "Cleaning up..." | ||
@echo "Remove tests/output directories" | ||
rm -rf tests/output | ||
|
||
@echo "Remove all python related directories" | ||
find . -type d -name __pycache__ -exec rm -rf {} \; | ||
rm -rf .mypy_cache | ||
|
||
|
||
.PHONY: test | ||
test: test/sanity test/units | ||
|
||
|
||
.PHONY: test/sanity | ||
test/sanity: | ||
ansible-test sanity --python 3.11 -v | ||
|
||
|
||
.PHONY: test/units | ||
test/units: | ||
ansible-test units --venv --python 3.11 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
# Sample responses | ||
|
||
This document lists sample responses using `incus query` for different tasks and | ||
is intended to help out during development. | ||
|
||
## POST instance response | ||
|
||
Creating a container instance called `testinstance`. | ||
|
||
```json | ||
{ | ||
"type": "sync", | ||
"status": "Success", | ||
"status_code": 200, | ||
"operation": "", | ||
"error_code": 0, | ||
"error": "", | ||
"metadata": { | ||
"id": "a252b7bd-7178-42ca-8d94-1680254eec7b", | ||
"class": "task", | ||
"description": "Creating instance", | ||
"created_at": "2024-11-28T23:11:49.303545747+01:00", | ||
"updated_at": "2024-11-28T23:11:50.724932671+01:00", | ||
"status": "Success", | ||
"status_code": 200, | ||
"resources": { | ||
"instances": [ | ||
"/1.0/instances/testinstance" | ||
] | ||
}, | ||
"metadata": { | ||
"create_instance_from_image_unpack_progress": "Unpacking image: 100% (3.97GB/s)", | ||
"progress": { | ||
"percent": "100", | ||
"speed": "3972972972", | ||
"stage": "create_instance_from_image_unpack" | ||
} | ||
}, | ||
"may_cancel": false, | ||
"err": "", | ||
"location": "none" | ||
} | ||
} | ||
``` | ||
|
||
## Get absent instance | ||
|
||
```json | ||
{ | ||
"type": "error", | ||
"status":"", | ||
"status_code": 0, | ||
"operation":"", | ||
"error_code":404, | ||
"error":"Not Found", | ||
"metadata":null | ||
} | ||
``` | ||
|
||
## GET state | ||
|
||
Using the path `/1.0/instances/testinstance/state?project=default` | ||
|
||
```json | ||
{ | ||
"type": "sync", | ||
"status": "Success", | ||
"status_code": 200, | ||
"operation": "", | ||
"error_code": 0, | ||
"error": "", | ||
"metadata": { | ||
"status": "Stopped", | ||
"status_code": 102, | ||
"disk": {}, | ||
"memory": { | ||
"usage": 0, | ||
"usage_peak": 0, | ||
"total": 0, | ||
"swap_usage": 0, | ||
"swap_usage_peak": 0 | ||
}, | ||
"network": null, | ||
"pid": 0, | ||
"processes": 0, | ||
"cpu": { | ||
"usage": 0 | ||
}, | ||
"started_at": "0001-01-01T00:00:00Z", | ||
"os_info": null | ||
} | ||
} | ||
``` | ||
|
||
## Get existing container | ||
|
||
```json | ||
{ | ||
"type": "sync", | ||
"status": "Success", | ||
"status_code": 200, | ||
"operation": "", | ||
"error_code": 0, | ||
"error": "", | ||
"metadata": { | ||
"architecture": "aarch64", | ||
"config": { | ||
"image.architecture": "arm64", | ||
"image.description": "Debian bookworm arm64 (20241128_05:24)", | ||
"image.os": "Debian", | ||
"image.release": "bookworm", | ||
"image.serial": "20241128_05:24", | ||
"image.type": "squashfs", | ||
"image.variant": "cloud", | ||
"volatile.apply_template": "create", | ||
"volatile.base_image": "5bfec5b4d6362bbf8755f637119ac3de7c15ca267573e47c9873388a5655e196", | ||
"volatile.cloud-init.instance-id": "4f144556-ea4c-4154-8a57-91ff4346e5e1", | ||
"volatile.eth0.hwaddr": "00:16:3e:9a:00:37", | ||
"volatile.idmap.base": "0", | ||
"volatile.idmap.next": "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000}]", | ||
"volatile.last_state.idmap": "[]", | ||
"volatile.uuid": "321b7285-2c66-4b62-924b-0c47d61ea7a0", | ||
"volatile.uuid.generation": "321b7285-2c66-4b62-924b-0c47d61ea7a0" | ||
}, | ||
"devices": {}, | ||
"ephemeral": false, | ||
"profiles": [ | ||
"default" | ||
], | ||
"stateful": false, | ||
"description": "", | ||
"created_at": "2024-11-28T22:11:50.701255415Z", | ||
"expanded_config": { | ||
"image.architecture": "arm64", | ||
"image.description": "Debian bookworm arm64 (20241128_05:24)", | ||
"image.os": "Debian", | ||
"image.release": "bookworm", | ||
"image.serial": "20241128_05:24", | ||
"image.type": "squashfs", | ||
"image.variant": "cloud", | ||
"volatile.apply_template": "create", | ||
"volatile.base_image": "5bfec5b4d6362bbf8755f637119ac3de7c15ca267573e47c9873388a5655e196", | ||
"volatile.cloud-init.instance-id": "4f144556-ea4c-4154-8a57-91ff4346e5e1", | ||
"volatile.eth0.hwaddr": "00:16:3e:9a:00:37", | ||
"volatile.idmap.base": "0", | ||
"volatile.idmap.next": "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":1000000,\"Nsid\":0,\"Maprange\":1000000000}]", | ||
"volatile.last_state.idmap": "[]", | ||
"volatile.uuid": "321b7285-2c66-4b62-924b-0c47d61ea7a0", | ||
"volatile.uuid.generation": "321b7285-2c66-4b62-924b-0c47d61ea7a0" | ||
}, | ||
"expanded_devices": { | ||
"eth0": { | ||
"name": "eth0", | ||
"network": "incusbr0", | ||
"type": "nic" | ||
}, | ||
"root": { | ||
"path": "/", | ||
"pool": "default", | ||
"type": "disk" | ||
} | ||
}, | ||
"name": "testinstance", | ||
"status": "Stopped", | ||
"status_code": 102, | ||
"last_used_at": "1970-01-01T00:00:00Z", | ||
"location": "none", | ||
"type": "container", | ||
"project": "default" | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
from __future__ import (absolute_import, division, print_function) | ||
__metaclass__ = type | ||
|
||
import json | ||
import os | ||
from ansible.module_utils.six.moves.urllib.parse import urlparse, parse_qs | ||
|
||
|
||
fixturedir = os.path.join(os.path.dirname(__file__), 'fixtures') | ||
status_messages = { | ||
0: '', | ||
200: 'Success', | ||
} | ||
|
||
error_messages = { | ||
0: '', | ||
404: 'Not Found', | ||
500: 'Internal Server Error', | ||
501: 'Not Implemented', | ||
} | ||
|
||
|
||
def load_fixture(name): | ||
with open(os.path.join(fixturedir, name)) as f: | ||
return json.load(f) | ||
|
||
|
||
def generate_response(**kwargs): | ||
resp = { | ||
'type': 'sync', | ||
'status': '', | ||
'status_code': 0, | ||
'operation': '', | ||
'error_code': 0, | ||
'error': '', | ||
'metadata': None | ||
} | ||
for k, v in kwargs.items(): | ||
if k in resp: | ||
resp[k] = v | ||
|
||
resp['status'] = status_messages.get(resp['status_code'], '') | ||
if resp['error_code']: | ||
resp['error'] = error_messages.get(resp['error_code'], '') | ||
resp['type'] = 'error' | ||
|
||
return resp | ||
|
||
|
||
class MockClient: | ||
instances = {} | ||
counter = 0 | ||
request = {} | ||
|
||
def _response(self, value): | ||
stringval = json.dumps(value) | ||
print("response", dict(requst=self.request, response=stringval)) | ||
return stringval | ||
|
||
def _instances_api(self, method, segments, queryargs, data): | ||
name = segments[0] if len(segments) > 0 else None | ||
if not name: | ||
if method == 'GET': | ||
return json.dumps([]) | ||
elif method == 'POST': | ||
# create instance | ||
name = data['name'] | ||
metadata = load_fixture('get_instance.json')['metadata'] | ||
metadata['name'] = name | ||
metadata['project'] = queryargs.get('project', ['default'])[0] | ||
self.instances[name] = metadata | ||
resp = load_fixture('post_instance.json') | ||
self.counter += 1 | ||
resp['metadata']['id'] = '1337-42-%d' % self.counter | ||
resp['metadata']['name'] = metadata['name'] | ||
resp['metadata']['project'] = metadata['project'] | ||
# print("creates instance", name, resp['metadata']) | ||
return self._response(resp) | ||
|
||
segments = segments[1:] | ||
if segments and segments[0] == 'state': | ||
segments = segments[1:] | ||
if method == 'GET': | ||
# return named instance state if any | ||
if name in self.instances: | ||
resp = load_fixture('get_instance_state.json') | ||
metadata = self.instances[name] | ||
resp['metadata'] = self.instances[name]['state'] | ||
return self._response(resp) | ||
else: | ||
return json.dumps(generate_response(error_code=404)) | ||
elif method == 'PUT': | ||
# update state | ||
if name in self.instances: | ||
resp = load_fixture('put_instance_state.json') | ||
self.instances[name]['status'] = "Running" | ||
self.instances[name]['status_code'] = "103" | ||
|
||
return self._response(resp) | ||
else: | ||
return self._response(generate_response(error_code=404)) | ||
else: | ||
if method == 'GET': | ||
# return named instance if any | ||
if name in self.instances: | ||
resp = generate_response(status_code=200) | ||
resp['metadata'] = self.instances[name] | ||
return self._response(resp) | ||
else: | ||
return self._response(generate_response(error_code=404)) | ||
|
||
raise Exception('instance action: %s %r' % (method, segments,)) | ||
|
||
def execute(self, client, *args, **kwargs): | ||
# if client.debug: | ||
client.debug = True | ||
client.logs.append(args) | ||
method = args[2] | ||
querypath = args[3] | ||
# extract queryargs from querypath | ||
p = urlparse(querypath) | ||
queryargs = parse_qs(p.query) | ||
data = None | ||
for i, arg in enumerate(args): | ||
if arg == '--data': | ||
data = json.loads(args[i + 1]) | ||
|
||
self.request = dict(path=querypath, data=data) | ||
# extract segments from querypath | ||
segments = p.path.split('/') | ||
# remove empty first segment | ||
segments = segments[1:] | ||
# is the first segment '1.0'? | ||
if segments[0] != '1.0': | ||
raise Exception('Unknown version: %s' % segments) | ||
|
||
# remove version segment | ||
segments = segments[1:] | ||
|
||
if segments[0] == 'instances': | ||
return self._instances_api(method, segments[1:], queryargs, data) | ||
|
||
else: | ||
print("log", client.logs) | ||
raise Exception('Unknown method or querypath: %s %s' % (method, querypath,)) |
Oops, something went wrong.