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
This commit is contained in:
David Kranz 2012-05-01 16:50:32 -04:00
parent 1e62242aca
commit 779c7f8008
8 changed files with 337 additions and 60 deletions

View File

@ -23,9 +23,11 @@ import time
# 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 @@ def _error_in_logs(keypath, logdir, user, nodes):
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 @@ def bash_openstack(manager,
"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 @@ def bash_openstack(manager,
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 @@ def bash_openstack(manager,
# 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 @@ def bash_openstack(manager,
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 ***')

View File

@ -17,6 +17,7 @@ that nova API calls such as create/delete are completed"""
import logging
import time
from tempest.exceptions import TimeoutException
class PendingAction(object):
@ -25,25 +26,55 @@ class PendingAction(object):
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 @@ class PendingAction(object):
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

View File

@ -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 @@ class State(object):
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']

95
stress/test_floating_ips.py Executable file
View File

@ -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

View File

@ -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 @@ import time
# 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 @@ class TestRebootVM(test_case.StressTestCase):
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 @@ class VerifyRebootVM(pending_action.PendingAction):
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 @@ class VerifyRebootVM(pending_action.PendingAction):
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 @@ class VerifyRebootVM(pending_action.PendingAction):
# 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 @@ class VerifyRebootVM(pending_action.PendingAction):
# 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 @@ class VerifyRebootVM(pending_action.PendingAction):
#
# 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 @@ class VerifyRebootVM(pending_action.PendingAction):
#
# 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'))

View File

@ -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 @@ import time
# local imports
import test_case
import pending_action
from tempest.exceptions import TimeoutException
class TestCreateVM(test_case.StressTestCase):
@ -101,7 +100,7 @@ class TestCreateVM(test_case.StressTestCase):
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 @@ class VerifyCreateVM(pending_action.PendingAction):
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 @@ class VerifyCreateVM(pending_action.PendingAction):
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 @@ class TestKillActiveVM(test_case.StressTestCase):
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 @@ class VerifyKillActiveVM(pending_action.PendingAction):
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 @@ class TestUpdateVMName(test_case.StressTestCase):
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 @@ class VerifyUpdateVMName(pending_action.PendingAction):
'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):

33
stress/tests/floating_ips.py Executable file
View File

@ -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)

View File

@ -31,6 +31,7 @@ server_list = nt.servers.list()
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 @@ for s in keypairs_list:
print "deleting all floating_ips"
for s in floating_ips_list:
s.delete()
print "deleting all volumes"
for s in volumes_list:
s.delete()