import json import logging import subprocess import kong.common.http from kong import exceptions class API(kong.common.http.Client): """Barebones Nova HTTP API client.""" def __init__(self, host, port, base_url, user, api_key, project_id=''): """Initialize Nova HTTP API client. :param host: Hostname/IP of the Nova API to test. :param port: Port of the Nova API to test. :param base_url: Version identifier (normally /v1.0 or /v1.1) :param user: The username to use for tests. :param api_key: The API key of the user. :returns: None """ super(API, self).__init__(host, port, base_url) self.user = user self.api_key = api_key self.project_id = project_id # Default to same as base_url, but will be change on auth self.management_url = self.base_url def authenticate(self, user, api_key, project_id): """Request and return an authentication token from Nova. :param user: The username we're authenticating. :param api_key: The API key for the user we're authenticating. :returns: Authentication token (string) :raises: KeyError if authentication fails. """ headers = { 'X-Auth-User': user, 'X-Auth-Key': api_key, 'X-Auth-Project-Id': project_id, } resp, body = super(API, self).request('GET', '', headers=headers, base_url=self.base_url) try: self.management_url = resp['x-server-management-url'] return resp['x-auth-token'] except KeyError: print "Failed to authenticate user" raise def _wait_for_entity_status(self, url, entity_name, status, **kwargs): """Poll the provided url until expected entity status is returned""" def check_response(resp, body): try: data = json.loads(body) return data[entity_name]['status'] == status except (ValueError, KeyError): return False try: self.poll_request('GET', url, check_response, **kwargs) except exceptions.TimeoutException: msg = "%s failed to reach status %s" % (entity_name, status) raise AssertionError(msg) def wait_for_server_status(self, server_id, status='ACTIVE', **kwargs): """Wait for the server status to be equal to the status passed in. :param server_id: Server ID to query. :param status: The status string to look for. :returns: None :raises: AssertionError if request times out """ url = '/servers/%s' % server_id return self._wait_for_entity_status(url, 'server', status, **kwargs) def wait_for_image_status(self, image_id, status='ACTIVE', **kwargs): """Wait for the image status to be equal to the status passed in. :param image_id: Image ID to query. :param status: The status string to look for. :returns: None :raises: AssertionError if request times out """ url = '/images/%s' % image_id return self._wait_for_entity_status(url, 'image', status, **kwargs) def request(self, method, url, **kwargs): """Generic HTTP request on the Nova API. :param method: Request verb to use (GET, PUT, POST, etc.) :param url: The API resource to request. :param kwargs: Additional keyword arguments to pass to the request. :returns: HTTP response object. """ headers = kwargs.get('headers', {}) project_id = kwargs.get('project_id', self.project_id) headers['X-Auth-Token'] = self.authenticate(self.user, self.api_key, self.project_id) kwargs['headers'] = headers return super(API, self).request(method, url, **kwargs) def get_server(self, server_id): """Fetch a server by id :param server_id: dict of server attributes :returns: dict of server attributes :raises: ServerNotFound if server does not exist """ resp, body = self.request('GET', '/servers/%s' % server_id) try: assert resp['status'] == '200' data = json.loads(body) return data['server'] except (AssertionError, ValueError, TypeError, KeyError): raise exceptions.ServerNotFound(server_id) def create_server(self, entity): """Attempt to create a new server. :param entity: dict of server attributes :returns: dict of server attributes after creation :raises: AssertionError if server creation fails """ post_body = json.dumps({ 'server': entity, }) resp, body = self.request('POST', '/servers', body=post_body) try: assert resp['status'] == '202' data = json.loads(body) return data['server'] except (AssertionError, ValueError, TypeError, KeyError): raise AssertionError("Failed to create server") def delete_server(self, server_id): """Attempt to delete a server. :param server_id: server identifier :returns: None """ url = '/servers/%s' % server_id response, body = self.request('DELETE', url)