blob: 276696e6a65eb1b2d96ef7692b1689702038caa4 [file] [log] [blame]
Daryl Walleck1465d612011-11-02 02:22:15 -05001import json
Jay Pipes7f757632011-12-02 15:53:32 -05002import httplib2
Daryl Walleck8a707db2012-01-25 00:46:24 -06003import logging
4import sys
Eoghan Glynna5598972012-03-01 09:27:17 -05005import time
Daryl Wallecked8bef32011-12-05 23:02:08 -06006from tempest import exceptions
Daryl Walleck1465d612011-11-02 02:22:15 -05007
8
Eoghan Glynna5598972012-03-01 09:27:17 -05009# redrive rate limited calls at most twice
10MAX_RECURSION_DEPTH = 2
11
12
Daryl Walleck1465d612011-11-02 02:22:15 -050013class RestClient(object):
14
Daryl Walleck587385b2012-03-03 13:00:26 -060015 def __init__(self, config, user, password, auth_url, service,
16 tenant_name=None):
Daryl Walleck8a707db2012-01-25 00:46:24 -060017 self.log = logging.getLogger(__name__)
18 self.log.setLevel(logging.ERROR)
Jay Pipes7f757632011-12-02 15:53:32 -050019 self.config = config
Daryl Walleck587385b2012-03-03 13:00:26 -060020 if self.config.identity.strategy == 'keystone':
21 self.token, self.base_url = self.keystone_auth(user,
22 password,
23 auth_url,
24 service,
25 tenant_name)
Daryl Walleck1465d612011-11-02 02:22:15 -050026 else:
27 self.token, self.base_url = self.basic_auth(user,
Daryl Walleck587385b2012-03-03 13:00:26 -060028 password,
Daryl Walleck1465d612011-11-02 02:22:15 -050029 auth_url)
30
Daryl Walleck587385b2012-03-03 13:00:26 -060031 def basic_auth(self, user, password, auth_url):
Daryl Walleck1465d612011-11-02 02:22:15 -050032 """
33 Provides authentication for the target API
34 """
35
36 params = {}
37 params['headers'] = {'User-Agent': 'Test-Client', 'X-Auth-User': user,
Daryl Walleck587385b2012-03-03 13:00:26 -060038 'X-Auth-Key': password}
Daryl Walleck1465d612011-11-02 02:22:15 -050039
40 self.http_obj = httplib2.Http()
41 resp, body = self.http_obj.request(auth_url, 'GET', **params)
42 try:
43 return resp['x-auth-token'], resp['x-server-management-url']
44 except:
45 raise
46
Daryl Walleck587385b2012-03-03 13:00:26 -060047 def keystone_auth(self, user, password, auth_url, service, tenant_name):
Daryl Walleck1465d612011-11-02 02:22:15 -050048 """
Daryl Walleck587385b2012-03-03 13:00:26 -060049 Provides authentication via Keystone
Daryl Walleck1465d612011-11-02 02:22:15 -050050 """
51
52 creds = {'auth': {
53 'passwordCredentials': {
54 'username': user,
Daryl Walleck587385b2012-03-03 13:00:26 -060055 'password': password,
Daryl Walleck1465d612011-11-02 02:22:15 -050056 },
57 'tenantName': tenant_name
58 }
59 }
60
61 self.http_obj = httplib2.Http()
62 headers = {'Content-Type': 'application/json'}
63 body = json.dumps(creds)
64 resp, body = self.http_obj.request(auth_url, 'POST',
65 headers=headers, body=body)
66
Jay Pipes7f757632011-12-02 15:53:32 -050067 if resp.status == 200:
68 try:
69 auth_data = json.loads(body)['access']
70 token = auth_data['token']['id']
Jay Pipes7f757632011-12-02 15:53:32 -050071 except Exception, e:
Adam Gandelmane2d46b42012-01-03 17:40:44 -080072 print "Failed to obtain token for user: %s" % e
Jay Pipes7f757632011-12-02 15:53:32 -050073 raise
Adam Gandelmane2d46b42012-01-03 17:40:44 -080074
75 mgmt_url = None
76 for ep in auth_data['serviceCatalog']:
Daryl Walleckb90a1a62012-02-27 11:23:10 -060077 if ep["type"] == service:
Adam Gandelmane2d46b42012-01-03 17:40:44 -080078 mgmt_url = ep['endpoints'][0]['publicURL']
Jay Pipes745259c2012-01-23 23:24:54 -050079 # See LP#920817. The tenantId is *supposed*
80 # to be returned for each endpoint accorsing to the
81 # Keystone spec. But... it isn't, so we have to parse
82 # the tenant ID out of hte public URL :(
83 tenant_id = mgmt_url.split('/')[-1]
Adam Gandelmane2d46b42012-01-03 17:40:44 -080084 break
85
86 if mgmt_url == None:
87 raise exceptions.EndpointNotFound(service)
88
89 #TODO (dwalleck): This is a horrible stopgap.
90 #Need to join strings more cleanly
91 temp = mgmt_url.rsplit('/')
92 service_url = temp[0] + '//' + temp[2] + '/' + temp[3] + '/'
Jay Pipes745259c2012-01-23 23:24:54 -050093 management_url = service_url + tenant_id
Adam Gandelmane2d46b42012-01-03 17:40:44 -080094 return token, management_url
Jay Pipes7f757632011-12-02 15:53:32 -050095 elif resp.status == 401:
Daryl Wallecka22f57b2012-03-20 16:52:07 -050096 raise exceptions.AuthenticationFailure(user=user,
97 password=password)
Daryl Walleck1465d612011-11-02 02:22:15 -050098
99 def post(self, url, body, headers):
100 return self.request('POST', url, headers, body)
101
102 def get(self, url):
103 return self.request('GET', url)
104
105 def delete(self, url):
106 return self.request('DELETE', url)
107
108 def put(self, url, body, headers):
109 return self.request('PUT', url, headers, body)
110
Daryl Walleck8a707db2012-01-25 00:46:24 -0600111 def _log(self, req_url, body, resp, resp_body):
112 self.log.error('Request URL: ' + req_url)
113 self.log.error('Request Body: ' + str(body))
114 self.log.error('Response Headers: ' + str(resp))
115 self.log.error('Response Body: ' + str(resp_body))
116
Eoghan Glynna5598972012-03-01 09:27:17 -0500117 def request(self, method, url, headers=None, body=None, depth=0):
Daryl Wallecke5b83d42011-11-10 14:39:02 -0600118 """A simple HTTP request interface."""
Daryl Walleck1465d612011-11-02 02:22:15 -0500119
120 self.http_obj = httplib2.Http()
121 if headers == None:
122 headers = {}
123 headers['X-Auth-Token'] = self.token
124
125 req_url = "%s/%s" % (self.base_url, url)
Daryl Walleck8a707db2012-01-25 00:46:24 -0600126 resp, resp_body = self.http_obj.request(req_url, method,
Daryl Walleck1465d612011-11-02 02:22:15 -0500127 headers=headers, body=body)
Jay Pipes5135bfc2012-01-05 15:46:49 -0500128
129 if resp.status == 404:
Daryl Walleck8a707db2012-01-25 00:46:24 -0600130 self._log(req_url, body, resp, resp_body)
131 raise exceptions.NotFound(resp_body)
Jay Pipes5135bfc2012-01-05 15:46:49 -0500132
Daryl Walleckadea1fa2011-11-15 18:36:39 -0600133 if resp.status == 400:
Daryl Walleck8a707db2012-01-25 00:46:24 -0600134 resp_body = json.loads(resp_body)
135 self._log(req_url, body, resp, resp_body)
136 raise exceptions.BadRequest(resp_body['badRequest']['message'])
Daryl Walleckadea1fa2011-11-15 18:36:39 -0600137
David Kranz5a23d862012-02-14 09:48:55 -0500138 if resp.status == 409:
139 resp_body = json.loads(resp_body)
140 self._log(req_url, body, resp, resp_body)
141 raise exceptions.Duplicate(resp_body)
142
Daryl Wallecked8bef32011-12-05 23:02:08 -0600143 if resp.status == 413:
Daryl Walleck8a707db2012-01-25 00:46:24 -0600144 resp_body = json.loads(resp_body)
145 self._log(req_url, body, resp, resp_body)
146 if 'overLimit' in resp_body:
147 raise exceptions.OverLimit(resp_body['overLimit']['message'])
Eoghan Glynna5598972012-03-01 09:27:17 -0500148 elif depth < MAX_RECURSION_DEPTH:
149 delay = resp['Retry-After'] if 'Retry-After' in resp else 60
150 time.sleep(int(delay))
151 return self.request(method, url, headers, body, depth + 1)
Jay Pipes9b043842012-01-23 23:34:26 -0500152 else:
153 raise exceptions.RateLimitExceeded(
Daryl Walleck8a707db2012-01-25 00:46:24 -0600154 message=resp_body['overLimitFault']['message'],
155 details=resp_body['overLimitFault']['details'])
Brian Lamar12d9b292011-12-08 12:41:21 -0500156
Daryl Wallecked8bef32011-12-05 23:02:08 -0600157 if resp.status in (500, 501):
Daryl Walleck8a707db2012-01-25 00:46:24 -0600158 resp_body = json.loads(resp_body)
159 self._log(req_url, body, resp, resp_body)
Daryl Walleckf0087032011-12-18 13:37:05 -0600160 #I'm seeing both computeFault and cloudServersFault come back.
161 #Will file a bug to fix, but leave as is for now.
162
Daryl Walleck8a707db2012-01-25 00:46:24 -0600163 if 'cloudServersFault' in resp_body:
164 message = resp_body['cloudServersFault']['message']
Daryl Walleckf0087032011-12-18 13:37:05 -0600165 else:
Daryl Walleck8a707db2012-01-25 00:46:24 -0600166 message = resp_body['computeFault']['message']
Daryl Walleckf0087032011-12-18 13:37:05 -0600167 raise exceptions.ComputeFault(message)
Daryl Wallecked8bef32011-12-05 23:02:08 -0600168
David Kranz5a23d862012-02-14 09:48:55 -0500169 if resp.status >= 400:
170 resp_body = json.loads(resp_body)
171 self._log(req_url, body, resp, resp_body)
172 raise exceptions.TempestException(str(resp.status))
173
Daryl Walleck8a707db2012-01-25 00:46:24 -0600174 return resp, resp_body