Add resources for floating_ip, keypair, volume. Add floating_ip test.
Provide mechanism to pre-allocate vms, floating_ips, keypairs and volumes.
Abstract time-related functions to PendingAction and move server-specific
stuff to PendingServerAction subclass.
Rename State to ClusterState.
Add test that associates/disassociates floating_ips and servers.
Change-Id: I1651c38cc75d755bde370fb6a49ff4231e96255e
diff --git a/stress/driver.py b/stress/driver.py
index 9f263f6..71d02a9 100644
--- a/stress/driver.py
+++ b/stress/driver.py
@@ -23,9 +23,11 @@
# local imports
from test_case import *
-from state import State
import utils.util
from config import StressConfig
+from state import ClusterState, KeyPairState, FloatingIpState, VolumeState
+from tempest.common.utils.data_utils import rand_name
+
# setup logging to file
logging.basicConfig(
@@ -96,6 +98,53 @@
return False
+def create_initial_vms(manager, state, count):
+ image = manager.config.compute.image_ref
+ flavor = manager.config.compute.flavor_ref
+ servers = []
+ logging.info('Creating %d vms' % count)
+ for _ in xrange(count):
+ name = rand_name('initial_vm-')
+ _, server = manager.servers_client.create_server(name, image, flavor)
+ servers.append(server)
+ for server in servers:
+ manager.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ logging.info('Server Name: %s Id: %s' % (name, server['id']))
+ state.set_instance_state(server['id'], (server, 'ACTIVE'))
+
+
+def create_initial_floating_ips(manager, state, count):
+ logging.info('Creating %d floating ips' % count)
+ for _ in xrange(count):
+ _, ip = manager.floating_ips_client.create_floating_ip()
+ logging.info('Ip: %s' % ip['ip'])
+ state.add_floating_ip(FloatingIpState(ip))
+
+
+def create_initial_keypairs(manager, state, count):
+ logging.info('Creating %d keypairs' % count)
+ for _ in xrange(count):
+ name = rand_name('keypair-')
+ _, keypair = manager.keypairs_client.create_keypair(name)
+ logging.info('Keypair: %s' % name)
+ state.add_keypair(KeyPairState(keypair))
+
+
+def create_initial_volumes(manager, state, count):
+ volumes = []
+ logging.info('Creating %d volumes' % count)
+ for _ in xrange(count):
+ name = rand_name('volume-')
+ _, volume = manager.volumes_client.create_volume(size=1,
+ display_name=name)
+ volumes.append(volume)
+ for volume in volumes:
+ manager.volumes_client.wait_for_volume_status(volume['id'],
+ 'available')
+ logging.info('Volume Name: %s Id: %s' % (name, volume['id']))
+ state.add_volume(VolumeState(volume))
+
+
def bash_openstack(manager,
choice_spec,
**kwargs):
@@ -130,8 +179,16 @@
"rm -f %s/*.log" % logdir)
random.seed(seed)
cases = _create_cases(choice_spec)
+ state = ClusterState(max_vms=max_vms)
+ create_initial_keypairs(manager, state,
+ int(kwargs.get('initial_keypairs', 0)))
+ create_initial_vms(manager, state,
+ int(kwargs.get('initial_vms', 0)))
+ create_initial_floating_ips(manager, state,
+ int(kwargs.get('initial_floating_ips', 0)))
+ create_initial_volumes(manager, state,
+ int(kwargs.get('initial_volumes', 0)))
test_end_time = time.time() + duration.seconds
- state = State(max_vms=max_vms)
retry_list = []
last_retry = time.time()
@@ -163,6 +220,7 @@
logging.debug('retry verifications for %d tasks', len(retry_list))
new_retry_list = []
for v in retry_list:
+ v.check_timeout()
if not v.retry():
new_retry_list.append(v)
retry_list = new_retry_list
@@ -180,7 +238,8 @@
# Cleanup
logging.info('Cleaning up: terminating virtual machines...')
vms = state.get_instances()
- active_vms = [v for _k, v in vms.iteritems() if v and v[1] == 'ACTIVE']
+ active_vms = [v for _k, v in vms.iteritems()
+ if v and v[1] != 'TERMINATING']
for target in active_vms:
manager.servers_client.delete_server(target[0]['id'])
# check to see that the server was actually killed
@@ -199,6 +258,13 @@
time.sleep(1)
logging.info('killed %s' % kill_id)
state.delete_instance_state(kill_id)
+ for floating_ip_state in state.get_floating_ips():
+ manager.floating_ips_client.delete_floating_ip(
+ floating_ip_state.resource_id)
+ for keypair_state in state.get_keypairs():
+ manager.keypairs_client.delete_keypair(keypair_state.name)
+ for volume_state in state.get_volumes():
+ manager.volumes_client.delete_volume(volume_state.resource_id)
if test_succeeded:
logging.info('*** Test succeeded ***')
diff --git a/stress/pending_action.py b/stress/pending_action.py
index 913cc42..67eba13 100644
--- a/stress/pending_action.py
+++ b/stress/pending_action.py
@@ -17,6 +17,7 @@
import logging
import time
+from tempest.exceptions import TimeoutException
class PendingAction(object):
@@ -25,25 +26,55 @@
is successful.
"""
- def __init__(self, nova_manager, state, target_server, timeout=600):
+ def __init__(self, nova_manager, timeout=None):
"""
`nova_manager` : Manager object.
+ `timeout` : time before we declare a TimeoutException
+ """
+ if timeout == None:
+ timeout = nova_manager.config.compute.build_timeout
+ self._manager = nova_manager
+ self._logger = logging.getLogger(self.__class__.__name__)
+ self._start_time = time.time()
+ self._timeout = timeout
+
+ def retry(self):
+ """
+ Invoked by user of this class to verify completion of
+ previous TestCase actions
+ """
+ return False
+
+ def check_timeout(self):
+ """Check for timeouts of TestCase actions"""
+ time_diff = time.time() - self._start_time
+ if time_diff > self._timeout:
+ self._logger.error('%s exceeded timeout of %d' %
+ (self.__class__.__name__, self._timeout))
+ raise TimeoutException
+
+ def elapsed(self):
+ return time.time() - self._start_time
+
+
+class PendingServerAction(PendingAction):
+ """
+ Initialize and describe actions to verify that a Nova API call that
+ changes server state is successful.
+ """
+
+ def __init__(self, nova_manager, state, target_server, timeout=None):
+ """
`state` : externally maintained data structure about
state of VMs or other persistent objects in
the nova cluster
`target_server` : server that actions were performed on
- `target_server` : time before we declare a TimeoutException
- `pargs` : positional arguments
- `kargs` : keyword arguments
"""
- self._manager = nova_manager
+ super(PendingServerAction, self).__init__(nova_manager,
+ timeout=timeout)
self._state = state
self._target = target_server
- self._logger = logging.getLogger(self.__class__.__name__)
- self._start_time = time.time()
- self._timeout = timeout
-
def _check_for_status(self, state_string):
"""Check to see if the machine has transitioned states"""
t = time.time() # for debugging
@@ -58,8 +89,3 @@
return temp_obj[1]
self._logger.debug('%s, time: %d' % (state_string, time.time() - t))
return state_string
-
- def retry(self):
- """Invoked by user of this class to verify completion of"""
- """previous TestCase actions"""
- return False
diff --git a/stress/state.py b/stress/state.py
index 60b1acc..3a9f12e 100644
--- a/stress/state.py
+++ b/stress/state.py
@@ -11,19 +11,21 @@
# 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.
-"""A class to store the state of various persistent objects in the Nova
-cluster, e.g. instances, volumes. Use methods to query to state which than
-can be compared to the current state of the objects in Nova"""
-class State(object):
+class ClusterState(object):
+ """A class to store the state of various persistent objects in the Nova
+ cluster, e.g. instances, volumes. Use methods to query to state which than
+ can be compared to the current state of the objects in Nova"""
def __init__(self, **kwargs):
self._max_vms = kwargs.get('max_vms', 32)
self._instances = {}
- self._volumes = {}
+ self._floating_ips = []
+ self._keypairs = []
+ self._volumes = []
- # machine state methods
+ # instance state methods
def get_instances(self):
"""return the instances dictionary that we believe are in cluster."""
return self._instances
@@ -39,3 +41,75 @@
def delete_instance_state(self, key):
"""Delete state indexed at `key`."""
del self._instances[key]
+
+ #floating_ip state methods
+ def get_floating_ips(self):
+ """return the floating ips list for the cluster."""
+ return self._floating_ips
+
+ def add_floating_ip(self, floating_ip_state):
+ """Add floating ip."""
+ self._floating_ips.append(floating_ip_state)
+
+ def remove_floating_ip(self, floating_ip_state):
+ """Remove floating ip."""
+ self._floating_ips.remove(floating_ip_state)
+
+ # keypair methods
+ def get_keypairs(self):
+ """return the keypairs list for the cluster."""
+ return self._keypairs
+
+ def add_keypair(self, keypair_state):
+ """Add keypair."""
+ self._keypairs.append(keypair_state)
+
+ def remove_keypair(self, keypair_state):
+ """Remove keypair."""
+ self._keypairs.remove(keypair_state)
+
+ # volume methods
+ def get_volumes(self):
+ """return the volumes list for the cluster."""
+ return self._volumes
+
+ def add_volume(self, volume_state):
+ """Add volume."""
+ self._volumes.append(volume_state)
+
+ def remove_volume(self, volume_state):
+ """Remove volume."""
+ self._volumes.remove(volume_state)
+
+
+class ServerAssociatedState(object):
+ """Class that tracks resources that are associated with a particular server
+ such as a volume or floating ip"""
+
+ def __init__(self, resource_id):
+ # The id of the server.
+ self.server_id = None
+ # The id of the resource that is attached to the server.
+ self.resource_id = resource_id
+ # True if in the process of attaching/detaching the resource.
+ self.change_pending = False
+
+
+class FloatingIpState(ServerAssociatedState):
+
+ def __init__(self, ip_desc):
+ super(FloatingIpState, self).__init__(ip_desc['id'])
+ self.address = ip_desc['ip']
+
+
+class VolumeState(ServerAssociatedState):
+
+ def __init__(self, volume_desc):
+ super(VolumeState, self).__init__(volume_desc['id'])
+
+
+class KeyPairState(object):
+
+ def __init__(self, keypair_spec):
+ self.name = keypair_spec['name']
+ self.private_key = keypair_spec['private_key']
diff --git a/stress/test_floating_ips.py b/stress/test_floating_ips.py
new file mode 100755
index 0000000..a2a20db
--- /dev/null
+++ b/stress/test_floating_ips.py
@@ -0,0 +1,95 @@
+# 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.
+
+
+# system imports
+import random
+import time
+import telnetlib
+import logging
+
+# local imports
+import test_case
+import pending_action
+
+
+class TestChangeFloatingIp(test_case.StressTestCase):
+ """Add or remove a floating ip from a vm."""
+
+ def __init__(self):
+ super(TestChangeFloatingIp, self).__init__()
+ self.server_ids = None
+
+ def run(self, manager, state, *pargs, **kwargs):
+ if self.server_ids == None:
+ vms = state.get_instances()
+ self.server_ids = [k for k, v in vms.iteritems()]
+ floating_ip = random.choice(state.get_floating_ips())
+ if floating_ip.change_pending:
+ return None
+ floating_ip.change_pending = True
+ timeout = int(kwargs.get('timeout', 60))
+ if floating_ip.server_id == None:
+ server = random.choice(self.server_ids)
+ address = floating_ip.address
+ self._logger.info('Adding %s to server %s' % (address, server))
+ resp, body =\
+ manager.floating_ips_client.associate_floating_ip_to_server(
+ address,
+ server)
+ if resp.status != 202:
+ raise Exception("response: %s body: %s" % (resp, body))
+ floating_ip.server_id = server
+ return VerifyChangeFloatingIp(manager, floating_ip,
+ timeout, add=True)
+ else:
+ server = floating_ip.server_id
+ address = floating_ip.address
+ self._logger.info('Removing %s from server %s' % (address, server))
+ resp, body =\
+ manager.floating_ips_client.disassociate_floating_ip_from_server(
+ address, server)
+ if resp.status != 202:
+ raise Exception("response: %s body: %s" % (resp, body))
+ return VerifyChangeFloatingIp(manager, floating_ip,
+ timeout, add=False)
+
+
+class VerifyChangeFloatingIp(pending_action.PendingAction):
+ """Verify that floating ip was changed"""
+ def __init__(self, manager, floating_ip, timeout, add=None):
+ super(VerifyChangeFloatingIp, self).__init__(manager, timeout=timeout)
+ self.floating_ip = floating_ip
+ self.add = add
+
+ def retry(self):
+ """
+ Check to see that we can contact the server at its new address.
+ """
+ try:
+ conn = telnetlib.Telnet(self.floating_ip.address, 22, timeout=0.5)
+ conn.close()
+ if self.add:
+ self._logger.info('%s added [%.1f secs elapsed]' %
+ (self.floating_ip.address, self.elapsed()))
+ self.floating_ip.change_pending = False
+ return True
+ except:
+ if not self.add:
+ self._logger.info('%s removed [%.1f secs elapsed]' %
+ (self.floating_ip.address, self.elapsed()))
+ self.floating_ip.change_pending = False
+ self.floating_ip.server_id = None
+ return True
+ return False
diff --git a/stress/test_server_actions.py b/stress/test_server_actions.py
index 7080630..6b4f462 100644
--- a/stress/test_server_actions.py
+++ b/stress/test_server_actions.py
@@ -12,9 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Defines various sub-classes of the `StressTestCase` and
-`PendingAction` class. The sub-classes of StressTestCase implement various
+`PendingServerAction` class. Sub-classes of StressTestCase implement various
API calls on the Nova cluster having to do with Server Actions. Each
-sub-class will have a corresponding PendingAction. These pending
+sub-class will have a corresponding PendingServerAction. These pending
actions veriy that the API call was successful or not."""
@@ -25,7 +25,7 @@
# local imports
import test_case
import pending_action
-from tempest.exceptions import TimeoutException, Duplicate
+from tempest.exceptions import Duplicate
from utils.util import *
@@ -83,7 +83,7 @@
reboot_state=reboot_state)
-class VerifyRebootVM(pending_action.PendingAction):
+class VerifyRebootVM(pending_action.PendingServerAction):
"""Class to verify that the reboot completed."""
States = enum('REBOOT_CHECK', 'ACTIVE_CHECK')
@@ -110,8 +110,6 @@
self._target['id'])
return True
- if time.time() - self._start_time > self._timeout:
- raise TimeoutException
reboot_state = self._reboot_state
if self._retry_state == self.States.REBOOT_CHECK:
server_state = self._check_for_status(reboot_state)
@@ -131,8 +129,7 @@
return False
target = self._target
self._logger.info('machine %s %s -> ACTIVE [%.1f secs elapsed]' %
- (target['id'], reboot_state,
- time.time() - self._start_time))
+ (target['id'], reboot_state, self.elapsed()))
self._state.set_instance_state(target['id'],
(target, 'ACTIVE'))
@@ -200,7 +197,7 @@
# state_name=state_name,
# timeout=_timeout)
#
-#class VerifyResizeVM(pending_action.PendingAction):
+#class VerifyResizeVM(pending_action.PendingServerAction):
# """Verify that resizing of a VM was successful"""
# States = enum('VERIFY_RESIZE_CHECK', 'ACTIVE_CHECK')
#
@@ -228,9 +225,6 @@
# self._target['id'])
# return True
#
-# if time.time() - self._start_time > self._timeout:
-# raise TimeoutException
-#
# if self._retry_state == self.States.VERIFY_RESIZE_CHECK:
# if self._check_for_status('VERIFY_RESIZE') == 'VERIFY_RESIZE':
# # now issue command to CONFIRM RESIZE
@@ -245,7 +239,7 @@
#
# self._logger.info(
# 'CONFIRMING RESIZE of machine %s [%.1f secs elapsed]' %
-# (self._target['id'], time.time() - self._start_time)
+# (self._target['id'], self.elapsed())
# )
# state.set_instance_state(self._target['id'],
# (self._target, 'CONFIRM_RESIZE'))
@@ -274,7 +268,7 @@
#
# self._logger.info(
# 'machine %s: VERIFY_RESIZE -> ACTIVE [%.1f sec elapsed]' %
-# (self._target['id'], time.time() - self._start_time)
+# (self._target['id'], self.elapsed())
# )
# self._state.set_instance_state(self._target['id'],
# (self._target, 'ACTIVE'))
diff --git a/stress/test_servers.py b/stress/test_servers.py
index a71bea2..57c923a 100644
--- a/stress/test_servers.py
+++ b/stress/test_servers.py
@@ -12,9 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Defines various sub-classes of the `StressTestCase` and
-`PendingAction` class. The sub-classes of StressTestCase implement various
+`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 PendingAction. These pending
+Each sub-class will have a corresponding PendingServerAction. These pending
actions veriy that the API call was successful or not."""
@@ -26,7 +26,6 @@
# local imports
import test_case
import pending_action
-from tempest.exceptions import TimeoutException
class TestCreateVM(test_case.StressTestCase):
@@ -101,7 +100,7 @@
expected_server)
-class VerifyCreateVM(pending_action.PendingAction):
+class VerifyCreateVM(pending_action.PendingServerAction):
"""Verify that VM was built and is running"""
def __init__(self, manager,
state,
@@ -127,12 +126,6 @@
self._target['id'])
return True
- time_diff = time.time() - self._start_time
- if time_diff > self._timeout:
- self._logger.error('%d exceeded launch server timeout of %d' %
- (time_diff, self._timeout))
- raise TimeoutException
-
admin_pass = self._target['adminPass']
# Could check more things here.
if (self._expected['adminPass'] != admin_pass):
@@ -146,7 +139,7 @@
return False
self._logger.info('machine %s: BUILD -> ACTIVE [%.1f secs elapsed]' %
- (self._target['id'], time.time() - self._start_time))
+ (self._target['id'], self.elapsed()))
self._state.set_instance_state(self._target['id'],
(self._target, 'ACTIVE'))
return True
@@ -186,7 +179,7 @@
killtarget, timeout=_timeout)
-class VerifyKillActiveVM(pending_action.PendingAction):
+class VerifyKillActiveVM(pending_action.PendingServerAction):
"""Verify that server was destroyed"""
def retry(self):
@@ -201,19 +194,13 @@
if (not tid in self._state.get_instances().keys()):
return False
- time_diff = time.time() - self._start_time
- if time_diff > self._timeout:
- self._logger.error('server %s: %d exceeds terminate timeout %d' %
- (tid, time_diff, self._timeout))
- raise TimeoutException
-
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'], time.time() - self._start_time))
+ (target['id'], self.elapsed()))
self._state.delete_instance_state(target['id'])
return True
@@ -305,7 +292,7 @@
timeout=_timeout)
-class VerifyUpdateVMName(pending_action.PendingAction):
+class VerifyUpdateVMName(pending_action.PendingServerAction):
"""Check that VM has new name"""
def retry(self):
"""
@@ -318,9 +305,6 @@
'TERMINATING'):
return False
- if time.time() - self._start_time > self._timeout:
- raise TimeoutException
-
response, body = \
self._manager.serverse_client.get_server(self._target['id'])
if (response.status != 200):
diff --git a/stress/tests/floating_ips.py b/stress/tests/floating_ips.py
new file mode 100755
index 0000000..03bd509
--- /dev/null
+++ b/stress/tests/floating_ips.py
@@ -0,0 +1,33 @@
+# 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.
+"""Stress test that associates/disasssociates floating ips"""
+
+# local imports
+from stress.test_floating_ips import TestChangeFloatingIp
+from stress.basher import BasherAction
+from stress.driver import *
+from tempest import openstack
+
+choice_spec = [
+ BasherAction(TestChangeFloatingIp(), 100)
+]
+
+nova = openstack.Manager()
+
+bash_openstack(nova,
+ choice_spec,
+ duration=datetime.timedelta(seconds=300),
+ test_name="floating_ips",
+ initial_floating_ips=8,
+ initial_vms=8)
diff --git a/stress/tools/nova_destroy_all.py b/stress/tools/nova_destroy_all.py
index e9010cd..21cac11 100755
--- a/stress/tools/nova_destroy_all.py
+++ b/stress/tools/nova_destroy_all.py
@@ -31,6 +31,7 @@
images_list = nt.images.list()
keypairs_list = nt.keypairs.list()
floating_ips_list = nt.floating_ips.list()
+volumes_list = nt.volumes.list()
print "total servers: %3d, total flavors: %3d, total images: %3d," % \
(len(server_list),
@@ -52,3 +53,7 @@
print "deleting all floating_ips"
for s in floating_ips_list:
s.delete()
+
+print "deleting all volumes"
+for s in volumes_list:
+ s.delete()