blob: 38af75b924f1ecffc46fa2ee365bb2e9baf5950a [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
Soren Hansena86180a2011-09-09 16:22:26 +020021 self.image_ref = self.glance['image_id']
22# self.image_ref_alt = self.os.config.env.image_ref_alt
23 self.flavor_ref = self.nova['flavor_ref']
24 self.flavor_ref_alt = self.nova['flavor_ref_alt']
25 self.ssh_timeout = self.nova['ssh_timeout']
26 self.build_timeout = self.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 Hansena86180a2011-09-09 16:22:26 +0200159 @tests.skip("rebuild test needs alternate image, but we only have one")
Soren Hansen5f4ad832011-09-09 14:08:19 +0200160 def test_rebuild(self):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200161 """Rebuild a server"""
162
Soren Hansen5f4ad832011-09-09 14:08:19 +0200163 FILENAME = '/tmp/testfile'
164 CONTENTS = 'WORDS'
165
166 # write file to server
167 self._write_file(FILENAME, CONTENTS)
168 self.assertEqual(self._read_file(FILENAME), CONTENTS)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200169
170 # Make rebuild request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200171 post_body = json.dumps({'rebuild': {'imageRef': self.image_ref_alt}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200172 url = '/servers/%s/action' % self.server_id
173 response, body = self.os.nova.request('POST', url, body=post_body)
174
Soren Hansen5f4ad832011-09-09 14:08:19 +0200175 # check output
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200176 self.assertEqual('202', response['status'])
Soren Hansen5f4ad832011-09-09 14:08:19 +0200177 rebuilt_server = json.loads(body)['server']
178 generated_password = rebuilt_server['adminPass']
179
180 # Ensure correct status transition
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200181 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200182 #self._wait_for_server_status(self.server_id, 'REBUILD')
183 self._wait_for_server_status(self.server_id, 'BUILD')
184 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200185
186 # Treats an issue where we ssh'd in too soon after rebuild
187 time.sleep(30)
188
189 # Check that the instance's imageRef matches the new imageRef
190 server = self.os.nova.get_server(self.server_id)
191 ref_match = self.image_ref_alt == server['image']['links'][0]['href']
192 id_match = self.image_ref_alt == server['image']['id']
193 self.assertTrue(ref_match or id_match)
194
195 # SSH into the server to ensure it came back up
Soren Hansen5f4ad832011-09-09 14:08:19 +0200196 self._assert_ssh_password(generated_password)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200197
198 # make sure file is gone
Soren Hansen5f4ad832011-09-09 14:08:19 +0200199 self.assertEqual(self._read_file(FILENAME, generated_password), '')
200
201 # test again with a specified password
202 self._write_file(FILENAME, CONTENTS, generated_password)
203 _contents = self._read_file(FILENAME, generated_password)
204 self.assertEqual(_contents, CONTENTS)
205
206 specified_password = 'some_password'
207
208 # Make rebuild request
209 post_body = json.dumps({
210 'rebuild': {
211 'imageRef': self.image_ref,
212 'adminPass': specified_password,
213 }
214 })
215 url = '/servers/%s/action' % self.server_id
216 response, body = self.os.nova.request('POST', url, body=post_body)
217
218 # check output
219 self.assertEqual('202', response['status'])
220 rebuilt_server = json.loads(body)['server']
221 self.assertEqual(rebuilt_server['adminPass'], specified_password)
222
223 # Ensure correct status transition
224 # KNOWN-ISSUE
225 #self._wait_for_server_status(self.server_id, 'REBUILD')
226 self._wait_for_server_status(self.server_id, 'BUILD')
227 self._wait_for_server_status(self.server_id, 'ACTIVE')
228
229 # Treats an issue where we ssh'd in too soon after rebuild
230 time.sleep(30)
231
232 # Check that the instance's imageRef matches the new imageRef
233 server = self.os.nova.get_server(self.server_id)
234 ref_match = self.image_ref == server['image']['links'][0]['href']
235 id_match = self.image_ref == server['image']['id']
236 self.assertTrue(ref_match or id_match)
237
238 # SSH into the server to ensure it came back up
239 self._assert_ssh_password(specified_password)
240
241 # make sure file is gone
242 self.assertEqual(self._read_file(FILENAME, specified_password), '')
Soren Hansenfce58c52011-09-09 16:07:13 +0200243 test_rebuild.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200244
245 @unittest.skipIf(not multi_node, 'Multiple compute nodes required')
246 def test_resize_server_confirm(self):
247 """Resize a server"""
248 # Make resize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200249 post_body = json.dumps({'resize': {'flavorRef': self.flavor_ref_alt}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200250 url = '/servers/%s/action' % self.server_id
251 response, body = self.os.nova.request('POST', url, body=post_body)
252
253 # Wait for status transition
254 self.assertEqual('202', response['status'])
255 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200256 #self._wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
257 self._wait_for_server_status(self.server_id, 'RESIZE-CONFIRM')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200258
259 # Ensure API reports new flavor
260 server = self.os.nova.get_server(self.server_id)
261 self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
262
263 #SSH into the server to ensure it came back up
264 self._assert_ssh_password()
265
266 # Make confirmResize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200267 post_body = json.dumps({'confirmResize': 'null'})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200268 url = '/servers/%s/action' % self.server_id
269 response, body = self.os.nova.request('POST', url, body=post_body)
270
271 # Wait for status transition
272 self.assertEqual('204', response['status'])
Soren Hansen5f4ad832011-09-09 14:08:19 +0200273 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200274
275 # Ensure API still reports new flavor
276 server = self.os.nova.get_server(self.server_id)
277 self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
Soren Hansenfce58c52011-09-09 16:07:13 +0200278 test_resize_server_confirm.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200279
280 @unittest.skipIf(not multi_node, 'Multiple compute nodes required')
281 def test_resize_server_revert(self):
282 """Resize a server, then revert"""
283
284 # Make resize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200285 post_body = json.dumps({'resize': {'flavorRef': self.flavor_ref_alt}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200286 url = '/servers/%s/action' % self.server_id
287 response, body = self.os.nova.request('POST', url, body=post_body)
288
289 # Wait for status transition
290 self.assertEqual('202', response['status'])
291 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200292 #self._wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
293 self._wait_for_server_status(self.server_id, 'RESIZE-CONFIRM')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200294
295 # SSH into the server to ensure it came back up
296 self._assert_ssh_password()
297
298 # Ensure API reports new flavor
299 server = self.os.nova.get_server(self.server_id)
300 self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
301
302 # Make revertResize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200303 post_body = json.dumps({'revertResize': 'null'})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200304 url = '/servers/%s/action' % self.server_id
305 response, body = self.os.nova.request('POST', url, body=post_body)
306
307 # Assert status transition
308 self.assertEqual('202', response['status'])
Soren Hansen5f4ad832011-09-09 14:08:19 +0200309 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200310
311 # Ensure flavor ref was reverted to original
312 server = self.os.nova.get_server(self.server_id)
313 self.assertEqual(self.flavor_ref, server['flavor']['id'])
Soren Hansenfce58c52011-09-09 16:07:13 +0200314 test_resize_server_revert.tags = ['nova']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200315
316
317class SnapshotTests(unittest.TestCase):
318
319 def setUp(self):
320 self.os = openstack.Manager()
321
322 self.image_ref = self.os.config.env.image_ref
323 self.flavor_ref = self.os.config.env.flavor_ref
324 self.ssh_timeout = self.os.config.nova.ssh_timeout
Soren Hansen5f4ad832011-09-09 14:08:19 +0200325 self.build_timeout = self.os.config.nova.build_timeout
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200326
Soren Hansen5f4ad832011-09-09 14:08:19 +0200327 self.server_name = 'stacktester1'
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200328
329 expected_server = {
Soren Hansen5f4ad832011-09-09 14:08:19 +0200330 'name': self.server_name,
331 'imageRef': self.image_ref,
332 'flavorRef': self.flavor_ref,
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200333 }
334
335 created_server = self.os.nova.create_server(expected_server)
336 self.server_id = created_server['id']
337
338 def tearDown(self):
339 self.os.nova.delete_server(self.server_id)
340
Soren Hansen5f4ad832011-09-09 14:08:19 +0200341 def _wait_for_server_status(self, server_id, status):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200342 try:
Soren Hansen5f4ad832011-09-09 14:08:19 +0200343 self.os.nova.wait_for_server_status(server_id, status,
344 timeout=self.build_timeout)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200345 except exceptions.TimeoutException:
346 self.fail("Server failed to change status to %s" % status)
347
348 def test_snapshot_server_active(self):
349 """Create image from an existing server"""
350
351 # Wait for server to come up before running this test
Soren Hansen5f4ad832011-09-09 14:08:19 +0200352 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200353
354 # Create snapshot
Soren Hansen5f4ad832011-09-09 14:08:19 +0200355 image_data = {'name': 'backup'}
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200356 req_body = json.dumps({'createImage': image_data})
357 url = '/servers/%s/action' % self.server_id
358 response, body = self.os.nova.request('POST', url, body=req_body)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200359
360 self.assertEqual(response['status'], '202')
361 image_ref = response['location']
Soren Hansen5f4ad832011-09-09 14:08:19 +0200362 snapshot_id = image_ref.rsplit('/', 1)[1]
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200363
364 # Get snapshot and check its attributes
365 resp, body = self.os.nova.request('GET', '/images/%s' % snapshot_id)
366 snapshot = json.loads(body)['image']
367 self.assertEqual(snapshot['name'], image_data['name'])
368 server_ref = snapshot['server']['links'][0]['href']
369 self.assertTrue(server_ref.endswith('/%s' % self.server_id))
370
371 # Ensure image is actually created
372 self.os.nova.wait_for_image_status(snapshot['id'], 'ACTIVE')
373
374 # Cleaning up
375 self.os.nova.request('DELETE', '/images/%s' % snapshot_id)
Soren Hansenfce58c52011-09-09 16:07:13 +0200376 test_snapshot_server_active.tags = ['nova', 'glance']
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200377
378 def test_snapshot_server_inactive(self):
379 """Ensure inability to snapshot server in BUILD state"""
380
381 # Create snapshot
Soren Hansen5f4ad832011-09-09 14:08:19 +0200382 req_body = json.dumps({'createImage': {'name': 'backup'}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200383 url = '/servers/%s/action' % self.server_id
384 response, body = self.os.nova.request('POST', url, body=req_body)
385
386 # KNOWN-ISSUE - we shouldn't be able to snapshot a building server
387 #self.assertEqual(response['status'], '400') # what status code?
388 self.assertEqual(response['status'], '202')
389 snapshot_id = response['location'].rsplit('/', 1)[1]
390 # Delete image for now, won't need this once correct status code is in
391 self.os.nova.request('DELETE', '/images/%s' % snapshot_id)
Soren Hansenfce58c52011-09-09 16:07:13 +0200392 test_snapshot_server_inactive.tags = ['nova', 'glance']