blob: 707f55e4bae0cddf2ec67e27695b6b4f6673fa97 [file] [log] [blame]
Soren Hansenbc1d3a02011-09-08 13:33:17 +02001
2import base64
Brian Waldonc062b442011-10-27 17:13:41 -04003import datetime
Soren Hansenbc1d3a02011-09-08 13:33:17 +02004import json
5import os
6
Soren Hansenbc1d3a02011-09-08 13:33:17 +02007from kong import openstack
8from kong import exceptions
Soren Hansen6adacc82011-09-09 13:34:35 +02009from kong import tests
Soren Hansenbc1d3a02011-09-08 13:33:17 +020010from kong.common import ssh
Brian Waldonc062b442011-10-27 17:13:41 -040011from kong.common import utils
Soren Hansenbc1d3a02011-09-08 13:33:17 +020012
13
Soren Hansen6adacc82011-09-09 13:34:35 +020014class ServersTest(tests.FunctionalTest):
Soren Hansen4480f602011-09-09 16:12:35 +020015 def setUp(self):
Soren Hansen6adacc82011-09-09 13:34:35 +020016 super(ServersTest, self).setUp()
Soren Hansend6b047a2011-09-09 13:39:32 +020017 self.os = openstack.Manager(self.nova)
Soren Hansena86180a2011-09-09 16:22:26 +020018 self.image_ref = self.glance['image_id']
19 self.flavor_ref = self.nova['flavor_ref']
20 self.ssh_timeout = self.nova['ssh_timeout']
21 self.build_timeout = self.nova['build_timeout']
Soren Hansenbc1d3a02011-09-08 13:33:17 +020022
Aaron Lee35b8c922011-10-18 17:07:19 -050023 def tearDown(self):
24 if getattr(self, 'server_id', False):
25 self.os.nova.delete_server(self.server_id)
26
Brian Lamar73aefd32011-11-03 14:22:00 -040027 def _assert_created_server_entity(self, created_server):
28 actual_keys = set(created_server.keys())
29 expected_keys = set((
30 'id',
31 'adminPass',
32 'links',
33 ))
34 print actual_keys
35 print expected_keys
36 self.assertTrue(expected_keys <= actual_keys)
37 self._assert_server_links(created_server)
38
Soren Hansenbc1d3a02011-09-08 13:33:17 +020039 def _assert_server_entity(self, server):
40 actual_keys = set(server.keys())
41 expected_keys = set((
Soren Hansenbc1d3a02011-09-08 13:33:17 +020042 'accessIPv4',
43 'accessIPv6',
Aaron Lee84d2f132011-10-25 11:47:47 -050044 'addresses',
45 'created',
46 'flavor',
47 'hostId',
48 'id',
49 'image',
50 'links',
51 'metadata',
52 'name',
53 'progress',
54 'status',
55 'updated',
Soren Hansenbc1d3a02011-09-08 13:33:17 +020056 ))
57 self.assertTrue(expected_keys <= actual_keys)
Brian Lamar73aefd32011-11-03 14:22:00 -040058 self._assert_server_links(server)
Soren Hansenbc1d3a02011-09-08 13:33:17 +020059
Brian Lamar73aefd32011-11-03 14:22:00 -040060 def _assert_server_links(self, server):
Soren Hansenbc1d3a02011-09-08 13:33:17 +020061 server_id = str(server['id'])
Soren Hansena86180a2011-09-09 16:22:26 +020062 host = self.nova['host']
63 port = self.nova['port']
Soren Hansenbc1d3a02011-09-08 13:33:17 +020064 api_url = '%s:%s' % (host, port)
Soren Hansena86180a2011-09-09 16:22:26 +020065 base_url = os.path.join(api_url, self.nova['apiver'])
Soren Hansenbc1d3a02011-09-08 13:33:17 +020066
67 self_link = 'http://' + os.path.join(base_url,
Aaron Lee35b8c922011-10-18 17:07:19 -050068 self.os.nova.project_id,
Soren Hansenbc1d3a02011-09-08 13:33:17 +020069 'servers', server_id)
70 bookmark_link = 'http://' + os.path.join(api_url,
Aaron Lee35b8c922011-10-18 17:07:19 -050071 self.os.nova.project_id,
Soren Hansenbc1d3a02011-09-08 13:33:17 +020072 'servers', server_id)
73
74 expected_links = [
75 {
76 'rel': 'self',
77 'href': self_link,
78 },
79 {
80 'rel': 'bookmark',
81 'href': bookmark_link,
82 },
83 ]
84
85 self.assertEqual(server['links'], expected_links)
86
Brian Waldonc062b442011-10-27 17:13:41 -040087 def test_build_update_delete(self):
88 """Build and delete a server"""
89
90 server_password = 'testpwd'
Soren Hansenbc1d3a02011-09-08 13:33:17 +020091
92 expected_server = {
93 'name': 'testserver',
Soren Hansenbc1d3a02011-09-08 13:33:17 +020094 'imageRef': self.image_ref,
95 'flavorRef': self.flavor_ref,
Brian Waldonc062b442011-10-27 17:13:41 -040096 'metadata': {'testEntry': 'testValue'},
Soren Hansenbc1d3a02011-09-08 13:33:17 +020097 }
98
99 post_body = json.dumps({'server': expected_server})
100 response, body = self.os.nova.request('POST',
101 '/servers',
102 body=post_body)
103
Brian Waldonc062b442011-10-27 17:13:41 -0400104 # Ensure attributes were returned
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200105 self.assertEqual(response.status, 202)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200106 _body = json.loads(body)
107 self.assertEqual(_body.keys(), ['server'])
108 created_server = _body['server']
Brian Lamar73aefd32011-11-03 14:22:00 -0400109 admin_pass = created_server['adminPass']
110 self._assert_created_server_entity(created_server)
Brian Waldonc062b442011-10-27 17:13:41 -0400111 self.server_id = created_server['id']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200112
Brian Waldonc062b442011-10-27 17:13:41 -0400113 # Get server again and ensure attributes stuck
114 server = self.os.nova.get_server(self.server_id)
115 self._assert_server_entity(server)
116 self.assertEqual(server['name'], expected_server['name'])
117 self.assertEqual(server['accessIPv4'], '')
118 self.assertEqual(server['accessIPv6'], '')
Brian Lamar73aefd32011-11-03 14:22:00 -0400119 self.assertEqual(server['metadata'], {'testEntry': 'testValue'})
Brian Waldonc062b442011-10-27 17:13:41 -0400120
121 # Parse last-updated time
122 update_time = utils.load_isotime(server['created'])
123
124 # Ensure server not returned with future changes-since
125 future_time = utils.dump_isotime(update_time + datetime.timedelta(100))
126 params = 'changes-since=%s' % future_time
127 response, body = self.os.nova.request('GET', '/servers?%s' % params)
128 servers = json.loads(body)['servers']
129 self.assertTrue(len(servers) == 0)
130
131 # Ensure server is returned with past changes-since
132 future_time = utils.dump_isotime(update_time - datetime.timedelta(1))
133 params = 'changes-since=%s' % future_time
134 response, body = self.os.nova.request('GET', '/servers?%s' % params)
135 servers = json.loads(body)['servers']
136 server_ids = map(lambda x: x['id'], servers)
137 self.assertTrue(self.server_id in server_ids)
138
139 # Update name
140 new_name = 'testserver2'
141 new_server = {'name': new_name}
142 put_body = json.dumps({'server': new_server})
143 url = '/servers/%s' % self.server_id
144 resp, body = self.os.nova.request('PUT', url, body=put_body)
145
146 # Output from update should be a full server
147 self.assertEqual(resp.status, 200)
148 data = json.loads(body)
149 self.assertEqual(data.keys(), ['server'])
150 self._assert_server_entity(data['server'])
151 self.assertEqual(new_name, data['server']['name'])
152
153 # Check that name was changed
154 updated_server = self.os.nova.get_server(self.server_id)
155 self._assert_server_entity(updated_server)
156 self.assertEqual(new_name, updated_server['name'])
157
158 # Update accessIPv4
159 new_server = {'accessIPv4': '192.168.0.200'}
160 put_body = json.dumps({'server': new_server})
161 url = '/servers/%s' % self.server_id
162 resp, body = self.os.nova.request('PUT', url, body=put_body)
163
164 # Output from update should be a full server
165 self.assertEqual(resp.status, 200)
166 data = json.loads(body)
167 self.assertEqual(data.keys(), ['server'])
168 self._assert_server_entity(data['server'])
169 self.assertEqual('192.168.0.200', data['server']['accessIPv4'])
170
171 # Check that accessIPv4 was changed
172 updated_server = self.os.nova.get_server(self.server_id)
173 self._assert_server_entity(updated_server)
174 self.assertEqual('192.168.0.200', updated_server['accessIPv4'])
175
176 # Update accessIPv6
177 new_server = {'accessIPv6': 'feed::beef'}
178 put_body = json.dumps({'server': new_server})
179 url = '/servers/%s' % self.server_id
180 resp, body = self.os.nova.request('PUT', url, body=put_body)
181
182 # Output from update should be a full server
183 self.assertEqual(resp.status, 200)
184 data = json.loads(body)
185 self.assertEqual(data.keys(), ['server'])
186 self._assert_server_entity(data['server'])
187 self.assertEqual('feed::beef', data['server']['accessIPv6'])
188
189 # Check that accessIPv6 was changed
190 updated_server = self.os.nova.get_server(self.server_id)
191 self._assert_server_entity(updated_server)
192 self.assertEqual('feed::beef', updated_server['accessIPv6'])
193
194 # Check metadata subresource
195 url = '/servers/%s/metadata' % self.server_id
196 response, body = self.os.nova.request('GET', url)
197 self.assertEqual(200, response.status)
198
199 result = json.loads(body)
200 expected = {'metadata': {'testEntry': 'testValue'}}
201 self.assertEqual(expected, result)
202
203 # Ensure metadata container can be modified
204 expected = {
205 'metadata': {
206 'new_meta1': 'new_value1',
207 'new_meta2': 'new_value2',
208 },
209 }
210 post_body = json.dumps(expected)
211 url = '/servers/%s/metadata' % self.server_id
212 response, body = self.os.nova.request('POST', url, body=post_body)
213 self.assertEqual(200, response.status)
214 result = json.loads(body)
215 expected['metadata']['testEntry'] = 'testValue'
216 self.assertEqual(expected, result)
217
218 # Ensure values stick
219 url = '/servers/%s/metadata' % self.server_id
220 response, body = self.os.nova.request('GET', url)
221 self.assertEqual(200, response.status)
222 result = json.loads(body)
223 self.assertEqual(expected, result)
224
225 # Ensure metadata container can be overwritten
226 expected = {
227 'metadata': {
228 'new_meta3': 'new_value3',
229 'new_meta4': 'new_value4',
230 },
231 }
232 url = '/servers/%s/metadata' % self.server_id
233 post_body = json.dumps(expected)
234 response, body = self.os.nova.request('PUT', url, body=post_body)
235 self.assertEqual(200, response.status)
236 result = json.loads(body)
237 self.assertEqual(expected, result)
238
239 # Ensure values stick
240 url = '/servers/%s/metadata' % self.server_id
241 response, body = self.os.nova.request('GET', url)
242 self.assertEqual(200, response.status)
243 result = json.loads(body)
244 self.assertEqual(expected, result)
245
246 # Set specific key
247 expected_meta = {'meta': {'new_meta5': 'new_value5'}}
248 put_body = json.dumps(expected_meta)
249 url = '/servers/%s/metadata/new_meta5' % self.server_id
250 response, body = self.os.nova.request('PUT', url, body=put_body)
251 self.assertEqual(200, response.status)
252 result = json.loads(body)
253 self.assertDictEqual(expected_meta, result)
254
255 # Ensure value sticks
256 expected_metadata = {
257 'metadata': {
258 'new_meta3': 'new_value3',
259 'new_meta4': 'new_value4',
260 'new_meta5': 'new_value5',
261 },
262 }
263 url = '/servers/%s/metadata' % self.server_id
264 response, body = self.os.nova.request('GET', url)
265 result = json.loads(body)
266 self.assertDictEqual(expected_metadata, result)
267
268 # Update existing key
269 expected_meta = {'meta': {'new_meta4': 'new_value6'}}
270 put_body = json.dumps(expected_meta)
271 url = '/servers/%s/metadata/new_meta4' % self.server_id
272 response, body = self.os.nova.request('PUT', url, body=put_body)
273 self.assertEqual(200, response.status)
274 result = json.loads(body)
275 self.assertEqual(expected_meta, result)
276
277 # Ensure value sticks
278 expected_metadata = {
279 'metadata': {
280 'new_meta3': 'new_value3',
281 'new_meta4': 'new_value6',
282 'new_meta5': 'new_value5',
283 },
284 }
285 url = '/servers/%s/metadata' % self.server_id
286 response, body = self.os.nova.request('GET', url)
287 result = json.loads(body)
288 self.assertDictEqual(expected_metadata, result)
289
290 # Delete a certain key
291 url = '/servers/%s/metadata/new_meta3' % self.server_id
292 response, body = self.os.nova.request('DELETE', url)
293 self.assertEquals(204, response.status)
294
295 # Make sure the key is gone
296 url = '/servers/%s/metadata/new_meta3' % self.server_id
297 response, body = self.os.nova.request('GET', url)
298 self.assertEquals(404, response.status)
299
300 # Delete a nonexistant key
301 url = '/servers/%s/metadata/new_meta3' % self.server_id
302 response, body = self.os.nova.request('DELETE', url)
303 self.assertEquals(404, response.status)
304
305 # Wait for instance to boot
306 self.os.nova.wait_for_server_status(self.server_id,
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200307 'ACTIVE',
308 timeout=self.build_timeout)
309
Brian Waldonc062b442011-10-27 17:13:41 -0400310 # Look for 'addresses' attribute on server
311 url = '/servers/%s' % self.server_id
312 response, body = self.os.nova.request('GET', url)
313 self.assertEqual(response.status, 200)
314 body = json.loads(body)
315 self.assertTrue('addresses' in body['server'].keys())
316 server_addresses = body['server']['addresses']
317
318 # Addresses should be available from subresource
319 url = '/servers/%s/ips' % self.server_id
320 response, body = self.os.nova.request('GET', url)
321 self.assertEqual(response.status, 200)
322 body = json.loads(body)
323 self.assertEqual(body.keys(), ['addresses'])
324 ips_addresses = body['addresses']
325
326 # Ensure both resources return identical information
327 self.assertEqual(server_addresses, ips_addresses)
328
329 # Validate entities within network containers
330 for (network, network_data) in ips_addresses.items():
331 url = '/servers/%s/ips/%s' % (self.server_id, network)
332 response, body = self.os.nova.request('GET', url)
333 self.assertEqual(response.status, 200)
334 body = json.loads(body)
335 self.assertEqual(body.keys(), [network])
336 self.assertEqual(body[network], network_data)
337
338 # Check each IP entity
339 for ip_data in network_data:
340 self.assertEqual(set(ip_data.keys()), set(['addr', 'version']))
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200341
342 # Find IP of server
343 try:
Brian Waldonc062b442011-10-27 17:13:41 -0400344 (_, network) = server_addresses.items()[0]
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200345 ip = network[0]['addr']
346 except KeyError:
347 self.fail("Failed to retrieve IP address from server entity")
348
349 # Assert password works
Aaron Lee35b8c922011-10-18 17:07:19 -0500350 if int(self.nova['ssh_timeout']) > 0:
351 client = ssh.Client(ip, 'root', admin_pass, self.ssh_timeout)
352 self.assertTrue(client.test_connection_auth())
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200353
Brian Waldonc062b442011-10-27 17:13:41 -0400354 self.os.nova.delete_server(self.server_id)
355
356 # Poll server until deleted
357 try:
358 url = '/servers/%s' % self.server_id
359 self.os.nova.poll_request_status('GET', url, 404)
360 except exceptions.TimeoutException:
361 self.fail("Server deletion timed out")
362 test_build_update_delete.tags = ['nova']
363
364 def test_build_with_password_and_file(self):
365 """Build a server with a custom password and an injected file"""
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200366
367 file_contents = 'testing'
368
369 expected_server = {
370 'name': 'testserver',
371 'metadata': {
372 'key1': 'value1',
373 'key2': 'value2',
374 },
375 'personality': [
376 {
377 'path': '/etc/test.txt',
378 'contents': base64.b64encode(file_contents),
379 },
380 ],
381 'imageRef': self.image_ref,
382 'flavorRef': self.flavor_ref,
Brian Waldonc062b442011-10-27 17:13:41 -0400383 'adminPass': 'secrete',
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200384 }
385
386 post_body = json.dumps({'server': expected_server})
387 response, body = self.os.nova.request('POST',
388 '/servers',
389 body=post_body)
390
391 self.assertEqual(response.status, 202)
392
393 _body = json.loads(body)
394 self.assertEqual(_body.keys(), ['server'])
395 created_server = _body['server']
Aaron Lee35b8c922011-10-18 17:07:19 -0500396 self.server_id = _body['server']['id']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200397
Brian Lamar73aefd32011-11-03 14:22:00 -0400398 admin_pass = created_server['adminPass']
Brian Waldonc062b442011-10-27 17:13:41 -0400399 self.assertEqual(expected_server['adminPass'], admin_pass)
Brian Lamar73aefd32011-11-03 14:22:00 -0400400 self._assert_created_server_entity(created_server)
401 self.assertEqual(expected_server['metadata'], {
402 'key1': 'value1',
403 'key2': 'value2',
404 })
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200405
406 self.os.nova.wait_for_server_status(created_server['id'],
407 'ACTIVE',
408 timeout=self.build_timeout)
409
410 server = self.os.nova.get_server(created_server['id'])
411
412 # Find IP of server
413 try:
414 (_, network) = server['addresses'].popitem()
415 ip = network[0]['addr']
416 except KeyError:
417 self.fail("Failed to retrieve IP address from server entity")
418
419 # Assert injected file is on instance, also verifying password works
Aaron Lee35b8c922011-10-18 17:07:19 -0500420 if int(self.nova['ssh_timeout']) > 0:
421 client = ssh.Client(ip, 'root', admin_pass, self.ssh_timeout)
422 injected_file = client.exec_command('cat /etc/test.txt')
423 self.assertEqual(injected_file, file_contents)
Brian Waldonc062b442011-10-27 17:13:41 -0400424 test_build_with_password_and_file.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200425
Brian Waldonc062b442011-10-27 17:13:41 -0400426 def test_delete_while_building(self):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200427 """Delete a server while building"""
428
429 # Make create server request
430 server = {
431 'name' : 'testserver',
432 'imageRef' : self.image_ref,
433 'flavorRef' : self.flavor_ref,
434 }
435 created_server = self.os.nova.create_server(server)
436
437 # Server should immediately be accessible, but in have building status
438 server = self.os.nova.get_server(created_server['id'])
439 self.assertEqual(server['status'], 'BUILD')
440
441 self.os.nova.delete_server(created_server['id'])
442
443 # Poll server until deleted
444 try:
445 url = '/servers/%s' % created_server['id']
446 self.os.nova.poll_request_status('GET', url, 404)
447 except exceptions.TimeoutException:
448 self.fail("Server deletion timed out")
Brian Waldonc062b442011-10-27 17:13:41 -0400449 test_delete_while_building.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200450
Brian Waldonc062b442011-10-27 17:13:41 -0400451 def test_create_with_invalid_image(self):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200452 """Create a server with an unknown image"""
453
454 post_body = json.dumps({
455 'server' : {
456 'name' : 'testserver',
457 'imageRef' : -1,
458 'flavorRef' : self.flavor_ref,
459 }
460 })
461
462 resp, body = self.os.nova.request('POST', '/servers', body=post_body)
463
464 self.assertEqual(400, resp.status)
465
466 fault = json.loads(body)
467 expected_fault = {
468 "badRequest": {
469 "message": "Cannot find requested image",
470 "code": 400,
471 },
472 }
473 # KNOWN-ISSUE - The error message is confusing and should be improved
474 #self.assertEqual(fault, expected_fault)
Brian Waldonc062b442011-10-27 17:13:41 -0400475 test_create_with_invalid_image.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200476
Brian Waldonc062b442011-10-27 17:13:41 -0400477 def test_create_with_invalid_flavor(self):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200478 """Create a server with an unknown flavor"""
479
480 post_body = json.dumps({
481 'server' : {
482 'name' : 'testserver',
483 'imageRef' : self.image_ref,
484 'flavorRef' : -1,
485 }
486 })
487
488 resp, body = self.os.nova.request('POST', '/servers', body=post_body)
489
490 self.assertEqual(400, resp.status)
491
492 fault = json.loads(body)
493 expected_fault = {
494 "badRequest": {
495 "message": "Cannot find requested flavor",
496 "code": 400,
497 },
498 }
499 # KNOWN-ISSUE lp804084
500 #self.assertEqual(fault, expected_fault)
Brian Waldonc062b442011-10-27 17:13:41 -0400501 test_create_with_invalid_flavor.tags = ['nova']