From: Stavros Sachtouris Date: Fri, 26 Jul 2013 14:30:49 +0000 (+0300) Subject: Merge branch 'feature-compute-v2' into develop X-Git-Tag: debian/0.10-1wheezy~28 X-Git-Url: https://code.grnet.gr/git/kamaki/commitdiff_plain/ff79a4235a6abf89f188dd1e3db4b6a5decafe9c?hp=703d7a77d790be1a7e9cde0ed726d8fe8240070e Merge branch 'feature-compute-v2' into develop --- diff --git a/Changelog b/Changelog index 7abbc6e..71f62a2 100644 --- a/Changelog +++ b/Changelog @@ -37,4 +37,5 @@ Features: commission_pending/info/action/accept/reject/resolve/issuejson/issue - Implement resize_server and expose it as server resize [#4153] - Expose the project API of astakosclient to kamaki CLI [#4155] +- Full support for compute v2 basic API [#4139] diff --git a/kamaki/clients/compute/__init__.py b/kamaki/clients/compute/__init__.py index b35f646..3cc7a86 100644 --- a/kamaki/clients/compute/__init__.py +++ b/kamaki/clients/compute/__init__.py @@ -39,27 +39,82 @@ from kamaki.clients.utils import path4url class ComputeClient(ComputeRestClient): """OpenStack Compute API 1.1 client""" - def list_servers(self, detail=False): + def list_servers( + self, + detail=False, + changes_since=None, + image=None, + flavor=None, + name=None, + marker=None, + limit=None, + status=None, + host=None, + response_headers=dict(previous=None, next=None)): """ :param detail: if true, append full server details to each item + :param response_headers: (dict) use it to get previous/next responses + Keep the existing dict format to actually get the server responses + Use it with very long lists or with marker + :returns: list of server ids and names """ - detail = 'detail' if detail else '' - r = self.servers_get(command=detail) + r = self.servers_get( + detail=bool(detail), + changes_since=changes_since, + image=image, + flavor=flavor, + name=name, + marker=marker, + limit=limit, + status=status, + host=host) + for k, v in response_headers.items(): + response_headers[k] = r.headers.get(k, v) return r.json['servers'] - def get_server_details(self, server_id, **kwargs): + def get_server_details( + self, server_id, + changes_since=None, + image=None, + flavor=None, + name=None, + marker=None, + limit=None, + status=None, + host=None, + response_headers=dict(previous=None, next=None), + **kwargs): """Return detailed info for a server :param server_id: integer (int or str) :returns: dict with server details """ - r = self.servers_get(server_id, **kwargs) + r = self.servers_get( + server_id, + changes_since=changes_since, + image=image, + flavor=flavor, + name=name, + marker=marker, + limit=limit, + status=status, + host=host, + **kwargs) + for k, v in response_headers.items(): + response_headers[k] = r.headers.get(k, v) return r.json['server'] - def create_server(self, name, flavor_id, image_id, personality=None): + def create_server( + self, name, flavor_id, image_id, + security_group=None, + user_data=None, + availability_zone=None, + metadata=None, + personality=None, + response_headers=dict(location=None)): """Submit request to create a new server :param name: (str) @@ -68,6 +123,8 @@ class ComputeClient(ComputeRestClient): :param image_id: (str) id denoting the OS image to run on the VM + :param metadata: (dict) vm metadata + :param personality: a list of (file path, file contents) tuples, describing files to be injected into VM upon creation. @@ -75,37 +132,22 @@ class ComputeClient(ComputeRestClient): :raises ClientError: wraps request errors """ - req = {'server': {'name': name, - 'flavorRef': flavor_id, - 'imageRef': image_id}} - - image = self.get_image_details(image_id) - metadata = {} - for key in ('os', 'users'): - try: - metadata[key] = image['metadata'][key] - except KeyError: - pass + req = {'server': { + 'name': name, 'flavorRef': flavor_id, 'imageRef': image_id}} + if metadata: req['server']['metadata'] = metadata if personality: req['server']['personality'] = personality - try: - r = self.servers_post(json_data=req) - except ClientError as err: - try: - if isinstance(err.details, list): - tmp_err = err.details - else: - errd = '%s' % err.details - tmp_err = errd.split(',') - tmp_err = tmp_err[0].split(':') - tmp_err = tmp_err[2].split('"') - err.message = tmp_err[1] - finally: - raise err + r = self.servers_post( + json_data=req, + security_group=security_group, + user_data=user_data, + availability_zone=availability_zone) + for k, v in response_headers.items(): + response_headers[k] = r.headers.get(k, v) return r.json['server'] def update_server_name(self, server_id, new_name): @@ -132,15 +174,33 @@ class ComputeClient(ComputeRestClient): r = self.servers_delete(server_id) return r.headers + def change_admin_password(self, server_id, new_password): + """ + :param server_id: (int) + + :param new_password: (str) + """ + req = {"changePassword": {"adminPass": new_password}} + r = self.servers_action_post(server_id, json_data=req) + return r.headers + + def rebuild_server(self, server_id, response_headers=dict(location=None)): + """OS""" + server = self.get_server_details(server_id) + r = self.servers_action_post( + server_id, json_data=dict(rebuild=server['server'])) + for k, v in response_headers.items(): + response_headers[k] = r.headers.get(k, v) + return r.json['server'] + def reboot_server(self, server_id, hard=False): """ :param server_id: integer (str or int) :param hard: perform a hard reboot if true, soft reboot otherwise """ - boot_type = 'HARD' if hard else 'SOFT' - req = {'reboot': {'type': boot_type}} - r = self.servers_post(server_id, 'action', json_data=req) + req = {'reboot': {'type': 'HARD' if hard else 'SOFT'}} + r = self.servers_action_post(server_id, json_data=req) return r.headers def resize_server(self, server_id, flavor_id): @@ -152,10 +212,41 @@ class ComputeClient(ComputeRestClient): :returns: (dict) request headers """ req = {'resize': {'flavorRef': flavor_id}} - r = self.servers_post(server_id, 'action', json_data=req) + r = self.servers_action_post(server_id, json_data=req) + return r.headers + + def confirm_resize_server(self, server_id): + """OS""" + r = self.servers_action_post( + server_id, json_data=dict(confirmResize=None)) return r.headers - def get_server_metadata(self, server_id, key=''): + def revert_resize_server(self, server_id): + """OS""" + r = self.servers_action_post( + server_id, json_data=dict(revertResize=None)) + return r.headers + + def create_server_image(self, server_id, image_name, **metadata): + """OpenStack method for taking snapshots""" + req = dict(createImage=dict(name=image_name, metadata=metadata)) + r = self.servers_action_post(server_id, json_data=req) + return r.headers['location'] + + def start_server(self, server_id): + """OS Extentions""" + req = {'os-start': None} + r = self.servers_action_post(server_id, json_data=req, success=202) + return r.headers + + def shutdown_server(self, server_id): + """OS Extentions""" + req = {'os-stop': None} + r = self.servers_action_post(server_id, json_data=req, success=202) + return r.headers + + def get_server_metadata(self, server_id, key='', response_headers=dict( + previous=None, next=None)): """ :param server_id: integer (str or int) @@ -163,8 +254,9 @@ class ComputeClient(ComputeRestClient): :returns: a key:val dict of requests metadata """ - command = path4url('metadata', key) - r = self.servers_get(server_id, command) + r = self.servers_metadata_get(server_id, key) + for k, v in response_headers.items(): + response_headers[k] = r.headers.get(k, v) return r.json['meta' if key else 'metadata'] def create_server_metadata(self, server_id, key, val): @@ -178,11 +270,13 @@ class ComputeClient(ComputeRestClient): :returns: dict of updated key:val metadata """ req = {'meta': {key: val}} - r = self.servers_put( - server_id, 'metadata/' + key, json_data=req, success=201) + r = self.servers_metadata_put( + server_id, key, json_data=req, success=201) return r.json['meta'] - def update_server_metadata(self, server_id, **metadata): + def update_server_metadata( + self, server_id, + response_headers=dict(previous=None, next=None), **metadata): """ :param server_id: integer (str or int) @@ -191,8 +285,9 @@ class ComputeClient(ComputeRestClient): :returns: dict of updated key:val metadata """ req = {'metadata': metadata} - r = self.servers_post( - server_id, 'metadata', json_data=req, success=201) + r = self.servers_metadata_post(server_id, json_data=req, success=201) + for k, v in response_headers.items(): + response_headers[k] = r.headers.get(k, v) return r.json['metadata'] def delete_server_metadata(self, server_id, key): @@ -203,16 +298,19 @@ class ComputeClient(ComputeRestClient): :returns: (dict) response headers """ - r = self.servers_delete(server_id, 'metadata/' + key) + r = self.servers_metadata_delete(server_id, key) return r.headers - def list_flavors(self, detail=False): + def list_flavors(self, detail=False, response_headers=dict( + previous=None, next=None)): """ :param detail: (bool) detailed flavor info if set, short if not :returns: (list) flavor info """ - r = self.flavors_get(command='detail' if detail else '') + r = self.flavors_get(detail=bool(detail)) + for k, v in response_headers.items(): + response_headers[k] = r.headers.get(k, v) return r.json['flavors'] def get_flavor_details(self, flavor_id): @@ -224,14 +322,16 @@ class ComputeClient(ComputeRestClient): r = self.flavors_get(flavor_id) return r.json['flavor'] - def list_images(self, detail=False): + def list_images(self, detail=False, response_headers=dict( + next=None, previous=None)): """ :param detail: (bool) detailed info if set, short if not :returns: dict id,name + full info if detail """ - detail = 'detail' if detail else '' - r = self.images_get(command=detail) + r = self.images_get(detail=bool(detail)) + for k, v in response_headers.items(): + response_headers[k] = r.headers.get(k, v) return r.json['images'] def get_image_details(self, image_id, **kwargs): @@ -256,7 +356,8 @@ class ComputeClient(ComputeRestClient): r = self.images_delete(image_id) return r.headers - def get_image_metadata(self, image_id, key=''): + def get_image_metadata(self, image_id, key='', response_headers=dict( + previous=None, next=None)): """ :param image_id: (str) @@ -264,8 +365,9 @@ class ComputeClient(ComputeRestClient): :returns (dict) metadata if key not set, specific metadatum otherwise """ - command = path4url('metadata', key) - r = self.images_get(image_id, command) + r = self.images_metadata_get(image_id, key) + for k, v in response_headers.items(): + response_headers[k] = r.headers.get(k, v) return r.json['meta' if key else 'metadata'] def create_image_metadata(self, image_id, key, val): @@ -279,10 +381,12 @@ class ComputeClient(ComputeRestClient): :returns: (dict) updated metadata """ req = {'meta': {key: val}} - r = self.images_put(image_id, 'metadata/' + key, json_data=req) + r = self.images_metadata_put(image_id, key, json_data=req) return r.json['meta'] - def update_image_metadata(self, image_id, **metadata): + def update_image_metadata( + self, image_id, + response_headers=dict(previous=None, next=None), **metadata): """ :param image_id: (str) @@ -291,7 +395,9 @@ class ComputeClient(ComputeRestClient): :returns: updated metadata """ req = {'metadata': metadata} - r = self.images_post(image_id, 'metadata', json_data=req) + r = self.images_metadata_post(image_id, json_data=req) + for k, v in response_headers.items(): + response_headers[k] = r.headers.get(k, v) return r.json['metadata'] def delete_image_metadata(self, image_id, key): @@ -302,8 +408,7 @@ class ComputeClient(ComputeRestClient): :returns: (dict) response headers """ - command = path4url('metadata', key) - r = self.images_delete(image_id, command) + r = self.images_metadata_delete(image_id, key) return r.headers def get_floating_ip_pools(self, tenant_id): diff --git a/kamaki/clients/compute/rest_api.py b/kamaki/clients/compute/rest_api.py index 31ec1ec..4db5e13 100644 --- a/kamaki/clients/compute/rest_api.py +++ b/kamaki/clients/compute/rest_api.py @@ -38,12 +38,9 @@ import json class ComputeRestClient(Client): - def servers_get(self, server_id='', command='', success=200, **kwargs): - """GET base_url/servers[/server_id][/command] request - - :param server_id: integer (as int or str) - - :param command: 'ips', 'stats', or '' + # NON-cyclades + def limits_get(self, success=200, **kwargs): + """GET base_url/limits :param success: success code or list or tupple of accepted success codes. if server response code is not in this list, a ClientError @@ -51,210 +48,363 @@ class ComputeRestClient(Client): :returns: request response """ - path = path4url('servers', server_id, command) + path = path4url('limits') return self.get(path, success=success, **kwargs) - def servers_delete(self, server_id='', command='', success=204, **kwargs): - """DEL ETE base_url/servers[/server_id][/command] request + def servers_get( + self, + server_id='', detail=False, + changes_since=None, + image=None, + flavor=None, + name=None, + marker=None, + limit=None, + status=None, + host=None, + success=200, + **kwargs): + """GET base_url/servers/['detail'|] - :param server_id: integer (as int or str) + :param server_id: (int or int str) ignored if detail - :param command: 'ips', 'stats', or '' + :param detail: (boolean) - :param success: success code or list or tupple of accepted success - codes. if server response code is not in this list, a ClientError - raises + --- Parameters --- + + :param changes-since: A time/date stamp for when the server last + changed status + + :param image: Name of the image in URL format + + :param flavor: Name of the flavor in URL format + + :param name: Name of the server as a string + + :param marker: UUID of the server at which you want to set a marker + + :param limit: (int) limit of values to return + + :param status: Status of the server (e.g. filter on "ACTIVE") + + :param host: Name of the host as a string :returns: request response """ - path = path4url('servers', server_id, command) - return self.delete(path, success=success, **kwargs) + if not server_id: + self.set_param('changes-since', changes_since, iff=changes_since) + self.set_param('image', image, iff=image) + self.set_param('flavor', flavor, iff=flavor) + self.set_param('name', name, iff=name) + self.set_param('marker', marker, iff=marker) + self.set_param('limit', limit, iff=limit) + self.set_param('status', status, iff=status) + self.set_param('host', host, iff=host) + + path = path4url('servers', 'detail' if detail else (server_id or '')) + return self.get(path, success=success, **kwargs) def servers_post( self, - server_id='', - command='', + security_group=None, + user_data=None, + availability_zone=None, json_data=None, success=202, **kwargs): - """POST base_url/servers[/server_id]/[command] request + """POST base_url/servers - :param server_id: integer (as int or str) + :param json_data: a json-formated dict that will be send as data - :param command: 'ips', 'stats', or '' + --- Parameters - :param json_data: a json-formated dict that will be send as data + :param security_group: (str) - :param success: success code or list or tupple of accepted success - codes. if server response code is not in this list, a ClientError - raises + :param user_data: Use to pass configuration information or scripts upon + launch. Must be Base64 encoded. + + :param availability_zone: (str) :returns: request response """ - data = json_data + + self.set_param('security_group', security_group, iff=security_group) + self.set_param('user_data', user_data, iff=user_data) + self.set_param( + 'availability_zone', availability_zone, iff=availability_zone) + if json_data: - data = json.dumps(json_data) + json_data = json.dumps(json_data) self.set_header('Content-Type', 'application/json') - self.set_header('Content-Length', len(data)) + self.set_header('Content-Length', len(json_data)) - path = path4url('servers', server_id, command) - return self.post(path, data=data, success=success, **kwargs) + path = path4url('servers') + return self.post(path, data=json_data, success=success, **kwargs) def servers_put( self, - server_id='', - command='', - json_data=None, - success=204, + server_id, server_name=None, json_data=None, success=204, **kwargs): - """PUT base_url/servers[/server_id]/[command] request - - :param server_id: integer (as int or str) - - :param command: 'ips', 'stats', or '' + """PUT base_url/servers/ :param json_data: a json-formated dict that will be send as data - :param success: success code or list or tupple of accepted success - codes. if server response code is not in this list, a ClientError - raises + :param success: success code (iterable of) codes + + :raises ClientError: if returned code not in success list :returns: request response """ - data = json_data - if json_data is not None: - data = json.dumps(json_data) + self.set_param('server', server_name, iff=server_name) + + if json_data: + json_data = json.dumps(json_data) self.set_header('Content-Type', 'application/json') - self.set_header('Content-Length', len(data)) + self.set_header('Content-Length', len(json_data)) + path = path4url('servers', server_id) + return self.put(path, data=json_data, success=success, **kwargs) - path = path4url('servers', server_id, command) - return self.put(path, data=data, success=success, **kwargs) + def servers_delete(self, server_id, success=204, **kwargs): + """DEL ETE base_url/servers/ - def flavors_get(self, flavor_id='', command='', success=200, **kwargs): - """GET base_url[/flavor_id][/command] + :param json_data: a json-formated dict that will be send as data - :param flavor_id: integer (str or int) + :param success: success code (iterable of) codes - :param command: flavor service command + :raises ClientError: if returned code not in success list - :param success: success code or list or tupple of accepted success - codes. if server response code is not in this list, a ClientError - raises + :returns: request response + """ + path = path4url('servers', server_id) + return self.delete(path, success=success, **kwargs) + + def servers_metadata_get(self, server_id, key=None, success=200, **kwargs): + """GET base_url/servers//metadata[/key] :returns: request response """ - path = path4url('flavors', flavor_id, command) + path = path4url('servers', server_id, 'metadata', key or '') return self.get(path, success=success, **kwargs) - def images_get(self, image_id='', command='', success=200, **kwargs): - """GET base_url[/image_id][/command] + def servers_metadata_post( + self, server_id, json_data=None, success=202, **kwargs): + """POST base_url/servers//metadata + + :returns: request response + """ + if json_data: + json_data = json.dumps(json_data) + self.set_header('Content-Type', 'application/json') + self.set_header('Content-Length', len(json_data)) + path = path4url('servers', server_id, 'metadata') + return self.post(path, data=json_data, success=success, **kwargs) - :param image_id: string + def servers_metadata_put( + self, server_id, key=None, json_data=None, success=204, **kwargs): + """PUT base_url/servers//metadata[/key] - :param command: image server command + :returns: request response + """ + if json_data: + json_data = json.dumps(json_data) + self.set_header('Content-Type', 'application/json') + self.set_header('Content-Length', len(json_data)) + path = path4url('servers', server_id, 'metadata', key or '') + return self.put(path, data=json_data, success=success, **kwargs) - :param success: success code or list or tupple of accepted success - codes. if server response code is not in this list, a ClientError - raises + def servers_metadata_delete(self, server_id, key, success=204, **kwargs): + """DEL ETE base_url/servers//metadata[/key] :returns: request response """ - path = path4url('images', image_id, command) - return self.get(path, success=success, **kwargs) + path = path4url('servers', server_id, 'metadata', key) + return self.delete(path, success=success, **kwargs) - def images_delete(self, image_id='', command='', success=204, **kwargs): - """DELETE base_url[/image_id][/command] + def servers_action_post( + self, server_id, json_data=None, success=202, **kwargs): + """POST base_url/servers//action - :param image_id: string + :returns: request response + """ + if json_data: + json_data = json.dumps(json_data) + self.set_header('Content-Type', 'application/json') + self.set_header('Content-Length', len(json_data)) + path = path4url('servers', server_id, 'action') + return self.post(path, data=json_data, success=success, **kwargs) - :param command: image server command + def servers_ips_get( + self, server_id, + network_id=None, changes_since=None, success=(304, 200), + **kwargs): + """GET base_url/servers//ips[/network_id] - :param success: success code or list or tuple of accepted success - codes. if server response code is not in this list, a ClientError - raises + :param changes_since: time/date stamp in UNIX/epoch time. Checks for + changes since a previous request. :returns: request response """ - path = path4url('images', image_id, command) - return self.delete(path, success=success, **kwargs) + self.set_param('changes-since', changes_since, iff=changes_since) + path = path4url('servers', server_id, 'ips', network_id or '') + return self.get(path, success=success, **kwargs) - def images_post( + def images_get( self, image_id='', - command='', - json_data=None, - success=201, + detail=False, + changes_since=None, + server_name=None, + name=None, + status=None, + marker=None, + limit=None, + type=None, + success=200, **kwargs): - """POST base_url/images[/image_id]/[command] request + """GET base_url[/image_id][/command] - :param image_id: string + :param image_id: (str) ignored if detail - :param command: image server command + :param detail: (bool) - :param json_data: (dict) will be send as data + --- Parameters --- + :param changes_since: when the image last changed status - :param success: success code or list or tuple of accepted success - codes. if server response code is not in this list, a ClientError - raises + :param server_name: Name of the server in URL format + + :param name: Name of the image + + :param status: Status of the image (e.g. filter on "ACTIVE") + + :param marker: UUID of the image at which you want to set a marker + + :param limit: Integer value for the limit of values to return + + :param type: Type of image (e.g. BASE, SERVER, or ALL) + + :returns: request response + """ + if not image_id: + self.set_param('changes-since', changes_since, iff=changes_since) + self.set_param('name', name, iff=name) + self.set_param('status', status, iff=status) + self.set_param('marker', marker, iff=marker) + self.set_param('limit', limit, iff=limit) + self.set_param('type', type, iff=type) + self.set_param('server', server_name, iff=server_name) + + path = path4url('images', 'detail' if detail else (image_id or '')) + return self.get(path, success=success, **kwargs) + + def images_delete(self, image_id='', success=204, **kwargs): + """DEL ETE base_url/images/ + + :returns: request response + """ + path = path4url('images', image_id) + return self.delete(path, success=success, **kwargs) + + def images_metadata_get(self, image_id, key=None, success=200, **kwargs): + """GET base_url//metadata[/key] + + :returns: request response + """ + path = path4url('images', image_id, 'metadata', key or '') + return self.get(path, success=success, **kwargs) + + def images_metadata_post( + self, image_id, json_data=None, success=201, **kwargs): + """POST base_url/images//metadata :returns: request response """ - data = json_data if json_data is not None: - data = json.dumps(json_data) + json_data = json.dumps(json_data) self.set_header('Content-Type', 'application/json') - self.set_header('Content-Length', len(data)) + self.set_header('Content-Length', len(json_data)) - path = path4url('images', image_id, command) - return self.post(path, data=data, success=success, **kwargs) + path = path4url('images', image_id, 'metadata') + return self.post(path, data=json_data, success=success, **kwargs) + + def images_metadata_put( + self, image_id, key=None, json_data=None, success=201, **kwargs): + """PUT base_url/images//metadata - def images_put( + :returns: request response + """ + if json_data is not None: + json_data = json.dumps(json_data) + self.set_header('Content-Type', 'application/json') + self.set_header('Content-Length', len(json_data)) + + path = path4url('images', image_id, 'metadata') + return self.put(path, data=json_data, success=success, **kwargs) + + def images_metadata_delete(self, image_id, key, success=204, **kwargs): + """DEL ETE base_url/images//metadata/key + + :returns: request response + """ + path = path4url('images', image_id, 'metadata', key) + return self.delete(path, success=success, **kwargs) + + def flavors_get( self, - image_id='', - command='', - json_data=None, - success=201, + flavor_id='', + detail=False, + changes_since=None, + minDisk=None, + minRam=None, + marker=None, + limit=None, + success=200, **kwargs): - """PUT base_url/images[/image_id]/[command] request + """GET base_url[/flavor_id][/command] - :param image_id: string + :param flavor_id: ignored if detail - :param command: image server command + :param detail: (bool) - :param json_data: (dict) will be send as data + --- Parameters --- - :param success: success code or list or tuple of accepted success - codes. if server response code is not in this list, a ClientError - raises + :param changes_since: when the flavor last changed + + :param minDisk: minimum disk space in GB filter + + :param minRam: minimum RAM filter + + :param marker: UUID of the flavor at which to set a marker + + :param limit: limit the number of returned values :returns: request response """ - data = json_data - if json_data is not None: - data = json.dumps(json_data) - self.set_header('Content-Type', 'application/json') - self.set_header('Content-Length', len(data)) - - path = path4url('images', image_id, command) - return self.put(path, data=data, success=success, **kwargs) + if not flavor_id: + self.set_param('changes-since', changes_since, iff=changes_since) + self.set_param('minDisk', minDisk, iff=minDisk) + self.set_param('minRam', minRam, iff=minRam) + self.set_param('marker', marker, iff=marker) + self.set_param('limit', limit, iff=limit) + + path = path4url('flavors', 'detail' if detail else (flavor_id or '')) + return self.get(path, success=success, **kwargs) - def floating_ip_pools_get(self, tenant_id, success=200, **kwargs): - path = path4url(tenant_id, 'os-floating-ip-pools') + def floating_ip_pools_get(self, success=200, **kwargs): + path = path4url('os-floating-ip-pools') return self.get(path, success=success, **kwargs) - def floating_ips_get(self, tenant_id, ip='', success=200, **kwargs): - path = path4url(tenant_id, 'os-floating-ips', ip or '') + def floating_ips_get(self, ip='', success=200, **kwargs): + path = path4url('os-floating-ips', ip or '') return self.get(path, success=success, **kwargs) - def floating_ips_post( - self, tenant_id, json_data, ip='', success=201, **kwargs): - path = path4url(tenant_id, 'os-floating-ips', ip or '') + def floating_ips_post(self, json_data, ip='', success=201, **kwargs): + path = path4url('os-floating-ips', ip or '') if json_data is not None: json_data = json.dumps(json_data) self.set_header('Content-Type', 'application/json') self.set_header('Content-Length', len(json_data)) return self.post(path, data=json_data, success=success, **kwargs) - def floating_ips_delete(self, tenant_id, ip='', success=204, **kwargs): - path = path4url(tenant_id, 'os-floating-ips', ip or '') + def floating_ips_delete(self, ip='', success=204, **kwargs): + path = path4url('os-floating-ips', ip or '') return self.delete(path, success=success, **kwargs) diff --git a/kamaki/clients/compute/test.py b/kamaki/clients/compute/test.py index f05df80..874b0f0 100644 --- a/kamaki/clients/compute/test.py +++ b/kamaki/clients/compute/test.py @@ -35,6 +35,8 @@ from mock import patch, call from unittest import TestCase from itertools import product from json import dumps +from sys import stdout +import json from kamaki.clients import ClientError, compute @@ -107,6 +109,18 @@ class FR(object): status_code = 200 +def print_iterations(old, new): + if new: + if new % 1000: + return old + stdout.write('\b' * len('%s' % old)) + stdout.write('%s' % new) + else: + stdout.write('# of loops: ') + stdout.flush() + return new + + class ComputeRestClient(TestCase): """Set up a ComputesRest thorough test""" @@ -118,154 +132,307 @@ class ComputeRestClient(TestCase): def tearDown(self): FR.json = vm_recv + @patch('%s.set_param' % rest_pkg) @patch('%s.get' % rest_pkg, return_value=FR()) - def _test_get(self, service, get): - for args in product( + def _test_get(self, service, params, get, set_param): + method = getattr(self.client, '%s_get' % service) + param_args = [({}, {k: k}, {k: v[1]}) for k, v in params.items()] + num_of_its = 0 + for i, args in enumerate(product( ('', '%s_id' % service), - ('', 'cmd'), + (None, False, True), (200, 204), - ({}, {'k': 'v'})): - (srv_id, command, success, kwargs) = args - method = getattr(self.client, '%s_get' % service) - method(*args[:3], **kwargs) - srv_str = '/%s' % srv_id if srv_id else '' - cmd_str = '/%s' % command if command else '' - self.assertEqual(get.mock_calls[-1], call( - '/%s%s%s' % (service, srv_str, cmd_str), - success=success, - **kwargs)) - - def test_servers_get(self): - self._test_get('servers') - - def test_flavors_get(self): - self._test_get('flavors') - - def test_images_get(self): - self._test_get('images') - - @patch('%s.delete' % rest_pkg, return_value=FR()) - def _test_delete(self, service, delete): - for args in product( - ('', '%s_id' % service), - ('', 'cmd'), - (204, 208), - ({}, {'k': 'v'})): - (srv_id, command, success, kwargs) = args - method = getattr(self.client, '%s_delete' % service) - method(*args[:3], **kwargs) - vm_str = '/%s' % srv_id if srv_id else '' - cmd_str = '/%s' % command if command else '' - self.assertEqual(delete.mock_calls[-1], call( - '/%s%s%s' % (service, vm_str, cmd_str), - success=success, - **kwargs)) - - def test_servers_delete(self): - self._test_delete('servers') - - def test_images_delete(self): - self._test_delete('images') + ({}, {'k': 'v'}), + *param_args)): + (srv_id, detail, success, kwargs) = args[:4] + kwargs['success'] = success + srv_kwargs = dict() + for param in args[4:]: + srv_kwargs.update(param) + srv_kwargs.update(kwargs) + method(*args[:2], **srv_kwargs) + srv_str = '/detail' if detail else ( + '/%s' % srv_id) if srv_id else '' + self.assertEqual( + get.mock_calls[-1], + call('/%s%s' % (service, srv_str), **kwargs)) + param_calls = [] + for k, v in params.items(): + real_v = srv_kwargs.get(k, v[1]) if not srv_id else v[1] + param_calls.append(call(v[0], real_v, iff=real_v)) + actual = set_param.mock_calls[- len(param_calls):] + self.assertEqual(sorted(actual), sorted(param_calls)) + + num_of_its = print_iterations(num_of_its, i) + print ('\b' * len('%s' % num_of_its)) + ('%s' % i) + + @patch('%s.set_param' % rest_pkg) + @patch('%s.get' % rest_pkg, return_value=FR()) + def _test_srv_cmd_get(self, srv, cmd, params, get, set_param): + method = getattr(self.client, '%s_%s_get' % (srv, cmd)) + param_args = [({}, {k: k}, {k: v[1]}) for k, v in params.items()] + num_of_its = 0 + for i, args in enumerate(product( + ('some_server_id', 'other_server_id'), + (None, 'xtra_id'), + ((304, 200), (1000)), + ({}, {'k': 'v'}), + *param_args)): + srv_id, xtra_id, success, kwargs = args[:4] + kwargs = dict(kwargs) + kwargs['success'] = success + srv_kwargs = dict() + for param in args[4:]: + srv_kwargs.update(param) + srv_kwargs.update(kwargs) + method(*args[:2], **srv_kwargs) + srv_str = '/%s/%s/%s' % (srv, srv_id, cmd) + srv_str += ('/%s' % xtra_id) if xtra_id else '' + self.assertEqual(get.mock_calls[-1], call(srv_str, **kwargs)) + param_calls = [] + for k, v in params.items(): + real_v = srv_kwargs.get(k, v[1]) + param_calls.append(call(v[0], real_v, iff=real_v)) + actual = set_param.mock_calls[- len(param_calls):] + self.assertEqual(sorted(actual), sorted(param_calls)) + + num_of_its = print_iterations(num_of_its, i) + print ('\b' * len('%s' % num_of_its)) + ('%s' % i) @patch('%s.set_header' % rest_pkg) + @patch('%s.set_param' % rest_pkg) @patch('%s.post' % rest_pkg, return_value=FR()) - def _test_post(self, service, post, SH): - for args in product( - ('', '%s_id' % service), - ('', 'cmd'), - (None, [dict(json="data"), dict(data="json")]), - (202, 204), - ({}, {'k': 'v'})): - (srv_id, command, json_data, success, kwargs) = args - method = getattr(self.client, '%s_post' % service) - method(*args[:4], **kwargs) - vm_str = '/%s' % srv_id if srv_id else '' - cmd_str = '/%s' % command if command else '' + def _test_post(self, srv, cmd, params, post, set_param, set_header): + method = getattr( + self.client, '%s_%spost' % (srv, ('%s_' % cmd) if cmd else '')) + param_args = [({}, {k: k}, {k: v[1]}) for k, v in params.items()] + num_of_its = 0 + for i, args in enumerate(product( + ('%s_id' % srv, 'some_value'), + ( + None, + {'some': {'data': 'in json'}}, + ['k1', {'k2': 'v2', 'k3': 'v3'}, 'k4']), + (202, 1453), + ({}, {'k': 'v'}), + *param_args)): + srv_id, json_data, success, kwargs = args[:4] + kwargs = dict(kwargs) + cmd_args = (srv_id, ) if cmd else () + kwargs['success'] = success + srv_kwargs = dict() + for param in args[4:]: + srv_kwargs.update(param) + srv_kwargs.update(kwargs) + srv_kwargs['json_data'] = json_data + method(*cmd_args, **srv_kwargs) + srv_str = '/%s%s' % ( + srv, (('/%s/%s' % (srv_id, cmd)) if cmd else '')) + kwargs['data'] = json.dumps(json_data) if json_data else None + self.assertEqual(post.mock_calls[-1], call(srv_str, **kwargs)) + + param_calls = [] + for k, v in params.items(): + real_v = srv_kwargs.get(k, v[1]) + param_calls.append(call(v[0], real_v, iff=real_v)) + actual = set_param.mock_calls[- len(param_calls):] + self.assertEqual(sorted(actual), sorted(param_calls)) + if json_data: - json_data = dumps(json_data) - self.assertEqual(SH.mock_calls[-2:], [ + self.assertEqual(set_header.mock_calls[-2:], [ call('Content-Type', 'application/json'), - call('Content-Length', len(json_data))]) - self.assertEqual(post.mock_calls[-1], call( - '/%s%s%s' % (service, vm_str, cmd_str), - data=json_data, success=success, - **kwargs)) + call('Content-Length', len(kwargs['data']))]) - def test_servers_post(self): - self._test_post('servers') - - def test_images_post(self): - self._test_post('images') + num_of_its = print_iterations(num_of_its, i) + print ('\b' * len('%s' % num_of_its)) + ('%s' % i) @patch('%s.set_header' % rest_pkg) + @patch('%s.set_param' % rest_pkg) @patch('%s.put' % rest_pkg, return_value=FR()) - def _test_put(self, service, put, SH): - for args in product( - ('', '%s_id' % service), - ('', 'cmd'), + def _test_put(self, srv, cmd, params, put, set_param, set_headers): + method = getattr(self.client, '%s_%sput' % ( + srv, ('%s_' % cmd) if cmd else '')) + param_args = [({}, {k: k}, {k: v[1]}) for k, v in params.items()] + num_of_its = 0 + for i, args in enumerate(product( + ('some_value', '%s_id' % srv), (None, [dict(json="data"), dict(data="json")]), (204, 504), - ({}, {'k': 'v'})): - (server_id, command, json_data, success, kwargs) = args - method = getattr(self.client, '%s_put' % service) - method(*args[:4], **kwargs) - vm_str = '/%s' % server_id if server_id else '' - cmd_str = '/%s' % command if command else '' + ({}, {'k': 'v'}), + *param_args)): + srv_id, json_data, success, kwargs = args[:4] + kwargs = dict(kwargs) + kwargs['success'] = success + srv_kwargs = dict() + for param in args[4:]: + srv_kwargs.update(param) + srv_kwargs.update(kwargs) + srv_kwargs['json_data'] = json_data + method(srv_id, **srv_kwargs) + srv_str = '/%s/%s%s' % (srv, srv_id, ('/%s' % cmd if cmd else '')) + if json_data: json_data = dumps(json_data) - self.assertEqual(SH.mock_calls[-2:], [ + self.assertEqual(set_headers.mock_calls[-2:], [ call('Content-Type', 'application/json'), call('Content-Length', len(json_data))]) - self.assertEqual(put.mock_calls[-1], call( - '/%s%s%s' % (service, vm_str, cmd_str), - data=json_data, success=success, - **kwargs)) + self.assertEqual( + put.mock_calls[-1], + call(srv_str, data=json_data, **kwargs)) + + param_calls = [] + for k, v in params.items(): + real_v = srv_kwargs.get(k, v[1]) + param_calls.append(call(v[0], real_v, iff=real_v)) + actual = set_param.mock_calls[- len(param_calls):] + self.assertEqual(sorted(actual), sorted(param_calls)) + + num_of_its = print_iterations(num_of_its, i) + print ('\b' * len('%s' % num_of_its)) + ('%s' % i) + + @patch('%s.delete' % rest_pkg, return_value=FR()) + def _test_delete(self, srv, cmd, delete): + method = getattr( + self.client, '%s_%sdelete' % (srv, ('%s_' % cmd) if cmd else '')) + cmd_params = ('some_cmd_value', 'some_other_value') if cmd else () + num_of_its = 0 + for i, args in enumerate(product( + ('%s_id' % srv, 'some_value'), + (204, 208), + ({}, {'k': 'v'}), + *cmd_params)): + (srv_id, success, kwargs) = args[:3] + kwargs = dict(kwargs) + kwargs['success'] = success + cmd_value = args[-1] if cmd else '' + method_args = (srv_id, cmd_value) if cmd else (srv_id, ) + method(*method_args, **kwargs) + srv_str = '/%s/%s' % (srv, srv_id) + cmd_str = ('/%s/%s' % (cmd, cmd_value)) if cmd else '' + self.assertEqual( + delete.mock_calls[-1], + call('%s%s' % (srv_str, cmd_str), **kwargs)) + num_of_its = print_iterations(num_of_its, i) + print ('\b' * len('%s' % num_of_its)) + ('%s' % i) + + @patch('%s.get' % rest_pkg, return_value=FR()) + def test_limits_get(self, get): + self.client.limits_get(success='some_val') + get.assert_called_once_with('/limits', success='some_val') + + def test_servers_get(self): + params = dict( + changes_since=('changes-since', None), + image=('image', None), + flavor=('flavor', None), + name=('name', None), + marker=('marker', None), + limit=('limit', None), + status=('status', None), + host=('host', None)) + self._test_get('servers', params) + + def test_servers_post(self): + params = dict( + security_group=('security_group', None), + user_data=('user_data', None), + availability_zone=('availability_zone', None)) + self._test_post('servers', None, params) def test_servers_put(self): - self._test_put('servers') + self._test_put('servers', None, dict(server_name=('server', None))) + + def test_servers_delete(self): + self._test_delete('servers', None) + + def test_servers_metadata_get(self): + self._test_srv_cmd_get('servers', 'metadata', {}) + + def test_servers_metadata_post(self): + self._test_post('servers', 'metadata', {}) + + def test_servers_metadata_put(self): + self._test_put('servers', 'metadata', {}) + + def test_images_metadata_delete(self): + self._test_delete('images', 'metadata') - def test_images_put(self): - self._test_put('images') + def test_servers_action_post(self): + self._test_post('servers', 'action', {}) + + def test_servers_ips_get(self): + params = dict(changes_since=('changes-since', None)) + self._test_srv_cmd_get('servers', 'ips', params) + + def test_images_get(self): + param = dict( + changes_since=('changes-since', None), + server_name=('server', None), + name=('name', None), + status=('status', None), + marker=('marker', None), + limit=('limit', None), + type=('type', None)) + self._test_get('images', param) + + def test_images_delete(self): + self._test_delete('images', None) + + def test_images_metadata_get(self): + self._test_srv_cmd_get('images', 'metadata', {}) + + def test_images_metadata_post(self): + self._test_post('images', 'metadata', {}) + + def test_images_metadata_put(self): + self._test_put('images', 'metadata', {}) + + def test_servers_metadata_delete(self): + self._test_delete('servers', 'metadata') + + def test_flavors_get(self): + params = dict( + changes_since=('changes-since', None), + minDisk=('minDisk', None), + minRam=('minRam', None), + marker=('marker', None), + limit=('limit', None)) + self._test_get('flavors', params) @patch('%s.get' % rest_pkg, return_value=FR()) def test_floating_ip_pools_get(self, get): for args in product( - ('tenant1', 'tenant2'), (200, 204), ({}, {'k': 'v'})): - tenant_id, success, kwargs = args - r = self.client.floating_ip_pools_get(tenant_id, success, **kwargs) + success, kwargs = args + r = self.client.floating_ip_pools_get(success, **kwargs) self.assertTrue(isinstance(r, FR)) self.assertEqual(get.mock_calls[-1], call( - '/%s/os-floating-ip-pools' % tenant_id, - success=success, **kwargs)) + '/os-floating-ip-pools', success=success, **kwargs)) @patch('%s.get' % rest_pkg, return_value=FR()) def test_floating_ips_get(self, get): for args in product( - ('tenant1', 'tenant2'), ('', '192.193.194.195'), (200, 204), ({}, {'k': 'v'})): - tenant_id, ip, success, kwargs = args - r = self.client.floating_ips_get(*args[:3], **kwargs) + ip, success, kwargs = args + r = self.client.floating_ips_get(*args[:2], **kwargs) self.assertTrue(isinstance(r, FR)) expected = '' if not ip else '/%s' % ip self.assertEqual(get.mock_calls[-1], call( - '/%s/os-floating-ips%s' % (tenant_id, expected), - success=success, **kwargs)) + '/os-floating-ips%s' % expected, success=success, **kwargs)) @patch('%s.set_header' % rest_pkg) @patch('%s.post' % rest_pkg, return_value=FR()) def test_floating_ips_post(self, post, SH): for args in product( - ('tenant1', 'tenant2'), (None, [dict(json="data"), dict(data="json")]), ('', '192.193.194.195'), (202, 204), ({}, {'k': 'v'})): - (tenant_id, json_data, ip, success, kwargs) = args - self.client.floating_ips_post(*args[:4], **kwargs) + json_data, ip, success, kwargs = args + self.client.floating_ips_post(*args[:3], **kwargs) if json_data: json_data = dumps(json_data) self.assertEqual(SH.mock_calls[-2:], [ @@ -273,24 +440,22 @@ class ComputeRestClient(TestCase): call('Content-Length', len(json_data))]) expected = '' if not ip else '/%s' % ip self.assertEqual(post.mock_calls[-1], call( - '/%s/os-floating-ips%s' % (tenant_id, expected), + '/os-floating-ips%s' % expected, data=json_data, success=success, **kwargs)) @patch('%s.delete' % rest_pkg, return_value=FR()) def test_floating_ips_delete(self, delete): for args in product( - ('tenant1', 'tenant2'), ('', '192.193.194.195'), (204,), ({}, {'k': 'v'})): - tenant_id, ip, success, kwargs = args - r = self.client.floating_ips_delete(*args[:3], **kwargs) + ip, success, kwargs = args + r = self.client.floating_ips_delete(*args[:2], **kwargs) self.assertTrue(isinstance(r, FR)) expected = '' if not ip else '/%s' % ip self.assertEqual(delete.mock_calls[-1], call( - '/%s/os-floating-ips%s' % (tenant_id, expected), - success=success, **kwargs)) + '/os-floating-ips%s' % expected, success=success, **kwargs)) class ComputeClient(TestCase): @@ -313,10 +478,7 @@ class ComputeClient(TestCase): FR.status_code = 200 FR.json = vm_recv - @patch( - '%s.get_image_details' % compute_pkg, - return_value=img_recv['image']) - def test_create_server(self, GID): + def test_create_server(self): with patch.object( compute.ComputeClient, 'servers_post', side_effect=ClientError( @@ -327,26 +489,70 @@ class ComputeClient(TestCase): self.client.create_server, vm_name, fid, img_ref) - with patch.object( - compute.ComputeClient, 'servers_post', - return_value=FR()) as post: - r = self.client.create_server(vm_name, fid, img_ref) - self.assertEqual(r, FR.json['server']) - self.assertEqual(GID.mock_calls[-1], call(img_ref)) - self.assertEqual(post.mock_calls[-1], call(json_data=vm_send)) - prsn = 'Personality string (does not work with real servers)' - self.client.create_server(vm_name, fid, img_ref, prsn) - expected = dict(server=dict(vm_send['server'])) - expected['server']['personality'] = prsn - self.assertEqual(post.mock_calls[-1], call(json_data=expected)) + for params in product( + ('security_group', None), + ('user_data', None), + ('availability_zone', None), + (None, {'os': 'debian', 'users': 'root'})): + kwargs = dict() + for i, k in enumerate(( + 'security_group', 'user_data', 'availability_zone')): + if params[i]: + kwargs[k] = params[i] + with patch.object( + compute.ComputeClient, 'servers_post', + return_value=FR()) as post: + r = self.client.create_server(vm_name, fid, img_ref, **kwargs) + self.assertEqual(r, FR.json['server']) + exp_json = dict(server=dict( + flavorRef=fid, name=vm_name, imageRef=img_ref)) + for k in set([ + 'security_group', + 'user_data', + 'availability_zone']).difference(kwargs): + kwargs[k] = None + self.assertEqual( + post.mock_calls[-1], call(json_data=exp_json, **kwargs)) + prsn = 'Personality string (does not work with real servers)' + self.client.create_server( + vm_name, fid, img_ref, personality=prsn, **kwargs) + exp_json['server']['personality'] = prsn + self.assertEqual( + post.mock_calls[-1], call(json_data=exp_json, **kwargs)) + kwargs.pop('personality', None) + exp_json['server'].pop('personality', None) + mtdt = 'Metadata dict here' + self.client.create_server( + vm_name, fid, img_ref, metadata=mtdt, **kwargs) + exp_json['server']['metadata'] = mtdt + self.assertEqual( + post.mock_calls[-1], call(json_data=exp_json, **kwargs)) @patch('%s.servers_get' % compute_pkg, return_value=FR()) def test_list_servers(self, SG): FR.json = vm_list - for detail in (False, True): - r = self.client.list_servers(detail) - self.assertEqual(SG.mock_calls[-1], call( - command='detail' if detail else '')) + for args in product( + (False, True), + ({}, dict(status='status')), + ({}, dict(name='name')), + ({}, dict(image='image')), + ({}, dict(flavor='flavor')), + ({}, dict(host='host')), + ({}, dict(limit='limit')), + ({}, dict(marker='marker')), + ({}, dict(changes_since='changes_since'))): + detail = args[0] + kwargs = dict() + for param in args[1:]: + kwargs.update(param) + r = self.client.list_servers(detail, **kwargs) + for k in set([ + 'status', 'name', + 'image', 'flavor', + 'host', 'limit', + 'marker', 'changes_since']).difference(kwargs): + kwargs[k] = None + self.assertEqual(SG.mock_calls[-1], call(detail=detail, **kwargs)) for i, vm in enumerate(vm_list['servers']): self.assert_dicts_are_equal(r[i], vm) self.assertEqual(i + 1, len(r)) @@ -354,9 +560,27 @@ class ComputeClient(TestCase): @patch('%s.servers_get' % compute_pkg, return_value=FR()) def test_get_server_details(self, SG): vm_id = vm_recv['server']['id'] - r = self.client.get_server_details(vm_id) - SG.assert_called_once_with(vm_id) - self.assert_dicts_are_equal(r, vm_recv['server']) + for args in product( + ({}, dict(status='status')), + ({}, dict(name='name')), + ({}, dict(image='image')), + ({}, dict(flavor='flavor')), + ({}, dict(host='host')), + ({}, dict(limit='limit')), + ({}, dict(marker='marker')), + ({}, dict(changes_since='changes_since'))): + kwargs = dict() + for param in args: + kwargs.update(param) + r = self.client.get_server_details(vm_id, **kwargs) + for k in set([ + 'status', 'name', + 'image', 'flavor', + 'host', 'limit', + 'marker', 'changes_since']).difference(kwargs): + kwargs[k] = None + self.assertEqual(SG.mock_calls[-1], call(vm_id, **kwargs)) + self.assert_dicts_are_equal(r, vm_recv['server']) @patch('%s.servers_put' % compute_pkg, return_value=FR()) def test_update_server_name(self, SP): @@ -366,23 +590,22 @@ class ComputeClient(TestCase): SP.assert_called_once_with(vm_id, json_data=dict( server=dict(name=new_name))) - @patch('%s.servers_post' % compute_pkg, return_value=FR()) + @patch('%s.servers_action_post' % compute_pkg, return_value=FR()) def test_reboot_server(self, SP): vm_id = vm_recv['server']['id'] for hard in (None, True): self.client.reboot_server(vm_id, hard=hard) - self.assertEqual(SP.mock_calls[-1], call( - vm_id, 'action', - json_data=dict(reboot=dict(type='HARD' if hard else 'SOFT')))) + self.assertEqual(SP.mock_calls[-1], call(vm_id, json_data=dict( + reboot=dict(type='HARD' if hard else 'SOFT')))) - @patch('%s.servers_post' % compute_pkg, return_value=FR()) + @patch('%s.servers_action_post' % compute_pkg, return_value=FR()) def test_resize_server(self, SP): vm_id, flavor = vm_recv['server']['id'], flavor_list['flavors'][1] self.client.resize_server(vm_id, flavor['id']) exp = dict(resize=dict(flavorRef=flavor['id'])) - SP.assert_called_once_with(vm_id, 'action', json_data=exp) + SP.assert_called_once_with(vm_id, json_data=exp) - @patch('%s.servers_put' % compute_pkg, return_value=FR()) + @patch('%s.servers_metadata_put' % compute_pkg, return_value=FR()) def test_create_server_metadata(self, SP): vm_id = vm_recv['server']['id'] metadata = dict(m1='v1', m2='v2', m3='v3') @@ -391,17 +614,17 @@ class ComputeClient(TestCase): r = self.client.create_server_metadata(vm_id, k, v) self.assert_dicts_are_equal(r, vm_recv['server']) self.assertEqual(SP.mock_calls[-1], call( - vm_id, 'metadata/%s' % k, + vm_id, '%s' % k, json_data=dict(meta={k: v}), success=201)) - @patch('%s.servers_get' % compute_pkg, return_value=FR()) + @patch('%s.servers_metadata_get' % compute_pkg, return_value=FR()) def test_get_server_metadata(self, SG): vm_id = vm_recv['server']['id'] metadata = dict(m1='v1', m2='v2', m3='v3') FR.json = dict(metadata=metadata) r = self.client.get_server_metadata(vm_id) FR.json = dict(meta=metadata) - SG.assert_called_once_with(vm_id, '/metadata') + SG.assert_called_once_with(vm_id, '') self.assert_dicts_are_equal(r, metadata) for k, v in metadata.items(): @@ -409,9 +632,9 @@ class ComputeClient(TestCase): r = self.client.get_server_metadata(vm_id, k) self.assert_dicts_are_equal(r, {k: v}) self.assertEqual( - SG.mock_calls[-1], call(vm_id, '/metadata/%s' % k)) + SG.mock_calls[-1], call(vm_id, '%s' % k)) - @patch('%s.servers_post' % compute_pkg, return_value=FR()) + @patch('%s.servers_metadata_post' % compute_pkg, return_value=FR()) def test_update_server_metadata(self, SP): vm_id = vm_recv['server']['id'] metadata = dict(m1='v1', m2='v2', m3='v3') @@ -419,22 +642,21 @@ class ComputeClient(TestCase): r = self.client.update_server_metadata(vm_id, **metadata) self.assert_dicts_are_equal(r, metadata) SP.assert_called_once_with( - vm_id, 'metadata', - json_data=dict(metadata=metadata), success=201) + vm_id, json_data=dict(metadata=metadata), success=201) - @patch('%s.servers_delete' % compute_pkg, return_value=FR()) + @patch('%s.servers_metadata_delete' % compute_pkg, return_value=FR()) def test_delete_server_metadata(self, SD): vm_id = vm_recv['server']['id'] key = 'metakey' self.client.delete_server_metadata(vm_id, key) - SD.assert_called_once_with(vm_id, 'metadata/' + key) + SD.assert_called_once_with(vm_id, key) @patch('%s.flavors_get' % compute_pkg, return_value=FR()) def test_list_flavors(self, FG): FR.json = flavor_list - for cmd in ('', 'detail'): - r = self.client.list_flavors(detail=(cmd == 'detail')) - self.assertEqual(FG.mock_calls[-1], call(command=cmd)) + for detail in ('', 'detail'): + r = self.client.list_flavors(detail=bool(detail)) + self.assertEqual(FG.mock_calls[-1], call(detail=bool(detail))) self.assertEqual(r, flavor_list['flavors']) @patch('%s.flavors_get' % compute_pkg, return_value=FR()) @@ -447,9 +669,9 @@ class ComputeClient(TestCase): @patch('%s.images_get' % compute_pkg, return_value=FR()) def test_list_images(self, IG): FR.json = img_list - for cmd in ('', 'detail'): - r = self.client.list_images(detail=(cmd == 'detail')) - self.assertEqual(IG.mock_calls[-1], call(command=cmd)) + for detail in ('', 'detail'): + r = self.client.list_images(detail=detail) + self.assertEqual(IG.mock_calls[-1], call(detail=bool(detail))) expected = img_list['images'] for i in range(len(r)): self.assert_dicts_are_equal(expected[i], r[i]) @@ -461,15 +683,13 @@ class ComputeClient(TestCase): IG.assert_called_once_with(img_ref) self.assert_dicts_are_equal(r, img_recv['image']) - @patch('%s.images_get' % compute_pkg, return_value=FR()) + @patch('%s.images_metadata_get' % compute_pkg, return_value=FR()) def test_get_image_metadata(self, IG): for key in ('', '50m3k3y'): FR.json = dict(meta=img_recv['image']) if ( key) else dict(metadata=img_recv['image']) r = self.client.get_image_metadata(img_ref, key) - self.assertEqual(IG.mock_calls[-1], call( - '%s' % img_ref, - '/metadata%s' % (('/%s' % key) if key else ''))) + self.assertEqual(IG.mock_calls[-1], call(img_ref, key or '')) self.assert_dicts_are_equal(img_recv['image'], r) @patch('%s.servers_delete' % compute_pkg, return_value=FR()) @@ -483,31 +703,30 @@ class ComputeClient(TestCase): self.client.delete_image(img_ref) ID.assert_called_once_with(img_ref) - @patch('%s.images_put' % compute_pkg, return_value=FR()) + @patch('%s.images_metadata_put' % compute_pkg, return_value=FR()) def test_create_image_metadata(self, IP): (key, val) = ('k1', 'v1') FR.json = dict(meta=img_recv['image']) r = self.client.create_image_metadata(img_ref, key, val) IP.assert_called_once_with( - img_ref, 'metadata/%s' % key, + img_ref, '%s' % key, json_data=dict(meta={key: val})) self.assert_dicts_are_equal(r, img_recv['image']) - @patch('%s.images_post' % compute_pkg, return_value=FR()) + @patch('%s.images_metadata_post' % compute_pkg, return_value=FR()) def test_update_image_metadata(self, IP): metadata = dict(m1='v1', m2='v2', m3='v3') FR.json = dict(metadata=metadata) r = self.client.update_image_metadata(img_ref, **metadata) IP.assert_called_once_with( - img_ref, 'metadata', - json_data=dict(metadata=metadata)) + img_ref, json_data=dict(metadata=metadata)) self.assert_dicts_are_equal(r, metadata) - @patch('%s.images_delete' % compute_pkg, return_value=FR()) + @patch('%s.images_metadata_delete' % compute_pkg, return_value=FR()) def test_delete_image_metadata(self, ID): key = 'metakey' self.client.delete_image_metadata(img_ref, key) - ID.assert_called_once_with(img_ref, '/metadata/%s' % key) + ID.assert_called_once_with(img_ref, '%s' % key) @patch('%s.floating_ip_pools_get' % compute_pkg, return_value=FR()) def test_get_floating_ip_pools(self, get): diff --git a/kamaki/clients/cyclades/__init__.py b/kamaki/clients/cyclades/__init__.py index 6981e4d..ecac9be 100644 --- a/kamaki/clients/cyclades/__init__.py +++ b/kamaki/clients/cyclades/__init__.py @@ -41,6 +41,38 @@ from kamaki.clients import ClientError class CycladesClient(CycladesRestClient): """Synnefo Cyclades Compute API client""" + def create_server( + self, name, flavor_id, image_id, + metadata=None, personality=None): + """Submit request to create a new server + + :param name: (str) + + :param flavor_id: integer id denoting a preset hardware configuration + + :param image_id: (str) id denoting the OS image to run on the VM + + :param metadata: (dict) vm metadata updated by os/users image metadata + + :param personality: a list of (file path, file contents) tuples, + describing files to be injected into VM upon creation. + + :returns: a dict with the new VMs details + + :raises ClientError: wraps request errors + """ + image = self.get_image_details(image_id) + metadata = metadata or dict() + for key in ('os', 'users'): + try: + metadata[key] = image['metadata'][key] + except KeyError: + pass + + return super(CycladesClient, self).create_server( + name, flavor_id, image_id, + metadata=metadata, personality=personality) + def start_server(self, server_id): """Submit a startup request @@ -49,7 +81,7 @@ class CycladesClient(CycladesRestClient): :returns: (dict) response headers """ req = {'start': {}} - r = self.servers_post(server_id, 'action', json_data=req, success=202) + r = self.servers_action_post(server_id, json_data=req, success=202) return r.headers def shutdown_server(self, server_id): @@ -60,7 +92,7 @@ class CycladesClient(CycladesRestClient): :returns: (dict) response headers """ req = {'shutdown': {}} - r = self.servers_post(server_id, 'action', json_data=req, success=202) + r = self.servers_action_post(server_id, json_data=req, success=202) return r.headers def get_server_console(self, server_id): @@ -70,7 +102,7 @@ class CycladesClient(CycladesRestClient): :returns: (dict) info to set a VNC connection to VM """ req = {'console': {'type': 'vnc'}} - r = self.servers_post(server_id, 'action', json_data=req, success=200) + r = self.servers_action_post(server_id, json_data=req, success=200) return r.json['console'] def get_firewall_profile(self, server_id): @@ -99,30 +131,17 @@ class CycladesClient(CycladesRestClient): :returns: (dict) response headers """ req = {'firewallProfile': {'profile': profile}} - r = self.servers_post(server_id, 'action', json_data=req, success=202) + r = self.servers_action_post(server_id, json_data=req, success=202) return r.headers - def list_servers(self, detail=False, changes_since=None): - """ - :param detail: (bool) append full server details to each item if true - - :param changes_since: (date) - - :returns: list of server ids and names - """ - detail = 'detail' if detail else '' - r = self.servers_get(command=detail, changes_since=changes_since) - return r.json['servers'] - def list_server_nics(self, server_id): """ :param server_id: integer (str or int) :returns: (dict) network interface connections """ - r = self.servers_get(server_id, 'ips') + r = self.servers_ips_get(server_id) return r.json['attachments'] - #return r.json['addresses'] def get_server_stats(self, server_id): """ @@ -130,7 +149,7 @@ class CycladesClient(CycladesRestClient): :returns: (dict) auto-generated graphs of statistics (urls) """ - r = self.servers_get(server_id, 'stats') + r = self.servers_stats_get(server_id) return r.json['stats'] def list_networks(self, detail=False): @@ -432,9 +451,8 @@ class CycladesClient(CycladesRestClient): """ server_id = int(server_id) assert address, 'address is needed for attach_floating_ip' - r = self.servers_post( - server_id, 'action', - json_data=dict(addFloatingIp=dict(address=address))) + req = dict(addFloatingIp=dict(address=address)) + r = self.servers_action_post(server_id, json_data=req) return r.headers def detach_floating_ip(self, server_id, address): @@ -454,7 +472,6 @@ class CycladesClient(CycladesRestClient): """ server_id = int(server_id) assert address, 'address is needed for detach_floating_ip' - r = self.servers_post( - server_id, 'action', - json_data=dict(removeFloatingIp=dict(address=address))) + req = dict(removeFloatingIp=dict(address=address)) + r = self.servers_action_post(server_id, json_data=req) return r.headers diff --git a/kamaki/clients/cyclades/rest_api.py b/kamaki/clients/cyclades/rest_api.py index 7ebdc6a..cfb1580 100644 --- a/kamaki/clients/cyclades/rest_api.py +++ b/kamaki/clients/cyclades/rest_api.py @@ -39,29 +39,9 @@ import json class CycladesRestClient(ComputeClient): """Synnefo Cyclades REST API Client""" - def servers_get( - self, - server_id='', - command='', - success=200, - changes_since=None, - **kwargs): - """GET base_url/servers[/server_id][/command] request - - :param server_id: integer (as int or str) - - :param command: 'ips', 'stats', or '' - - :param success: success code or list or tupple of accepted success - codes. if server response code is not in this list, a ClientError - raises - - :param changes_since: (date) - - :returns: request response - """ - path = path4url('servers', server_id, command) - self.set_param('changes-since', changes_since, changes_since) + def servers_stats_get(self, server_id, success=200, **kwargs): + """GET base_url/servers//stats""" + path = path4url('servers', server_id, 'stats') return self.get(path, success=success, **kwargs) def networks_get( @@ -165,23 +145,3 @@ class CycladesRestClient(ComputeClient): path = path4url('networks', network_id, command) return self.put(path, data=data, success=success, **kwargs) - - def floating_ip_pools_get(self, success=200, **kwargs): - path = path4url('os-floating-ip-pools') - return self.get(path, success=success, **kwargs) - - def floating_ips_get(self, fip_id='', success=200, **kwargs): - path = path4url('os-floating-ips', fip_id) - return self.get(path, success=success, **kwargs) - - def floating_ips_post(self, json_data, fip_id='', success=201, **kwargs): - path = path4url('os-floating-ips', fip_id) - if json_data is not None: - json_data = json.dumps(json_data) - self.set_header('Content-Type', 'application/json') - self.set_header('Content-Length', len(json_data)) - return self.post(path, data=json_data, success=success, **kwargs) - - def floating_ips_delete(self, fip_id, success=200, **kwargs): - path = path4url('os-floating-ips', fip_id) - return self.delete(path, success=success, **kwargs) diff --git a/kamaki/clients/cyclades/test.py b/kamaki/clients/cyclades/test.py index b1b84c5..0073ec3 100644 --- a/kamaki/clients/cyclades/test.py +++ b/kamaki/clients/cyclades/test.py @@ -103,28 +103,6 @@ class CycladesRestClient(TestCase): def tearDown(self): FR.json = vm_recv - @patch('%s.set_param' % rest_pkg) - @patch('%s.get' % rest_pkg, return_value=FR()) - def test_servers_get(self, get, SP): - for args in product( - ('', 'vm_id'), - ('', 'cmd'), - (200, 204), - (None, '50m3-d473'), - ({}, {'k': 'v'})): - (srv_id, command, success, changes_since, kwargs) = args - self.client.servers_get(*args[:4], **kwargs) - srv_str = '/%s' % srv_id if srv_id else '' - cmd_str = '/%s' % command if command else '' - self.assertEqual(get.mock_calls[-1], call( - '/servers%s%s' % (srv_str, cmd_str), - success=success, - **kwargs)) - if changes_since: - self.assertEqual( - SP.mock_calls[-1], - call('changes-since', changes_since, changes_since)) - @patch('%s.get' % rest_pkg, return_value=FR()) def test_networks_get(self, get): for args in product( @@ -281,44 +259,28 @@ class CycladesClient(TestCase): FR.status_code = 200 FR.json = vm_recv - @patch('%s.servers_get' % cyclades_pkg, return_value=FR()) - def test_list_servers(self, SG): - FR.json = vm_list - for detail, since in ((0, 0), (True, 0), (0, 'd473'), (True, 'd473')): - r = self.client.list_servers(detail=detail, changes_since=since) - self.assertEqual(SG.mock_calls[-1], call( - command='detail' if detail else '', - changes_since=since)) - expected = vm_list['servers'] - for i, vm in enumerate(r): - self.assert_dicts_are_equal(vm, expected[i]) - self.assertEqual(i + 1, len(expected)) - - @patch('%s.servers_post' % cyclades_pkg, return_value=FR()) + @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR()) def test_shutdown_server(self, SP): vm_id = vm_recv['server']['id'] self.client.shutdown_server(vm_id) SP.assert_called_once_with( - vm_id, 'action', - json_data=dict(shutdown=dict()), success=202) + vm_id, json_data=dict(shutdown=dict()), success=202) - @patch('%s.servers_post' % cyclades_pkg, return_value=FR()) + @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR()) def test_start_server(self, SP): vm_id = vm_recv['server']['id'] self.client.start_server(vm_id) SP.assert_called_once_with( - vm_id, 'action', - json_data=dict(start=dict()), success=202) + vm_id, json_data=dict(start=dict()), success=202) - @patch('%s.servers_post' % cyclades_pkg, return_value=FR()) + @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR()) def test_get_server_console(self, SP): cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3')) FR.json = cnsl vm_id = vm_recv['server']['id'] r = self.client.get_server_console(vm_id) SP.assert_called_once_with( - vm_id, 'action', - json_data=dict(console=dict(type='vnc')), success=200) + vm_id, json_data=dict(console=dict(type='vnc')), success=200) self.assert_dicts_are_equal(r, cnsl['console']) def test_get_firewall_profile(self): @@ -338,23 +300,13 @@ class CycladesClient(TestCase): self.client.get_firewall_profile, vm_id) - @patch('%s.servers_post' % cyclades_pkg, return_value=FR()) + @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR()) def test_set_firewall_profile(self, SP): vm_id = vm_recv['server']['id'] v = firewalls['attachments'][0]['firewallProfile'] self.client.set_firewall_profile(vm_id, v) - SP.assert_called_once_with( - vm_id, 'action', - json_data=dict(firewallProfile=dict(profile=v)), success=202) - - @patch('%s.servers_get' % cyclades_pkg, return_value=FR()) - def test_get_server_stats(self, SG): - vm_id = vm_recv['server']['id'] - stats = dict(stat1='v1', stat2='v2', stat3='v3', stat4='v4') - FR.json = dict(stats=stats) - r = self.client.get_server_stats(vm_id) - SG.assert_called_once_with(vm_id, 'stats') - self.assert_dicts_are_equal(stats, r) + SP.assert_called_once_with(vm_id, json_data=dict( + firewallProfile=dict(profile=v)), success=202) @patch('%s.networks_post' % cyclades_pkg, return_value=FR()) def test_create_network(self, NP): @@ -407,13 +359,13 @@ class CycladesClient(TestCase): json_data=dict(remove=dict(attachment=nic_id))) self.assertEqual(r, 1) - @patch('%s.servers_get' % cyclades_pkg, return_value=FR()) + @patch('%s.servers_ips_get' % cyclades_pkg, return_value=FR()) def test_list_server_nics(self, SG): vm_id = vm_recv['server']['id'] nics = dict(attachments=[dict(id='nic1'), dict(id='nic2')]) FR.json = nics r = self.client.list_server_nics(vm_id) - SG.assert_called_once_with(vm_id, 'ips') + SG.assert_called_once_with(vm_id) expected = nics['attachments'] for i in range(len(r)): self.assert_dicts_are_equal(r[i], expected[i]) @@ -544,7 +496,7 @@ class CycladesClient(TestCase): self.assert_dicts_are_equal(r, FR.headers) self.assertEqual(delete.mock_calls[-1], call(fip)) - @patch('%s.servers_post' % cyclades_pkg, return_value=FR()) + @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR()) def test_attach_floating_ip(self, spost): vmid, addr = 42, 'anIpAddress' for err, args in { @@ -557,10 +509,9 @@ class CycladesClient(TestCase): r = self.client.attach_floating_ip(vmid, addr) self.assert_dicts_are_equal(r, FR.headers) expected = dict(addFloatingIp=dict(address=addr)) - self.assertEqual( - spost.mock_calls[-1], call(vmid, 'action', json_data=expected)) + self.assertEqual(spost.mock_calls[-1], call(vmid, json_data=expected)) - @patch('%s.servers_post' % cyclades_pkg, return_value=FR()) + @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR()) def test_detach_floating_ip(self, spost): vmid, addr = 42, 'anIpAddress' for err, args in { @@ -573,8 +524,7 @@ class CycladesClient(TestCase): r = self.client.detach_floating_ip(vmid, addr) self.assert_dicts_are_equal(r, FR.headers) expected = dict(removeFloatingIp=dict(address=addr)) - self.assertEqual( - spost.mock_calls[-1], call(vmid, 'action', json_data=expected)) + self.assertEqual(spost.mock_calls[-1], call(vmid, json_data=expected)) if __name__ == '__main__':