blob: f995c6750959bf68e2849a33f4ed5c34d9976d9d [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)
112
113 def test_reboot_server_hard(self):
114 """Reboot a server (HARD)"""
115
116 # SSH and get the uptime
117 initial_time_started = self._get_boot_time()
118
119 # Make reboot request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200120 post_body = json.dumps({'reboot': {'type': 'HARD'}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200121 url = "/servers/%s/action" % self.server_id
122 response, body = self.os.nova.request('POST', url, body=post_body)
123 self.assertEqual(response['status'], '202')
124
125 # Assert status transition
126 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200127 #self._wait_for_server_status(self.server_id, 'HARD_REBOOT')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200128 ssh_client = self._get_ssh_client(self.server_password)
129 ssh_client.connect_until_closed()
Soren Hansen5f4ad832011-09-09 14:08:19 +0200130 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200131
132 # SSH and verify uptime is less than before
133 post_reboot_time_started = self._get_boot_time()
134 self.assertTrue(initial_time_started < post_reboot_time_started)
135
136 def test_change_server_password(self):
137 """Change root password of a server"""
138
139 # SSH into server using original password
140 self._assert_ssh_password()
141
142 # Change server password
Soren Hansen5f4ad832011-09-09 14:08:19 +0200143 post_body = json.dumps({'changePassword': {'adminPass': 'test123'}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200144 url = '/servers/%s/action' % self.server_id
145 response, body = self.os.nova.request('POST', url, body=post_body)
146
147 # Assert status transition
148 self.assertEqual('202', response['status'])
149 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200150 self._wait_for_server_status(self.server_id, 'PASSWORD')
151 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200152
153 # SSH into server using new password
154 self._assert_ssh_password('test123')
155
Soren Hansen5f4ad832011-09-09 14:08:19 +0200156 def test_rebuild(self):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200157 """Rebuild a server"""
158
Soren Hansen5f4ad832011-09-09 14:08:19 +0200159 FILENAME = '/tmp/testfile'
160 CONTENTS = 'WORDS'
161
162 # write file to server
163 self._write_file(FILENAME, CONTENTS)
164 self.assertEqual(self._read_file(FILENAME), CONTENTS)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200165
166 # Make rebuild request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200167 post_body = json.dumps({'rebuild': {'imageRef': self.image_ref_alt}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200168 url = '/servers/%s/action' % self.server_id
169 response, body = self.os.nova.request('POST', url, body=post_body)
170
Soren Hansen5f4ad832011-09-09 14:08:19 +0200171 # check output
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200172 self.assertEqual('202', response['status'])
Soren Hansen5f4ad832011-09-09 14:08:19 +0200173 rebuilt_server = json.loads(body)['server']
174 generated_password = rebuilt_server['adminPass']
175
176 # Ensure correct status transition
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200177 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200178 #self._wait_for_server_status(self.server_id, 'REBUILD')
179 self._wait_for_server_status(self.server_id, 'BUILD')
180 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200181
182 # Treats an issue where we ssh'd in too soon after rebuild
183 time.sleep(30)
184
185 # Check that the instance's imageRef matches the new imageRef
186 server = self.os.nova.get_server(self.server_id)
187 ref_match = self.image_ref_alt == server['image']['links'][0]['href']
188 id_match = self.image_ref_alt == server['image']['id']
189 self.assertTrue(ref_match or id_match)
190
191 # SSH into the server to ensure it came back up
Soren Hansen5f4ad832011-09-09 14:08:19 +0200192 self._assert_ssh_password(generated_password)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200193
194 # make sure file is gone
Soren Hansen5f4ad832011-09-09 14:08:19 +0200195 self.assertEqual(self._read_file(FILENAME, generated_password), '')
196
197 # test again with a specified password
198 self._write_file(FILENAME, CONTENTS, generated_password)
199 _contents = self._read_file(FILENAME, generated_password)
200 self.assertEqual(_contents, CONTENTS)
201
202 specified_password = 'some_password'
203
204 # Make rebuild request
205 post_body = json.dumps({
206 'rebuild': {
207 'imageRef': self.image_ref,
208 'adminPass': specified_password,
209 }
210 })
211 url = '/servers/%s/action' % self.server_id
212 response, body = self.os.nova.request('POST', url, body=post_body)
213
214 # check output
215 self.assertEqual('202', response['status'])
216 rebuilt_server = json.loads(body)['server']
217 self.assertEqual(rebuilt_server['adminPass'], specified_password)
218
219 # Ensure correct status transition
220 # KNOWN-ISSUE
221 #self._wait_for_server_status(self.server_id, 'REBUILD')
222 self._wait_for_server_status(self.server_id, 'BUILD')
223 self._wait_for_server_status(self.server_id, 'ACTIVE')
224
225 # Treats an issue where we ssh'd in too soon after rebuild
226 time.sleep(30)
227
228 # Check that the instance's imageRef matches the new imageRef
229 server = self.os.nova.get_server(self.server_id)
230 ref_match = self.image_ref == server['image']['links'][0]['href']
231 id_match = self.image_ref == server['image']['id']
232 self.assertTrue(ref_match or id_match)
233
234 # SSH into the server to ensure it came back up
235 self._assert_ssh_password(specified_password)
236
237 # make sure file is gone
238 self.assertEqual(self._read_file(FILENAME, specified_password), '')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200239
240 @unittest.skipIf(not multi_node, 'Multiple compute nodes required')
241 def test_resize_server_confirm(self):
242 """Resize a server"""
243 # Make resize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200244 post_body = json.dumps({'resize': {'flavorRef': self.flavor_ref_alt}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200245 url = '/servers/%s/action' % self.server_id
246 response, body = self.os.nova.request('POST', url, body=post_body)
247
248 # Wait for status transition
249 self.assertEqual('202', response['status'])
250 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200251 #self._wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
252 self._wait_for_server_status(self.server_id, 'RESIZE-CONFIRM')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200253
254 # Ensure API reports new flavor
255 server = self.os.nova.get_server(self.server_id)
256 self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
257
258 #SSH into the server to ensure it came back up
259 self._assert_ssh_password()
260
261 # Make confirmResize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200262 post_body = json.dumps({'confirmResize': 'null'})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200263 url = '/servers/%s/action' % self.server_id
264 response, body = self.os.nova.request('POST', url, body=post_body)
265
266 # Wait for status transition
267 self.assertEqual('204', response['status'])
Soren Hansen5f4ad832011-09-09 14:08:19 +0200268 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200269
270 # Ensure API still reports new flavor
271 server = self.os.nova.get_server(self.server_id)
272 self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
273
274 @unittest.skipIf(not multi_node, 'Multiple compute nodes required')
275 def test_resize_server_revert(self):
276 """Resize a server, then revert"""
277
278 # Make resize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200279 post_body = json.dumps({'resize': {'flavorRef': self.flavor_ref_alt}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200280 url = '/servers/%s/action' % self.server_id
281 response, body = self.os.nova.request('POST', url, body=post_body)
282
283 # Wait for status transition
284 self.assertEqual('202', response['status'])
285 # KNOWN-ISSUE
Soren Hansen5f4ad832011-09-09 14:08:19 +0200286 #self._wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
287 self._wait_for_server_status(self.server_id, 'RESIZE-CONFIRM')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200288
289 # SSH into the server to ensure it came back up
290 self._assert_ssh_password()
291
292 # Ensure API reports new flavor
293 server = self.os.nova.get_server(self.server_id)
294 self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
295
296 # Make revertResize request
Soren Hansen5f4ad832011-09-09 14:08:19 +0200297 post_body = json.dumps({'revertResize': 'null'})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200298 url = '/servers/%s/action' % self.server_id
299 response, body = self.os.nova.request('POST', url, body=post_body)
300
301 # Assert status transition
302 self.assertEqual('202', response['status'])
Soren Hansen5f4ad832011-09-09 14:08:19 +0200303 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200304
305 # Ensure flavor ref was reverted to original
306 server = self.os.nova.get_server(self.server_id)
307 self.assertEqual(self.flavor_ref, server['flavor']['id'])
308
309
310class SnapshotTests(unittest.TestCase):
311
312 def setUp(self):
313 self.os = openstack.Manager()
314
315 self.image_ref = self.os.config.env.image_ref
316 self.flavor_ref = self.os.config.env.flavor_ref
317 self.ssh_timeout = self.os.config.nova.ssh_timeout
Soren Hansen5f4ad832011-09-09 14:08:19 +0200318 self.build_timeout = self.os.config.nova.build_timeout
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200319
Soren Hansen5f4ad832011-09-09 14:08:19 +0200320 self.server_name = 'stacktester1'
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200321
322 expected_server = {
Soren Hansen5f4ad832011-09-09 14:08:19 +0200323 'name': self.server_name,
324 'imageRef': self.image_ref,
325 'flavorRef': self.flavor_ref,
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200326 }
327
328 created_server = self.os.nova.create_server(expected_server)
329 self.server_id = created_server['id']
330
331 def tearDown(self):
332 self.os.nova.delete_server(self.server_id)
333
Soren Hansen5f4ad832011-09-09 14:08:19 +0200334 def _wait_for_server_status(self, server_id, status):
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200335 try:
Soren Hansen5f4ad832011-09-09 14:08:19 +0200336 self.os.nova.wait_for_server_status(server_id, status,
337 timeout=self.build_timeout)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200338 except exceptions.TimeoutException:
339 self.fail("Server failed to change status to %s" % status)
340
341 def test_snapshot_server_active(self):
342 """Create image from an existing server"""
343
344 # Wait for server to come up before running this test
Soren Hansen5f4ad832011-09-09 14:08:19 +0200345 self._wait_for_server_status(self.server_id, 'ACTIVE')
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200346
347 # Create snapshot
Soren Hansen5f4ad832011-09-09 14:08:19 +0200348 image_data = {'name': 'backup'}
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200349 req_body = json.dumps({'createImage': image_data})
350 url = '/servers/%s/action' % self.server_id
351 response, body = self.os.nova.request('POST', url, body=req_body)
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200352
353 self.assertEqual(response['status'], '202')
354 image_ref = response['location']
Soren Hansen5f4ad832011-09-09 14:08:19 +0200355 snapshot_id = image_ref.rsplit('/', 1)[1]
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200356
357 # Get snapshot and check its attributes
358 resp, body = self.os.nova.request('GET', '/images/%s' % snapshot_id)
359 snapshot = json.loads(body)['image']
360 self.assertEqual(snapshot['name'], image_data['name'])
361 server_ref = snapshot['server']['links'][0]['href']
362 self.assertTrue(server_ref.endswith('/%s' % self.server_id))
363
364 # Ensure image is actually created
365 self.os.nova.wait_for_image_status(snapshot['id'], 'ACTIVE')
366
367 # Cleaning up
368 self.os.nova.request('DELETE', '/images/%s' % snapshot_id)
369
370 def test_snapshot_server_inactive(self):
371 """Ensure inability to snapshot server in BUILD state"""
372
373 # Create snapshot
Soren Hansen5f4ad832011-09-09 14:08:19 +0200374 req_body = json.dumps({'createImage': {'name': 'backup'}})
Soren Hansenbc1d3a02011-09-08 13:33:17 +0200375 url = '/servers/%s/action' % self.server_id
376 response, body = self.os.nova.request('POST', url, body=req_body)
377
378 # KNOWN-ISSUE - we shouldn't be able to snapshot a building server
379 #self.assertEqual(response['status'], '400') # what status code?
380 self.assertEqual(response['status'], '202')
381 snapshot_id = response['location'].rsplit('/', 1)[1]
382 # Delete image for now, won't need this once correct status code is in
383 self.os.nova.request('DELETE', '/images/%s' % snapshot_id)