diff --git a/autotools/check-news b/autotools/check-news index 9a2299526081b178f7d735e022cc7a721743c5a3..146faf2ea6229bb73b62b8f39eefd57c0e7f894d 100755 --- a/autotools/check-news +++ b/autotools/check-news @@ -1,7 +1,7 @@ #!/usr/bin/python # -# Copyright (C) 2011, 2012 Google Inc. +# Copyright (C) 2011, 2012, 2013 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -40,6 +40,9 @@ RELEASED_RE = re.compile(r"^\*\(Released (?P<day>[A-Z][a-z]{2})," UNRELEASED_RE = re.compile(r"^\*\(unreleased\)\*$") VERSION_RE = re.compile(r"^Version \d+(\.\d+)+( (beta|rc)\d+)?$") +#: How many days release timestamps may be in the future +TIMESTAMP_FUTURE_DAYS_MAX = 3 + errors = [] @@ -115,6 +118,13 @@ def main(): # would return an inconsistent result if the weekday is incorrect. parsed_ts = time.mktime(time.strptime(m.group("date"), "%d %b %Y")) parsed = datetime.date.fromtimestamp(parsed_ts) + today = datetime.date.today() + + if (parsed - datetime.timedelta(TIMESTAMP_FUTURE_DAYS_MAX)) > today: + Error("Line %s: %s is more than %s days in the future (today is %s)" % + (fileinput.filelineno(), parsed, TIMESTAMP_FUTURE_DAYS_MAX, + today)) + weekday = parsed.strftime("%a") # Check weekday diff --git a/autotools/convert-constants b/autotools/convert-constants index c9695f4fd332656c5704696eca0f38a18405696a..e5296ea78ee80f6dc66213b7a5495640a906acfe 100755 --- a/autotools/convert-constants +++ b/autotools/convert-constants @@ -1,7 +1,7 @@ #!/usr/bin/python # -# Copyright (C) 2011, 2012 Google Inc. +# Copyright (C) 2011, 2012, 2013 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -87,6 +87,8 @@ def HaskellTypeVal(value): """ if isinstance(value, basestring): return ("String", "\"%s\"" % StringValueRules(value)) + elif isinstance(value, bool): + return ("Bool", "%s" % value) elif isinstance(value, int): return ("Int", "%d" % value) elif isinstance(value, long): diff --git a/doc/move-instance.rst b/doc/move-instance.rst index 6e616fb6aa1c0c22e3d64ace1e0b34167fc0e4fb..a7f06b7000419155527c365f1ef351b76b7766a6 100644 --- a/doc/move-instance.rst +++ b/doc/move-instance.rst @@ -69,9 +69,10 @@ destination-related options default to the source value (e.g. setting ``--src-ca-file``/``--dest-ca-file`` Path to file containing source cluster Certificate Authority (CA) in PEM format. For self-signed certificates, this is the certificate - itself. For certificates signed by a third party CA, the complete - chain must be in the file (see documentation for - :manpage:`SSL_CTX_load_verify_locations(3)`). + itself (see more details below in + :ref:`instance-move-certificates`). For certificates signed by a third + party CA, the complete chain must be in the file (see documentation + for :manpage:`SSL_CTX_load_verify_locations(3)`). ``--src-username``/``--dest-username`` RAPI username, must have write access to cluster. ``--src-password-file``/``--dest-password-file`` @@ -96,6 +97,28 @@ destination-related options default to the source value (e.g. setting The exit value of the tool is zero if and only if all instance moves were successful. +.. _instance-move-certificates: + +Certificates +------------ + +If using certificates signed by a CA, then you need to pass the same CA +certificate via both ``--src-ca-file`` and ``dest-ca-file``. + +However, if you're using self-signed certificates, this has a few +(security) implications: + +- the certificates of both the source and destinations clusters + (``rapi.pem`` from the Ganeti configuration directory, usually + ``/var/lib/ganeti/rapi.pem``) must be available to the tool +- by default, the certificates include the private key as well, so + simply copying them to a third machine means that machine can now + impersonate both the source and destination clusters RAPI endpoint + +It is therefore recommended to copy only the certificate from the +``rapi.pem`` files, and pass these to ``--src-ca-file`` and +``--dest-ca-file`` appropriately. + .. vim: set textwidth=72 : .. Local Variables: .. mode: rst diff --git a/lib/bdev.py b/lib/bdev.py index 080880cafa017992cbfb7a162b6b12441f84fc1d..3c453d0f133748c67d8f58ccec1f45c7cba2ae8c 100644 --- a/lib/bdev.py +++ b/lib/bdev.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc. +# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -255,7 +255,7 @@ class BlockDev(object): an attached instance (lvcreate) - attaching of a python instance to an existing (real) device - The second point, the attachement to a device, is different + The second point, the attachment to a device, is different depending on whether the device is assembled or not. At init() time, we search for a device with the same unique_id as us. If found, good. It also means that the device is already assembled. If not, diff --git a/lib/cmdlib.py b/lib/cmdlib.py index b8c2318cab736f0094867d1767137688c96fedcd..13eddd000f532dc8fb7f7b96993f9fb5b8fedb4a 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -13497,23 +13497,28 @@ class LUInstanceSetParams(LogicalUnit): params[constants.INIC_MAC] = \ self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId()) - #if there is a change in nic's ip/network configuration + # if there is a change in (ip, network) tuple new_ip = params.get(constants.INIC_IP, old_ip) if (new_ip, new_net_uuid) != (old_ip, old_net_uuid): if new_ip: + # if IP is pool then require a network and generate one IP if new_ip.lower() == constants.NIC_IP_POOL: - if not new_net_uuid: + if new_net_uuid: + try: + new_ip = self.cfg.GenerateIp(new_net_uuid, self.proc.GetECId()) + except errors.ReservationError: + raise errors.OpPrereqError("Unable to get a free IP" + " from the address pool", + errors.ECODE_STATE) + self.LogInfo("Chose IP %s from network %s", + new_ip, + new_net_obj.name) + params[constants.INIC_IP] = new_ip + else: raise errors.OpPrereqError("ip=pool, but no network found", errors.ECODE_INVAL) - try: - new_ip = self.cfg.GenerateIp(new_net_uuid, self.proc.GetECId()) - except errors.ReservationError: - raise errors.OpPrereqError("Unable to get a free IP" - " from the address pool", - errors.ECODE_STATE) - self.LogInfo("Chose IP %s from network %s", new_ip, new_net_obj.name) - params[constants.INIC_IP] = new_ip - elif new_ip != old_ip or new_net_uuid != old_net_uuid: + # Reserve new IP if in the new network if any + elif new_net_uuid: try: self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId()) self.LogInfo("Reserving IP %s in network %s", @@ -13522,19 +13527,19 @@ class LUInstanceSetParams(LogicalUnit): raise errors.OpPrereqError("IP %s not available in network %s" % (new_ip, new_net_obj.name), errors.ECODE_NOTUNIQUE) - - # new net is None - elif not new_net_uuid and self.op.conflicts_check: + # new network is None so check if new IP is a conflicting IP + elif self.op.conflicts_check: _CheckForConflictingIp(self, new_ip, pnode) - if old_ip: + # release old IP if old network is not None + if old_ip and old_net_uuid: try: self.cfg.ReleaseIp(old_net_uuid, old_ip, self.proc.GetECId()) except errors.AddressPoolError: logging.warning("Release IP %s not contained in network %s", old_ip, old_net_obj.name) - # there are no changes in (net, ip) tuple + # there are no changes in (ip, network) tuple and old network is not None elif (old_net_uuid is not None and (req_link is not None or req_mode is not None)): raise errors.OpPrereqError("Not allowed to change link or mode of" @@ -14112,18 +14117,19 @@ class LUInstanceSetParams(LogicalUnit): if root.dev_type in constants.LDS_DRBD: self.cfg.AddTcpUdpPort(root.logical_id[2]) - @staticmethod - def _CreateNewNic(idx, params, private): + def _CreateNewNic(self, idx, params, private): """Creates data structure for a new network interface. """ mac = params[constants.INIC_MAC] ip = params.get(constants.INIC_IP, None) net = params.get(constants.INIC_NETWORK, None) + net_uuid = self.cfg.LookupNetwork(net) #TODO: not private.filled?? can a nic have no nicparams?? nicparams = private.filled + nobj = objects.NIC(mac=mac, ip=ip, network=net_uuid, nicparams=nicparams) - return (objects.NIC(mac=mac, ip=ip, network=net, nicparams=nicparams), [ + return (nobj, [ ("nic.%d" % idx, "add:mac=%s,ip=%s,mode=%s,link=%s,network=%s" % (mac, ip, private.filled[constants.NIC_MODE], @@ -14131,18 +14137,23 @@ class LUInstanceSetParams(LogicalUnit): net)), ]) - @staticmethod - def _ApplyNicMods(idx, nic, params, private): + def _ApplyNicMods(self, idx, nic, params, private): """Modifies a network interface. """ changes = [] - for key in [constants.INIC_MAC, constants.INIC_IP, constants.INIC_NETWORK]: + for key in [constants.INIC_MAC, constants.INIC_IP]: if key in params: changes.append(("nic.%s/%d" % (key, idx), params[key])) setattr(nic, key, params[key]) + new_net = params.get(constants.INIC_NETWORK, nic.network) + new_net_uuid = self.cfg.LookupNetwork(new_net) + if new_net_uuid != nic.network: + changes.append(("nic.network/%d" % idx, new_net)) + nic.network = new_net_uuid + if private.filled: nic.nicparams = private.filled @@ -16166,7 +16177,8 @@ class LUTestAllocator(NoHooksLU): nics=self.op.nics, vcpus=self.op.vcpus, spindle_use=self.op.spindle_use, - hypervisor=self.op.hypervisor) + hypervisor=self.op.hypervisor, + node_whitelist=None) elif self.op.mode == constants.IALLOCATOR_MODE_RELOC: req = iallocator.IAReqRelocate(name=self.op.name, relocate_from=list(self.relocate_from)) @@ -16702,6 +16714,11 @@ class LUNetworkConnect(LogicalUnit): assert self.group_uuid in owned_groups + # Check if locked instances are still correct + owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE)) + if self.op.conflicts_check: + _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances) + self.netparams = { constants.NIC_MODE: self.network_mode, constants.NIC_LINK: self.network_link, @@ -16716,23 +16733,22 @@ class LUNetworkConnect(LogicalUnit): self.LogWarning("Network '%s' is already mapped to group '%s'" % (self.network_name, self.group.name)) self.connected = True - return - if self.op.conflicts_check: + # check only if not already connected + elif self.op.conflicts_check: pool = network.AddressPool(self.cfg.GetNetwork(self.network_uuid)) _NetworkConflictCheck(self, lambda nic: pool.Contains(nic.ip), - "connect to") + "connect to", owned_instances) def Exec(self, feedback_fn): - if self.connected: - return - - self.group.networks[self.network_uuid] = self.netparams - self.cfg.Update(self.group, feedback_fn) + # Connect the network and update the group only if not already connected + if not self.connected: + self.group.networks[self.network_uuid] = self.netparams + self.cfg.Update(self.group, feedback_fn) -def _NetworkConflictCheck(lu, check_fn, action): +def _NetworkConflictCheck(lu, check_fn, action, instances): """Checks for network interface conflicts with a network. @type lu: L{LogicalUnit} @@ -16744,13 +16760,9 @@ def _NetworkConflictCheck(lu, check_fn, action): @raise errors.OpPrereqError: If conflicting IP addresses are found. """ - # Check if locked instances are still correct - owned_instances = frozenset(lu.owned_locks(locking.LEVEL_INSTANCE)) - _CheckNodeGroupInstances(lu.cfg, lu.group_uuid, owned_instances) - conflicts = [] - for (_, instance) in lu.cfg.GetMultiInstanceInfo(owned_instances): + for (_, instance) in lu.cfg.GetMultiInstanceInfo(instances): instconflicts = [(idx, nic.ip) for (idx, nic) in enumerate(instance.nics) if check_fn(nic)] @@ -16824,23 +16836,27 @@ class LUNetworkDisconnect(LogicalUnit): assert self.group_uuid in owned_groups + # Check if locked instances are still correct + owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE)) + _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances) + self.group = self.cfg.GetNodeGroup(self.group_uuid) self.connected = True if self.network_uuid not in self.group.networks: self.LogWarning("Network '%s' is not mapped to group '%s'", self.network_name, self.group.name) self.connected = False - return - _NetworkConflictCheck(self, lambda nic: nic.network == self.network_uuid, - "disconnect from") + # We need this check only if network is not already connected + else: + _NetworkConflictCheck(self, lambda nic: nic.network == self.network_uuid, + "disconnect from", owned_instances) def Exec(self, feedback_fn): - if not self.connected: - return - - del self.group.networks[self.network_uuid] - self.cfg.Update(self.group, feedback_fn) + # Disconnect the network and update the group only if network is connected + if self.connected: + del self.group.networks[self.network_uuid] + self.cfg.Update(self.group, feedback_fn) #: Query type implementations diff --git a/lib/config.py b/lib/config.py index 13f9823604416439c49f819a866ff298a2b65890..93ccec8bbb00618ccdd54168dcc93f95ebff29d3 100644 --- a/lib/config.py +++ b/lib/config.py @@ -2021,6 +2021,7 @@ class ConfigWriter: return (self._config_data.instances.values() + self._config_data.nodes.values() + self._config_data.nodegroups.values() + + self._config_data.networks.values() + [self._config_data.cluster]) def _OpenConfig(self, accept_foreign): diff --git a/lib/masterd/iallocator.py b/lib/masterd/iallocator.py index d5762b669c02adf33873a955800a5a71f28a8e4f..7179b0ea6de5715c2c78d21cef711cbbf9c4a291 100644 --- a/lib/masterd/iallocator.py +++ b/lib/masterd/iallocator.py @@ -431,7 +431,14 @@ class IAllocator(object): node_whitelist = None es_flags = rpc.GetExclusiveStorageForNodeNames(cfg, node_list) - node_data = self.rpc.call_node_info(node_list, [cfg.GetVGName()], + vg_name = cfg.GetVGName() + if vg_name is not None: + has_lvm = True + vg_req = [vg_name] + else: + has_lvm = False + vg_req = [] + node_data = self.rpc.call_node_info(node_list, vg_req, [hypervisor_name], es_flags) node_iinfo = \ self.rpc.call_all_instances_info(node_list, @@ -441,7 +448,7 @@ class IAllocator(object): config_ndata = self._ComputeBasicNodeData(cfg, ninfo, node_whitelist) data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo, - i_list, config_ndata) + i_list, config_ndata, has_lvm) assert len(data["nodes"]) == len(ninfo), \ "Incomplete node data computed" @@ -494,7 +501,7 @@ class IAllocator(object): @staticmethod def _ComputeDynamicNodeData(node_cfg, node_data, node_iinfo, i_list, - node_results): + node_results, has_lvm): """Compute global node data. @param node_results: the basic node structures as filled from the config @@ -511,17 +518,22 @@ class IAllocator(object): nresult.Raise("Can't get data for node %s" % nname) node_iinfo[nname].Raise("Can't get node instance info from node %s" % nname) - remote_info = rpc.MakeLegacyNodeInfo(nresult.payload) + remote_info = rpc.MakeLegacyNodeInfo(nresult.payload, + require_vg_info=has_lvm) - for attr in ["memory_total", "memory_free", "memory_dom0", - "vg_size", "vg_free", "cpu_total"]: + def get_attr(attr): if attr not in remote_info: raise errors.OpExecError("Node '%s' didn't return attribute" " '%s'" % (nname, attr)) - if not isinstance(remote_info[attr], int): + value = remote_info[attr] + if not isinstance(value, int): raise errors.OpExecError("Node '%s' returned invalid value" " for '%s': %s" % - (nname, attr, remote_info[attr])) + (nname, attr, value)) + return value + + mem_free = get_attr("memory_free") + # compute memory used by primary instances i_p_mem = i_p_up_mem = 0 for iinfo, beinfo in i_list: @@ -532,19 +544,27 @@ class IAllocator(object): else: i_used_mem = int(node_iinfo[nname].payload[iinfo.name]["memory"]) i_mem_diff = beinfo[constants.BE_MAXMEM] - i_used_mem - remote_info["memory_free"] -= max(0, i_mem_diff) + mem_free -= max(0, i_mem_diff) if iinfo.admin_state == constants.ADMINST_UP: i_p_up_mem += beinfo[constants.BE_MAXMEM] + # TODO: replace this with proper storage reporting + if has_lvm: + total_disk = get_attr("vg_size") + free_disk = get_attr("vg_free") + else: + # we didn't even ask the node for VG status, so use zeros + total_disk = free_disk = 0 + # compute memory used by instances pnr_dyn = { - "total_memory": remote_info["memory_total"], - "reserved_memory": remote_info["memory_dom0"], - "free_memory": remote_info["memory_free"], - "total_disk": remote_info["vg_size"], - "free_disk": remote_info["vg_free"], - "total_cpus": remote_info["cpu_total"], + "total_memory": get_attr("memory_total"), + "reserved_memory": get_attr("memory_dom0"), + "free_memory": mem_free, + "total_disk": total_disk, + "free_disk": free_disk, + "total_cpus": get_attr("cpu_total"), "i_pri_memory": i_p_mem, "i_pri_up_memory": i_p_up_mem, } diff --git a/lib/objects.py b/lib/objects.py index fa811a62119da0495a53cfc90c2f39b050dadfcf..e2f9a1a0bba479fb77b5de1864081c6bba7144b5 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -2027,7 +2027,7 @@ class Network(TaggableObject): result = { "%sNETWORK_NAME" % prefix: self.name, "%sNETWORK_UUID" % prefix: self.uuid, - "%sNETWORK_TAGS" % prefix: " ".join(self.tags), + "%sNETWORK_TAGS" % prefix: " ".join(self.GetTags()), } if self.network: result["%sNETWORK_SUBNET" % prefix] = self.network diff --git a/lib/pathutils.py b/lib/pathutils.py index 8affef7439ec48abed685316cd9fa5d6931cef69..540148d0df310829e05f689ea85f928a79ef998c 100644 --- a/lib/pathutils.py +++ b/lib/pathutils.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,9 +29,15 @@ from ganeti import vcluster # Build-time constants -DEFAULT_FILE_STORAGE_DIR = vcluster.AddNodePrefix(_autoconf.FILE_STORAGE_DIR) -DEFAULT_SHARED_FILE_STORAGE_DIR = \ - vcluster.AddNodePrefix(_autoconf.SHARED_FILE_STORAGE_DIR) +if _autoconf.ENABLE_FILE_STORAGE: + DEFAULT_FILE_STORAGE_DIR = vcluster.AddNodePrefix(_autoconf.FILE_STORAGE_DIR) +else: + DEFAULT_FILE_STORAGE_DIR = _autoconf.FILE_STORAGE_DIR +if _autoconf.ENABLE_SHARED_FILE_STORAGE: + DEFAULT_SHARED_FILE_STORAGE_DIR = \ + vcluster.AddNodePrefix(_autoconf.SHARED_FILE_STORAGE_DIR) +else: + DEFAULT_SHARED_FILE_STORAGE_DIR = _autoconf.SHARED_FILE_STORAGE_DIR EXPORT_DIR = vcluster.AddNodePrefix(_autoconf.EXPORT_DIR) OS_SEARCH_PATH = _autoconf.OS_SEARCH_PATH ES_SEARCH_PATH = _autoconf.ES_SEARCH_PATH diff --git a/lib/rpc.py b/lib/rpc.py index 0c4bad0792c7117c1c17879392f540e02042aa6a..f6da48984d65402de0e495d35842b4ef71a64090 100644 --- a/lib/rpc.py +++ b/lib/rpc.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -571,18 +571,25 @@ def _EncodeBlockdevRename(value): return [(d.ToDict(), uid) for d, uid in value] -def MakeLegacyNodeInfo(data): +def MakeLegacyNodeInfo(data, require_vg_info=True): """Formats the data returned by L{rpc.RpcRunner.call_node_info}. Converts the data into a single dictionary. This is fine for most use cases, but some require information from more than one volume group or hypervisor. + @param require_vg_info: raise an error if the returnd vg_info + doesn't have any values + """ - (bootid, (vg_info, ), (hv_info, )) = data + (bootid, vgs_info, (hv_info, )) = data + + ret = utils.JoinDisjointDicts(hv_info, {"bootid": bootid}) + + if require_vg_info or vgs_info: + (vg0_info, ) = vgs_info + ret = utils.JoinDisjointDicts(vg0_info, ret) - return utils.JoinDisjointDicts(utils.JoinDisjointDicts(vg_info, hv_info), { - "bootid": bootid, - }) + return ret def _AnnotateDParamsDRBD(disk, (drbd_params, data_params, meta_params)): diff --git a/qa/qa_node.py b/qa/qa_node.py index 809f8795a39275554af0c0f11dc1af2af26c8409..d99004acdeaca56fee9018773abb021cdacc17fc 100644 --- a/qa/qa_node.py +++ b/qa/qa_node.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2007, 2011, 2012 Google Inc. +# Copyright (C) 2007, 2011, 2012, 2013 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -115,8 +115,18 @@ def TestNodeStorage(): master = qa_config.GetMasterNode() for storage_type in constants.VALID_STORAGE_TYPES: + + cmd = ["gnt-node", "list-storage", "--storage-type", storage_type] + + # Skip file storage if not enabled, otherwise QA will fail; we + # just test for basic failure, but otherwise skip the rest of the + # tests + if storage_type == constants.ST_FILE and not constants.ENABLE_FILE_STORAGE: + AssertCommand(cmd, fail=True) + continue + # Test simple list - AssertCommand(["gnt-node", "list-storage", "--storage-type", storage_type]) + AssertCommand(cmd) # Test all storage fields cmd = ["gnt-node", "list-storage", "--storage-type", storage_type, diff --git a/src/Ganeti/HTools/Program/Hbal.hs b/src/Ganeti/HTools/Program/Hbal.hs index be10c1a5a250ee0fa5a6cf646ab10c96ac31ebab..26b1e3c83a1618007cc835941342b1684516bffa 100644 --- a/src/Ganeti/HTools/Program/Hbal.hs +++ b/src/Ganeti/HTools/Program/Hbal.hs @@ -319,7 +319,7 @@ checkGroup verbose gname nl il = do putStrLn $ "Selected node group: " ++ gname let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il - unless (verbose == 0) $ printf + unless (verbose < 1) $ printf "Initial check done: %d bad nodes, %d bad instances.\n" (length bad_nodes) (length bad_instances) @@ -399,7 +399,7 @@ main opts args = do putStr sol_msg - unless (verbose == 0) $ + unless (verbose < 1) $ printf "Solution length=%d\n" (length ord_plc) let cmd_jobs = Cluster.splitJobs cmd_strs diff --git a/src/Ganeti/HTools/Program/Hcheck.hs b/src/Ganeti/HTools/Program/Hcheck.hs index b86dc5de7d3f7059e51326856e3e82951a1bc2e4..35f32efbf466405cb691cbe6744b571609798879 100644 --- a/src/Ganeti/HTools/Program/Hcheck.hs +++ b/src/Ganeti/HTools/Program/Hcheck.hs @@ -4,7 +4,7 @@ {- -Copyright (C) 2012 Google Inc. +Copyright (C) 2012, 2013 Google Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -191,7 +191,7 @@ printStats _ True level phase values = do printStats verbose False level phase values = do let prefix = phaseLevelDescr phase level descr = descrData level - unless (verbose == 0) $ do + unless (verbose < 1) $ do putStrLn "" putStr prefix mapM_ (uncurry (printf " %s: %s\n")) (zip descr values) @@ -311,7 +311,7 @@ main opts args = do clusterstats = map sum . transpose . map snd $ groupsstats needrebalance = clusterNeedsRebalance clusterstats - unless (verbose == 0 || machineread) . + unless (verbose < 1 || machineread) . putStrLn $ if nosimulation then "Running in no-simulation mode." else if needrebalance diff --git a/src/Ganeti/Objects.hs b/src/Ganeti/Objects.hs index 6e568e63c89afd757a360b9abc2d57dc37d179a1..bb7f84157f46a931464e206f29e2fa1a3d53212c 100644 --- a/src/Ganeti/Objects.hs +++ b/src/Ganeti/Objects.hs @@ -578,7 +578,8 @@ $(buildObject "Cluster" "cluster" $ , simpleField "highest_used_port" [t| Int |] , simpleField "tcpudp_port_pool" [t| [Int] |] , simpleField "mac_prefix" [t| String |] - , simpleField "volume_group_name" [t| String |] + , optionalField $ + simpleField "volume_group_name" [t| String |] , simpleField "reserved_lvs" [t| [String] |] , optionalField $ simpleField "drbd_usermode_helper" [t| String |] diff --git a/src/Ganeti/Query/Node.hs b/src/Ganeti/Query/Node.hs index e639351ab1e9aaee327fcfd975fe9aa9ef525022..77eef3ec6c07f3824d2dd74bda1660256bfdace2 100644 --- a/src/Ganeti/Query/Node.hs +++ b/src/Ganeti/Query/Node.hs @@ -31,6 +31,7 @@ module Ganeti.Query.Node import Control.Applicative import Data.List +import Data.Maybe import qualified Data.Map as Map import qualified Text.JSON as J @@ -109,7 +110,7 @@ nodeLiveFieldBuilder (fname, ftitle, ftype, _, fdoc) = , FieldRuntime $ nodeLiveRpcCall fname , QffNormal) --- | The docstring for the node role. Note that we use 'reverse in +-- | The docstring for the node role. Note that we use 'reverse' in -- order to keep the same order as Python. nodeRoleDoc :: String nodeRoleDoc = @@ -221,7 +222,7 @@ collectLiveData:: Bool -> ConfigData -> [Node] -> IO [(Node, Runtime)] collectLiveData False _ nodes = return $ zip nodes (repeat $ Left (RpcResultError "Live data disabled")) collectLiveData True cfg nodes = do - let vgs = [clusterVolumeGroupName $ configCluster cfg] + let vgs = maybeToList . clusterVolumeGroupName $ configCluster cfg hvs = [getDefaultHypervisor cfg] step n (bn, gn, em) = let ndp' = getNodeNdParams cfg n diff --git a/src/Ganeti/Query/Server.hs b/src/Ganeti/Query/Server.hs index 07fbce0fd869d96cfb492ba3f8f63c7db381a2aa..9176a1a1f0ee899134b9c5016fbe3a88dbb98830 100644 --- a/src/Ganeti/Query/Server.hs +++ b/src/Ganeti/Query/Server.hs @@ -116,7 +116,8 @@ handleCall cdata QueryClusterInfo = , ("master_netmask", showJSON $ clusterMasterNetmask cluster) , ("use_external_mip_script", showJSON $ clusterUseExternalMipScript cluster) - , ("volume_group_name", showJSON $ clusterVolumeGroupName cluster) + , ("volume_group_name", + maybe JSNull showJSON (clusterVolumeGroupName cluster)) , ("drbd_usermode_helper", maybe JSNull showJSON (clusterDrbdUsermodeHelper cluster)) , ("file_storage_dir", showJSON $ clusterFileStorageDir cluster) diff --git a/test/hs/Test/Ganeti/HTools/Types.hs b/test/hs/Test/Ganeti/HTools/Types.hs index 0cb187a21af933117f9bf5f35085adbcc32e4a01..da21725438e27ed0f7d53d352246f7eaa0ccbf05 100644 --- a/test/hs/Test/Ganeti/HTools/Types.hs +++ b/test/hs/Test/Ganeti/HTools/Types.hs @@ -7,7 +7,7 @@ {- -Copyright (C) 2009, 2010, 2011, 2012 Google Inc. +Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -46,7 +46,7 @@ import Data.List (sort) import Test.Ganeti.TestHelper import Test.Ganeti.TestCommon import Test.Ganeti.TestHTools -import Test.Ganeti.Types () +import Test.Ganeti.Types (allDiskTemplates) import Ganeti.BasicTypes import qualified Ganeti.Constants as C @@ -56,10 +56,6 @@ import qualified Ganeti.HTools.Types as Types -- * Helpers --- | All disk templates (used later) -allDiskTemplates :: [Types.DiskTemplate] -allDiskTemplates = [minBound..maxBound] - -- * Arbitrary instance $(genArbitrary ''Types.FailMode) diff --git a/test/hs/Test/Ganeti/TestCommon.hs b/test/hs/Test/Ganeti/TestCommon.hs index 91351b68b7396f7a56fd21b1abcbe76cf723699d..43765e5a9016809d9189d45fe87698bf6a148fe1 100644 --- a/test/hs/Test/Ganeti/TestCommon.hs +++ b/test/hs/Test/Ganeti/TestCommon.hs @@ -23,7 +23,43 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -} -module Test.Ganeti.TestCommon where +module Test.Ganeti.TestCommon + ( maxMem + , maxDsk + , maxCpu + , maxVcpuRatio + , maxSpindleRatio + , maxNodes + , maxOpCodes + , (==?) + , (/=?) + , failTest + , passTest + , pythonCmd + , runPython + , checkPythonResult + , DNSChar(..) + , genName + , genFQDN + , genMaybe + , genTags + , genFields + , genUniquesList + , SmallRatio(..) + , genSetHelper + , genSet + , genIp4AddrStr + , genIp4Addr + , genIp4NetWithNetmask + , genIp4Net + , genIp6Addr + , genIp6Net + , netmask2NumHosts + , testSerialisation + , resultProp + , readTestData + , genSample + ) where import Control.Applicative import Control.Exception (catchJust) diff --git a/test/hs/Test/Ganeti/TestHTools.hs b/test/hs/Test/Ganeti/TestHTools.hs index 44b53c87a4bc28581a98b45ca720cec3ec6182e2..b27c34c0439ba9af268efeb40f5ca469dd5fb0fe 100644 --- a/test/hs/Test/Ganeti/TestHTools.hs +++ b/test/hs/Test/Ganeti/TestHTools.hs @@ -6,7 +6,7 @@ {- -Copyright (C) 2009, 2010, 2011, 2012 Google Inc. +Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,7 +25,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -} -module Test.Ganeti.TestHTools where +module Test.Ganeti.TestHTools + ( nullIPolicy + , defGroup + , defGroupList + , defGroupAssoc + , createInstance + , makeSmallCluster + , setInstanceSmallerThanNode + ) where import qualified Data.Map as Map @@ -71,14 +79,17 @@ nullIPolicy = Types.IPolicy , Types.iPolicySpindleRatio = maxSpindleRatio } +-- | Default group definition. defGroup :: Group.Group defGroup = flip Group.setIdx 0 $ Group.create "default" Types.defaultGroupID Types.AllocPreferred nullIPolicy [] +-- | Default group, as a (singleton) 'Group.List'. defGroupList :: Group.List defGroupList = Container.fromList [(Group.idx defGroup, defGroup)] +-- | Default group, as a string map. defGroupAssoc :: Map.Map String Types.Gdx defGroupAssoc = Map.singleton (Group.uuid defGroup) (Group.idx defGroup) diff --git a/test/hs/Test/Ganeti/Types.hs b/test/hs/Test/Ganeti/Types.hs index 6246e6e0cde5fb2e1ae573dce758b7cf285c51f6..3eff2f0ce9a40e9e32646c55c2ce5fca04356d40 100644 --- a/test/hs/Test/Ganeti/Types.hs +++ b/test/hs/Test/Ganeti/Types.hs @@ -30,13 +30,14 @@ module Test.Ganeti.Types ( testTypes , AllocPolicy(..) , DiskTemplate(..) + , allDiskTemplates , InstanceStatus(..) , NonEmpty(..) , Hypervisor(..) , JobId(..) ) where -import Data.List (sort) +import Data.List (delete, sort) import Test.QuickCheck as QuickCheck hiding (Result) import Test.HUnit import qualified Text.JSON as J @@ -78,7 +79,22 @@ instance (Arbitrary a) => Arbitrary (Types.NonEmpty a) where $(genArbitrary ''AllocPolicy) -$(genArbitrary ''DiskTemplate) +-- | Valid disk templates (depending on configure options). +allDiskTemplates :: [DiskTemplate] +allDiskTemplates = + let all_vals = [minBound..maxBound]::[DiskTemplate] + sel1 = if C.enableFileStorage + then all_vals + else delete DTFile all_vals + sel2 = if C.enableSharedFileStorage + then sel1 + else delete DTSharedFile sel1 + in sel2 + +-- | Custom 'Arbitrary' instance for 'DiskTemplate', which needs to +-- handle the case of file storage being disabled at configure time. +instance Arbitrary DiskTemplate where + arbitrary = elements allDiskTemplates $(genArbitrary ''InstanceStatus) @@ -96,7 +112,18 @@ $(genArbitrary ''Hypervisor) $(genArbitrary ''OobCommand) -$(genArbitrary ''StorageType) +-- | Valid storage types. +allStorageTypes :: [StorageType] +allStorageTypes = + let all_vals = [minBound..maxBound]::[StorageType] + in if C.enableFileStorage + then all_vals + else delete StorageFile all_vals + +-- | Custom 'Arbitrary' instance for 'StorageType', which needs to +-- handle the case of file storage being disabled at configure time. +instance Arbitrary StorageType where + arbitrary = elements allStorageTypes $(genArbitrary ''NodeEvacMode) diff --git a/test/py/ganeti.rpc_unittest.py b/test/py/ganeti.rpc_unittest.py index ace49af8993700a262a49f00557aaf09ce68ce26..7967173b485e0480a254c3a7026f18da9c12adf1 100755 --- a/test/py/ganeti.rpc_unittest.py +++ b/test/py/ganeti.rpc_unittest.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -# Copyright (C) 2010, 2011, 2012 Google Inc. +# Copyright (C) 2010, 2011, 2012, 2013 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -889,5 +889,38 @@ class TestRpcRunner(unittest.TestCase): msg="Configuration objects were modified") +class TestLegacyNodeInfo(unittest.TestCase): + KEY_BOOT = "bootid" + KEY_VG = "disk_free" + KEY_HV = "cpu_count" + VAL_BOOT = 0 + VAL_VG = 1 + VAL_HV = 2 + DICT_VG = {KEY_VG: VAL_VG} + DICT_HV = {KEY_HV: VAL_HV} + STD_LST = [VAL_BOOT, [DICT_VG], [DICT_HV]] + STD_DICT = { + KEY_BOOT: VAL_BOOT, + KEY_VG: VAL_VG, + KEY_HV: VAL_HV + } + + def testStandard(self): + result = rpc.MakeLegacyNodeInfo(self.STD_LST) + self.assertEqual(result, self.STD_DICT) + + def testReqVg(self): + my_lst = [self.VAL_BOOT, [], [self.DICT_HV]] + self.assertRaises(ValueError, rpc.MakeLegacyNodeInfo, my_lst) + + def testNoReqVg(self): + my_lst = [self.VAL_BOOT, [], [self.DICT_HV]] + result = rpc.MakeLegacyNodeInfo(my_lst, require_vg_info = False) + self.assertEqual(result, {self.KEY_BOOT: self.VAL_BOOT, + self.KEY_HV: self.VAL_HV}) + result = rpc.MakeLegacyNodeInfo(self.STD_LST, require_vg_info = False) + self.assertEqual(result, self.STD_DICT) + + if __name__ == "__main__": testutils.GanetiTestProgram()