The attach/detach test is merged with the add/remove test. The test is the following: an extra disk is added in each instance and is given a random name. Once it has been added successfully, we detach it and check if the instance operates normally. Then, we pick instances randomly and attach a detached disk to them, using an algorithm that matches a disk to an instance based on its disk nodes. Then, we remove the disk.
Also, move the start/stop at the top of the tests, since the start/stop functionality is erroneously used first by other functions (e.g. rename) and then tested on its own. Signed-off-by: Alex Pyrgiotis <[email protected]> diff --git a/lib/tools/burnin.py b/lib/tools/burnin.py index 63f5a4c..82be852 100755 --- a/lib/tools/burnin.py +++ b/lib/tools/burnin.py @@ -37,6 +37,8 @@ import optparse import time import socket import urllib +import random +import string # pylint: disable=W0402 from itertools import izip, islice, cycle from cStringIO import StringIO @@ -130,6 +132,10 @@ def Err(msg, exit_code=1): sys.exit(exit_code) +def RandomString(size=8, chars=string.ascii_uppercase + string.digits): + return ''.join(random.choice(chars) for x in range(size)) + + class SimpleOpener(urllib.FancyURLopener): """A simple url opener""" # pylint: disable=W0221 @@ -329,6 +335,7 @@ class Burner(object): self.hvp = self.bep = None self.ParseOptions() self.cl = cli.GetClient() + self.disk_nodes = {} self.GetState() def ClearFeedbackBuf(self): @@ -597,6 +604,16 @@ class Burner(object): self.hv_can_migrate = \ hypervisor.GetHypervisorClass(self.hypervisor).CAN_MIGRATE + def FindMatchingDisk(self, instance, disks): + """Find a disk whose nodes match the instance's disk nodes.""" + instance_nodes = self.disk_nodes[instance] + for disk, disk_nodes in disks.iteritems(): + if set(instance_nodes) == set(disk_nodes): + # Erase that disk from the dictionary so that we don't pick it again. + del disks[disk] + return disk + Err("Couldn't find matching detached disk for instance %s" % instance) + @_DoCheckInstances @_DoBatch(False) def BurnCreateInstances(self): @@ -622,6 +639,15 @@ class Burner(object): Log(msg, indent=2) + # Calculate the disk nodes for the instance based on the disk template. + if self.opts.disk_template in constants.DTS_EXT_MIRROR: + nodes = [] + elif self.opts.disk_template in constants.DTS_INT_MIRROR: + nodes = [pnode, snode] + else: + nodes = [pnode] + self.disk_nodes[instance] = nodes + op = opcodes.OpInstanceCreate(instance_name=instance, disks=[{"size": size} for size in self.disk_size], @@ -646,6 +672,64 @@ class Burner(object): remove_instance = lambda name: lambda: self.to_rem.append(name) self.ExecOrQueue(instance, [op], post_process=remove_instance(instance)) + @_DoCheckInstances + @_DoBatch(False) + def BurnAddDisks(self): + """Add an extra disk to every instance and then detach it.""" + Log("Adding and detaching disks") + + # Temporary dict that keeps the generated disk names and their nodes. + self._disks = {} + + # Iterate all instances, start them, add a disk with a unique name and + # detach it. Do all disk operations with hotplugging (if possible). + for instance in self.instances: + Log("instance %s", instance, indent=1) + disk_name = RandomString() + self._disks[disk_name] = self.disk_nodes[instance] + op_stop = self.StopInstanceOp(instance) + op_add = opcodes.OpInstanceSetParams( + instance_name=instance, hotplug_if_possible=True, + disks=[(constants.DDM_ADD, {"size": self.disk_size[0], + "name": disk_name})]) + op_detach = opcodes.OpInstanceSetParams( + instance_name=instance, hotplug_if_possible=True, + disks=[(constants.DDM_DETACH, {})]) + op_start = self.StartInstanceOp(instance) + Log("adding a disk with name %s" % disk_name, indent=2) + Log("detaching last disk", indent=2) + self.ExecOrQueue(instance, [op_start, op_add, op_detach, op_stop, + op_start]) + + @_DoCheckInstances + @_DoBatch(False) + def BurnRemoveDisks(self): + """Attach a previously detached disk to an instance and then remove it.""" + Log("Attaching and removing disks") + + # Iterate all instances in random order, attach the detached disks, remove + # them and then restart the instances. Do all disk operation with + # hotplugging (if possible). + instances_copy = list(self.instances) + random.shuffle(instances_copy) + for instance in instances_copy: + disk_name = self.FindMatchingDisk(instance, self._disks) + op_attach = opcodes.OpInstanceSetParams( + instance_name=instance, hotplug_if_possible=True, + disks=[(constants.DDM_ATTACH, {"name": disk_name})]) + op_rem = opcodes.OpInstanceSetParams( + instance_name=instance, hotplug_if_possible=True, + disks=[(constants.DDM_REMOVE, {})]) + op_stop = self.StopInstanceOp(instance) + op_start = self.StartInstanceOp(instance) + Log("attaching a disk with name %s" % disk_name, indent=2) + Log("removing last disk", indent=2) + self.ExecOrQueue(instance, [op_attach, op_rem, op_stop, op_start]) + + # Disk nodes are useful only for this test. + delattr(self, "disk_nodes") + delattr(self, "_disks") + @_DoBatch(False) def BurnModifyRuntimeMemory(self): """Alter the runtime memory.""" @@ -941,24 +1025,6 @@ class Burner(object): Log("deactivate disks (when offline)", indent=2) self.ExecOrQueue(instance, [op_act, op_stop, op_act, op_deact, op_start]) - @_DoCheckInstances - @_DoBatch(False) - def BurnAddRemoveDisks(self): - """Add and remove an extra disk for the instances.""" - Log("Adding and removing disks") - for instance in self.instances: - Log("instance %s", instance, indent=1) - op_add = opcodes.OpInstanceSetParams( - instance_name=instance, - disks=[(constants.DDM_ADD, {"size": self.disk_size[0]})]) - op_rem = opcodes.OpInstanceSetParams( - instance_name=instance, disks=[(constants.DDM_REMOVE, {})]) - op_stop = self.StopInstanceOp(instance) - op_start = self.StartInstanceOp(instance) - Log("adding a disk", indent=2) - Log("removing last disk", indent=2) - self.ExecOrQueue(instance, [op_add, op_stop, op_rem, op_start]) - @_DoBatch(False) def BurnAddRemoveNICs(self): """Add, change and remove an extra NIC for the instances.""" @@ -1083,6 +1149,20 @@ class Burner(object): try: self.BurnCreateInstances() + if self.opts.do_startstop: + self.BurnStopStart() + + # In lieu of a proper way to read the config, the BurnCreateInstances() + # function creates a mapping ('self.disk_nodes') between each instance + # and its disk nodes. This mapping is necessary for the add/remove test, + # in order to create pairs of instances and detached disks and test the + # attach functionality. However, since this mapping is static, some tests + # might change the actual instance nodes and render this mapping useless. + # Therefore, this test should run before any of these tests. + if self.opts.do_addremove_disks: + self.BurnAddDisks() + self.BurnRemoveDisks() + if self.bep[constants.BE_MINMEM] < self.bep[constants.BE_MAXMEM]: self.BurnModifyRuntimeMemory() @@ -1128,9 +1208,6 @@ class Burner(object): if self.opts.do_renamesame: self.BurnRenameSame(self.opts.name_check, self.opts.ip_check) - if self.opts.do_addremove_disks: - self.BurnAddRemoveDisks() - default_nic_mode = self.cluster_default_nicparams[constants.NIC_MODE] # Don't add/remove nics in routed mode, as we would need an ip to add # them with @@ -1149,9 +1226,6 @@ class Burner(object): if self.opts.do_confd_tests: self.BurnConfd() - if self.opts.do_startstop: - self.BurnStopStart() - has_err = False finally: if has_err: -- 1.7.10.4
