From 08db7c5cd2748c55676018f2aad9499b0f5530dc Mon Sep 17 00:00:00 2001 From: Iustin Pop Date: Thu, 20 Nov 2008 06:22:39 +0000 Subject: [PATCH] Initial multi-disk/multi-nic support This patch adds support for mult-disk/multi-nic in: - instance add - burnin The start/stop/failover/cluster verify work as expected. Replace disk and grow disk are TODO. There's also a change gnt-job to allow dictionaries to be listed in gnt-job info. Reviewed-by: imsnah --- lib/cmdlib.py | 185 ++++++++++++++++++++++++++------------------------ lib/constants.py | 5 ++ lib/objects.py | 2 +- lib/opcodes.py | 9 +-- scripts/gnt-instance | 56 +++++++++++---- scripts/gnt-job | 2 +- tools/burnin | 44 +++++++----- 7 files changed, 181 insertions(+), 122 deletions(-) diff --git a/lib/cmdlib.py b/lib/cmdlib.py index f5e2b96..4b66c19 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -3141,7 +3141,7 @@ def _GenerateDRBD8Branch(lu, primary, secondary, size, names, iv_name, def _GenerateDiskTemplate(lu, template_name, instance_name, primary_node, - secondary_nodes, disk_sz, swap_sz, + secondary_nodes, disk_info, file_storage_dir, file_driver): """Generate the entire disk layout for a given template type. @@ -3149,48 +3149,51 @@ def _GenerateDiskTemplate(lu, template_name, #TODO: compute space requirements vgname = lu.cfg.GetVGName() + disk_count = len(disk_info) + disks = [] if template_name == constants.DT_DISKLESS: - disks = [] + pass elif template_name == constants.DT_PLAIN: if len(secondary_nodes) != 0: raise errors.ProgrammerError("Wrong template configuration") - names = _GenerateUniqueNames(lu, [".sda", ".sdb"]) - sda_dev = objects.Disk(dev_type=constants.LD_LV, size=disk_sz, - logical_id=(vgname, names[0]), - iv_name = "sda") - sdb_dev = objects.Disk(dev_type=constants.LD_LV, size=swap_sz, - logical_id=(vgname, names[1]), - iv_name = "sdb") - disks = [sda_dev, sdb_dev] + names = _GenerateUniqueNames(lu, [".disk%d" % i + for i in range(disk_count)]) + for idx, disk in enumerate(disk_info): + disk_dev = objects.Disk(dev_type=constants.LD_LV, size=disk["size"], + logical_id=(vgname, names[idx]), + iv_name = "disk/%d" % idx) + disks.append(disk_dev) elif template_name == constants.DT_DRBD8: if len(secondary_nodes) != 1: raise errors.ProgrammerError("Wrong template configuration") remote_node = secondary_nodes[0] - (minor_pa, minor_pb, - minor_sa, minor_sb) = lu.cfg.AllocateDRBDMinor( - [primary_node, primary_node, remote_node, remote_node], instance_name) - - names = _GenerateUniqueNames(lu, [".sda_data", ".sda_meta", - ".sdb_data", ".sdb_meta"]) - drbd_sda_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node, - disk_sz, names[0:2], "sda", - minor_pa, minor_sa) - drbd_sdb_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node, - swap_sz, names[2:4], "sdb", - minor_pb, minor_sb) - disks = [drbd_sda_dev, drbd_sdb_dev] + minors = lu.cfg.AllocateDRBDMinor( + [primary_node, remote_node] * len(disk_info), instance_name) + + names = _GenerateUniqueNames(lu, + [".disk%d_%s" % (i, s) + for i in range(disk_count) + for s in ("data", "meta") + ]) + for idx, disk in enumerate(disk_info): + disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node, + disk["size"], names[idx*2:idx*2+2], + "disk/%d" % idx, + minors[idx*2], minors[idx*2+1]) + disks.append(disk_dev) elif template_name == constants.DT_FILE: if len(secondary_nodes) != 0: raise errors.ProgrammerError("Wrong template configuration") - file_sda_dev = objects.Disk(dev_type=constants.LD_FILE, size=disk_sz, - iv_name="sda", logical_id=(file_driver, - "%s/sda" % file_storage_dir)) - file_sdb_dev = objects.Disk(dev_type=constants.LD_FILE, size=swap_sz, - iv_name="sdb", logical_id=(file_driver, - "%s/sdb" % file_storage_dir)) - disks = [file_sda_dev, file_sdb_dev] + for idx, disk in enumerate(disk_info): + + disk_dev = objects.Disk(dev_type=constants.LD_FILE, size=disk["size"], + iv_name="disk/%d" % idx, + logical_id=(file_driver, + "%s/disk%d" % (file_storage_dir, + idx))) + disks.append(disk_dev) else: raise errors.ProgrammerError("Invalid disk template '%s'" % template_name) return disks @@ -3285,7 +3288,7 @@ def _RemoveDisks(lu, instance): return result -def _ComputeDiskSize(disk_template, disk_size, swap_size): +def _ComputeDiskSize(disk_template, disks): """Compute disk size requirements in the volume group This is currently hard-coded for the two-drive layout. @@ -3294,9 +3297,9 @@ def _ComputeDiskSize(disk_template, disk_size, swap_size): # Required free disk space as a function of disk and swap space req_size_dict = { constants.DT_DISKLESS: None, - constants.DT_PLAIN: disk_size + swap_size, - # 256 MB are added for drbd metadata, 128MB for each drbd device - constants.DT_DRBD8: disk_size + swap_size + 256, + constants.DT_PLAIN: sum(d["size"] for d in disks), + # 128 MB are added for drbd metadata for each disk + constants.DT_DRBD8: sum(d["size"] + 128 for d in disks), constants.DT_FILE: None, } @@ -3343,9 +3346,9 @@ class LUCreateInstance(LogicalUnit): """ HPATH = "instance-add" HTYPE = constants.HTYPE_INSTANCE - _OP_REQP = ["instance_name", "disk_size", - "disk_template", "swap_size", "mode", "start", - "wait_for_sync", "ip_check", "mac", + _OP_REQP = ["instance_name", "disks", "disk_template", + "mode", "start", + "wait_for_sync", "ip_check", "nics", "hvparams", "beparams"] REQ_BGL = False @@ -3418,27 +3421,50 @@ class LUCreateInstance(LogicalUnit): self.add_locks[locking.LEVEL_INSTANCE] = instance_name - # ip validity checks - ip = getattr(self.op, "ip", None) - if ip is None or ip.lower() == "none": - inst_ip = None - elif ip.lower() == constants.VALUE_AUTO: - inst_ip = hostname1.ip - else: - if not utils.IsValidIP(ip): - raise errors.OpPrereqError("given IP address '%s' doesn't look" - " like a valid IP" % ip) - inst_ip = ip - self.inst_ip = self.op.ip = inst_ip + # NIC buildup + self.nics = [] + for nic in self.op.nics: + # ip validity checks + ip = nic.get("ip", None) + if ip is None or ip.lower() == "none": + nic_ip = None + elif ip.lower() == constants.VALUE_AUTO: + nic_ip = hostname1.ip + else: + if not utils.IsValidIP(ip): + raise errors.OpPrereqError("Given IP address '%s' doesn't look" + " like a valid IP" % ip) + nic_ip = ip + + # MAC address verification + mac = nic.get("mac", constants.VALUE_AUTO) + if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE): + if not utils.IsValidMac(mac.lower()): + raise errors.OpPrereqError("Invalid MAC address specified: %s" % + mac) + # bridge verification + bridge = nic.get("bridge", self.cfg.GetDefBridge()) + self.nics.append(objects.NIC(mac=mac, ip=nic_ip, bridge=bridge)) + + # disk checks/pre-build + self.disks = [] + for disk in self.op.disks: + mode = disk.get("mode", constants.DISK_RDWR) + if mode not in constants.DISK_ACCESS_SET: + raise errors.OpPrereqError("Invalid disk access mode '%s'" % + mode) + size = disk.get("size", None) + if size is None: + raise errors.OpPrereqError("Missing disk size") + try: + size = int(size) + except ValueError: + raise errors.OpPrereqError("Invalid disk size '%s'" % size) + self.disks.append({"size": size, "mode": mode}) + # used in CheckPrereq for ip ping check self.check_ip = hostname1.ip - # MAC address verification - if self.op.mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE): - if not utils.IsValidMac(self.op.mac.lower()): - raise errors.OpPrereqError("invalid MAC address specified: %s" % - self.op.mac) - # file storage checks if (self.op.file_driver and not self.op.file_driver in constants.FILE_DRIVER): @@ -3487,10 +3513,7 @@ class LUCreateInstance(LogicalUnit): """Run the allocator based on input opcode. """ - disks = [{"size": self.op.disk_size, "mode": "w"}, - {"size": self.op.swap_size, "mode": "w"}] - nics = [{"mac": self.op.mac, "ip": getattr(self.op, "ip", None), - "bridge": self.op.bridge}] + nics = [n.ToDict() for n in self.nics] ial = IAllocator(self, mode=constants.IALLOCATOR_MODE_ALLOC, name=self.op.instance_name, @@ -3499,7 +3522,7 @@ class LUCreateInstance(LogicalUnit): os=self.op.os_type, vcpus=self.be_full[constants.BE_VCPUS], mem_size=self.be_full[constants.BE_MEMORY], - disks=disks, + disks=self.disks, nics=nics, ) @@ -3529,8 +3552,7 @@ class LUCreateInstance(LogicalUnit): """ env = { "INSTANCE_DISK_TEMPLATE": self.op.disk_template, - "INSTANCE_DISK_SIZE": self.op.disk_size, - "INSTANCE_SWAP_SIZE": self.op.swap_size, + "INSTANCE_DISK_SIZE": ",".join(str(d["size"]) for d in self.disks), "INSTANCE_ADD_MODE": self.op.mode, } if self.op.mode == constants.INSTANCE_IMPORT: @@ -3545,7 +3567,7 @@ class LUCreateInstance(LogicalUnit): os_type=self.op.os_type, memory=self.be_full[constants.BE_MEMORY], vcpus=self.be_full[constants.BE_VCPUS], - nics=[(self.inst_ip, self.op.bridge, self.op.mac)], + nics=[(n.ip, n.bridge, n.mac) for n in self.nics], )) nl = ([self.cfg.GetMasterNode(), self.op.pnode] + @@ -3581,8 +3603,7 @@ class LUCreateInstance(LogicalUnit): (ei_version, constants.EXPORT_VERSION)) # Check that the new instance doesn't have less disks than the export - # TODO: substitute "2" with the actual number of disks requested - instance_disks = 2 + instance_disks = len(self.disks) export_disks = export_info.getint(constants.INISECT_INS, 'disk_count') if instance_disks < export_disks: raise errors.OpPrereqError("Not enough disks to import." @@ -3622,13 +3643,6 @@ class LUCreateInstance(LogicalUnit): raise errors.OpPrereqError("IP %s of instance %s already in use" % (self.check_ip, self.op.instance_name)) - # bridge verification - bridge = getattr(self.op, "bridge", None) - if bridge is None: - self.op.bridge = self.cfg.GetDefBridge() - else: - self.op.bridge = bridge - #### allocator run if self.op.iallocator is not None: @@ -3655,7 +3669,7 @@ class LUCreateInstance(LogicalUnit): nodenames = [pnode.name] + self.secondaries req_size = _ComputeDiskSize(self.op.disk_template, - self.op.disk_size, self.op.swap_size) + self.disks) # Check lv size requirements if req_size is not None: @@ -3684,10 +3698,12 @@ class LUCreateInstance(LogicalUnit): " primary node" % self.op.os_type) # bridge check on primary node - if not self.rpc.call_bridges_exist(self.pnode.name, [self.op.bridge]): - raise errors.OpPrereqError("target bridge '%s' does not exist on" + bridges = [n.bridge for n in self.nics] + if not self.rpc.call_bridges_exist(self.pnode.name, bridges): + raise errors.OpPrereqError("one of the target bridges '%s' does not" + " exist on" " destination node '%s'" % - (self.op.bridge, pnode.name)) + (",".join(bridges), pnode.name)) # memory check on primary node if self.op.start: @@ -3708,14 +3724,9 @@ class LUCreateInstance(LogicalUnit): instance = self.op.instance_name pnode_name = self.pnode.name - if self.op.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE): - mac_address = self.cfg.GenerateMAC() - else: - mac_address = self.op.mac - - nic = objects.NIC(bridge=self.op.bridge, mac=mac_address) - if self.inst_ip is not None: - nic.ip = self.inst_ip + for nic in self.nics: + if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE): + nic.mac = self.cfg.GenerateMAC() ht_kind = self.op.hypervisor if ht_kind in constants.HTS_REQ_PORT: @@ -3741,14 +3752,14 @@ class LUCreateInstance(LogicalUnit): disks = _GenerateDiskTemplate(self, self.op.disk_template, instance, pnode_name, - self.secondaries, self.op.disk_size, - self.op.swap_size, + self.secondaries, + self.disks, file_storage_dir, self.op.file_driver) iobj = objects.Instance(name=instance, os=self.op.os_type, primary_node=pnode_name, - nics=[nic], disks=disks, + nics=self.nics, disks=disks, disk_template=self.op.disk_template, status=self.instance_status, network_port=network_port, diff --git a/lib/constants.py b/lib/constants.py index 925432a..1972303 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -190,6 +190,11 @@ FD_BLKTAP = "blktap" # the set of drbd-like disk types LDS_DRBD = frozenset([LD_DRBD8]) +# disk access mode +DISK_RDONLY = "r" +DISK_RDWR = "w" +DISK_ACCESS_SET = frozenset([DISK_RDONLY, DISK_RDWR]) + # disk replacement mode REPLACE_DISK_PRI = "replace_primary" REPLACE_DISK_SEC = "replace_secondary" diff --git a/lib/objects.py b/lib/objects.py index 6c74876..26cdc3d 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -274,7 +274,7 @@ class NIC(ConfigObject): class Disk(ConfigObject): """Config object representing a block device.""" __slots__ = ["dev_type", "logical_id", "physical_id", - "children", "iv_name", "size"] + "children", "iv_name", "size", "mode"] def CreateOnSecondary(self): """Test if this device needs to be created on a secondary node.""" diff --git a/lib/opcodes.py b/lib/opcodes.py index 533215f..9b178c5 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -319,10 +319,11 @@ class OpCreateInstance(OpCode): OP_ID = "OP_INSTANCE_CREATE" OP_DSC_FIELD = "instance_name" __slots__ = [ - "instance_name", "disk_size", "os_type", "pnode", - "disk_template", "snode", "swap_size", "mode", - "ip", "bridge", "src_node", "src_path", "start", - "wait_for_sync", "ip_check", "mac", + "instance_name", "os_type", "pnode", + "disk_template", "snode", "mode", + "disks", "nics", + "src_node", "src_path", "start", + "wait_for_sync", "ip_check", "file_storage_dir", "file_driver", "iallocator", "hypervisor", "hvparams", "beparams", diff --git a/scripts/gnt-instance b/scripts/gnt-instance index 4802328..5b4162c 100755 --- a/scripts/gnt-instance +++ b/scripts/gnt-instance @@ -293,6 +293,40 @@ def AddInstance(opts, args): if opts.hypervisor: hypervisor, hvparams = opts.hypervisor + if opts.nics: + try: + nic_max = max(int(nidx[0])+1 for nidx in opts.nics) + except ValueError, err: + raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err)) + nics = [{}] * nic_max + for nidx, ndict in opts.nics.items(): + nidx = int(nidx) + nics[nidx] = ndict + else: + # default of one nic, all auto + nics = [{}] + + if not opts.disks and opts.disk_template != constants.DT_DISKLESS: + raise errors.OpPrereqError("No disk information specified") + elif opts.disks and opts.disk_template == constants.DT_DISKLESS: + raise errors.OpPrereqError("Diskless instance but disk information passeD") + else: + try: + disk_max = max(int(didx[0])+1 for didx in opts.disks) + except ValueError, err: + raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err)) + disks = [{}] * disk_max + for didx, ddict in opts.disks: + didx = int(didx) + if "size" not in ddict: + raise errors.OpPrereqError("Missing size for disk %d" % didx) + try: + ddict["size"] = utils.ParseUnit(ddict["size"]) + except ValueError, err: + raise errors.OpPrereqError("Invalid disk size for disk %d: %s" % + (didx, err)) + disks[didx] = ddict + ValidateBeParams(opts.beparams) ## kernel_path = _TransformPath(opts.kernel_path) @@ -308,15 +342,14 @@ def AddInstance(opts, args): ## hvm_cdrom_image_path = opts.hvm_cdrom_image_path op = opcodes.OpCreateInstance(instance_name=instance, - disk_size=opts.size, swap_size=opts.swap, + disks=disks, disk_template=opts.disk_template, + nics=nics, mode=constants.INSTANCE_CREATE, os_type=opts.os, pnode=pnode, snode=snode, - ip=opts.ip, bridge=opts.bridge, start=opts.start, ip_check=opts.ip_check, wait_for_sync=opts.wait_for_sync, - mac=opts.mac, hypervisor=hypervisor, hvparams=hvparams, beparams=opts.beparams, @@ -1075,17 +1108,16 @@ add_opts = [ make_option("-t", "--disk-template", dest="disk_template", help="Custom disk setup (diskless, file, plain or drbd)", default=None, metavar="TEMPL"), - make_option("-i", "--ip", dest="ip", - help="IP address ('none' [default], 'auto', or specify address)", - default='none', type="string", metavar="
"), - make_option("--mac", dest="mac", - help="MAC address ('auto' [default], or specify address)", - default='auto', type="string", metavar=""), + ikv_option("--disk", help="Disk information", + default=[], dest="disks", + action="append", + type="identkeyval"), + ikv_option("--net", help="NIC information", + default=[], dest="nics", + action="append", + type="identkeyval"), make_option("--no-wait-for-sync", dest="wait_for_sync", default=True, action="store_false", help="Don't wait for sync (DANGEROUS!)"), - make_option("-b", "--bridge", dest="bridge", - help="Bridge to connect this instance to", - default=None, metavar=""), make_option("--no-start", dest="start", default=True, action="store_false", help="Don't start the instance after" " creation"), diff --git a/scripts/gnt-job b/scripts/gnt-job index 29df6a9..39f0743 100755 --- a/scripts/gnt-job +++ b/scripts/gnt-job @@ -280,7 +280,7 @@ def ShowJobs(opts, args): if key == "OP_ID": continue if isinstance(val, (tuple, list)): - val = ",".join(val) + val = ",".join([str(item) for item in val]) format(4, "%s: %s" % (key, val)) if result is None: format(3, "No output data") diff --git a/tools/burnin b/tools/burnin index 295ea7d..d328d47 100755 --- a/tools/burnin +++ b/tools/burnin @@ -126,14 +126,11 @@ class Burner(object): parser.add_option("-o", "--os", dest="os", default=None, help="OS to use during burnin", metavar="") - parser.add_option("--os-size", dest="os_size", help="Disk size", - default=4 * 1024, type="unit", metavar="") - parser.add_option("--os-growth", dest="sda_growth", help="Disk growth", - default=1024, type="unit", metavar="") - parser.add_option("--swap-size", dest="swap_size", help="Swap size", - default=4 * 1024, type="unit", metavar="") - parser.add_option("--swap-growth", dest="sdb_growth", help="Swap growth", - default=1024, type="unit", metavar="") + parser.add_option("--disk-size", dest="disk_size", + help="Disk size (determines disk count)", + default="128m", type="string", metavar="") + parser.add_option("--disk-growth", dest="disk_growth", help="Disk growth", + default=128, type="string", metavar="") parser.add_option("--mem-size", dest="mem_size", help="Memory size", default=128, type="unit", metavar="") parser.add_option("-v", "--verbose", @@ -188,6 +185,20 @@ class Burner(object): Log("Unknown disk template '%s'" % options.disk_template) sys.exit(1) + disk_size = [utils.ParseUnit(v) for v in options.disk_size.split(",")] + disk_growth = [utils.ParseUnit(v) for v in options.disk_growth.split(",")] + if len(disk_growth) != len(disk_size): + Log("Wrong disk sizes/growth combination") + sys.exit(1) + if ((disk_size and options.disk_template == constants.DT_DISKLESS) or + (not disk_size and options.disk_template != constants.DT_DISKLESS)): + Log("Wrong disk count/disk template combination") + sys.exit(1) + + self.disk_size = disk_size + self.disk_growth = disk_growth + self.disk_count = len(disk_size) + if options.nodes and options.iallocator: Log("Give either the nodes option or the iallocator option, not both") sys.exit(1) @@ -251,9 +262,10 @@ class Burner(object): Log("- Add instance %s on nodes %s/%s" % (instance, pnode, snode)) op = opcodes.OpCreateInstance(instance_name=instance, - disk_size=self.opts.os_size, - swap_size=self.opts.swap_size, + disks = [ {"size": size} + for size in self.disk_size], disk_template=self.opts.disk_template, + nics=[{}], mode=constants.INSTANCE_CREATE, os_type=self.opts.os, pnode=pnode, @@ -261,7 +273,6 @@ class Burner(object): start=True, ip_check=True, wait_for_sync=True, - mac="auto", file_driver="loop", file_storage_dir=None, iallocator=self.opts.iallocator, @@ -283,12 +294,11 @@ class Burner(object): def GrowDisks(self): """Grow both the os and the swap disks by the requested amount, if any.""" for instance in self.instances: - for disk in ['sda', 'sdb']: - growth = getattr(self.opts, '%s_growth' % disk) + for idx, growth in enumerate(self.disk_growth): if growth > 0: - op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, + op = opcodes.OpGrowDisk(instance_name=instance, disk=idx, amount=growth, wait_for_sync=True) - Log("- Increase %s's %s disk by %s MB" % (instance, disk, growth)) + Log("- Increase %s's %s disk by %s MB" % (instance, idx, growth)) self.ExecOp(op) def ReplaceDisks1D8(self): @@ -297,7 +307,7 @@ class Burner(object): for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI: op = opcodes.OpReplaceDisks(instance_name=instance, mode=mode, - disks=["sda", "sdb"]) + disks=[i for i in range(self.disk_count)]) Log("- Replace disks (%s) for instance %s" % (mode, instance)) self.ExecOp(op) @@ -314,7 +324,7 @@ class Burner(object): mode=mode, remote_node=tnode, iallocator=self.opts.iallocator, - disks=["sda", "sdb"]) + disks=[i for i in range(self.disk_count)]) Log("- Replace secondary (%s) for instance %s" % (mode, instance)) self.ExecOp(op) -- 1.7.10.4