PageRenderTime 53ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/nova/tests/integrated/api/client.py

https://github.com/jkoelker/nova
Python | 291 lines | 230 code | 39 blank | 22 comment | 21 complexity | b1e6168d7ecc552c3d271404d97268ab MD5 | raw file
  1. # vim: tabstop=4 shiftwidth=4 softtabstop=4
  2. # Copyright (c) 2011 Justin Santa Barbara
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. import json
  16. import httplib
  17. import urllib
  18. import urlparse
  19. from nova import log as logging
  20. LOG = logging.getLogger(__name__)
  21. class OpenStackApiException(Exception):
  22. def __init__(self, message=None, response=None):
  23. self.response = response
  24. if not message:
  25. message = 'Unspecified error'
  26. if response:
  27. _status = response.status
  28. _body = response.read()
  29. message = _('%(message)s\nStatus Code: %(_status)s\n'
  30. 'Body: %(_body)s') % locals()
  31. super(OpenStackApiException, self).__init__(message)
  32. class OpenStackApiAuthenticationException(OpenStackApiException):
  33. def __init__(self, response=None, message=None):
  34. if not message:
  35. message = _("Authentication error")
  36. super(OpenStackApiAuthenticationException, self).__init__(message,
  37. response)
  38. class OpenStackApiAuthorizationException(OpenStackApiException):
  39. def __init__(self, response=None, message=None):
  40. if not message:
  41. message = _("Authorization error")
  42. super(OpenStackApiAuthorizationException, self).__init__(message,
  43. response)
  44. class OpenStackApiNotFoundException(OpenStackApiException):
  45. def __init__(self, response=None, message=None):
  46. if not message:
  47. message = _("Item not found")
  48. super(OpenStackApiNotFoundException, self).__init__(message, response)
  49. class TestOpenStackClient(object):
  50. """Simple OpenStack API Client.
  51. This is a really basic OpenStack API client that is under our control,
  52. so we can make changes / insert hooks for testing
  53. """
  54. def __init__(self, auth_user, auth_key, auth_uri):
  55. super(TestOpenStackClient, self).__init__()
  56. self.auth_result = None
  57. self.auth_user = auth_user
  58. self.auth_key = auth_key
  59. self.auth_uri = auth_uri
  60. # default project_id
  61. self.project_id = 'openstack'
  62. def request(self, url, method='GET', body=None, headers=None):
  63. _headers = {'Content-Type': 'application/json'}
  64. _headers.update(headers or {})
  65. parsed_url = urlparse.urlparse(url)
  66. port = parsed_url.port
  67. hostname = parsed_url.hostname
  68. scheme = parsed_url.scheme
  69. if scheme == 'http':
  70. conn = httplib.HTTPConnection(hostname,
  71. port=port)
  72. elif scheme == 'https':
  73. conn = httplib.HTTPSConnection(hostname,
  74. port=port)
  75. else:
  76. raise OpenStackApiException("Unknown scheme: %s" % url)
  77. relative_url = parsed_url.path
  78. if parsed_url.query:
  79. relative_url = relative_url + "?" + parsed_url.query
  80. LOG.info(_("Doing %(method)s on %(relative_url)s") % locals())
  81. if body:
  82. LOG.info(_("Body: %s") % body)
  83. conn.request(method, relative_url, body, _headers)
  84. response = conn.getresponse()
  85. return response
  86. def _authenticate(self):
  87. if self.auth_result:
  88. return self.auth_result
  89. auth_uri = self.auth_uri
  90. headers = {'X-Auth-User': self.auth_user,
  91. 'X-Auth-Key': self.auth_key,
  92. 'X-Auth-Project-Id': self.project_id}
  93. response = self.request(auth_uri,
  94. headers=headers)
  95. http_status = response.status
  96. LOG.debug(_("%(auth_uri)s => code %(http_status)s") % locals())
  97. if http_status == 401:
  98. raise OpenStackApiAuthenticationException(response=response)
  99. auth_headers = {}
  100. for k, v in response.getheaders():
  101. auth_headers[k] = v
  102. self.auth_result = auth_headers
  103. return self.auth_result
  104. def api_request(self, relative_uri, check_response_status=None, **kwargs):
  105. auth_result = self._authenticate()
  106. # NOTE(justinsb): httplib 'helpfully' converts headers to lower case
  107. base_uri = auth_result['x-server-management-url']
  108. full_uri = '%s/%s' % (base_uri, relative_uri)
  109. headers = kwargs.setdefault('headers', {})
  110. headers['X-Auth-Token'] = auth_result['x-auth-token']
  111. response = self.request(full_uri, **kwargs)
  112. http_status = response.status
  113. LOG.debug(_("%(relative_uri)s => code %(http_status)s") % locals())
  114. if check_response_status:
  115. if not http_status in check_response_status:
  116. if http_status == 404:
  117. raise OpenStackApiNotFoundException(response=response)
  118. elif http_status == 401:
  119. raise OpenStackApiAuthorizationException(response=response)
  120. else:
  121. raise OpenStackApiException(
  122. message=_("Unexpected status code"),
  123. response=response)
  124. return response
  125. def _decode_json(self, response):
  126. body = response.read()
  127. LOG.debug(_("Decoding JSON: %s") % (body))
  128. if body:
  129. return json.loads(body)
  130. else:
  131. return ""
  132. def api_get(self, relative_uri, **kwargs):
  133. kwargs.setdefault('check_response_status', [200])
  134. response = self.api_request(relative_uri, **kwargs)
  135. return self._decode_json(response)
  136. def api_post(self, relative_uri, body, **kwargs):
  137. kwargs['method'] = 'POST'
  138. if body:
  139. headers = kwargs.setdefault('headers', {})
  140. headers['Content-Type'] = 'application/json'
  141. kwargs['body'] = json.dumps(body)
  142. kwargs.setdefault('check_response_status', [200, 202])
  143. response = self.api_request(relative_uri, **kwargs)
  144. return self._decode_json(response)
  145. def api_put(self, relative_uri, body, **kwargs):
  146. kwargs['method'] = 'PUT'
  147. if body:
  148. headers = kwargs.setdefault('headers', {})
  149. headers['Content-Type'] = 'application/json'
  150. kwargs['body'] = json.dumps(body)
  151. kwargs.setdefault('check_response_status', [200, 202, 204])
  152. response = self.api_request(relative_uri, **kwargs)
  153. return self._decode_json(response)
  154. def api_delete(self, relative_uri, **kwargs):
  155. kwargs['method'] = 'DELETE'
  156. kwargs.setdefault('check_response_status', [200, 202, 204])
  157. return self.api_request(relative_uri, **kwargs)
  158. def get_server(self, server_id):
  159. return self.api_get('/servers/%s' % server_id)['server']
  160. def get_servers(self, detail=True, search_opts=None):
  161. rel_url = '/servers/detail' if detail else '/servers'
  162. if search_opts is not None:
  163. qparams = {}
  164. for opt, val in search_opts.iteritems():
  165. qparams[opt] = val
  166. if qparams:
  167. query_string = "?%s" % urllib.urlencode(qparams)
  168. rel_url += query_string
  169. return self.api_get(rel_url)['servers']
  170. def post_server(self, server):
  171. response = self.api_post('/servers', server)
  172. if 'reservation_id' in response:
  173. return response
  174. else:
  175. return response['server']
  176. def put_server(self, server_id, server):
  177. return self.api_put('/servers/%s' % server_id, server)
  178. def post_server_action(self, server_id, data):
  179. return self.api_post('/servers/%s/action' % server_id, data)
  180. def delete_server(self, server_id):
  181. return self.api_delete('/servers/%s' % server_id)
  182. def get_image(self, image_id):
  183. return self.api_get('/images/%s' % image_id)['image']
  184. def get_images(self, detail=True):
  185. rel_url = '/images/detail' if detail else '/images'
  186. return self.api_get(rel_url)['images']
  187. def post_image(self, image):
  188. return self.api_post('/images', image)['image']
  189. def delete_image(self, image_id):
  190. return self.api_delete('/images/%s' % image_id)
  191. def get_flavor(self, flavor_id):
  192. return self.api_get('/flavors/%s' % flavor_id)['flavor']
  193. def get_flavors(self, detail=True):
  194. rel_url = '/flavors/detail' if detail else '/flavors'
  195. return self.api_get(rel_url)['flavors']
  196. def post_flavor(self, flavor):
  197. return self.api_post('/flavors', flavor)['flavor']
  198. def delete_flavor(self, flavor_id):
  199. return self.api_delete('/flavors/%s' % flavor_id)
  200. def get_volume(self, volume_id):
  201. return self.api_get('/volumes/%s' % volume_id)['volume']
  202. def get_volumes(self, detail=True):
  203. rel_url = '/volumes/detail' if detail else '/volumes'
  204. return self.api_get(rel_url)['volumes']
  205. def post_volume(self, volume):
  206. return self.api_post('/volumes', volume)['volume']
  207. def delete_volume(self, volume_id):
  208. return self.api_delete('/volumes/%s' % volume_id)
  209. def get_server_volume(self, server_id, attachment_id):
  210. return self.api_get('/servers/%s/os-volume_attachments/%s' %
  211. (server_id, attachment_id))['volumeAttachment']
  212. def get_server_volumes(self, server_id):
  213. return self.api_get('/servers/%s/os-volume_attachments' %
  214. (server_id))['volumeAttachments']
  215. def post_server_volume(self, server_id, volume_attachment):
  216. return self.api_post('/servers/%s/os-volume_attachments' %
  217. (server_id), volume_attachment
  218. )['volumeAttachment']
  219. def delete_server_volume(self, server_id, attachment_id):
  220. return self.api_delete('/servers/%s/os-volume_attachments/%s' %
  221. (server_id, attachment_id))