blob: b8f81db7c14a35d1692a802d91d7916c6c72dd9d [file] [log] [blame]
Soren Hansenbc1d3a02011-09-08 13:33:17 +02001
2import json
3import time
4
5from kong import exceptions
6from kong import openstack
Soren Hansen6adacc82011-09-09 13:34:35 +02007from kong import tests
Soren Hansenbc1d3a02011-09-08 13:33:17 +02008from kong.common import ssh
9
10import unittest2 as unittest
11
12
Soren Hansen6adacc82011-09-09 13:34:35 +020013class ServerActionsTest(tests.FunctionalTest):
Soren Hansenbc1d3a02011-09-08 13:33:17 +020014
15 multi_node = openstack.Manager().config.env.multi_node
16
17 def setUp(self):
Soren Hansen6adacc82011-09-09 13:34:35 +020018 super(ServerActionsTest, self).setUp()
Soren Hansend6b047a2011-09-09 13:39:32 +020019 self.os = openstack.Manager(self.nova)
Soren Hansenbc1d3a02011-09-08 13:33:17 +020020
21 self.image_ref = self.os.config.env.image_ref
22 self.image_ref_alt = self.os.config.env.image_ref_alt
23 self.flavor_ref = self.os.config.env.flavor_ref
24 self.flavor_ref_alt = self.os.config.env.flavor_ref_alt
25 self.ssh_timeout = self.os.config.nova.ssh_timeout
Soren Hansen5f4ad832011-09-09 14:08:19 +020026 self.build_timeout = self.os.config.nova.build_timeout
Soren Hansenbc1d3a02011-09-08 13:33:17 +020027
28 self.server_password = 'testpwd'
Soren Hansen5f4ad832011-09-09 14:08:19 +020029 self.server_name = 'stacktester1'
Soren Hansenbc1d3a02011-09-08 13:33:17 +020030
31 expected_server = {
Soren Hansen5f4ad832011-09-09 14:08:19 +020032 'name': self.server_name,
33 'imageRef': self.image_ref,
34 'flavorRef': self.flavor_ref,
35 'adminPass': self.server_password,
Soren Hansenbc1d3a02011-09-08 13:33:17 +020036 }
37
38 created_server = self.os.nova.create_server(expected_server)
39
40 self.server_id = created_server['id']
Soren Hansen5f4ad832011-09-09 14:08:19 +020041 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +020042
43 server = self.os.nova.get_server(self.server_id)
44
45 # KNOWN-ISSUE lp?
46 #self.access_ip = server['accessIPv4']
47 self.access_ip = server['addresses']['public'][0]['addr']
48
49 # Ensure server came up
50 self._assert_ssh_password()
51
52 def tearDown(self):
53 self.os.nova.delete_server(self.server_id)
54
55 def _get_ssh_client(self, password):
56 return ssh.Client(self.access_ip, 'root', password, self.ssh_timeout)
57
58 def _assert_ssh_password(self, password=None):
59 _password = password or self.server_password
60 client = self._get_ssh_client(_password)
61 self.assertTrue(client.test_connection_auth())
62
Soren Hansen5f4ad832011-09-09 14:08:19 +020063 def _wait_for_server_status(self, server_id, status):
Soren Hansenbc1d3a02011-09-08 13:33:17 +020064 try:
Soren Hansen5f4ad832011-09-09 14:08:19 +020065 self.os.nova.wait_for_server_status(server_id, status,
66 timeout=self.build_timeout)
Soren Hansenbc1d3a02011-09-08 13:33:17 +020067 except exceptions.TimeoutException:
68 self.fail("Server failed to change status to %s" % status)
69
70 def _get_boot_time(self):
71 """Return the time the server was started"""
72 output = self._read_file("/proc/uptime")
73 uptime = float(output.split().pop(0))
74 return time.time() - uptime
75
Soren Hansen5f4ad832011-09-09 14:08:19 +020076 def _write_file(self, filename, contents, password=None):
77 command = "echo -n %s > %s" % (contents, filename)
78 return self._exec_command(command, password)
Soren Hansenbc1d3a02011-09-08 13:33:17 +020079
Soren Hansen5f4ad832011-09-09 14:08:19 +020080 def _read_file(self, filename, password=None):
81 command = "cat %s" % filename
82 return self._exec_command(command, password)
Soren Hansenbc1d3a02011-09-08 13:33:17 +020083
Soren Hansen5f4ad832011-09-09 14:08:19 +020084 def _exec_command(self, command, password=None):
85 if password is None:
86 password = self.server_password
87 client = self._get_ssh_client(password)
Soren Hansenbc1d3a02011-09-08 13:33:17 +020088 return client.exec_command(command)
89
90 def test_reboot_server_soft(self):
91 """Reboot a server (SOFT)"""
92
93 # SSH and get the uptime
94 initial_time_started = self._get_boot_time()
95
96 # Make reboot request
Soren Hansen5f4ad832011-09-09 14:08:19 +020097 post_body = json.dumps({'reboot': {'type': 'SOFT'}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +020098 url = "/servers/%s/action" % self.server_id
99 response, body = self.os.nova.request('POST', url, body=post_body)
100 self.assertEqual(response['status'], '202')
101
102 # Assert status transition
103 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200104 #self._wait_for_server_status(self.server_id, 'REBOOT')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200105 ssh_client = self._get_ssh_client(self.server_password)
106 ssh_client.connect_until_closed()
Soren Hansen5f4ad832011-09-09 14:08:19 +0200107 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200108
109 # SSH and verify uptime is less than before
110 post_reboot_time_started = self._get_boot_time()
111 self.assertTrue(initial_time_started < post_reboot_time_started)
Soren Hansenfce58c52011-09-09 16:07:13 +0200112 test_reboot_server_soft.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200113
114 def test_reboot_server_hard(self):
115 """Reboot a server (HARD)"""
116
117 # SSH and get the uptime
118 initial_time_started = self._get_boot_time()
119
120 # Make reboot request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200121 post_body = json.dumps({'reboot': {'type': 'HARD'}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200122 url = "/servers/%s/action" % self.server_id
123 response, body = self.os.nova.request('POST', url, body=post_body)
124 self.assertEqual(response['status'], '202')
125
126 # Assert status transition
127 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200128 #self._wait_for_server_status(self.server_id, 'HARD_REBOOT')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200129 ssh_client = self._get_ssh_client(self.server_password)
130 ssh_client.connect_until_closed()
Soren Hansen5f4ad832011-09-09 14:08:19 +0200131 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200132
133 # SSH and verify uptime is less than before
134 post_reboot_time_started = self._get_boot_time()
135 self.assertTrue(initial_time_started < post_reboot_time_started)
Soren Hansenfce58c52011-09-09 16:07:13 +0200136 test_reboot_server_hard.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200137
138 def test_change_server_password(self):
139 """Change root password of a server"""
140
141 # SSH into server using original password
142 self._assert_ssh_password()
143
144 # Change server password
Soren Hansen5f4ad832011-09-09 14:08:19 +0200145 post_body = json.dumps({'changePassword': {'adminPass': 'test123'}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200146 url = '/servers/%s/action' % self.server_id
147 response, body = self.os.nova.request('POST', url, body=post_body)
148
149 # Assert status transition
150 self.assertEqual('202', response['status'])
151 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200152 self._wait_for_server_status(self.server_id, 'PASSWORD')
153 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200154
155 # SSH into server using new password
156 self._assert_ssh_password('test123')
Soren Hansenfce58c52011-09-09 16:07:13 +0200157 test_change_server_password.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200158
Soren Hansen5f4ad832011-09-09 14:08:19 +0200159 def test_rebuild(self):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200160 """Rebuild a server"""
161
Soren Hansen5f4ad832011-09-09 14:08:19 +0200162 FILENAME = '/tmp/testfile'
163 CONTENTS = 'WORDS'
164
165 # write file to server
166 self._write_file(FILENAME, CONTENTS)
167 self.assertEqual(self._read_file(FILENAME), CONTENTS)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200168
169 # Make rebuild request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200170 post_body = json.dumps({'rebuild': {'imageRef': self.image_ref_alt}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200171 url = '/servers/%s/action' % self.server_id
172 response, body = self.os.nova.request('POST', url, body=post_body)
173
Soren Hansen5f4ad832011-09-09 14:08:19 +0200174 # check output
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200175 self.assertEqual('202', response['status'])
Soren Hansen5f4ad832011-09-09 14:08:19 +0200176 rebuilt_server = json.loads(body)['server']
177 generated_password = rebuilt_server['adminPass']
178
179 # Ensure correct status transition
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200180 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200181 #self._wait_for_server_status(self.server_id, 'REBUILD')
182 self._wait_for_server_status(self.server_id, 'BUILD')
183 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200184
185 # Treats an issue where we ssh'd in too soon after rebuild
186 time.sleep(30)
187
188 # Check that the instance's imageRef matches the new imageRef
189 server = self.os.nova.get_server(self.server_id)
190 ref_match = self.image_ref_alt == server['image']['links'][0]['href']
191 id_match = self.image_ref_alt == server['image']['id']
192 self.assertTrue(ref_match or id_match)
193
194 # SSH into the server to ensure it came back up
Soren Hansen5f4ad832011-09-09 14:08:19 +0200195 self._assert_ssh_password(generated_password)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200196
197 # make sure file is gone
Soren Hansen5f4ad832011-09-09 14:08:19 +0200198 self.assertEqual(self._read_file(FILENAME, generated_password), '')
199
200 # test again with a specified password
201 self._write_file(FILENAME, CONTENTS, generated_password)
202 _contents = self._read_file(FILENAME, generated_password)
203 self.assertEqual(_contents, CONTENTS)
204
205 specified_password = 'some_password'
206
207 # Make rebuild request
208 post_body = json.dumps({
209 'rebuild': {
210 'imageRef': self.image_ref,
211 'adminPass': specified_password,
212 }
213 })
214 url = '/servers/%s/action' % self.server_id
215 response, body = self.os.nova.request('POST', url, body=post_body)
216
217 # check output
218 self.assertEqual('202', response['status'])
219 rebuilt_server = json.loads(body)['server']
220 self.assertEqual(rebuilt_server['adminPass'], specified_password)
221
222 # Ensure correct status transition
223 # KNOWN-ISSUE
224 #self._wait_for_server_status(self.server_id, 'REBUILD')
225 self._wait_for_server_status(self.server_id, 'BUILD')
226 self._wait_for_server_status(self.server_id, 'ACTIVE')
227
228 # Treats an issue where we ssh'd in too soon after rebuild
229 time.sleep(30)
230
231 # Check that the instance's imageRef matches the new imageRef
232 server = self.os.nova.get_server(self.server_id)
233 ref_match = self.image_ref == server['image']['links'][0]['href']
234 id_match = self.image_ref == server['image']['id']
235 self.assertTrue(ref_match or id_match)
236
237 # SSH into the server to ensure it came back up
238 self._assert_ssh_password(specified_password)
239
240 # make sure file is gone
241 self.assertEqual(self._read_file(FILENAME, specified_password), '')
Soren Hansenfce58c52011-09-09 16:07:13 +0200242 test_rebuild.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200243
244 @unittest.skipIf(not multi_node, 'Multiple compute nodes required')
245 def test_resize_server_confirm(self):
246 """Resize a server"""
247 # Make resize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200248 post_body = json.dumps({'resize': {'flavorRef': self.flavor_ref_alt}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200249 url = '/servers/%s/action' % self.server_id
250 response, body = self.os.nova.request('POST', url, body=post_body)
251
252 # Wait for status transition
253 self.assertEqual('202', response['status'])
254 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200255 #self._wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
256 self._wait_for_server_status(self.server_id, 'RESIZE-CONFIRM')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200257
258 # Ensure API reports new flavor
259 server = self.os.nova.get_server(self.server_id)
260 self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
261
262 #SSH into the server to ensure it came back up
263 self._assert_ssh_password()
264
265 # Make confirmResize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200266 post_body = json.dumps({'confirmResize': 'null'})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200267 url = '/servers/%s/action' % self.server_id
268 response, body = self.os.nova.request('POST', url, body=post_body)
269
270 # Wait for status transition
271 self.assertEqual('204', response['status'])
Soren Hansen5f4ad832011-09-09 14:08:19 +0200272 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200273
274 # Ensure API still reports new flavor
275 server = self.os.nova.get_server(self.server_id)
276 self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
Soren Hansenfce58c52011-09-09 16:07:13 +0200277 test_resize_server_confirm.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200278
279 @unittest.skipIf(not multi_node, 'Multiple compute nodes required')
280 def test_resize_server_revert(self):
281 """Resize a server, then revert"""
282
283 # Make resize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200284 post_body = json.dumps({'resize': {'flavorRef': self.flavor_ref_alt}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200285 url = '/servers/%s/action' % self.server_id
286 response, body = self.os.nova.request('POST', url, body=post_body)
287
288 # Wait for status transition
289 self.assertEqual('202', response['status'])
290 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200291 #self._wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
292 self._wait_for_server_status(self.server_id, 'RESIZE-CONFIRM')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200293
294 # SSH into the server to ensure it came back up
295 self._assert_ssh_password()
296
297 # Ensure API reports new flavor
298 server = self.os.nova.get_server(self.server_id)
299 self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
300
301 # Make revertResize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200302 post_body = json.dumps({'revertResize': 'null'})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200303 url = '/servers/%s/action' % self.server_id
304 response, body = self.os.nova.request('POST', url, body=post_body)
305
306 # Assert status transition
307 self.assertEqual('202', response['status'])
Soren Hansen5f4ad832011-09-09 14:08:19 +0200308 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200309
310 # Ensure flavor ref was reverted to original
311 server = self.os.nova.get_server(self.server_id)
312 self.assertEqual(self.flavor_ref, server['flavor']['id'])
Soren Hansenfce58c52011-09-09 16:07:13 +0200313 test_resize_server_revert.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200314
315
316class SnapshotTests(unittest.TestCase):
317
318 def setUp(self):
319 self.os = openstack.Manager()
320
321 self.image_ref = self.os.config.env.image_ref
322 self.flavor_ref = self.os.config.env.flavor_ref
323 self.ssh_timeout = self.os.config.nova.ssh_timeout
Soren Hansen5f4ad832011-09-09 14:08:19 +0200324 self.build_timeout = self.os.config.nova.build_timeout
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200325
Soren Hansen5f4ad832011-09-09 14:08:19 +0200326 self.server_name = 'stacktester1'
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200327
328 expected_server = {
Soren Hansen5f4ad832011-09-09 14:08:19 +0200329 'name': self.server_name,
330 'imageRef': self.image_ref,
331 'flavorRef': self.flavor_ref,
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200332 }
333
334 created_server = self.os.nova.create_server(expected_server)
335 self.server_id = created_server['id']
336
337 def tearDown(self):
338 self.os.nova.delete_server(self.server_id)
339
Soren Hansen5f4ad832011-09-09 14:08:19 +0200340 def _wait_for_server_status(self, server_id, status):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200341 try:
Soren Hansen5f4ad832011-09-09 14:08:19 +0200342 self.os.nova.wait_for_server_status(server_id, status,
343 timeout=self.build_timeout)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200344 except exceptions.TimeoutException:
345 self.fail("Server failed to change status to %s" % status)
346
347 def test_snapshot_server_active(self):
348 """Create image from an existing server"""
349
350 # Wait for server to come up before running this test
Soren Hansen5f4ad832011-09-09 14:08:19 +0200351 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200352
353 # Create snapshot
Soren Hansen5f4ad832011-09-09 14:08:19 +0200354 image_data = {'name': 'backup'}
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200355 req_body = json.dumps({'createImage': image_data})
356 url = '/servers/%s/action' % self.server_id
357 response, body = self.os.nova.request('POST', url, body=req_body)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200358
359 self.assertEqual(response['status'], '202')
360 image_ref = response['location']
Soren Hansen5f4ad832011-09-09 14:08:19 +0200361 snapshot_id = image_ref.rsplit('/', 1)[1]
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200362
363 # Get snapshot and check its attributes
364 resp, body = self.os.nova.request('GET', '/images/%s' % snapshot_id)
365 snapshot = json.loads(body)['image']
366 self.assertEqual(snapshot['name'], image_data['name'])
367 server_ref = snapshot['server']['links'][0]['href']
368 self.assertTrue(server_ref.endswith('/%s' % self.server_id))
369
370 # Ensure image is actually created
371 self.os.nova.wait_for_image_status(snapshot['id'], 'ACTIVE')
372
373 # Cleaning up
374 self.os.nova.request('DELETE', '/images/%s' % snapshot_id)
Soren Hansenfce58c52011-09-09 16:07:13 +0200375 test_snapshot_server_active.tags = ['nova', 'glance']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200376
377 def test_snapshot_server_inactive(self):
378 """Ensure inability to snapshot server in BUILD state"""
379
380 # Create snapshot
Soren Hansen5f4ad832011-09-09 14:08:19 +0200381 req_body = json.dumps({'createImage': {'name': 'backup'}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200382 url = '/servers/%s/action' % self.server_id
383 response, body = self.os.nova.request('POST', url, body=req_body)
384
385 # KNOWN-ISSUE - we shouldn't be able to snapshot a building server
386 #self.assertEqual(response['status'], '400') # what status code?
387 self.assertEqual(response['status'], '202')
388 snapshot_id = response['location'].rsplit('/', 1)[1]
389 # Delete image for now, won't need this once correct status code is in
390 self.os.nova.request('DELETE', '/images/%s' % snapshot_id)
Soren Hansenfce58c52011-09-09 16:07:13 +0200391 test_snapshot_server_inactive.tags = ['nova', 'glance']