blob: 0957327744be54914c37d070f2057a8021d6bd56 [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
Soren Hansenbc1d3a02011-09-08 13:33:17 +020027 def _assert_server_entity(self, server):
28 actual_keys = set(server.keys())
29 expected_keys = set((
Soren Hansenbc1d3a02011-09-08 13:33:17 +020030 'accessIPv4',
31 'accessIPv6',
Aaron Lee84d2f132011-10-25 11:47:47 -050032 'addresses',
33 'created',
34 'flavor',
35 'hostId',
36 'id',
37 'image',
38 'links',
39 'metadata',
40 'name',
41 'progress',
42 'status',
43 'updated',
Soren Hansenbc1d3a02011-09-08 13:33:17 +020044 ))
45 self.assertTrue(expected_keys <= actual_keys)
46
47 server_id = str(server['id'])
Soren Hansena86180a2011-09-09 16:22:26 +020048 host = self.nova['host']
49 port = self.nova['port']
Soren Hansenbc1d3a02011-09-08 13:33:17 +020050 api_url = '%s:%s' % (host, port)
Soren Hansena86180a2011-09-09 16:22:26 +020051 base_url = os.path.join(api_url, self.nova['apiver'])
Soren Hansenbc1d3a02011-09-08 13:33:17 +020052
53 self_link = 'http://' + os.path.join(base_url,
Aaron Lee35b8c922011-10-18 17:07:19 -050054 self.os.nova.project_id,
Soren Hansenbc1d3a02011-09-08 13:33:17 +020055 'servers', server_id)
56 bookmark_link = 'http://' + os.path.join(api_url,
Aaron Lee35b8c922011-10-18 17:07:19 -050057 self.os.nova.project_id,
Soren Hansenbc1d3a02011-09-08 13:33:17 +020058 'servers', server_id)
59
60 expected_links = [
61 {
62 'rel': 'self',
63 'href': self_link,
64 },
65 {
66 'rel': 'bookmark',
67 'href': bookmark_link,
68 },
69 ]
70
71 self.assertEqual(server['links'], expected_links)
72
Brian Waldonc062b442011-10-27 17:13:41 -040073 def test_build_update_delete(self):
74 """Build and delete a server"""
75
76 server_password = 'testpwd'
Soren Hansenbc1d3a02011-09-08 13:33:17 +020077
78 expected_server = {
79 'name': 'testserver',
Soren Hansenbc1d3a02011-09-08 13:33:17 +020080 'imageRef': self.image_ref,
81 'flavorRef': self.flavor_ref,
Brian Waldonc062b442011-10-27 17:13:41 -040082 'metadata': {'testEntry': 'testValue'},
Soren Hansenbc1d3a02011-09-08 13:33:17 +020083 }
84
85 post_body = json.dumps({'server': expected_server})
86 response, body = self.os.nova.request('POST',
87 '/servers',
88 body=post_body)
89
Brian Waldonc062b442011-10-27 17:13:41 -040090 # Ensure attributes were returned
Soren Hansenbc1d3a02011-09-08 13:33:17 +020091 self.assertEqual(response.status, 202)
Soren Hansenbc1d3a02011-09-08 13:33:17 +020092 _body = json.loads(body)
93 self.assertEqual(_body.keys(), ['server'])
94 created_server = _body['server']
Soren Hansenbc1d3a02011-09-08 13:33:17 +020095 admin_pass = created_server.pop('adminPass')
96 self._assert_server_entity(created_server)
97 self.assertEqual(expected_server['name'], created_server['name'])
Brian Waldonc062b442011-10-27 17:13:41 -040098 self.assertEqual(created_server['accessIPv4'], '')
99 self.assertEqual(created_server['accessIPv6'], '')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200100 self.assertEqual(expected_server['metadata'],
101 created_server['metadata'])
Brian Waldonc062b442011-10-27 17:13:41 -0400102 self.server_id = created_server['id']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200103
Brian Waldonc062b442011-10-27 17:13:41 -0400104 # Get server again and ensure attributes stuck
105 server = self.os.nova.get_server(self.server_id)
106 self._assert_server_entity(server)
107 self.assertEqual(server['name'], expected_server['name'])
108 self.assertEqual(server['accessIPv4'], '')
109 self.assertEqual(server['accessIPv6'], '')
110 self.assertEqual(server['metadata'], created_server['metadata'])
111
112 # Parse last-updated time
113 update_time = utils.load_isotime(server['created'])
114
115 # Ensure server not returned with future changes-since
116 future_time = utils.dump_isotime(update_time + datetime.timedelta(100))
117 params = 'changes-since=%s' % future_time
118 response, body = self.os.nova.request('GET', '/servers?%s' % params)
119 servers = json.loads(body)['servers']
120 self.assertTrue(len(servers) == 0)
121
122 # Ensure server is returned with past changes-since
123 future_time = utils.dump_isotime(update_time - datetime.timedelta(1))
124 params = 'changes-since=%s' % future_time
125 response, body = self.os.nova.request('GET', '/servers?%s' % params)
126 servers = json.loads(body)['servers']
127 server_ids = map(lambda x: x['id'], servers)
128 self.assertTrue(self.server_id in server_ids)
129
130 # Update name
131 new_name = 'testserver2'
132 new_server = {'name': new_name}
133 put_body = json.dumps({'server': new_server})
134 url = '/servers/%s' % self.server_id
135 resp, body = self.os.nova.request('PUT', url, body=put_body)
136
137 # Output from update should be a full server
138 self.assertEqual(resp.status, 200)
139 data = json.loads(body)
140 self.assertEqual(data.keys(), ['server'])
141 self._assert_server_entity(data['server'])
142 self.assertEqual(new_name, data['server']['name'])
143
144 # Check that name was changed
145 updated_server = self.os.nova.get_server(self.server_id)
146 self._assert_server_entity(updated_server)
147 self.assertEqual(new_name, updated_server['name'])
148
149 # Update accessIPv4
150 new_server = {'accessIPv4': '192.168.0.200'}
151 put_body = json.dumps({'server': new_server})
152 url = '/servers/%s' % self.server_id
153 resp, body = self.os.nova.request('PUT', url, body=put_body)
154
155 # Output from update should be a full server
156 self.assertEqual(resp.status, 200)
157 data = json.loads(body)
158 self.assertEqual(data.keys(), ['server'])
159 self._assert_server_entity(data['server'])
160 self.assertEqual('192.168.0.200', data['server']['accessIPv4'])
161
162 # Check that accessIPv4 was changed
163 updated_server = self.os.nova.get_server(self.server_id)
164 self._assert_server_entity(updated_server)
165 self.assertEqual('192.168.0.200', updated_server['accessIPv4'])
166
167 # Update accessIPv6
168 new_server = {'accessIPv6': 'feed::beef'}
169 put_body = json.dumps({'server': new_server})
170 url = '/servers/%s' % self.server_id
171 resp, body = self.os.nova.request('PUT', url, body=put_body)
172
173 # Output from update should be a full server
174 self.assertEqual(resp.status, 200)
175 data = json.loads(body)
176 self.assertEqual(data.keys(), ['server'])
177 self._assert_server_entity(data['server'])
178 self.assertEqual('feed::beef', data['server']['accessIPv6'])
179
180 # Check that accessIPv6 was changed
181 updated_server = self.os.nova.get_server(self.server_id)
182 self._assert_server_entity(updated_server)
183 self.assertEqual('feed::beef', updated_server['accessIPv6'])
184
185 # Check metadata subresource
186 url = '/servers/%s/metadata' % self.server_id
187 response, body = self.os.nova.request('GET', url)
188 self.assertEqual(200, response.status)
189
190 result = json.loads(body)
191 expected = {'metadata': {'testEntry': 'testValue'}}
192 self.assertEqual(expected, result)
193
194 # Ensure metadata container can be modified
195 expected = {
196 'metadata': {
197 'new_meta1': 'new_value1',
198 'new_meta2': 'new_value2',
199 },
200 }
201 post_body = json.dumps(expected)
202 url = '/servers/%s/metadata' % self.server_id
203 response, body = self.os.nova.request('POST', url, body=post_body)
204 self.assertEqual(200, response.status)
205 result = json.loads(body)
206 expected['metadata']['testEntry'] = 'testValue'
207 self.assertEqual(expected, result)
208
209 # Ensure values stick
210 url = '/servers/%s/metadata' % self.server_id
211 response, body = self.os.nova.request('GET', url)
212 self.assertEqual(200, response.status)
213 result = json.loads(body)
214 self.assertEqual(expected, result)
215
216 # Ensure metadata container can be overwritten
217 expected = {
218 'metadata': {
219 'new_meta3': 'new_value3',
220 'new_meta4': 'new_value4',
221 },
222 }
223 url = '/servers/%s/metadata' % self.server_id
224 post_body = json.dumps(expected)
225 response, body = self.os.nova.request('PUT', url, body=post_body)
226 self.assertEqual(200, response.status)
227 result = json.loads(body)
228 self.assertEqual(expected, result)
229
230 # Ensure values stick
231 url = '/servers/%s/metadata' % self.server_id
232 response, body = self.os.nova.request('GET', url)
233 self.assertEqual(200, response.status)
234 result = json.loads(body)
235 self.assertEqual(expected, result)
236
237 # Set specific key
238 expected_meta = {'meta': {'new_meta5': 'new_value5'}}
239 put_body = json.dumps(expected_meta)
240 url = '/servers/%s/metadata/new_meta5' % self.server_id
241 response, body = self.os.nova.request('PUT', url, body=put_body)
242 self.assertEqual(200, response.status)
243 result = json.loads(body)
244 self.assertDictEqual(expected_meta, result)
245
246 # Ensure value sticks
247 expected_metadata = {
248 'metadata': {
249 'new_meta3': 'new_value3',
250 'new_meta4': 'new_value4',
251 'new_meta5': 'new_value5',
252 },
253 }
254 url = '/servers/%s/metadata' % self.server_id
255 response, body = self.os.nova.request('GET', url)
256 result = json.loads(body)
257 self.assertDictEqual(expected_metadata, result)
258
259 # Update existing key
260 expected_meta = {'meta': {'new_meta4': 'new_value6'}}
261 put_body = json.dumps(expected_meta)
262 url = '/servers/%s/metadata/new_meta4' % self.server_id
263 response, body = self.os.nova.request('PUT', url, body=put_body)
264 self.assertEqual(200, response.status)
265 result = json.loads(body)
266 self.assertEqual(expected_meta, result)
267
268 # Ensure value sticks
269 expected_metadata = {
270 'metadata': {
271 'new_meta3': 'new_value3',
272 'new_meta4': 'new_value6',
273 'new_meta5': 'new_value5',
274 },
275 }
276 url = '/servers/%s/metadata' % self.server_id
277 response, body = self.os.nova.request('GET', url)
278 result = json.loads(body)
279 self.assertDictEqual(expected_metadata, result)
280
281 # Delete a certain key
282 url = '/servers/%s/metadata/new_meta3' % self.server_id
283 response, body = self.os.nova.request('DELETE', url)
284 self.assertEquals(204, response.status)
285
286 # Make sure the key is gone
287 url = '/servers/%s/metadata/new_meta3' % self.server_id
288 response, body = self.os.nova.request('GET', url)
289 self.assertEquals(404, response.status)
290
291 # Delete a nonexistant key
292 url = '/servers/%s/metadata/new_meta3' % self.server_id
293 response, body = self.os.nova.request('DELETE', url)
294 self.assertEquals(404, response.status)
295
296 # Wait for instance to boot
297 self.os.nova.wait_for_server_status(self.server_id,
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200298 'ACTIVE',
299 timeout=self.build_timeout)
300
Brian Waldonc062b442011-10-27 17:13:41 -0400301 # Look for 'addresses' attribute on server
302 url = '/servers/%s' % self.server_id
303 response, body = self.os.nova.request('GET', url)
304 self.assertEqual(response.status, 200)
305 body = json.loads(body)
306 self.assertTrue('addresses' in body['server'].keys())
307 server_addresses = body['server']['addresses']
308
309 # Addresses should be available from subresource
310 url = '/servers/%s/ips' % self.server_id
311 response, body = self.os.nova.request('GET', url)
312 self.assertEqual(response.status, 200)
313 body = json.loads(body)
314 self.assertEqual(body.keys(), ['addresses'])
315 ips_addresses = body['addresses']
316
317 # Ensure both resources return identical information
318 self.assertEqual(server_addresses, ips_addresses)
319
320 # Validate entities within network containers
321 for (network, network_data) in ips_addresses.items():
322 url = '/servers/%s/ips/%s' % (self.server_id, network)
323 response, body = self.os.nova.request('GET', url)
324 self.assertEqual(response.status, 200)
325 body = json.loads(body)
326 self.assertEqual(body.keys(), [network])
327 self.assertEqual(body[network], network_data)
328
329 # Check each IP entity
330 for ip_data in network_data:
331 self.assertEqual(set(ip_data.keys()), set(['addr', 'version']))
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200332
333 # Find IP of server
334 try:
Brian Waldonc062b442011-10-27 17:13:41 -0400335 (_, network) = server_addresses.items()[0]
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200336 ip = network[0]['addr']
337 except KeyError:
338 self.fail("Failed to retrieve IP address from server entity")
339
340 # Assert password works
Aaron Lee35b8c922011-10-18 17:07:19 -0500341 if int(self.nova['ssh_timeout']) > 0:
342 client = ssh.Client(ip, 'root', admin_pass, self.ssh_timeout)
343 self.assertTrue(client.test_connection_auth())
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200344
Brian Waldonc062b442011-10-27 17:13:41 -0400345 self.os.nova.delete_server(self.server_id)
346
347 # Poll server until deleted
348 try:
349 url = '/servers/%s' % self.server_id
350 self.os.nova.poll_request_status('GET', url, 404)
351 except exceptions.TimeoutException:
352 self.fail("Server deletion timed out")
353 test_build_update_delete.tags = ['nova']
354
355 def test_build_with_password_and_file(self):
356 """Build a server with a custom password and an injected file"""
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200357
358 file_contents = 'testing'
359
360 expected_server = {
361 'name': 'testserver',
362 'metadata': {
363 'key1': 'value1',
364 'key2': 'value2',
365 },
366 'personality': [
367 {
368 'path': '/etc/test.txt',
369 'contents': base64.b64encode(file_contents),
370 },
371 ],
372 'imageRef': self.image_ref,
373 'flavorRef': self.flavor_ref,
Brian Waldonc062b442011-10-27 17:13:41 -0400374 'adminPass': 'secrete',
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200375 }
376
377 post_body = json.dumps({'server': expected_server})
378 response, body = self.os.nova.request('POST',
379 '/servers',
380 body=post_body)
381
382 self.assertEqual(response.status, 202)
383
384 _body = json.loads(body)
385 self.assertEqual(_body.keys(), ['server'])
386 created_server = _body['server']
Aaron Lee35b8c922011-10-18 17:07:19 -0500387 self.server_id = _body['server']['id']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200388
389 admin_pass = created_server.pop('adminPass', None)
Brian Waldonc062b442011-10-27 17:13:41 -0400390 self.assertEqual(expected_server['adminPass'], admin_pass)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200391 self._assert_server_entity(created_server)
392 self.assertEqual(expected_server['name'], created_server['name'])
393 self.assertEqual(expected_server['metadata'],
394 created_server['metadata'])
395
396 self.os.nova.wait_for_server_status(created_server['id'],
397 'ACTIVE',
398 timeout=self.build_timeout)
399
400 server = self.os.nova.get_server(created_server['id'])
401
402 # Find IP of server
403 try:
404 (_, network) = server['addresses'].popitem()
405 ip = network[0]['addr']
406 except KeyError:
407 self.fail("Failed to retrieve IP address from server entity")
408
409 # Assert injected file is on instance, also verifying password works
Aaron Lee35b8c922011-10-18 17:07:19 -0500410 if int(self.nova['ssh_timeout']) > 0:
411 client = ssh.Client(ip, 'root', admin_pass, self.ssh_timeout)
412 injected_file = client.exec_command('cat /etc/test.txt')
413 self.assertEqual(injected_file, file_contents)
Brian Waldonc062b442011-10-27 17:13:41 -0400414 test_build_with_password_and_file.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200415
Brian Waldonc062b442011-10-27 17:13:41 -0400416 def test_delete_while_building(self):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200417 """Delete a server while building"""
418
419 # Make create server request
420 server = {
421 'name' : 'testserver',
422 'imageRef' : self.image_ref,
423 'flavorRef' : self.flavor_ref,
424 }
425 created_server = self.os.nova.create_server(server)
426
427 # Server should immediately be accessible, but in have building status
428 server = self.os.nova.get_server(created_server['id'])
429 self.assertEqual(server['status'], 'BUILD')
430
431 self.os.nova.delete_server(created_server['id'])
432
433 # Poll server until deleted
434 try:
435 url = '/servers/%s' % created_server['id']
436 self.os.nova.poll_request_status('GET', url, 404)
437 except exceptions.TimeoutException:
438 self.fail("Server deletion timed out")
Brian Waldonc062b442011-10-27 17:13:41 -0400439 test_delete_while_building.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200440
Brian Waldonc062b442011-10-27 17:13:41 -0400441 def test_create_with_invalid_image(self):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200442 """Create a server with an unknown image"""
443
444 post_body = json.dumps({
445 'server' : {
446 'name' : 'testserver',
447 'imageRef' : -1,
448 'flavorRef' : self.flavor_ref,
449 }
450 })
451
452 resp, body = self.os.nova.request('POST', '/servers', body=post_body)
453
454 self.assertEqual(400, resp.status)
455
456 fault = json.loads(body)
457 expected_fault = {
458 "badRequest": {
459 "message": "Cannot find requested image",
460 "code": 400,
461 },
462 }
463 # KNOWN-ISSUE - The error message is confusing and should be improved
464 #self.assertEqual(fault, expected_fault)
Brian Waldonc062b442011-10-27 17:13:41 -0400465 test_create_with_invalid_image.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200466
Brian Waldonc062b442011-10-27 17:13:41 -0400467 def test_create_with_invalid_flavor(self):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200468 """Create a server with an unknown flavor"""
469
470 post_body = json.dumps({
471 'server' : {
472 'name' : 'testserver',
473 'imageRef' : self.image_ref,
474 'flavorRef' : -1,
475 }
476 })
477
478 resp, body = self.os.nova.request('POST', '/servers', body=post_body)
479
480 self.assertEqual(400, resp.status)
481
482 fault = json.loads(body)
483 expected_fault = {
484 "badRequest": {
485 "message": "Cannot find requested flavor",
486 "code": 400,
487 },
488 }
489 # KNOWN-ISSUE lp804084
490 #self.assertEqual(fault, expected_fault)
Brian Waldonc062b442011-10-27 17:13:41 -0400491 test_create_with_invalid_flavor.tags = ['nova']