David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 1 | # Copyright 2011 Quanta Research Cambridge, Inc. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | """Defines various sub-classes of the `StressTestCase` and |
David Kranz | 779c7f8 | 2012-05-01 16:50:32 -0400 | [diff] [blame] | 15 | `PendingServerAction` class. Sub-classes of StressTestCase implement various |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 16 | API calls on the Nova cluster having to do with creating and deleting VMs. |
David Kranz | 779c7f8 | 2012-05-01 16:50:32 -0400 | [diff] [blame] | 17 | Each sub-class will have a corresponding PendingServerAction. These pending |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 18 | actions veriy that the API call was successful or not.""" |
| 19 | |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 20 | import random |
| 21 | import time |
| 22 | |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 23 | import pending_action |
Matthew Treinish | 8d6836b | 2012-12-10 10:07:56 -0500 | [diff] [blame] | 24 | import test_case |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 25 | |
| 26 | |
| 27 | class TestCreateVM(test_case.StressTestCase): |
| 28 | """Create a virtual machine in the Nova cluster.""" |
| 29 | _vm_id = 0 |
| 30 | |
| 31 | def run(self, manager, state, *pargs, **kwargs): |
| 32 | """ |
| 33 | Send an HTTP POST request to the nova cluster to build a |
| 34 | server. Update the state variable to track state of new server |
| 35 | and set to PENDING state. |
| 36 | |
| 37 | `manager` : Manager object. |
| 38 | `state` : `State` object describing our view of state of cluster |
| 39 | `pargs` : positional arguments |
| 40 | `kwargs` : keyword arguments, which include: |
| 41 | `key_name` : name of keypair |
| 42 | `timeout` : how long to wait before issuing Exception |
| 43 | `image_ref` : index to image types availablexs |
| 44 | `flavor_ref`: index to flavor types available |
| 45 | (default = 1, which is tiny) |
| 46 | """ |
| 47 | |
| 48 | # restrict number of instances we can launch |
| 49 | if len(state.get_instances()) >= state.get_max_instances(): |
| 50 | self._logger.debug("maximum number of instances created: %d" % |
| 51 | state.get_max_instances()) |
| 52 | return None |
| 53 | |
| 54 | _key_name = kwargs.get('key_name', '') |
David Kranz | 30fe84a | 2012-03-20 16:25:47 -0400 | [diff] [blame] | 55 | _timeout = int(kwargs.get('timeout', |
| 56 | manager.config.compute.build_timeout)) |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 57 | _image_ref = kwargs.get('image_ref', manager.config.compute.image_ref) |
| 58 | _flavor_ref = kwargs.get('flavor_ref', |
| 59 | manager.config.compute.flavor_ref) |
| 60 | |
| 61 | expected_server = { |
| 62 | 'name': 'server' + str(TestCreateVM._vm_id), |
| 63 | 'metadata': { |
| 64 | 'key1': 'value1', |
| 65 | 'key2': 'value2', |
Zhongyue Luo | 30a563f | 2012-09-30 23:43:50 +0900 | [diff] [blame] | 66 | }, |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 67 | 'imageRef': _image_ref, |
| 68 | 'flavorRef': _flavor_ref, |
| 69 | 'adminPass': 'testpwd', |
Zhongyue Luo | 30a563f | 2012-09-30 23:43:50 +0900 | [diff] [blame] | 70 | 'key_name': _key_name, |
| 71 | } |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 72 | TestCreateVM._vm_id = TestCreateVM._vm_id + 1 |
Zhongyue Luo | 30a563f | 2012-09-30 23:43:50 +0900 | [diff] [blame] | 73 | create_server = manager.servers_client.create_server |
| 74 | response, body = create_server(expected_server['name'], |
| 75 | _image_ref, |
| 76 | _flavor_ref, |
| 77 | meta=expected_server['metadata'], |
| 78 | adminPass=expected_server['adminPass']) |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 79 | |
| 80 | if (response.status != 202): |
| 81 | self._logger.error("response: %s" % response) |
| 82 | self._logger.error("body: %s" % body) |
| 83 | raise Exception |
| 84 | |
| 85 | created_server = body |
| 86 | |
| 87 | self._logger.info('setting machine %s to BUILD' % |
| 88 | created_server['id']) |
| 89 | state.set_instance_state(created_server['id'], |
| 90 | (created_server, 'BUILD')) |
| 91 | |
| 92 | return VerifyCreateVM(manager, |
| 93 | state, |
| 94 | created_server, |
| 95 | expected_server) |
| 96 | |
| 97 | |
David Kranz | 779c7f8 | 2012-05-01 16:50:32 -0400 | [diff] [blame] | 98 | class VerifyCreateVM(pending_action.PendingServerAction): |
Sean Dague | f237ccb | 2013-01-04 15:19:14 -0500 | [diff] [blame] | 99 | """Verify that VM was built and is running.""" |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 100 | def __init__(self, manager, |
| 101 | state, |
| 102 | created_server, |
| 103 | expected_server): |
| 104 | super(VerifyCreateVM, self).__init__(manager, |
| 105 | state, |
| 106 | created_server, |
| 107 | ) |
| 108 | self._expected = expected_server |
| 109 | |
| 110 | def retry(self): |
| 111 | """ |
| 112 | Check to see that the server was created and is running. |
| 113 | Update local view of state to indicate that it is running. |
| 114 | """ |
| 115 | # don't run create verification |
| 116 | # if target machine has been deleted or is going to be deleted |
Zhongyue Luo | 76888ee | 2012-09-30 23:58:52 +0900 | [diff] [blame] | 117 | target_id = self._target['id'] |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 118 | if (self._target['id'] not in self._state.get_instances().keys() or |
Zhongyue Luo | 76888ee | 2012-09-30 23:58:52 +0900 | [diff] [blame] | 119 | self._state.get_instances()[target_id][1] == 'TERMINATING'): |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 120 | self._logger.info('machine %s is deleted or TERMINATING' % |
Zhongyue Luo | e0884a3 | 2012-09-25 17:24:17 +0800 | [diff] [blame] | 121 | self._target['id']) |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 122 | return True |
| 123 | |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 124 | admin_pass = self._target['adminPass'] |
| 125 | # Could check more things here. |
| 126 | if (self._expected['adminPass'] != admin_pass): |
| 127 | self._logger.error('expected: %s' % |
| 128 | (self._expected['adminPass'])) |
| 129 | self._logger.error('returned: %s' % |
| 130 | (admin_pass)) |
| 131 | raise Exception |
| 132 | |
| 133 | if self._check_for_status('ACTIVE') != 'ACTIVE': |
| 134 | return False |
| 135 | |
| 136 | self._logger.info('machine %s: BUILD -> ACTIVE [%.1f secs elapsed]' % |
David Kranz | 779c7f8 | 2012-05-01 16:50:32 -0400 | [diff] [blame] | 137 | (self._target['id'], self.elapsed())) |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 138 | self._state.set_instance_state(self._target['id'], |
| 139 | (self._target, 'ACTIVE')) |
| 140 | return True |
| 141 | |
| 142 | |
| 143 | class TestKillActiveVM(test_case.StressTestCase): |
| 144 | """Class to destroy a random ACTIVE server.""" |
| 145 | def run(self, manager, state, *pargs, **kwargs): |
| 146 | """ |
| 147 | Send an HTTP POST request to the nova cluster to destroy |
| 148 | a random ACTIVE server. Update `state` to indicate TERMINATING. |
| 149 | |
| 150 | `manager` : Manager object. |
| 151 | `state` : `State` object describing our view of state of cluster |
| 152 | `pargs` : positional arguments |
| 153 | `kwargs` : keyword arguments, which include: |
| 154 | `timeout` : how long to wait before issuing Exception |
| 155 | """ |
| 156 | # check for active instances |
| 157 | vms = state.get_instances() |
| 158 | active_vms = [v for k, v in vms.iteritems() if v and v[1] == 'ACTIVE'] |
| 159 | # no active vms, so return null |
| 160 | if not active_vms: |
| 161 | self._logger.info('no ACTIVE instances to delete') |
| 162 | return |
| 163 | |
David Kranz | 30fe84a | 2012-03-20 16:25:47 -0400 | [diff] [blame] | 164 | _timeout = kwargs.get('timeout', manager.config.compute.build_timeout) |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 165 | |
| 166 | target = random.choice(active_vms) |
| 167 | killtarget = target[0] |
| 168 | manager.servers_client.delete_server(killtarget['id']) |
| 169 | self._logger.info('machine %s: ACTIVE -> TERMINATING' % |
| 170 | killtarget['id']) |
| 171 | state.set_instance_state(killtarget['id'], |
| 172 | (killtarget, 'TERMINATING')) |
| 173 | return VerifyKillActiveVM(manager, state, |
| 174 | killtarget, timeout=_timeout) |
| 175 | |
| 176 | |
David Kranz | 779c7f8 | 2012-05-01 16:50:32 -0400 | [diff] [blame] | 177 | class VerifyKillActiveVM(pending_action.PendingServerAction): |
Sean Dague | f237ccb | 2013-01-04 15:19:14 -0500 | [diff] [blame] | 178 | """Verify that server was destroyed.""" |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 179 | |
| 180 | def retry(self): |
| 181 | """ |
| 182 | Check to see that the server of interest is destroyed. Update |
| 183 | state to indicate that server is destroyed by deleting it from local |
| 184 | view of state. |
| 185 | """ |
| 186 | tid = self._target['id'] |
| 187 | # if target machine has been deleted from the state, then it was |
| 188 | # already verified to be deleted |
| 189 | if (not tid in self._state.get_instances().keys()): |
| 190 | return False |
| 191 | |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 192 | try: |
| 193 | self._manager.servers_client.get_server(tid) |
| 194 | except Exception: |
| 195 | # if we get a 404 response, is the machine really gone? |
| 196 | target = self._target |
| 197 | self._logger.info('machine %s: DELETED [%.1f secs elapsed]' % |
David Kranz | 779c7f8 | 2012-05-01 16:50:32 -0400 | [diff] [blame] | 198 | (target['id'], self.elapsed())) |
David Kranz | 180fed1 | 2012-03-27 14:31:29 -0400 | [diff] [blame] | 199 | self._state.delete_instance_state(target['id']) |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 200 | return True |
| 201 | |
| 202 | return False |
| 203 | |
| 204 | |
| 205 | class TestKillAnyVM(test_case.StressTestCase): |
| 206 | """Class to destroy a random server regardless of state.""" |
| 207 | |
| 208 | def run(self, manager, state, *pargs, **kwargs): |
| 209 | """ |
| 210 | Send an HTTP POST request to the nova cluster to destroy |
| 211 | a random server. Update state to TERMINATING. |
| 212 | |
| 213 | `manager` : Manager object. |
| 214 | `state` : `State` object describing our view of state of cluster |
| 215 | `pargs` : positional arguments |
| 216 | `kwargs` : keyword arguments, which include: |
| 217 | `timeout` : how long to wait before issuing Exception |
| 218 | """ |
| 219 | |
| 220 | vms = state.get_instances() |
| 221 | # no vms, so return null |
| 222 | if not vms: |
| 223 | self._logger.info('no active instances to delete') |
| 224 | return |
| 225 | |
David Kranz | 30fe84a | 2012-03-20 16:25:47 -0400 | [diff] [blame] | 226 | _timeout = kwargs.get('timeout', manager.config.compute.build_timeout) |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 227 | |
| 228 | target = random.choice(vms) |
| 229 | killtarget = target[0] |
| 230 | |
| 231 | manager.servers_client.delete_server(killtarget['id']) |
| 232 | self._state.set_instance_state(killtarget['id'], |
| 233 | (killtarget, 'TERMINATING')) |
| 234 | # verify object will do the same thing as the active VM |
| 235 | return VerifyKillAnyVM(manager, state, killtarget, timeout=_timeout) |
| 236 | |
| 237 | VerifyKillAnyVM = VerifyKillActiveVM |
| 238 | |
| 239 | |
| 240 | class TestUpdateVMName(test_case.StressTestCase): |
Sean Dague | f237ccb | 2013-01-04 15:19:14 -0500 | [diff] [blame] | 241 | """Class to change the name of the active server.""" |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 242 | def run(self, manager, state, *pargs, **kwargs): |
| 243 | """ |
| 244 | Issue HTTP POST request to change the name of active server. |
| 245 | Update state of server to reflect name changing. |
| 246 | |
| 247 | `manager` : Manager object. |
| 248 | `state` : `State` object describing our view of state of cluster |
| 249 | `pargs` : positional arguments |
| 250 | `kwargs` : keyword arguments, which include: |
| 251 | `timeout` : how long to wait before issuing Exception |
| 252 | """ |
| 253 | |
| 254 | # select one machine from active ones |
| 255 | vms = state.get_instances() |
| 256 | active_vms = [v for k, v in vms.iteritems() if v and v[1] == 'ACTIVE'] |
| 257 | # no active vms, so return null |
| 258 | if not active_vms: |
| 259 | self._logger.info('no active instances to update') |
| 260 | return |
| 261 | |
David Kranz | 30fe84a | 2012-03-20 16:25:47 -0400 | [diff] [blame] | 262 | _timeout = kwargs.get('timeout', manager.config.compute.build_timeout) |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 263 | |
| 264 | target = random.choice(active_vms) |
| 265 | update_target = target[0] |
| 266 | |
| 267 | # Update name by appending '_updated' to the name |
| 268 | new_name = update_target['name'] + '_updated' |
| 269 | (response, body) = \ |
| 270 | manager.servers_client.update_server(update_target['id'], |
Zhongyue Luo | e0884a3 | 2012-09-25 17:24:17 +0800 | [diff] [blame] | 271 | name=new_name) |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 272 | if (response.status != 200): |
| 273 | self._logger.error("response: %s " % response) |
| 274 | self._logger.error("body: %s " % body) |
| 275 | raise Exception |
| 276 | |
| 277 | assert(new_name == body['name']) |
| 278 | |
| 279 | self._logger.info('machine %s: ACTIVE -> UPDATING_NAME' % |
| 280 | body['id']) |
| 281 | state.set_instance_state(body['id'], |
| 282 | (body, 'UPDATING_NAME')) |
| 283 | |
| 284 | return VerifyUpdateVMName(manager, |
| 285 | state, |
| 286 | body, |
| 287 | timeout=_timeout) |
| 288 | |
| 289 | |
David Kranz | 779c7f8 | 2012-05-01 16:50:32 -0400 | [diff] [blame] | 290 | class VerifyUpdateVMName(pending_action.PendingServerAction): |
Sean Dague | f237ccb | 2013-01-04 15:19:14 -0500 | [diff] [blame] | 291 | """Check that VM has new name.""" |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 292 | def retry(self): |
| 293 | """ |
| 294 | Check that VM has new name. Update local view of `state` to RUNNING. |
| 295 | """ |
| 296 | # don't run update verification |
| 297 | # if target machine has been deleted or is going to be deleted |
Zhongyue Luo | 76888ee | 2012-09-30 23:58:52 +0900 | [diff] [blame] | 298 | target_id = self._target['id'] |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 299 | if (not self._target['id'] in self._state.get_instances().keys() or |
Zhongyue Luo | 76888ee | 2012-09-30 23:58:52 +0900 | [diff] [blame] | 300 | self._state.get_instances()[target_id][1] == 'TERMINATING'): |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 301 | return False |
| 302 | |
David Kranz | 6308ec2 | 2012-02-22 09:36:48 -0500 | [diff] [blame] | 303 | response, body = \ |
| 304 | self._manager.serverse_client.get_server(self._target['id']) |
| 305 | if (response.status != 200): |
| 306 | self._logger.error("response: %s " % response) |
| 307 | self._logger.error("body: %s " % body) |
| 308 | raise Exception |
| 309 | |
| 310 | if self._target['name'] != body['name']: |
| 311 | self._logger.error(self._target['name'] + |
| 312 | ' vs. ' + |
| 313 | body['name']) |
| 314 | raise Exception |
| 315 | |
| 316 | # log the update |
| 317 | self._logger.info('machine %s: UPDATING_NAME -> ACTIVE' % |
| 318 | self._target['id']) |
| 319 | self._state.set_instance_state(self._target['id'], |
| 320 | (body, |
| 321 | 'ACTIVE')) |
| 322 | return True |