| # Copyright 2011 Quanta Research Cambridge, Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Defines various sub-classes of the `StressTestCase` and |
| `PendingServerAction` class. Sub-classes of StressTestCase implement various |
| API calls on the Nova cluster having to do with creating and deleting VMs. |
| Each sub-class will have a corresponding PendingServerAction. These pending |
| actions veriy that the API call was successful or not.""" |
| |
| |
| # system imports |
| import random |
| import time |
| |
| |
| # local imports |
| import test_case |
| import pending_action |
| |
| |
| class TestCreateVM(test_case.StressTestCase): |
| """Create a virtual machine in the Nova cluster.""" |
| _vm_id = 0 |
| |
| def run(self, manager, state, *pargs, **kwargs): |
| """ |
| Send an HTTP POST request to the nova cluster to build a |
| server. Update the state variable to track state of new server |
| and set to PENDING state. |
| |
| `manager` : Manager object. |
| `state` : `State` object describing our view of state of cluster |
| `pargs` : positional arguments |
| `kwargs` : keyword arguments, which include: |
| `key_name` : name of keypair |
| `timeout` : how long to wait before issuing Exception |
| `image_ref` : index to image types availablexs |
| `flavor_ref`: index to flavor types available |
| (default = 1, which is tiny) |
| """ |
| |
| # restrict number of instances we can launch |
| if len(state.get_instances()) >= state.get_max_instances(): |
| self._logger.debug("maximum number of instances created: %d" % |
| state.get_max_instances()) |
| return None |
| |
| _key_name = kwargs.get('key_name', '') |
| _timeout = int(kwargs.get('timeout', |
| manager.config.compute.build_timeout)) |
| _image_ref = kwargs.get('image_ref', manager.config.compute.image_ref) |
| _flavor_ref = kwargs.get('flavor_ref', |
| manager.config.compute.flavor_ref) |
| |
| expected_server = { |
| 'name': 'server' + str(TestCreateVM._vm_id), |
| 'metadata': { |
| 'key1': 'value1', |
| 'key2': 'value2', |
| }, |
| 'imageRef': _image_ref, |
| 'flavorRef': _flavor_ref, |
| 'adminPass': 'testpwd', |
| 'key_name': _key_name |
| } |
| TestCreateVM._vm_id = TestCreateVM._vm_id + 1 |
| response, body = manager.servers_client.create_server( |
| expected_server['name'], |
| _image_ref, |
| _flavor_ref, |
| meta=expected_server['metadata'], |
| adminPass=expected_server['adminPass'] |
| ) |
| |
| if (response.status != 202): |
| self._logger.error("response: %s" % response) |
| self._logger.error("body: %s" % body) |
| raise Exception |
| |
| created_server = body |
| |
| self._logger.info('setting machine %s to BUILD' % |
| created_server['id']) |
| state.set_instance_state(created_server['id'], |
| (created_server, 'BUILD')) |
| |
| return VerifyCreateVM(manager, |
| state, |
| created_server, |
| expected_server) |
| |
| |
| class VerifyCreateVM(pending_action.PendingServerAction): |
| """Verify that VM was built and is running""" |
| def __init__(self, manager, |
| state, |
| created_server, |
| expected_server): |
| super(VerifyCreateVM, self).__init__(manager, |
| state, |
| created_server, |
| ) |
| self._expected = expected_server |
| |
| def retry(self): |
| """ |
| Check to see that the server was created and is running. |
| Update local view of state to indicate that it is running. |
| """ |
| # don't run create verification |
| # if target machine has been deleted or is going to be deleted |
| if (self._target['id'] not in self._state.get_instances().keys() or |
| self._state.get_instances()[self._target['id']][1] == |
| 'TERMINATING'): |
| self._logger.info('machine %s is deleted or TERMINATING' % |
| self._target['id']) |
| return True |
| |
| admin_pass = self._target['adminPass'] |
| # Could check more things here. |
| if (self._expected['adminPass'] != admin_pass): |
| self._logger.error('expected: %s' % |
| (self._expected['adminPass'])) |
| self._logger.error('returned: %s' % |
| (admin_pass)) |
| raise Exception |
| |
| if self._check_for_status('ACTIVE') != 'ACTIVE': |
| return False |
| |
| self._logger.info('machine %s: BUILD -> ACTIVE [%.1f secs elapsed]' % |
| (self._target['id'], self.elapsed())) |
| self._state.set_instance_state(self._target['id'], |
| (self._target, 'ACTIVE')) |
| return True |
| |
| |
| class TestKillActiveVM(test_case.StressTestCase): |
| """Class to destroy a random ACTIVE server.""" |
| def run(self, manager, state, *pargs, **kwargs): |
| """ |
| Send an HTTP POST request to the nova cluster to destroy |
| a random ACTIVE server. Update `state` to indicate TERMINATING. |
| |
| `manager` : Manager object. |
| `state` : `State` object describing our view of state of cluster |
| `pargs` : positional arguments |
| `kwargs` : keyword arguments, which include: |
| `timeout` : how long to wait before issuing Exception |
| """ |
| # check for active instances |
| vms = state.get_instances() |
| active_vms = [v for k, v in vms.iteritems() if v and v[1] == 'ACTIVE'] |
| # no active vms, so return null |
| if not active_vms: |
| self._logger.info('no ACTIVE instances to delete') |
| return |
| |
| _timeout = kwargs.get('timeout', manager.config.compute.build_timeout) |
| |
| target = random.choice(active_vms) |
| killtarget = target[0] |
| manager.servers_client.delete_server(killtarget['id']) |
| self._logger.info('machine %s: ACTIVE -> TERMINATING' % |
| killtarget['id']) |
| state.set_instance_state(killtarget['id'], |
| (killtarget, 'TERMINATING')) |
| return VerifyKillActiveVM(manager, state, |
| killtarget, timeout=_timeout) |
| |
| |
| class VerifyKillActiveVM(pending_action.PendingServerAction): |
| """Verify that server was destroyed""" |
| |
| def retry(self): |
| """ |
| Check to see that the server of interest is destroyed. Update |
| state to indicate that server is destroyed by deleting it from local |
| view of state. |
| """ |
| tid = self._target['id'] |
| # if target machine has been deleted from the state, then it was |
| # already verified to be deleted |
| if (not tid in self._state.get_instances().keys()): |
| return False |
| |
| try: |
| self._manager.servers_client.get_server(tid) |
| except Exception: |
| # if we get a 404 response, is the machine really gone? |
| target = self._target |
| self._logger.info('machine %s: DELETED [%.1f secs elapsed]' % |
| (target['id'], self.elapsed())) |
| self._state.delete_instance_state(target['id']) |
| return True |
| |
| return False |
| |
| |
| class TestKillAnyVM(test_case.StressTestCase): |
| """Class to destroy a random server regardless of state.""" |
| |
| def run(self, manager, state, *pargs, **kwargs): |
| """ |
| Send an HTTP POST request to the nova cluster to destroy |
| a random server. Update state to TERMINATING. |
| |
| `manager` : Manager object. |
| `state` : `State` object describing our view of state of cluster |
| `pargs` : positional arguments |
| `kwargs` : keyword arguments, which include: |
| `timeout` : how long to wait before issuing Exception |
| """ |
| |
| vms = state.get_instances() |
| # no vms, so return null |
| if not vms: |
| self._logger.info('no active instances to delete') |
| return |
| |
| _timeout = kwargs.get('timeout', manager.config.compute.build_timeout) |
| |
| target = random.choice(vms) |
| killtarget = target[0] |
| |
| manager.servers_client.delete_server(killtarget['id']) |
| self._state.set_instance_state(killtarget['id'], |
| (killtarget, 'TERMINATING')) |
| # verify object will do the same thing as the active VM |
| return VerifyKillAnyVM(manager, state, killtarget, timeout=_timeout) |
| |
| VerifyKillAnyVM = VerifyKillActiveVM |
| |
| |
| class TestUpdateVMName(test_case.StressTestCase): |
| """Class to change the name of the active server""" |
| def run(self, manager, state, *pargs, **kwargs): |
| """ |
| Issue HTTP POST request to change the name of active server. |
| Update state of server to reflect name changing. |
| |
| `manager` : Manager object. |
| `state` : `State` object describing our view of state of cluster |
| `pargs` : positional arguments |
| `kwargs` : keyword arguments, which include: |
| `timeout` : how long to wait before issuing Exception |
| """ |
| |
| # select one machine from active ones |
| vms = state.get_instances() |
| active_vms = [v for k, v in vms.iteritems() if v and v[1] == 'ACTIVE'] |
| # no active vms, so return null |
| if not active_vms: |
| self._logger.info('no active instances to update') |
| return |
| |
| _timeout = kwargs.get('timeout', manager.config.compute.build_timeout) |
| |
| target = random.choice(active_vms) |
| update_target = target[0] |
| |
| # Update name by appending '_updated' to the name |
| new_name = update_target['name'] + '_updated' |
| (response, body) = \ |
| manager.servers_client.update_server(update_target['id'], |
| name=new_name) |
| if (response.status != 200): |
| self._logger.error("response: %s " % response) |
| self._logger.error("body: %s " % body) |
| raise Exception |
| |
| assert(new_name == body['name']) |
| |
| self._logger.info('machine %s: ACTIVE -> UPDATING_NAME' % |
| body['id']) |
| state.set_instance_state(body['id'], |
| (body, 'UPDATING_NAME')) |
| |
| return VerifyUpdateVMName(manager, |
| state, |
| body, |
| timeout=_timeout) |
| |
| |
| class VerifyUpdateVMName(pending_action.PendingServerAction): |
| """Check that VM has new name""" |
| def retry(self): |
| """ |
| Check that VM has new name. Update local view of `state` to RUNNING. |
| """ |
| # don't run update verification |
| # if target machine has been deleted or is going to be deleted |
| if (not self._target['id'] in self._state.get_instances().keys() or |
| self._state.get_instances()[self._target['id']][1] == |
| 'TERMINATING'): |
| return False |
| |
| response, body = \ |
| self._manager.serverse_client.get_server(self._target['id']) |
| if (response.status != 200): |
| self._logger.error("response: %s " % response) |
| self._logger.error("body: %s " % body) |
| raise Exception |
| |
| if self._target['name'] != body['name']: |
| self._logger.error(self._target['name'] + |
| ' vs. ' + |
| body['name']) |
| raise Exception |
| |
| # log the update |
| self._logger.info('machine %s: UPDATING_NAME -> ACTIVE' % |
| self._target['id']) |
| self._state.set_instance_state(self._target['id'], |
| (body, |
| 'ACTIVE')) |
| return True |