diff --git a/Makefile.am b/Makefile.am index 7a06d127fffcca9e1da5a912e0824be76f7e6d57..748704437d726ccd77b41097d081a5c63088f195 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1705,6 +1705,7 @@ HLINT_EXCLUDES = src/Ganeti/THH.hs test/hs/hpc-htools.hs .PHONY: hlint hlint: $(HS_BUILT_SRCS) src/lint-hints.hs @test -n "$(HLINT)" || { echo 'hlint' not found during configure; exit 1; } + @rm -f doc/hs-lint.html if tty -s; then C="-c"; else C=""; fi; \ $(HLINT) --utf8 --report=doc/hs-lint.html --cross $$C \ --ignore "Use first" \ @@ -1713,6 +1714,9 @@ hlint: $(HS_BUILT_SRCS) src/lint-hints.hs --ignore "Reduce duplication" \ --hint src/lint-hints \ $(filter-out $(HLINT_EXCLUDES),$(HS_LIBTEST_SRCS) $(HS_PROG_SRCS)) + @if [ ! -f doc/hs-lint.html ]; then \ + echo "All good" > doc/hs-lint.html; \ + fi # a dist hook rule for updating the vcs-version file; this is # hardcoded due to where it needs to build the file... diff --git a/NEWS b/NEWS index 30b3a00ab6df3f5c78ba3fb3d39f625916e8bba7..568f37653a92530e8b9153a4d5f06860a3018736 100644 --- a/NEWS +++ b/NEWS @@ -5,7 +5,7 @@ News Version 2.7.0 beta1 ------------------- -*(unreleased)* +*(Released Wed, 6 Mar 2013)* - ``gnt-instance batch-create`` has been changed to use the bulk create opcode from Ganeti. This lead to incompatible changes in the format of @@ -82,6 +82,9 @@ Version 2.7.0 beta1 - New external storage backend, to allow managing arbitrary storage systems external to the cluster. See :manpage:`ganeti-extstorage-interface(7)`. +- Instance renames of LVM-based instances will now update the LV tags + (which can be used to recover the instance-to-LV mapping in case of + emergencies) Version 2.6.2 diff --git a/README b/README index 67d5d48f9feee1dc6f55c0447a636eaeaeebffe9..20282ee1da350a2e3e36877e2f7379b1502ea096 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Ganeti 2.6 +Ganeti 2.7 ========== For installation instructions, read the INSTALL and the doc/install.rst diff --git a/configure.ac b/configure.ac index 2c93bfc6347b018e7d1adf3d1b3a2368e071a5c6..0683f5fcb8b6465e1234f628ee11bdd760434391 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,8 @@ # Configure script for Ganeti m4_define([gnt_version_major], [2]) -m4_define([gnt_version_minor], [6]) -m4_define([gnt_version_revision], [2]) -m4_define([gnt_version_suffix], []) +m4_define([gnt_version_minor], [7]) +m4_define([gnt_version_revision], [0]) +m4_define([gnt_version_suffix], [~beta1]) m4_define([gnt_version_full], m4_format([%d.%d.%d%s], gnt_version_major, gnt_version_minor, diff --git a/devel/upload b/devel/upload index efe00217e35fee13476f82a75b708559b8b612b9..8e6cb675fb06a5b4212c26d1c23e2a9c459f383c 100755 --- a/devel/upload +++ b/devel/upload @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2009, 2010, 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,7 @@ # $prefix/lib/python2.4/site-packages/ganeti and the command line utils to # $prefix/sbin. It needs passwordless root login to the nodes. -set -e +set -e -u usage() { echo "Usage: $0 [--no-restart] [--no-cron] [--no-debug] hosts..." >&2 @@ -92,6 +92,7 @@ make $make_args install DESTDIR="$TXD" # fixed; we can read the prefix vars/etc. PREFIX="$(echo @PREFIX@ | $SED)" SYSCONFDIR="$(echo @SYSCONFDIR@ | $SED)" +LIBDIR="$(echo @LIBDIR@ | $SED)" PKGLIBDIR="$(echo @PKGLIBDIR@ | $SED)" # copy additional needed files diff --git a/doc/design-draft.rst b/doc/design-draft.rst index 36b052abf02688d5d739575ac05a48d8a62448fa..581a2d43bddcc83f6b0c1ae670ed4dfdc1fbe539 100644 --- a/doc/design-draft.rst +++ b/doc/design-draft.rst @@ -2,7 +2,7 @@ Design document drafts ====================== -.. Last updated for Ganeti 2.6 +.. Last updated for Ganeti 2.7 .. toctree:: :maxdepth: 2 diff --git a/doc/hooks.rst b/doc/hooks.rst index 956ad303616537b29634a25338f0b7bb0ab808cd..5ccf93575d39ea9795ebbc00775fd46c9fb9b157 100644 --- a/doc/hooks.rst +++ b/doc/hooks.rst @@ -1,7 +1,7 @@ Ganeti customisation using hooks ================================ -Documents Ganeti version 2.6 +Documents Ganeti version 2.7 .. contents:: @@ -224,7 +224,7 @@ Adds a network to the cluster. :directory: network-add :env. vars: NETWORK_NAME, NETWORK_SUBNET, NETWORK_GATEWAY, NETWORK_SUBNET6, - NETWORK_GATEWAY6, NETWORK_TYPE, NETWORK_MAC_PREFIX, NETWORK_TAGS + NETWORK_GATEWAY6, NETWORK_MAC_PREFIX, NETWORK_TAGS :pre-execution: master node :post-execution: master node @@ -247,7 +247,7 @@ Connects a network to a nodegroup. :env. vars: GROUP_NAME, NETWORK_NAME, GROUP_NETWORK_MODE, GROUP_NETWORK_LINK, NETWORK_SUBNET, NETWORK_GATEWAY, NETWORK_SUBNET6, - NETWORK_GATEWAY6, NETWORK_TYPE, NETWORK_MAC_PREFIX, NETWORK_TAGS + NETWORK_GATEWAY6, NETWORK_MAC_PREFIX, NETWORK_TAGS :pre-execution: nodegroup nodes :post-execution: nodegroup nodes @@ -261,7 +261,7 @@ Disconnects a network from a nodegroup. :env. vars: GROUP_NAME, NETWORK_NAME, GROUP_NETWORK_MODE, GROUP_NETWORK_LINK, NETWORK_SUBNET, NETWORK_GATEWAY, NETWORK_SUBNET6, - NETWORK_GATEWAY6, NETWORK_TYPE, NETWORK_MAC_PREFIX, NETWORK_TAGS + NETWORK_GATEWAY6, NETWORK_MAC_PREFIX, NETWORK_TAGS :pre-execution: nodegroup nodes :post-execution: nodegroup nodes @@ -273,7 +273,7 @@ Modifies a network. :directory: network-modify :env. vars: NETWORK_NAME, NETWORK_SUBNET, NETWORK_GATEWAY, NETWORK_SUBNET6, - NETWORK_GATEWAY6, NETWORK_TYPE, NETWORK_MAC_PREFIX, NETWORK_TAGS + NETWORK_GATEWAY6, NETWORK_MAC_PREFIX, NETWORK_TAGS :pre-execution: master node :post-execution: master node @@ -286,11 +286,19 @@ INSTANCE_NAME, INSTANCE_PRIMARY, INSTANCE_SECONDARY, INSTANCE_OS_TYPE, INSTANCE_DISK_TEMPLATE, INSTANCE_MEMORY, INSTANCE_DISK_SIZES, INSTANCE_VCPUS, INSTANCE_NIC_COUNT, INSTANCE_NICn_IP, INSTANCE_NICn_BRIDGE, INSTANCE_NICn_MAC, +INSTANCE_NICn_NETWORK, +INSTANCE_NICn_NETWORK_UUID, INSTANCE_NICn_NETWORK_SUBNET, +INSTANCE_NICn_NETWORK_GATEWAY, INSTANCE_NICn_NETWORK_SUBNET6, +INSTANCE_NICn_NETWORK_GATEWAY6, INSTANCE_NICn_NETWORK_MAC_PREFIX, INSTANCE_DISK_COUNT, INSTANCE_DISKn_SIZE, INSTANCE_DISKn_MODE. The INSTANCE_NICn_* and INSTANCE_DISKn_* variables represent the properties of the *n* -th NIC and disk, and are zero-indexed. +The INSTANCE_NICn_NETWORK_* variables are only passed if a NIC's network +parameter is set (that is if the NIC is associated to a network defined +via ``gnt-network``) + OP_INSTANCE_CREATE ++++++++++++++++++ @@ -383,7 +391,7 @@ and secondary before migration. :directory: instance-migrate :env. vars: MIGRATE_LIVE, MIGRATE_CLEANUP, OLD_PRIMARY, OLD_SECONDARY, NEW_PRIMARY, NEW_SECONDARY -:pre-execution: master node, secondary node +:pre-execution: master node, primary and secondary nodes :post-execution: master node, primary and secondary nodes diff --git a/doc/iallocator.rst b/doc/iallocator.rst index 633d5586da9c62edc2e2bdf763871e83365a9301..957c2bb78a429d083a1c2a18203401422da5e46b 100644 --- a/doc/iallocator.rst +++ b/doc/iallocator.rst @@ -1,7 +1,7 @@ Ganeti automatic instance allocation ==================================== -Documents Ganeti version 2.6 +Documents Ganeti version 2.7 .. contents:: diff --git a/doc/rapi.rst b/doc/rapi.rst index f70f8ca1acb1ba4ae8ee2b15eea5da1fa0928fde..11c64c15ae087cb322587d78e08ca37a1f7f7710 100644 --- a/doc/rapi.rst +++ b/doc/rapi.rst @@ -698,7 +698,6 @@ Example:: 'name': 'nat', 'network': '10.0.0.0/28', 'network6': None, - 'network_type': 'private', 'reserved_count': 3, 'tags': ['nfdhcpd'], β¦ diff --git a/doc/security.rst b/doc/security.rst index 70d1d9556ac584c1e7fb46e49d4dd7c3b918ac98..4b4e9765d2a3a3f25d12c4a41c8a93c1d7d92683 100644 --- a/doc/security.rst +++ b/doc/security.rst @@ -1,7 +1,7 @@ Security in Ganeti ================== -Documents Ganeti version 2.6 +Documents Ganeti version 2.7 Ganeti was developed to run on internal, trusted systems. As such, the security model is all-or-nothing. @@ -50,10 +50,38 @@ on this node; the RPC method will run only: drbd devices, start/stop instances, etc; - run well-defined SSH commands on other nodes in the cluster - scripts under the ``/etc/ganeti/hooks`` directory +- scripts under the ``/etc/ganeti/restricted-commands`` directory, if + this feature has been enabled at build time (see below) It is therefore important to make sure that the contents of the -``/etc/ganeti/hooks`` directory is supervised and only trusted sources -can populate it. +``/etc/ganeti/hooks`` and ``/etc/ganeti/restricted-commands`` +directories are supervised and only trusted sources can populate them. + +Restricted commands +~~~~~~~~~~~~~~~~~~~ + +The restricted commands feature is new in Ganeti 2.7. It enables the +administrator to run any commands in the +``/etc/ganeti/restricted-commands`` directory, if the feature has been +enabled at build time, subject to the following restrictions: + +- No parameters may be passed +- No absolute or relative path may be passed, only a filename +- The ``/etc/ganeti/restricted-commands`` directory must + be owned by root:root and have mode 0755 or stricter +- Executables must be regular files or symlinks, and must be executable + by root:root + +Note that it's not possible to list the contents of the directory, and +there is an intentional delay when trying to execute a non-existing +command (to slow-down dictionary attacks). + +Since for Ganeti itself this functionality is not needed, and is only +provided as a way to help administrate or recover nodes, it is a local +site decision whether to enable or not the restricted commands feature. + +By default, this feature is disabled. + Cluster issues -------------- @@ -94,6 +122,25 @@ before serving requests. This permission-based protection is documented and works on Linux, but is not-portable; however, Ganeti doesn't work on non-Linux system at the moment. +Conf daemon +----------- + +In Ganeti 2.7, the ``confd`` daemon (if enabled at build time), serves +both network-originated queries (about the static configuration) and +local (UNIX socket) queries (about the run-time configuration; answering +these means talking to other cluster nodes, which makes use of the +internal RPC SSL certificate). This makes it a bit more sensitive to +bugs (a remote attacker could get direct access to the intra-cluster +RPC), so to harden security it's recommended to: + +- disable confd at build time if it's not needed in your setup +- otherwise, configure Ganeti (at build time) to use separate users, so + that the confd daemon doesn't also have access to the server SSL/TLS + certificates + +It is planned to split the two functionalities (local/remote querying) +of confd into two separate daemons in a future Ganeti version. + Remote API ---------- diff --git a/doc/virtual-cluster.rst b/doc/virtual-cluster.rst index e2c0798d6a838f299bc733490221cd03cd0af2a3..54abd04dfc27af0c827a5bc5a235db6ef071dc4b 100644 --- a/doc/virtual-cluster.rst +++ b/doc/virtual-cluster.rst @@ -1,7 +1,7 @@ Virtual cluster support ======================= -Documents Ganeti version 2.6 +Documents Ganeti version 2.7 .. contents:: diff --git a/lib/backend.py b/lib/backend.py index b6918c87fbb2e475fc272613b9af4ad592e012c7..b7dd2ab3aa383c390e9eea7f658c27bda566103c 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -2498,8 +2498,14 @@ def OSEnvironment(instance, inst_os, debug=0): result["NIC_%d_BRIDGE" % idx] = nic.nicparams[constants.NIC_LINK] if nic.nicparams[constants.NIC_LINK]: result["NIC_%d_LINK" % idx] = nic.nicparams[constants.NIC_LINK] - if nic.network: - result["NIC_%d_NETWORK" % idx] = nic.network + if nic.netinfo: + nobj = objects.Network.FromDict(nic.netinfo) + result.update(nobj.HooksDict("NIC_%d_" % idx)) + elif nic.network: + # FIXME: broken network reference: the instance NIC specifies a network, + # but the relevant network entry was not in the config. This should be + # made impossible. + result["INSTANCE_NIC%d_NETWORK" % idx] = nic.network if constants.HV_NIC_TYPE in instance.hvparams: result["NIC_%d_FRONTEND_TYPE" % idx] = \ instance.hvparams[constants.HV_NIC_TYPE] diff --git a/lib/cli.py b/lib/cli.py index 950578eea79e55e9f79db1d1cd14eb6f535a42e4..c82de3355660f2fafcfb6fb16db638f5672694c7 100644 --- a/lib/cli.py +++ b/lib/cli.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 @@ -117,7 +117,6 @@ __all__ = [ "NET_OPT", "NETWORK_OPT", "NETWORK6_OPT", - "NETWORK_TYPE_OPT", "NEW_CLUSTER_CERT_OPT", "NEW_CLUSTER_DOMAIN_SECRET_OPT", "NEW_CONFD_HMAC_KEY_OPT", @@ -1527,10 +1526,6 @@ REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips", help="Comma-delimited list of" " reserved IPs to remove") -NETWORK_TYPE_OPT = cli_option("--network-type", - action="store", default=None, dest="network_type", - help="Network type: private, public, None") - NETWORK6_OPT = cli_option("--network6", action="store", default=None, dest="network6", help="IP network in CIDR notation") @@ -2302,8 +2297,12 @@ def FormatError(err): elif isinstance(err, errors.ParameterError): obuf.write("Failure: unknown/wrong parameter name '%s'" % msg) elif isinstance(err, luxi.NoMasterError): - obuf.write("Cannot communicate with the master daemon.\nIs it running" - " and listening for connections?") + if err.args[0] == pathutils.MASTER_SOCKET: + daemon = "master" + else: + daemon = "config" + obuf.write("Cannot communicate with the %s daemon.\nIs it running" + " and listening for connections?" % daemon) elif isinstance(err, luxi.TimeoutError): obuf.write("Timeout while talking to the master daemon. Jobs might have" " been submitted and will continue to run even if the call" diff --git a/lib/client/gnt_network.py b/lib/client/gnt_network.py index a51d5119868d5c433f4532c727fc8f9d309aa774..17c1bace7f63f3b430f52f61d480ebff0c898533 100644 --- a/lib/client/gnt_network.py +++ b/lib/client/gnt_network.py @@ -36,7 +36,7 @@ from ganeti import errors #: default list of fields for L{ListNetworks} _LIST_DEF_FIELDS = ["name", "network", "gateway", - "network_type", "mac_prefix", "group_list", "tags"] + "mac_prefix", "group_list", "tags"] def _HandleReservedIPs(ips): @@ -77,7 +77,6 @@ def AddNetwork(opts, args): gateway6=opts.gateway6, network6=opts.network6, mac_prefix=opts.mac_prefix, - network_type=opts.network_type, add_reserved_ips=reserved_ips, conflicts_check=opts.conflicts_check, tags=tags) @@ -203,7 +202,7 @@ def ShowNetworkConfig(_, args): cl = GetClient() result = cl.QueryNetworks(fields=["name", "network", "gateway", "network6", "gateway6", - "mac_prefix", "network_type", + "mac_prefix", "free_count", "reserved_count", "map", "group_list", "inst_list", "external_reservations", @@ -211,7 +210,7 @@ def ShowNetworkConfig(_, args): names=args, use_locking=False) for (name, network, gateway, network6, gateway6, - mac_prefix, network_type, free_count, reserved_count, + mac_prefix, free_count, reserved_count, mapping, group_list, instances, ext_res, serial, uuid) in result: size = free_count + reserved_count ToStdout("Network name: %s", name) @@ -222,7 +221,6 @@ def ShowNetworkConfig(_, args): ToStdout(" IPv6 Subnet: %s", network6) ToStdout(" IPv6 Gateway: %s", gateway6) ToStdout(" Mac Prefix: %s", mac_prefix) - ToStdout(" Type: %s", network_type) ToStdout(" Size: %d", size) ToStdout(" Free: %d (%.2f%%)", free_count, 100 * float(free_count) / float(size)) @@ -278,7 +276,6 @@ def SetNetworkParams(opts, args): "add_reserved_ips": _HandleReservedIPs(opts.add_reserved_ips), "remove_reserved_ips": _HandleReservedIPs(opts.remove_reserved_ips), "mac_prefix": opts.mac_prefix, - "network_type": opts.network_type, "gateway6": opts.gateway6, "network6": opts.network6, } @@ -313,7 +310,7 @@ commands = { "add": ( AddNetwork, ARGS_ONE_NETWORK, [DRY_RUN_OPT, NETWORK_OPT, GATEWAY_OPT, ADD_RESERVED_IPS_OPT, - MAC_PREFIX_OPT, NETWORK_TYPE_OPT, NETWORK6_OPT, GATEWAY6_OPT, + MAC_PREFIX_OPT, NETWORK6_OPT, GATEWAY6_OPT, NOCONFLICTSCHECK_OPT, TAG_ADD_OPT, PRIORITY_OPT, SUBMIT_OPT], "<network_name>", "Add a new IP network to the cluster"), "list": ( @@ -332,7 +329,7 @@ commands = { "modify": ( SetNetworkParams, ARGS_ONE_NETWORK, [DRY_RUN_OPT, SUBMIT_OPT, ADD_RESERVED_IPS_OPT, REMOVE_RESERVED_IPS_OPT, - GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK_TYPE_OPT, NETWORK6_OPT, GATEWAY6_OPT, + GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK6_OPT, GATEWAY6_OPT, PRIORITY_OPT], "<network_name>", "Alters the parameters of a network"), "connect": ( diff --git a/lib/client/gnt_os.py b/lib/client/gnt_os.py index 0ffa9aca4c22c58f0ee0ae94a83f51f9e5bd0664..4bec976593d4d530b4ad55b10f56570e1b6fa962 100644 --- a/lib/client/gnt_os.py +++ b/lib/client/gnt_os.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2010 Google Inc. +# Copyright (C) 2006, 2007, 2010, 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 @@ -45,10 +45,6 @@ def ListOS(opts, args): op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[]) result = SubmitOpCode(op, opts=opts) - if not result: - ToStderr("Can't get the OS list") - return 1 - if not opts.no_headers: headers = {"name": "Name"} else: diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 6556f709372574bf5d4cd645de0b34ed3f7daec0..36376443f808a841f74c9b5735cab50f48d47885 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -1020,18 +1020,32 @@ def _CheckOutputFields(static, dynamic, selected): % ",".join(delta), errors.ECODE_INVAL) -def _CheckGlobalHvParams(params): - """Validates that given hypervisor params are not global ones. +def _CheckParamsNotGlobal(params, glob_pars, kind, bad_levels, good_levels): + """Make sure that none of the given paramters is global. - This will ensure that instances don't get customised versions of - global params. + If a global parameter is found, an L{errors.OpPrereqError} exception is + raised. This is used to avoid setting global parameters for individual nodes. + + @type params: dictionary + @param params: Parameters to check + @type glob_pars: dictionary + @param glob_pars: Forbidden parameters + @type kind: string + @param kind: Kind of parameters (e.g. "node") + @type bad_levels: string + @param bad_levels: Level(s) at which the parameters are forbidden (e.g. + "instance") + @type good_levels: strings + @param good_levels: Level(s) at which the parameters are allowed (e.g. + "cluster or group") """ - used_globals = constants.HVC_GLOBALS.intersection(params) + used_globals = glob_pars.intersection(params) if used_globals: - msg = ("The following hypervisor parameters are global and cannot" - " be customized at instance level, please modify them at" - " cluster level: %s" % utils.CommaJoin(used_globals)) + msg = ("The following %s parameters are global and cannot" + " be customized at %s level, please modify them at" + " %s level: %s" % + (kind, bad_levels, good_levels, utils.CommaJoin(used_globals))) raise errors.OpPrereqError(msg, errors.ECODE_INVAL) @@ -1385,7 +1399,7 @@ def _ExpandInstanceName(cfg, name): def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6, - network_type, mac_prefix, tags): + mac_prefix, tags): """Builds network related env variables for hooks This builds the hook environment from individual variables. @@ -1400,8 +1414,6 @@ def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6, @param network6: the ipv6 subnet @type gateway6: string @param gateway6: the ipv6 gateway - @type network_type: string - @param network_type: the type of the network @type mac_prefix: string @param mac_prefix: the mac_prefix @type tags: list @@ -1421,8 +1433,6 @@ def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6, env["NETWORK_GATEWAY6"] = gateway6 if mac_prefix: env["NETWORK_MAC_PREFIX"] = mac_prefix - if network_type: - env["NETWORK_TYPE"] = network_type if tags: env["NETWORK_TAGS"] = " ".join(tags) @@ -1453,7 +1463,7 @@ def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status, @type vcpus: string @param vcpus: the count of VCPUs the instance has @type nics: list - @param nics: list of tuples (ip, mac, mode, link, network) representing + @param nics: list of tuples (ip, mac, mode, link, net, netinfo) representing the NICs the instance has @type disk_template: string @param disk_template: the disk template of the instance @@ -1495,24 +1505,14 @@ def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status, env["INSTANCE_NIC%d_MAC" % idx] = mac env["INSTANCE_NIC%d_MODE" % idx] = mode env["INSTANCE_NIC%d_LINK" % idx] = link - if network: - env["INSTANCE_NIC%d_NETWORK" % idx] = net - if netinfo: - nobj = objects.Network.FromDict(netinfo) - if nobj.network: - env["INSTANCE_NIC%d_NETWORK_SUBNET" % idx] = nobj.network - if nobj.gateway: - env["INSTANCE_NIC%d_NETWORK_GATEWAY" % idx] = nobj.gateway - if nobj.network6: - env["INSTANCE_NIC%d_NETWORK_SUBNET6" % idx] = nobj.network6 - if nobj.gateway6: - env["INSTANCE_NIC%d_NETWORK_GATEWAY6" % idx] = nobj.gateway6 - if nobj.mac_prefix: - env["INSTANCE_NIC%d_NETWORK_MAC_PREFIX" % idx] = nobj.mac_prefix - if nobj.network_type: - env["INSTANCE_NIC%d_NETWORK_TYPE" % idx] = nobj.network_type - if nobj.tags: - env["INSTANCE_NIC%d_NETWORK_TAGS" % idx] = " ".join(nobj.tags) + if netinfo: + nobj = objects.Network.FromDict(netinfo) + env.update(nobj.HooksDict("INSTANCE_NIC%d_" % idx)) + elif network: + # FIXME: broken network reference: the instance NIC specifies a + # network, but the relevant network entry was not in the config. This + # should be made impossible. + env["INSTANCE_NIC%d_NETWORK_NAME" % idx] = net if mode == constants.NIC_MODE_BRIDGED: env["INSTANCE_NIC%d_BRIDGE" % idx] = link else: @@ -3490,22 +3490,11 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): nimg.sbp[pnode].append(instance) es_flags = rpc.GetExclusiveStorageForNodeNames(self.cfg, self.my_node_names) - es_unset_nodes = [] # The value of exclusive_storage should be the same across the group, so if # it's True for at least a node, we act as if it were set for all the nodes self._exclusive_storage = compat.any(es_flags.values()) if self._exclusive_storage: node_verify_param[constants.NV_EXCLUSIVEPVS] = True - es_unset_nodes = [n for (n, es) in es_flags.items() - if not es] - - if es_unset_nodes: - self._Error(constants.CV_EGROUPMIXEDESFLAG, self.group_info.name, - "The exclusive_storage flag should be uniform in a group," - " but these nodes have it unset: %s", - utils.CommaJoin(utils.NiceSort(es_unset_nodes))) - self.LogWarning("Some checks required by exclusive storage will be" - " performed also on nodes with the flag unset") # At this point, we have the in-memory data structures complete, # except for the runtime information, which we'll gather next @@ -6135,6 +6124,8 @@ class LUNodeAdd(LogicalUnit): if self.op.ndparams: utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES) + _CheckParamsNotGlobal(self.op.ndparams, constants.NDC_GLOBALS, "node", + "node", "cluster or group") if self.op.hv_state: self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None) @@ -6160,9 +6151,6 @@ class LUNodeAdd(LogicalUnit): if vg_name is not None: vparams = {constants.NV_PVLIST: [vg_name]} excl_stor = _IsExclusiveStorageEnabledNode(cfg, self.new_node) - if self.op.ndparams: - excl_stor = self.op.ndparams.get(constants.ND_EXCLUSIVE_STORAGE, - excl_stor) cname = self.cfg.GetClusterName() result = rpcrunner.call_node_verify_light([node], vparams, cname)[node] (errmsgs, _) = _CheckNodePVs(result.payload, excl_stor) @@ -6560,6 +6548,8 @@ class LUNodeSetParams(LogicalUnit): if self.op.ndparams: new_ndparams = _GetUpdatedParams(self.node.ndparams, self.op.ndparams) utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES) + _CheckParamsNotGlobal(self.op.ndparams, constants.NDC_GLOBALS, "node", + "node", "cluster or group") self.new_ndparams = new_ndparams if self.op.hv_state: @@ -10598,7 +10588,8 @@ class LUInstanceCreate(LogicalUnit): hv_type.CheckParameterSyntax(filled_hvp) self.hv_full = filled_hvp # check that we don't specify global parameters on an instance - _CheckGlobalHvParams(self.op.hvparams) + _CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS, "hypervisor", + "instance", "cluster") # fill and remember the beparams dict self.be_full = _ComputeFullBeParams(self.op, cluster) @@ -13296,7 +13287,8 @@ class LUInstanceSetParams(LogicalUnit): raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL) if self.op.hvparams: - _CheckGlobalHvParams(self.op.hvparams) + _CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS, + "hypervisor", "instance", "cluster") self.op.disks = self._UpgradeDiskNicMods( "disk", self.op.disks, opcodes.OpInstanceSetParams.TestDiskModifications) @@ -13499,13 +13491,12 @@ class LUInstanceSetParams(LogicalUnit): elif self.op.conflicts_check: _CheckForConflictingIp(self, new_ip, pnode) - if old_ip: - if old_net: - try: - self.cfg.ReleaseIp(old_net, old_ip, self.proc.GetECId()) - except errors.AddressPoolError: - logging.warning("Release IP %s not contained in network %s", - old_ip, old_net) + if old_ip and old_net: + try: + self.cfg.ReleaseIp(old_net, old_ip, self.proc.GetECId()) + except errors.AddressPoolError, err: + logging.warning("Releasing IP address '%s' from network '%s'" + " failed: %s", old_ip, old_net, err) # there are no changes in (net, ip) tuple elif (old_net is not None and @@ -16236,7 +16227,6 @@ class LUNetworkAdd(LogicalUnit): "network6": self.op.network6, "gateway6": self.op.gateway6, "mac_prefix": self.op.mac_prefix, - "network_type": self.op.network_type, "tags": self.op.tags, } return _BuildNetworkHookEnv(**args) # pylint: disable=W0142 @@ -16251,14 +16241,13 @@ class LUNetworkAdd(LogicalUnit): network6=self.op.network6, gateway6=self.op.gateway6, mac_prefix=self.op.mac_prefix, - network_type=self.op.network_type, - uuid=self.network_uuid, - family=constants.IP4_VERSION) + uuid=self.network_uuid) # Initialize the associated address pool try: pool = network.AddressPool.InitializeNetwork(nobj) - except errors.AddressPoolError, e: - raise errors.OpExecError("Cannot create IP pool for this network: %s" % e) + except errors.AddressPoolError, err: + raise errors.OpExecError("Cannot create IP address pool for network" + " '%s': %s" % (self.op.network_name, err)) # Check if we need to reserve the nodes and the cluster master IP # These may not be allocated to any instances in routed mode, as @@ -16271,25 +16260,26 @@ class LUNetworkAdd(LogicalUnit): pool.Reserve(ip) self.LogInfo("Reserved IP address of node '%s' (%s)", node.name, ip) - except errors.AddressPoolError: - self.LogWarning("Cannot reserve IP address of node '%s' (%s)", - node.name, ip) + except errors.AddressPoolError, err: + self.LogWarning("Cannot reserve IP address '%s' of node '%s': %s", + ip, node.name, err) master_ip = self.cfg.GetClusterInfo().master_ip try: if pool.Contains(master_ip): pool.Reserve(master_ip) self.LogInfo("Reserved cluster master IP address (%s)", master_ip) - except errors.AddressPoolError: - self.LogWarning("Cannot reserve cluster master IP address (%s)", - master_ip) + except errors.AddressPoolError, err: + self.LogWarning("Cannot reserve cluster master IP address (%s): %s", + master_ip, err) if self.op.add_reserved_ips: for ip in self.op.add_reserved_ips: try: pool.Reserve(ip, external=True) - except errors.AddressPoolError, e: - raise errors.OpExecError("Cannot reserve IP %s. %s " % (ip, e)) + except errors.AddressPoolError, err: + raise errors.OpExecError("Cannot reserve IP address '%s': %s" % + (ip, err)) if self.op.tags: for tag in self.op.tags: @@ -16386,7 +16376,6 @@ class LUNetworkSetParams(LogicalUnit): """ self.network = self.cfg.GetNetwork(self.network_uuid) self.gateway = self.network.gateway - self.network_type = self.network.network_type self.mac_prefix = self.network.mac_prefix self.network6 = self.network.network6 self.gateway6 = self.network.gateway6 @@ -16404,12 +16393,6 @@ class LUNetworkSetParams(LogicalUnit): " reserved" % self.gateway, errors.ECODE_STATE) - if self.op.network_type: - if self.op.network_type == constants.VALUE_NONE: - self.network_type = None - else: - self.network_type = self.op.network_type - if self.op.mac_prefix: if self.op.mac_prefix == constants.VALUE_NONE: self.mac_prefix = None @@ -16440,7 +16423,6 @@ class LUNetworkSetParams(LogicalUnit): "network6": self.network6, "gateway6": self.gateway6, "mac_prefix": self.mac_prefix, - "network_type": self.network_type, "tags": self.tags, } return _BuildNetworkHookEnv(**args) # pylint: disable=W0142 @@ -16500,9 +16482,6 @@ class LUNetworkSetParams(LogicalUnit): if self.op.gateway6: self.network.gateway6 = self.gateway6 - if self.op.network_type: - self.network.network_type = self.network_type - self.pool.Validate() self.cfg.Update(self.network, feedback_fn) diff --git a/lib/config.py b/lib/config.py index 55bef8d5dae0ade2e6c688f480ea347e5a83c33b..4f8c36db0d01fa76ee81ed01ebf5b46b7bd2b0cd 100644 --- a/lib/config.py +++ b/lib/config.py @@ -34,6 +34,7 @@ much memory. # pylint: disable=R0904 # R0904: Too many public methods +import copy import os import random import logging @@ -741,6 +742,10 @@ class ConfigWriter: _helper("node %s" % node.name, "ndparams", cluster.FillND(node, data.nodegroups[node.group]), constants.NDS_PARAMETER_TYPES) + used_globals = constants.NDC_GLOBALS.intersection(node.ndparams) + if used_globals: + result.append("Node '%s' has some global parameters set: %s" % + (node.name, utils.CommaJoin(used_globals))) # nodegroups checks nodegroups_names = set() @@ -2029,24 +2034,22 @@ class ConfigWriter: (data.cluster.master_node, self._my_hostname)) raise errors.ConfigurationError(msg) - # Upgrade configuration if needed - data.UpgradeConfig() - self._config_data = data # reset the last serial as -1 so that the next write will cause # ssconf update self._last_cluster_serial = -1 - # And finally run our (custom) config upgrade sequence + # Upgrade configuration if needed self._UpgradeConfig() self._cfg_id = utils.GetFileID(path=self._cfg_file) def _UpgradeConfig(self): - """Run upgrade steps that cannot be done purely in the objects. + """Run any upgrade steps. - This is because some data elements need uniqueness across the - whole configuration, etc. + This method performs both in-object upgrades and also update some data + elements that need uniqueness across the whole configuration or interact + with other objects. @warning: this function will call L{_WriteConfig()}, but also L{DropECReservations} so it needs to be called only from a @@ -2055,26 +2058,31 @@ class ConfigWriter: created first, to avoid causing deadlock. """ - modified = False + # Keep a copy of the persistent part of _config_data to check for changes + # Serialization doesn't guarantee order in dictionaries + oldconf = copy.deepcopy(self._config_data.ToDict()) + + # In-object upgrades + self._config_data.UpgradeConfig() + for item in self._AllUUIDObjects(): if item.uuid is None: item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID) - modified = True if not self._config_data.nodegroups: default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME default_nodegroup = objects.NodeGroup(name=default_nodegroup_name, members=[]) self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True) - modified = True for node in self._config_data.nodes.values(): if not node.group: node.group = self.LookupNodeGroup(None) - modified = True # This is technically *not* an upgrade, but needs to be done both when # nodegroups are being added, and upon normally loading the config, # because the members list of a node group is discarded upon # serializing/deserializing the object. self._UnlockedAddNodeToGroup(node.name, node.group) + + modified = (oldconf != self._config_data.ToDict()) if modified: self._WriteConfig() # This is ok even if it acquires the internal lock, as _UpgradeConfig is diff --git a/lib/constants.py b/lib/constants.py index f7bd856343b7ea92cdb3beee2a02fae25b4f5c12..add7c0c166f57f957bfffaec0b68a81359d09ad3 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -1219,16 +1219,6 @@ NIC_VALID_MODES = compat.UniqueFrozenset([ RESERVE_ACTION = "reserve" RELEASE_ACTION = "release" -# An extra description of the network. -# Can be used by hooks/kvm-vif-bridge to apply different rules -NETWORK_TYPE_PRIVATE = "private" -NETWORK_TYPE_PUBLIC = "public" - -NETWORK_VALID_TYPES = compat.UniqueFrozenset([ - NETWORK_TYPE_PRIVATE, - NETWORK_TYPE_PUBLIC, - ]) - NICS_PARAMETER_TYPES = { NIC_MODE: VTYPE_STRING, NIC_LINK: VTYPE_STRING, @@ -1461,9 +1451,6 @@ CV_ECLUSTERDANGLINGNODES = \ CV_ECLUSTERDANGLINGINST = \ (CV_TNODE, "ECLUSTERDANGLINGINST", "Some instances have a non-existing primary node") -CV_EGROUPMIXEDESFLAG = \ - (CV_TGROUP, "EGROUPMIXEDESFLAG", - "exclusive_storage flag is not uniform within the group") CV_EGROUPDIFFERENTPVSIZE = \ (CV_TGROUP, "EGROUPDIFFERENTPVSIZE", "PVs in the group have different sizes") CV_EINSTANCEBADNODE = \ @@ -2029,6 +2016,10 @@ NDC_DEFAULTS = { ND_EXCLUSIVE_STORAGE: False, } +NDC_GLOBALS = compat.UniqueFrozenset([ + ND_EXCLUSIVE_STORAGE, + ]) + DISK_LD_DEFAULTS = { LD_DRBD8: { LDP_RESYNC_RATE: CLASSIC_DRBD_SYNC_SPEED, diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py index efe8ab620ac3fe794656a6ca5a41dfc45bf1d8fb..25e43b7399bd212c6bea4919ef173719656c4098 100644 --- a/lib/hypervisor/hv_kvm.py +++ b/lib/hypervisor/hv_kvm.py @@ -164,31 +164,6 @@ def _OpenTap(vnet_hdr=True): return (ifname, tapfd) -def _BuildNetworkEnv(name, network, gateway, network6, gateway6, - network_type, mac_prefix, tags, env): - """Build environment variables concerning a Network. - - """ - if name: - env["NETWORK_NAME"] = name - if network: - env["NETWORK_SUBNET"] = network - if gateway: - env["NETWORK_GATEWAY"] = gateway - if network6: - env["NETWORK_SUBNET6"] = network6 - if gateway6: - env["NETWORK_GATEWAY6"] = gateway6 - if mac_prefix: - env["NETWORK_MAC_PREFIX"] = mac_prefix - if network_type: - env["NETWORK_TYPE"] = network_type - if tags: - env["NETWORK_TAGS"] = " ".join(tags) - - return env - - class QmpMessage: """QEMU Messaging Protocol (QMP) message. @@ -872,9 +847,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): if nic.network: n = objects.Network.FromDict(nic.netinfo) - _BuildNetworkEnv(nic.network, n.network, n.gateway, - n.network6, n.gateway6, n.network_type, - n.mac_prefix, n.tags, env) + env.update(n.HooksDict()) if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: env["BRIDGE"] = nic.nicparams[constants.NIC_LINK] diff --git a/lib/network.py b/lib/network.py index eaa2ddc203fd6607b28ab3f734425b2099cf3325..170a8b778935a8e3c298be971af4654047dd5de9 100644 --- a/lib/network.py +++ b/lib/network.py @@ -132,14 +132,12 @@ class AddressPool(object): return (self.reservations | self.ext_reservations) def Validate(self): - assert self.net.family == 4 assert len(self.reservations) == self._GetSize() assert len(self.ext_reservations) == self._GetSize() all_res = self.reservations & self.ext_reservations assert not all_res.any() if self.gateway is not None: - assert self.net.family == self.gateway.version assert self.gateway in self.network if self.network6 and self.gateway6: diff --git a/lib/objects.py b/lib/objects.py index fc47c84cbf95705b1cf661e270580640bbc21cf3..6340d8d9559211b92f56d1f5091f826e4d8fc5d9 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -38,6 +38,7 @@ pass to and from external parties. import ConfigParser import re import copy +import logging import time from cStringIO import StringIO @@ -460,6 +461,8 @@ class ConfigData(ConfigObject): self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER if self.networks is None: self.networks = {} + for network in self.networks.values(): + network.UpgradeConfig() class NIC(ConfigObject): @@ -1293,6 +1296,12 @@ class Node(TaggableObject): if self.ndparams is None: self.ndparams = {} + # And remove any global parameter + for key in constants.NDC_GLOBALS: + if key in self.ndparams: + logging.warning("Ignoring %s node parameter for node %s", + key, self.name) + del self.ndparams[key] if self.powered is None: self.powered = True @@ -1999,18 +2008,54 @@ class Network(TaggableObject): __slots__ = [ "name", "serial_no", - "network_type", "mac_prefix", - "family", "network", "network6", "gateway", "gateway6", - "size", "reservations", "ext_reservations", ] + _TIMESTAMPS + _UUID + def HooksDict(self, prefix=""): + """Export a dictionary used by hooks with a network's information. + + @type prefix: String + @param prefix: Prefix to prepend to the dict entries + + """ + result = { + "%sNETWORK_NAME" % prefix: self.name, + "%sNETWORK_UUID" % prefix: self.uuid, + "%sNETWORK_TAGS" % prefix: " ".join(self.tags), + } + if self.network: + result["%sNETWORK_SUBNET" % prefix] = self.network + if self.gateway: + result["%sNETWORK_GATEWAY" % prefix] = self.gateway + if self.network6: + result["%sNETWORK_SUBNET6" % prefix] = self.network6 + if self.gateway6: + result["%sNETWORK_GATEWAY6" % prefix] = self.gateway6 + if self.mac_prefix: + result["%sNETWORK_MAC_PREFIX" % prefix] = self.mac_prefix + + return result + + @classmethod + def FromDict(cls, val): + """Custom function for networks. + + Remove deprecated network_type and family. + + """ + if "network_type" in val: + del val["network_type"] + if "family" in val: + del val["family"] + obj = super(Network, cls).FromDict(val) + return obj + class SerializableConfigParser(ConfigParser.SafeConfigParser): """Simple wrapper over ConfigParse that allows serialization. diff --git a/lib/opcodes.py b/lib/opcodes.py index 7d67282eebd8cb64f90215277a8d7bd570065efd..2ebf29c3d9bef3e62a0a683e55023f34a9c3372c 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -361,8 +361,6 @@ def _CheckStorageType(storage_type): _PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType, "Storage type") -_CheckNetworkType = ht.TElemOf(constants.NETWORK_VALID_TYPES) - @ht.WithDesc("IPv4 network") def _CheckCIDRNetNotation(value): @@ -2065,7 +2063,6 @@ class OpNetworkAdd(OpCode): OP_DSC_FIELD = "network_name" OP_PARAMS = [ _PNetworkName, - ("network_type", None, ht.TMaybe(_CheckNetworkType), "Network type"), ("network", ht.NoDefault, _TIpNetwork4, "IPv4 subnet"), ("gateway", None, ht.TMaybe(_TIpAddress4), "IPv4 gateway"), ("network6", None, ht.TMaybe(_TIpNetwork6), "IPv6 subnet"), @@ -2099,8 +2096,6 @@ class OpNetworkSetParams(OpCode): OP_DSC_FIELD = "network_name" OP_PARAMS = [ _PNetworkName, - ("network_type", None, ht.TMaybeValueNone(_CheckNetworkType), - "Network type"), ("gateway", None, ht.TMaybeValueNone(_TIpAddress4), "IPv4 gateway"), ("network6", None, ht.TMaybeValueNone(_TIpNetwork6), "IPv6 subnet"), ("gateway6", None, ht.TMaybeValueNone(_TIpAddress6), "IPv6 gateway"), diff --git a/lib/query.py b/lib/query.py index dc07159fb150e69a16697d143f09ebc500f86b78..ef52d0899cd4a5593faa961cc177109c6f8a7b0b 100644 --- a/lib/query.py +++ b/lib/query.py @@ -2531,7 +2531,6 @@ _NETWORK_SIMPLE_FIELDS = { "network6": ("IPv6Subnet", QFT_OTHER, 0, "IPv6 subnet"), "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "IPv6 gateway"), "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "MAC address prefix"), - "network_type": ("NetworkType", QFT_OTHER, 0, "Network type"), "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Network"), "uuid": ("UUID", QFT_TEXT, 0, "Network UUID"), } diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py index 15d1e3286097f320c66a42dfc3a9a2df325a46ee..ce42166ba6c7bc8af8508aad37708ff94ea4fd5d 100644 --- a/lib/rapi/baserlib.py +++ b/lib/rapi/baserlib.py @@ -465,13 +465,13 @@ class OpcodeResource(ResourceBase): @cvar POST_OPCODE: Set this to a class derived from L{opcodes.OpCode} to automatically generate a POST handler submitting the opcode - @cvar POST_RENAME: Set this to rename parameters in the DELETE handler (see + @cvar POST_RENAME: Set this to rename parameters in the POST handler (see L{baserlib.FillOpcode}) @ivar GetPostOpInput: Define this to override the default method for getting opcode parameters (see L{baserlib.OpcodeResource._GetDefaultData}) @cvar DELETE_OPCODE: Set this to a class derived from L{opcodes.OpCode} to - automatically generate a GET handler submitting the opcode + automatically generate a DELETE handler submitting the opcode @cvar DELETE_RENAME: Set this to rename parameters in the DELETE handler (see L{baserlib.FillOpcode}) @ivar GetDeleteOpInput: Define this to override the default method for diff --git a/lib/rapi/client.py b/lib/rapi/client.py index 452fa95c85e8e96be2cca1ff44db1f2d445f155f..cf7971132300264afe96f5216e0e392626211580 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -1768,7 +1768,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904 None, None) def CreateNetwork(self, network_name, network, gateway=None, network6=None, - gateway6=None, mac_prefix=None, network_type=None, + gateway6=None, mac_prefix=None, add_reserved_ips=None, tags=None, dry_run=False): """Creates a new network. @@ -1797,7 +1797,6 @@ class GanetiRapiClient(object): # pylint: disable=R0904 "gateway6": gateway6, "network6": network6, "mac_prefix": mac_prefix, - "network_type": network_type, "add_reserved_ips": add_reserved_ips, "tags": tags, } diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index d7489139d74a86552a021df128daeba794270e10..3a28a8472d4111b4f69534337d3074c250557d9a 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -94,7 +94,7 @@ N_FIELDS = ["name", "offline", "master_candidate", "drained", NET_FIELDS = ["name", "network", "gateway", "network6", "gateway6", - "mac_prefix", "network_type", + "mac_prefix", "free_count", "reserved_count", "map", "group_list", "inst_list", "external_reservations", "tags", diff --git a/man/ganeti-os-interface.rst b/man/ganeti-os-interface.rst index 2e44021ff8289b9b56bcdc080a584217c3aa7558..ec8ec860cce8a2c19a415604cb92674fb0a639e5 100644 --- a/man/ganeti-os-interface.rst +++ b/man/ganeti-os-interface.rst @@ -118,6 +118,36 @@ NIC_%N_FRONTEND_TYPE instance, this can be one of: ``rtl8139``, ``ne2k_pci``, ``ne2k_isa``, ``paravirtual``. +NIC_%d_NETWORK_NAME + (Optional) If a NIC network is specified, the network's name. + +NIC_%d_NETWORK_UUID + (Optional) If a NIC network is specified, the network's uuid. + +NIC_%d_NETWORK_FAMILY + (Optional) If a NIC network is specified, the network's family. + +NIC_%d_NETWORK_SUBNET + (Optional) If a NIC network is specified, the network's IPv4 subnet. + +NIC_%d_NETWORK_GATEWAY + (Optional) If a NIC network is specified, the network's IPv4 + gateway. + +NIC_%d_NETWORK_SUBNET6 + (Optional) If a NIC network is specified, the network's IPv6 subnet. + +NIC_%d_NETWORK_GATEWAY6 + (Optional) If a NIC network is specified, the network's IPv6 + gateway. + +NIC_%d_NETWORK_MAC_PREFIX + (Optional) If a NIC network is specified, the network's mac prefix. + +NIC_%d_NETWORK_TAGS + (Optional) If a NIC network is specified, the network's tags, space + separated. + OSP_*name* Each OS parameter (see below) will be exported in its own variable, prefixed with ``OSP_``, and upper-cased. For example, a diff --git a/man/ganeti.rst b/man/ganeti.rst index 03fe65272d05e0028d02db5487e7623f100497b3..16800d128253cb924441fda24b300d23b2a5e7dd 100644 --- a/man/ganeti.rst +++ b/man/ganeti.rst @@ -120,7 +120,9 @@ exclusive_storage When this Boolean flag is enabled, physical disks on the node are assigned to instance disks in an exclusive manner, so as to lower I/O interference between instances. See the `Partitioned Ganeti - <design-partitioned.rst>`_ design document for more details. + <design-partitioned.rst>`_ design document for more details. This + parameter cannot be set on individual nodes, as its value must be + the same within each node group. Hypervisor State Parameters diff --git a/man/gnt-network.rst b/man/gnt-network.rst index 3e4a551f26fe3b37c4032be43c6d61674fd095a5..69848cde32a279e9a94b2dba154e8719f4fd203d 100644 --- a/man/gnt-network.rst +++ b/man/gnt-network.rst @@ -32,7 +32,6 @@ ADD | [--network6=*NETWORK6*] | [--gateway6=*GATEWAY6*] | [--mac-prefix=*MACPREFIX*] -| [--network-type=*NETWORKTYPE*] | [--submit] | {*network*} @@ -46,8 +45,6 @@ notation. The ``--gateway`` option allows you to specify the default gateway for this network. -The ``--network-type`` can be none, private or public. - IPv6 semantics can be assigned to the network via the ``--network6`` and ``--gateway6`` options. IP pool is meaningless for IPV6 so those two values can be used for EUI64 generation from a NIC's MAC address. @@ -71,7 +68,6 @@ MODIFY | [--network6=*NETWORK6*] | [--gateway6=*GATEWAY6*] | [--mac-prefix=*MACPREFIX*] -| [--network-type=*NETWORKTYPE*] | [--submit] | {*network*} diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py index e4dc578f51873caec11f523df79b406297f7f6ad..a346eacb3e16398cdc1fbdaf195b57bc1bc27bc0 100755 --- a/qa/ganeti-qa.py +++ b/qa/ganeti-qa.py @@ -461,7 +461,7 @@ def RunExclusiveStorageTests(): node = qa_config.AcquireNode() try: old_es = qa_cluster.TestSetExclStorCluster(False) - qa_cluster.TestExclStorSingleNode(node) + qa_node.TestExclStorSingleNode(node) qa_cluster.TestSetExclStorCluster(True) qa_cluster.TestExclStorSharedPv(node) diff --git a/qa/qa-sample.json b/qa/qa-sample.json index 17dc800c227007345e3b7246280bab2e5b19e2db..10b93a89f47973c8a49bdf8b23aa5872e069776c 100644 --- a/qa/qa-sample.json +++ b/qa/qa-sample.json @@ -24,6 +24,9 @@ "# Cluster-level value of the exclusive-storage flag": null, "exclusive-storage": null, + "# Additional arguments for initializing cluster": null, + "cluster-init-args": [], + "# Network interface for master role": null, "#master-netdev": "xen-br0", diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py index 410196ba8d46357322d63615af43edf8ef0ab52d..60b7208164376c5659d66b222d34d110f038a348 100644 --- a/qa/qa_cluster.py +++ b/qa/qa_cluster.py @@ -209,7 +209,12 @@ def TestClusterInit(rapi_user, rapi_secret): e_s = False qa_config.SetExclusiveStorage(e_s) + extra_args = qa_config.get("cluster-init-args") + if extra_args: + cmd.extend(extra_args) + cmd.append(qa_config.get("name")) + AssertCommand(cmd) cmd = ["gnt-cluster", "modify"] diff --git a/qa/qa_node.py b/qa/qa_node.py index 6081a0231b56b912a51fc09a788e64b43cde8ea1..809f8795a39275554af0c0f11dc1af2af26c8409 100644 --- a/qa/qa_node.py +++ b/qa/qa_node.py @@ -425,3 +425,22 @@ def TestNodeListFields(): def TestNodeListDrbd(node): """gnt-node list-drbd""" AssertCommand(["gnt-node", "list-drbd", node.primary]) + + +def _BuildSetESCmd(action, value, node_name): + cmd = ["gnt-node"] + if action == "add": + cmd.extend(["add", "--readd"]) + else: + cmd.append("modify") + cmd.extend(["--node-parameters", "exclusive_storage=%s" % value, node_name]) + return cmd + + +def TestExclStorSingleNode(node): + """gnt-node add/modify cannot change the exclusive_storage flag. + + """ + for action in ["add", "modify"]: + for value in (True, False, "default"): + AssertCommand(_BuildSetESCmd(action, value, node.primary), fail=True) diff --git a/qa/qa_utils.py b/qa/qa_utils.py index 68656730bed21015a02ca37cd59a543e58efa6cf..9533c280b109ddb9b1a1e5c6f173a6ea217430b8 100644 --- a/qa/qa_utils.py +++ b/qa/qa_utils.py @@ -322,7 +322,7 @@ def GetCommandOutput(node, cmd, tty=None, fail=False): p = StartLocalCommand(GetSSHCommand(node, cmd, tty=tty), stdout=subprocess.PIPE) rcode = p.wait() - _AssertRetCode(rcode, fail, node, cmd) + _AssertRetCode(rcode, fail, cmd, node) return p.stdout.read() diff --git a/src/Ganeti/HTools/Backend/IAlloc.hs b/src/Ganeti/HTools/Backend/IAlloc.hs index 6c3fdf161d5c0dbd409b314dce50cd4ceffc1589..d1d1436119b8f4c15af79908f394bd6c09731ca7 100644 --- a/src/Ganeti/HTools/Backend/IAlloc.hs +++ b/src/Ganeti/HTools/Backend/IAlloc.hs @@ -4,7 +4,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 @@ -82,14 +82,14 @@ parseInstance :: NameAssoc -- ^ The node name-to-index association list parseInstance ktn n a = do base <- parseBaseInstance n a nodes <- fromObj a "nodes" - pnode <- if null nodes - then Bad $ "empty node list for instance " ++ n - else readEitherString $ head nodes + (pnode, snodes) <- + case nodes of + [] -> Bad $ "empty node list for instance " ++ n + x:xs -> readEitherString x >>= \x' -> return (x', xs) pidx <- lookupNode ktn n pnode - let snodes = tail nodes - sidx <- if null snodes - then return Node.noSecondary - else readEitherString (head snodes) >>= lookupNode ktn n + sidx <- case snodes of + [] -> return Node.noSecondary + x:_ -> readEitherString x >>= lookupNode ktn n return (n, Instance.setBoth (snd base) pidx sidx) -- | Parses a node as found in the cluster node list. diff --git a/src/Ganeti/HTools/Backend/Luxi.hs b/src/Ganeti/HTools/Backend/Luxi.hs index b3178089788a617512fd200fad78328b1a6a0ce4..febb0ab434832b4f37a00b60b532b4729383bf84 100644 --- a/src/Ganeti/HTools/Backend/Luxi.hs +++ b/src/Ganeti/HTools/Backend/Luxi.hs @@ -4,7 +4,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 @@ -163,10 +163,10 @@ parseInstance ktn [ name, disk, mem, vcpus _ -> convert "be/memory" mem xvcpus <- convert "be/vcpus" vcpus xpnode <- convert "pnode" pnode >>= lookupNode ktn xname - xsnodes <- convert "snodes" snodes::Result [JSString] - snode <- if null xsnodes - then return Node.noSecondary - else lookupNode ktn xname (fromJSString $ head xsnodes) + xsnodes <- convert "snodes" snodes::Result [String] + snode <- case xsnodes of + [] -> return Node.noSecondary + x:_ -> lookupNode ktn xname x xrunning <- convert "status" status xtags <- convert "tags" tags xauto_balance <- convert "auto_balance" auto_balance diff --git a/src/Ganeti/HTools/Backend/Rapi.hs b/src/Ganeti/HTools/Backend/Rapi.hs index 005cfdbf17554b73774c152dd3caead885c1ae6a..eaf061c4c81d026a5900c23536ead2f455cce624 100644 --- a/src/Ganeti/HTools/Backend/Rapi.hs +++ b/src/Ganeti/HTools/Backend/Rapi.hs @@ -4,7 +4,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 @@ -138,9 +138,9 @@ parseInstance ktn a = do vcpus <- extract "vcpus" beparams pnode <- extract "pnode" a >>= lookupNode ktn name snodes <- extract "snodes" a - snode <- if null snodes - then return Node.noSecondary - else readEitherString (head snodes) >>= lookupNode ktn name + snode <- case snodes of + [] -> return Node.noSecondary + x:_ -> readEitherString x >>= lookupNode ktn name running <- extract "status" a tags <- extract "tags" a auto_balance <- extract "auto_balance" beparams diff --git a/src/Ganeti/HTools/Cluster.hs b/src/Ganeti/HTools/Cluster.hs index 7655f366a1e0f1ba9e8938f5fb8378c22a18e60d..ddd5c17f76d83d93c70fb404fc53eba0f0d174f8 100644 --- a/src/Ganeti/HTools/Cluster.hs +++ b/src/Ganeti/HTools/Cluster.hs @@ -829,14 +829,13 @@ findBestAllocGroup mggl mgnl mgil allowed_gdxs inst cnt = all_msgs = concatMap (solutionDescription mggl) sols goodSols = filterMGResults mggl sols sortedSols = sortMGResults mggl goodSols - in if null sortedSols - then Bad $ if null groups' - then "no groups for evacuation: allowed groups was" ++ - show allowed_gdxs ++ ", all groups: " ++ - show (map fst groups) - else intercalate ", " all_msgs - else let (final_group, final_sol) = head sortedSols - in return (final_group, final_sol, all_msgs) + in case sortedSols of + [] -> Bad $ if null groups' + then "no groups for evacuation: allowed groups was" ++ + show allowed_gdxs ++ ", all groups: " ++ + show (map fst groups) + else intercalate ", " all_msgs + (final_group, final_sol):_ -> return (final_group, final_sol, all_msgs) -- | Try to allocate an instance on a multi-group cluster. tryMGAlloc :: Group.List -- ^ The group list diff --git a/src/Ganeti/HTools/Graph.hs b/src/Ganeti/HTools/Graph.hs index 1fa350094b0a07dd2691bb26abb9afd1ebd8c896..3bc42d8e5ae3cfc04a2b093dbc6bffecc5cb59f1 100644 --- a/src/Ganeti/HTools/Graph.hs +++ b/src/Ganeti/HTools/Graph.hs @@ -28,7 +28,7 @@ University Clausthal, 1-9. {- -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 @@ -147,6 +147,7 @@ verticesColorSet cMap = IntSet.fromList . verticesColors cMap neighColors :: Graph.Graph -> VertColorMap -> Graph.Vertex -> [Color] neighColors g cMap v = verticesColors cMap $ neighbors g v +{-# ANN colorNode "HLint: ignore Use alternative" #-} -- | Color one node. colorNode :: Graph.Graph -> VertColorMap -> Graph.Vertex -> Color -- use of "head" is A-ok as the source is an infinite list diff --git a/src/Ganeti/HTools/Node.hs b/src/Ganeti/HTools/Node.hs index eff4fb51515970301d3dfa9cce8e50d1fbb4449c..42180bf84d76c952f5cfa1158e452a3f8331230b 100644 --- a/src/Ganeti/HTools/Node.hs +++ b/src/Ganeti/HTools/Node.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 @@ -679,10 +679,13 @@ defaultFields = , "pfmem", "pfdsk", "rcpu" , "cload", "mload", "dload", "nload" ] +{-# ANN computeGroups "HLint: ignore Use alternative" #-} -- | Split a list of nodes into a list of (node group UUID, list of -- associated nodes). computeGroups :: [Node] -> [(T.Gdx, [Node])] computeGroups nodes = let nodes' = sortBy (comparing group) nodes nodes'' = groupBy ((==) `on` group) nodes' + -- use of head here is OK, since groupBy returns non-empty lists; if + -- you remove groupBy, also remove use of head in map (\nl -> (group (head nl), nl)) nodes'' diff --git a/src/Ganeti/HTools/Program/Hail.hs b/src/Ganeti/HTools/Program/Hail.hs index adcddf9892e2705abf104d7e933558d00c5310ab..50009a39ae46fa20c9e0637ec47c2752bd35358e 100644 --- a/src/Ganeti/HTools/Program/Hail.hs +++ b/src/Ganeti/HTools/Program/Hail.hs @@ -4,7 +4,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 @@ -59,9 +59,11 @@ arguments = [ArgCompletion OptComplFile 1 (Just 1)] wrapReadRequest :: Options -> [String] -> IO Request wrapReadRequest opts args = do - when (null args) $ exitErr "This program needs an input file." + r1 <- case args of + [] -> exitErr "This program needs an input file." + _:_:_ -> exitErr "Only one argument is accepted (the input file)" + x:_ -> readRequest x - r1 <- readRequest (head args) if isJust (optDataFile opts) || (not . null . optNodeSim) opts then do cdata <- loadExternalData opts diff --git a/src/Ganeti/HTools/Program/Hbal.hs b/src/Ganeti/HTools/Program/Hbal.hs index 956d31827eb687c3744ef3cfb68573439418fc24..a5207fb7b60d682a272b319d4b0d0a400fc6bc24 100644 --- a/src/Ganeti/HTools/Program/Hbal.hs +++ b/src/Ganeti/HTools/Program/Hbal.hs @@ -4,7 +4,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 @@ -135,8 +135,9 @@ iterateDepth printmove ini_tbl max_rounds disk_moves inst_moves nmlen imlen Just fin_tbl -> do let (Cluster.Table _ _ _ fin_plc) = fin_tbl - fin_plc_len = length fin_plc - cur_plc@(idx, _, _, move, _) = head fin_plc + cur_plc@(idx, _, _, move, _) <- + exitIfEmpty "Empty placement list returned for solution?!" fin_plc + let fin_plc_len = length fin_plc (sol_line, cmds) = Cluster.printSolutionLine ini_nl ini_il nmlen imlen cur_plc fin_plc_len afn = Cluster.involvedNodes ini_il cur_plc @@ -261,8 +262,8 @@ selectGroup opts gl nlf ilf = do case optGroup opts of Nothing -> do - let (gidx, cdata) = head ngroups - grp = Container.find gidx gl + (gidx, cdata) <- exitIfEmpty "No groups found by splitCluster?!" ngroups + let grp = Container.find gidx gl return (Group.name grp, cdata) Just g -> case Container.findByName gl g of Nothing -> do diff --git a/src/Ganeti/HTools/Program/Hspace.hs b/src/Ganeti/HTools/Program/Hspace.hs index d1b62c34de3fdd64d9983980e1d9e5c25a50f5ab..02c81bf4a27637f7442226208e8491836e3056f6 100644 --- a/src/Ganeti/HTools/Program/Hspace.hs +++ b/src/Ganeti/HTools/Program/Hspace.hs @@ -4,7 +4,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 @@ -105,6 +105,11 @@ specDescription :: SpecType -> String specDescription SpecNormal = "Standard (fixed-size)" specDescription SpecTiered = "Tiered (initial size)" +-- | The \"name\" of a 'SpecType'. +specName :: SpecType -> String +specName SpecNormal = "Standard" +specName SpecTiered = "Tiered" + -- | Efficiency generic function. effFn :: (Cluster.CStats -> Integer) -> (Cluster.CStats -> Double) @@ -191,12 +196,14 @@ printResults True _ fin_nl num_instances allocs sreason = do \ != counted (%d)\n" (num_instances + allocs) (Cluster.csNinst fin_stats) + main_reason <- exitIfEmpty "Internal error, no failure reasons?!" sreason + printKeysHTS $ printStats PFinal fin_stats printKeysHTS [ ("ALLOC_USAGE", printf "%.8f" ((fromIntegral num_instances::Double) / fromIntegral fin_instances)) , ("ALLOC_INSTANCES", printf "%d" allocs) - , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason) + , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ main_reason) ] printKeysHTS $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x), printf "%d" y)) sreason @@ -210,6 +217,7 @@ printResults False ini_nl fin_nl _ allocs sreason = do printFinalHTS :: Bool -> IO () printFinalHTS = printFinal htsPrefix +{-# ANN tieredSpecMap "HLint: ignore Use alternative" #-} -- | Compute the tiered spec counts from a list of allocated -- instances. tieredSpecMap :: [Instance.Instance] @@ -217,6 +225,7 @@ tieredSpecMap :: [Instance.Instance] tieredSpecMap trl_ixes = let fin_trl_ixes = reverse trl_ixes ix_byspec = groupBy ((==) `on` Instance.specOf) fin_trl_ixes + -- head is "safe" here, as groupBy returns list of non-empty lists spec_map = map (\ixs -> (Instance.specOf $ head ixs, length ixs)) ix_byspec in spec_map @@ -365,7 +374,7 @@ runAllocation cdata stop_allocation actual_result spec dt mode opts = do Just result_noalloc -> return result_noalloc Nothing -> exitIfBad "failure during allocation" actual_result - let name = head . words . specDescription $ mode + let name = specName mode descr = name ++ " allocation" ldescr = "after " ++ map toLower descr diff --git a/src/Ganeti/Objects.hs b/src/Ganeti/Objects.hs index 388e5cfe5379089daa60599feca32a60457ed36e..6e568e63c89afd757a360b9abc2d57dc37d179a1 100644 --- a/src/Ganeti/Objects.hs +++ b/src/Ganeti/Objects.hs @@ -172,12 +172,8 @@ roleDescription NRMaster = "master" -- haven't been exhaustively deduced from the python code yet. $(buildObject "Network" "network" $ [ simpleField "name" [t| NonEmptyString |] - , optionalField $ - simpleField "network_type" [t| NetworkType |] , optionalField $ simpleField "mac_prefix" [t| String |] - , optionalField $ - simpleField "family" [t| Int |] , simpleField "network" [t| NonEmptyString |] , optionalField $ simpleField "network6" [t| String |] @@ -185,8 +181,6 @@ $(buildObject "Network" "network" $ simpleField "gateway" [t| String |] , optionalField $ simpleField "gateway6" [t| String |] - , optionalField $ - simpleField "size" [t| J.JSValue |] , optionalField $ simpleField "reservations" [t| String |] , optionalField $ diff --git a/src/Ganeti/OpCodes.hs b/src/Ganeti/OpCodes.hs index cd5466379c533d21a5b7d104ad09c42ac6b8b8b0..fde6b640c2628eb1e0a4a5a832e4e732c18496fb 100644 --- a/src/Ganeti/OpCodes.hs +++ b/src/Ganeti/OpCodes.hs @@ -501,7 +501,6 @@ $(genOpCode "OpCode" ]) , ("OpNetworkAdd", [ pNetworkName - , pNetworkType , pNetworkAddress4 , pNetworkGateway4 , pNetworkAddress6 @@ -517,7 +516,6 @@ $(genOpCode "OpCode" ]) , ("OpNetworkSetParams", [ pNetworkName - , pNetworkType , pNetworkGateway4 , pNetworkAddress6 , pNetworkGateway6 diff --git a/src/Ganeti/OpParams.hs b/src/Ganeti/OpParams.hs index b247f00501f193374976a7012c64f97953c79020..8e91b8f7dae53994d091b906a6a1530922c01f65 100644 --- a/src/Ganeti/OpParams.hs +++ b/src/Ganeti/OpParams.hs @@ -222,7 +222,6 @@ module Ganeti.OpParams , pTestDummyFail , pTestDummySubmitJobs , pNetworkName - , pNetworkType , pNetworkAddress4 , pNetworkGateway4 , pNetworkAddress6 @@ -1358,10 +1357,6 @@ pTestDummySubmitJobs = pNetworkName :: Field pNetworkName = simpleField "network_name" [t| NonEmptyString |] --- | Network type field. -pNetworkType :: Field -pNetworkType = optionalField $ simpleField "network_type" [t| NetworkType |] - -- | Network address (IPv4 subnet). FIXME: no real type for this. pNetworkAddress4 :: Field pNetworkAddress4 = diff --git a/src/Ganeti/Query/Network.hs b/src/Ganeti/Query/Network.hs index e0b5a13ed298453d025300ee400b8cf5b10e3cfd..2d262bfa0a422c3f4a2783cd888c3b9091cf2ec4 100644 --- a/src/Ganeti/Query/Network.hs +++ b/src/Ganeti/Query/Network.hs @@ -56,8 +56,6 @@ networkFields = FieldSimple (rsMaybeUnavail . networkGateway6), QffNormal) , (FieldDefinition "mac_prefix" "MacPrefix" QFTOther "MAC address prefix", FieldSimple (rsMaybeUnavail . networkMacPrefix), QffNormal) - , (FieldDefinition "network_type" "NetworkType" QFTOther "Network type", - FieldSimple (rsMaybeUnavail . networkNetworkType), QffNormal) , (FieldDefinition "free_count" "FreeCount" QFTOther "Number of free IPs", FieldSimple (rsMaybeNoData . fmap getFreeCount . createAddressPool), QffNormal) diff --git a/src/Ganeti/Query/Server.hs b/src/Ganeti/Query/Server.hs index db0af61fcd05b8973add545e2c9f11218e1ead92..07fbce0fd869d96cfb492ba3f8f63c7db381a2aa 100644 --- a/src/Ganeti/Query/Server.hs +++ b/src/Ganeti/Query/Server.hs @@ -6,7 +6,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 @@ -87,6 +87,9 @@ handleCall :: ConfigData -> LuxiOp -> IO (ErrorResult JSValue) handleCall cdata QueryClusterInfo = let cluster = configCluster cdata hypervisors = clusterEnabledHypervisors cluster + def_hv = case hypervisors of + x:_ -> showJSON x + [] -> JSNull bits = show (bitSize (0::Int)) ++ "bits" arch_tuple = [bits, arch] obj = [ ("software_version", showJSON C.releaseVersion) @@ -97,7 +100,7 @@ handleCall cdata QueryClusterInfo = , ("architecture", showJSON arch_tuple) , ("name", showJSON $ clusterClusterName cluster) , ("master", showJSON $ clusterMasterNode cluster) - , ("default_hypervisor", showJSON $ head hypervisors) + , ("default_hypervisor", def_hv) , ("enabled_hypervisors", showJSON hypervisors) , ("hvparams", showJSON $ clusterHvparams cluster) , ("os_hvp", showJSON $ clusterOsHvp cluster) diff --git a/src/Ganeti/Types.hs b/src/Ganeti/Types.hs index 2e3779a67594f6a88f36ecb529864c0e10ac87b3..c57ee2d760dfefc72c28fff6792faa16277ec872 100644 --- a/src/Ganeti/Types.hs +++ b/src/Ganeti/Types.hs @@ -70,8 +70,6 @@ module Ganeti.Types , IAllocatorTestDir(..) , IAllocatorMode(..) , iAllocatorModeToRaw - , NetworkType(..) - , networkTypeToRaw , NICMode(..) , nICModeToRaw , JobStatus(..) @@ -359,13 +357,6 @@ $(THH.declareSADT "IAllocatorMode" ]) $(THH.makeJSONInstance ''IAllocatorMode) --- | Network type. -$(THH.declareSADT "NetworkType" - [ ("PrivateNetwork", 'C.networkTypePrivate) - , ("PublicNetwork", 'C.networkTypePublic) - ]) -$(THH.makeJSONInstance ''NetworkType) - -- | Network mode. $(THH.declareSADT "NICMode" [ ("NMBridged", 'C.nicModeBridged) diff --git a/src/Ganeti/Utils.hs b/src/Ganeti/Utils.hs index 74377aa43450091dc63bacb534021715235b28b0..0ff1984aabfd7a665ad5390a86901757f0418698 100644 --- a/src/Ganeti/Utils.hs +++ b/src/Ganeti/Utils.hs @@ -51,6 +51,8 @@ module Ganeti.Utils , chompPrefix , wrap , trim + , defaultHead + , exitIfEmpty ) where import Data.Char (toUpper, isAlphaNum, isDigit, isSpace) @@ -369,3 +371,14 @@ wrap maxWidth = filter (not . null) . map trim . wrap0 -- strings. trim :: String -> String trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace + +-- | A safer head version, with a default value. +defaultHead :: a -> [a] -> a +defaultHead def [] = def +defaultHead _ (x:_) = x + +-- | A 'head' version in the I/O monad, for validating parameters +-- without which we cannot continue. +exitIfEmpty :: String -> [a] -> IO a +exitIfEmpty _ (x:_) = return x +exitIfEmpty s [] = exitErr s diff --git a/src/lint-hints.hs b/src/lint-hints.hs index a85a477d93b67c5341b6340da1014fc962f70d43..063f0dfbaf704f3a2a95a8f1d954d5fc38c3e682 100644 --- a/src/lint-hints.hs +++ b/src/lint-hints.hs @@ -20,3 +20,9 @@ warn = map (\v -> (x, v)) ==> zip (repeat x) warn = length x > 0 ==> not (null x) warn = length x /= 0 ==> not (null x) warn = length x == 0 ==> null x + +-- Never use head, use 'case' which covers all possibilities +warn = head x ==> case x of { y:_ -> y } where note = "Head is unsafe, please use case and handle the empty list as well" + +-- Never use tail, use 'case' which covers all possibilities +warn = tail x ==> case x of { _:y -> y } where note = "Tail is unsafe, please use case and handle the empty list as well" diff --git a/test/hs/Test/Ganeti/Common.hs b/test/hs/Test/Ganeti/Common.hs index 4b163449dc14463a7fe8aaeccae235d792f2309d..b9a505f0caed88ec577f5d24f801e66338175a60 100644 --- a/test/hs/Test/Ganeti/Common.hs +++ b/test/hs/Test/Ganeti/Common.hs @@ -83,7 +83,10 @@ passFailOpt :: (StandardOptions b) => -> c passFailOpt defaults failfn passfn (opt@(GetOpt.Option _ longs _ _, _), bad, good) = - let prefix = "--" ++ head longs ++ "=" + let first_opt = case longs of + [] -> error "no long options?" + x:_ -> x + prefix = "--" ++ first_opt ++ "=" good_cmd = prefix ++ good bad_cmd = prefix ++ bad in case (parseOptsInner defaults [bad_cmd] "prog" [opt] [], diff --git a/test/hs/Test/Ganeti/HTools/Container.hs b/test/hs/Test/Ganeti/HTools/Container.hs index 5ea85f6bacc12fbbcd0f9a42f4343e54421067f5..ccd94f6a49415dd66a56dea8a655ce8b20f26e4a 100644 --- a/test/hs/Test/Ganeti/HTools/Container.hs +++ b/test/hs/Test/Ganeti/HTools/Container.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 @@ -52,8 +52,10 @@ prop_addTwo cdata i1 i2 = prop_nameOf :: Node.Node -> Property prop_nameOf node = let nl = makeSmallCluster node 1 - fnode = head (Container.elems nl) - in Container.nameOf nl (Node.idx fnode) ==? Node.name fnode + in case Container.elems nl of + [] -> failTest "makeSmallCluster 1 returned empty cluster?" + _:_:_ -> failTest "makeSmallCluster 1 returned >1 node?" + fnode:_ -> Container.nameOf nl (Node.idx fnode) ==? Node.name fnode -- | We test that in a cluster, given a random node, we can find it by -- its name and alias, as long as all names and aliases are unique, diff --git a/test/hs/Test/Ganeti/JQueue.hs b/test/hs/Test/Ganeti/JQueue.hs index 29f525a356275e6a0fc1b9b704dddd6698556010..d2d946fec743c2c3e94bafdeb9607b95b1455ff6 100644 --- a/test/hs/Test/Ganeti/JQueue.hs +++ b/test/hs/Test/Ganeti/JQueue.hs @@ -6,7 +6,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 @@ -135,14 +135,12 @@ prop_JobStatus = case_JobStatusPri_py_equiv :: Assertion case_JobStatusPri_py_equiv = do let num_jobs = 2000::Int - sample_jobs <- sample' (vectorOf num_jobs $ do - num_ops <- choose (1, 5) - ops <- vectorOf num_ops genQueuedOpCode - jid <- genJobId - return $ QueuedJob jid ops justNoTs justNoTs - justNoTs) - let jobs = head sample_jobs - serialized = encode jobs + jobs <- genSample (vectorOf num_jobs $ do + num_ops <- choose (1, 5) + ops <- vectorOf num_ops genQueuedOpCode + jid <- genJobId + return $ QueuedJob jid ops justNoTs justNoTs justNoTs) + let serialized = encode jobs -- check for non-ASCII fields, usually due to 'arbitrary :: String' mapM_ (\job -> when (any (not . isAscii) (encode job)) . assertFailure $ "Job has non-ASCII fields: " ++ show job diff --git a/test/hs/Test/Ganeti/Network.hs b/test/hs/Test/Ganeti/Network.hs index 4867830c95d90433483e542ccabcee2d08444a15..57793ca3f972435493336e3dc0fe0ce7a70662a7 100644 --- a/test/hs/Test/Ganeti/Network.hs +++ b/test/hs/Test/Ganeti/Network.hs @@ -4,7 +4,6 @@ module Test.Ganeti.Network ( testNetwork , genBitStringMaxLen - , genNetworkType ) where import Test.QuickCheck @@ -14,7 +13,6 @@ import Ganeti.Objects as Objects import Test.Ganeti.Objects ( genBitStringMaxLen - , genNetworkType , genValidNetwork ) import Test.Ganeti.TestHelper import Test.Ganeti.TestCommon diff --git a/test/hs/Test/Ganeti/Objects.hs b/test/hs/Test/Ganeti/Objects.hs index 818227993157321071e5f21d8d5517b1c95b0630..dc619eccfe2e5e3ac934a88caed269db5a39369c 100644 --- a/test/hs/Test/Ganeti/Objects.hs +++ b/test/hs/Test/Ganeti/Objects.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 @@ -31,7 +31,6 @@ module Test.Ganeti.Objects , Node(..) , genEmptyCluster , genValidNetwork - , genNetworkType , genBitStringMaxLen ) where @@ -45,7 +44,6 @@ import qualified Data.Map as Map import qualified Data.Set as Set import qualified Text.JSON as J -import Test.Ganeti.Query.Language (genJSValue) import Test.Ganeti.TestHelper import Test.Ganeti.TestCommon import Test.Ganeti.Types () @@ -169,25 +167,18 @@ genValidNetwork = do -- generate netmask for the IPv4 network netmask <- choose (24::Int, 30) name <- genName >>= mkNonEmpty - network_type <- genMaybe genNetworkType mac_prefix <- genMaybe genName - net_family <- arbitrary net <- genIp4NetWithNetmask netmask net6 <- genMaybe genIp6Net gateway <- genMaybe genIp4AddrStr gateway6 <- genMaybe genIp6Addr - size <- genMaybe genJSValue res <- liftM Just (genBitString $ netmask2NumHosts netmask) ext_res <- liftM Just (genBitString $ netmask2NumHosts netmask) uuid <- arbitrary - let n = Network name network_type mac_prefix net_family net net6 gateway - gateway6 size res ext_res uuid 0 Set.empty + let n = Network name mac_prefix net net6 gateway + gateway6 res ext_res uuid 0 Set.empty return n --- | Generates an arbitrary network type. -genNetworkType :: Gen NetworkType -genNetworkType = elements [ PrivateNetwork, PublicNetwork ] - -- | Generate an arbitrary string consisting of '0' and '1' of the given length. genBitString :: Int -> Gen String genBitString len = vectorOf len (elements "01") @@ -275,9 +266,8 @@ prop_Config_serialisation = case_py_compat_networks :: HUnit.Assertion case_py_compat_networks = do let num_networks = 500::Int - sample_networks <- sample' (vectorOf num_networks genValidNetwork) - let networks = head sample_networks - networks_with_properties = map getNetworkProperties networks + networks <- genSample (vectorOf num_networks genValidNetwork) + let networks_with_properties = map getNetworkProperties networks serialized = J.encode networks -- check for non-ASCII fields, usually due to 'arbitrary :: String' mapM_ (\net -> when (any (not . isAscii) (J.encode net)) . @@ -325,9 +315,8 @@ getNetworkProperties net = case_py_compat_nodegroups :: HUnit.Assertion case_py_compat_nodegroups = do let num_groups = 500::Int - sample_groups <- sample' (vectorOf num_groups genNodeGroup) - let groups = head sample_groups - serialized = J.encode groups + groups <- genSample (vectorOf num_groups genNodeGroup) + let serialized = J.encode groups -- check for non-ASCII fields, usually due to 'arbitrary :: String' mapM_ (\group -> when (any (not . isAscii) (J.encode group)) . HUnit.assertFailure $ diff --git a/test/hs/Test/Ganeti/OpCodes.hs b/test/hs/Test/Ganeti/OpCodes.hs index cc45f663d0758b04cfcefa55b0424bdc6fd6ba2a..5015fd2e95e4c20f3b0f61e3888d66930680578e 100644 --- a/test/hs/Test/Ganeti/OpCodes.hs +++ b/test/hs/Test/Ganeti/OpCodes.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 @@ -318,14 +318,14 @@ instance Arbitrary OpCodes.OpCode where OpCodes.OpTestDummy <$> pure J.JSNull <*> pure J.JSNull <*> pure J.JSNull <*> pure J.JSNull "OP_NETWORK_ADD" -> - OpCodes.OpNetworkAdd <$> genNameNE <*> arbitrary <*> genIp4Net <*> + OpCodes.OpNetworkAdd <$> genNameNE <*> genIp4Net <*> genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*> genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*> arbitrary <*> (genTags >>= mapM mkNonEmpty) "OP_NETWORK_REMOVE" -> OpCodes.OpNetworkRemove <$> genNameNE <*> arbitrary "OP_NETWORK_SET_PARAMS" -> - OpCodes.OpNetworkSetParams <$> genNameNE <*> arbitrary <*> + OpCodes.OpNetworkSetParams <$> genNameNE <*> genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*> genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*> genMaybe (listOf genIp4Addr) @@ -440,10 +440,9 @@ case_AllDefined = do case_py_compat_types :: HUnit.Assertion case_py_compat_types = do let num_opcodes = length OpCodes.allOpIDs * 100 - sample_opcodes <- sample' (vectorOf num_opcodes - (arbitrary::Gen OpCodes.MetaOpCode)) - let opcodes = head sample_opcodes - with_sum = map (\o -> (OpCodes.opSummary $ + opcodes <- genSample (vectorOf num_opcodes + (arbitrary::Gen OpCodes.MetaOpCode)) + let with_sum = map (\o -> (OpCodes.opSummary $ OpCodes.metaOpCode o, o)) opcodes serialized = J.encode opcodes -- check for non-ASCII fields, usually due to 'arbitrary :: String' diff --git a/test/hs/Test/Ganeti/TestCommon.hs b/test/hs/Test/Ganeti/TestCommon.hs index 6dcc8ada7e857d9a11a2f149ed791872259bf780..91351b68b7396f7a56fd21b1abcbe76cf723699d 100644 --- a/test/hs/Test/Ganeti/TestCommon.hs +++ b/test/hs/Test/Ganeti/TestCommon.hs @@ -4,7 +4,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 @@ -294,3 +294,12 @@ readTestData :: String -> IO String readTestData filename = do name <- testDataFilename "/test/data/" filename readFile name + +-- | Generate arbitrary values in the IO monad. This is a simple +-- wrapper over 'sample''. +genSample :: Gen a -> IO a +genSample gen = do + values <- sample' gen + case values of + [] -> error "sample' returned an empty list of values??" + x:_ -> return x diff --git a/test/hs/Test/Ganeti/Types.hs b/test/hs/Test/Ganeti/Types.hs index 7526e01efcb9a85fcfd8c78d12da75b761a3384d..6f84782cac602a30078963032f5392db3a2ec83c 100644 --- a/test/hs/Test/Ganeti/Types.hs +++ b/test/hs/Test/Ganeti/Types.hs @@ -112,8 +112,6 @@ $(genArbitrary ''IAllocatorTestDir) $(genArbitrary ''IAllocatorMode) -$(genArbitrary ''NetworkType) - $(genArbitrary ''NICMode) $(genArbitrary ''JobStatus) @@ -287,17 +285,6 @@ case_IAllocatorMode_pyequiv = do all_hs_codes = sort $ map Types.iAllocatorModeToRaw [minBound..maxBound] assertEqual "for IAllocatorMode equivalence" all_py_codes all_hs_codes --- | Test 'NetworkType' serialisation. -prop_NetworkType_serialisation :: NetworkType -> Property -prop_NetworkType_serialisation = testSerialisation - --- | Tests equivalence with Python, based on Constants.hs code. -case_NetworkType_pyequiv :: Assertion -case_NetworkType_pyequiv = do - let all_py_codes = sort C.networkValidTypes - all_hs_codes = sort $ map Types.networkTypeToRaw [minBound..maxBound] - assertEqual "for NetworkType equivalence" all_py_codes all_hs_codes - -- | Test 'NICMode' serialisation. prop_NICMode_serialisation :: NICMode -> Property prop_NICMode_serialisation = testSerialisation @@ -417,8 +404,6 @@ testSuite "Types" , 'prop_IAllocatorTestDir_serialisation , 'prop_IAllocatorMode_serialisation , 'case_IAllocatorMode_pyequiv - , 'prop_NetworkType_serialisation - , 'case_NetworkType_pyequiv , 'prop_NICMode_serialisation , 'prop_OpStatus_serialization , 'prop_JobStatus_serialization diff --git a/test/hs/Test/Ganeti/Utils.hs b/test/hs/Test/Ganeti/Utils.hs index 548e4cfa318aaabc27a15e56019d593e576a5dda..28610ae2ba8d9a1b117f55d170677e7c924ff60d 100644 --- a/test/hs/Test/Ganeti/Utils.hs +++ b/test/hs/Test/Ganeti/Utils.hs @@ -88,24 +88,30 @@ prop_select :: Int -- ^ Default result -> Gen Prop -- ^ Test result prop_select def lst1 lst2 = select def (flist ++ tlist) ==? expectedresult - where expectedresult = if' (null lst2) def (head lst2) + where expectedresult = defaultHead def lst2 flist = zip (repeat False) lst1 tlist = zip (repeat True) lst2 +{-# ANN prop_select_undefd "HLint: ignore Use alternative" #-} -- | Test basic select functionality with undefined default prop_select_undefd :: [Int] -- ^ List of False values -> NonEmptyList Int -- ^ List of True values -> Gen Prop -- ^ Test result prop_select_undefd lst1 (NonEmpty lst2) = + -- head is fine as NonEmpty "guarantees" a non-empty list, but not + -- via types select undefined (flist ++ tlist) ==? head lst2 where flist = zip (repeat False) lst1 tlist = zip (repeat True) lst2 +{-# ANN prop_select_undefv "HLint: ignore Use alternative" #-} -- | Test basic select functionality with undefined list values prop_select_undefv :: [Int] -- ^ List of False values -> NonEmptyList Int -- ^ List of True values -> Gen Prop -- ^ Test result prop_select_undefv lst1 (NonEmpty lst2) = + -- head is fine as NonEmpty "guarantees" a non-empty list, but not + -- via types select undefined cndlist ==? head lst2 where flist = zip (repeat False) lst1 tlist = zip (repeat True) lst2 diff --git a/test/py/ganeti.config_unittest.py b/test/py/ganeti.config_unittest.py index b06f774c404ba76a15d9e3eda554317542c3eba4..da82fc0bd93ab5db5e792751ebd4a27a9f5645a0 100755 --- a/test/py/ganeti.config_unittest.py +++ b/test/py/ganeti.config_unittest.py @@ -74,6 +74,8 @@ class TestConfigRunner(unittest.TestCase): """Initializes the cfg object""" me = netutils.Hostname() ip = constants.IP4_ADDRESS_LOCALHOST + # master_ip must not conflict with the node ip address + master_ip = "127.0.0.2" cluster_config = objects.Cluster( serial_no=1, @@ -87,7 +89,7 @@ class TestConfigRunner(unittest.TestCase): tcpudp_port_pool=set(), enabled_hypervisors=[constants.HT_FAKE], master_node=me.name, - master_ip="127.0.0.1", + master_ip=master_ip, master_netdev=constants.DEFAULT_BRIDGE, cluster_name="cluster.local", file_storage_dir="/tmp", @@ -174,6 +176,44 @@ class TestConfigRunner(unittest.TestCase): self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_instance, None) + def testUpgradeSave(self): + """Test that any modification done during upgrading is saved back""" + cfg = self._get_object() + + # Remove an element, run upgrade, and check if the element is + # back and the file upgraded + node = cfg.GetNodeInfo(cfg.GetNodeList()[0]) + # For a ConfigObject, None is the same as a missing field + node.ndparams = None + oldsaved = utils.ReadFile(self.cfg_file) + cfg._UpgradeConfig() + self.assertTrue(node.ndparams is not None) + newsaved = utils.ReadFile(self.cfg_file) + # We rely on the fact that at least the serial number changes + self.assertNotEqual(oldsaved, newsaved) + + # Add something that should not be there this time + key = list(constants.NDC_GLOBALS)[0] + node.ndparams[key] = constants.NDC_DEFAULTS[key] + cfg._WriteConfig(None) + oldsaved = utils.ReadFile(self.cfg_file) + cfg._UpgradeConfig() + self.assertTrue(node.ndparams.get(key) is None) + newsaved = utils.ReadFile(self.cfg_file) + self.assertNotEqual(oldsaved, newsaved) + + # Do the upgrade again, this time there should be no update + oldsaved = newsaved + cfg._UpgradeConfig() + newsaved = utils.ReadFile(self.cfg_file) + self.assertEqual(oldsaved, newsaved) + + # Reload the configuration again: it shouldn't change the file + oldsaved = newsaved + self._get_object() + newsaved = utils.ReadFile(self.cfg_file) + self.assertEqual(oldsaved, newsaved) + def testNICParameterSyntaxCheck(self): """Test the NIC's CheckParameterSyntax function""" mode = constants.NIC_MODE @@ -388,6 +428,27 @@ class TestConfigRunner(unittest.TestCase): finally: node2.group = orig_group + def testVerifyConfig(self): + cfg = self._get_object() + + errs = cfg.VerifyConfig() + self.assertFalse(errs) + + node = cfg.GetNodeInfo(cfg.GetNodeList()[0]) + key = list(constants.NDC_GLOBALS)[0] + node.ndparams[key] = constants.NDC_DEFAULTS[key] + errs = cfg.VerifyConfig() + self.assertTrue(len(errs) >= 1) + self.assertTrue(_IsErrorInList("has some global parameters set", errs)) + + del node.ndparams[key] + errs = cfg.VerifyConfig() + self.assertFalse(errs) + + +def _IsErrorInList(err_str, err_list): + return any(map(lambda e: err_str in e, err_list)) + class TestTRM(unittest.TestCase): EC_ID = 1 diff --git a/test/py/ganeti.objects_unittest.py b/test/py/ganeti.objects_unittest.py index 400c32b2fd9260ac779d9ceceaa8f1a5a5e00300..fc9a6027cc694bcd4e4b4d5a7e3ab7ed2647268e 100755 --- a/test/py/ganeti.objects_unittest.py +++ b/test/py/ganeti.objects_unittest.py @@ -382,6 +382,23 @@ class TestNode(unittest.TestCase): self.assertEqual(node2.disk_state[constants.LD_LV]["lv2082"].total, 512) self.assertEqual(node2.disk_state[constants.LD_LV]["lv32352"].total, 128) + def testFilterEsNdp(self): + node1 = objects.Node(name="node11673.example.com", ndparams={ + constants.ND_EXCLUSIVE_STORAGE: True, + }) + node2 = objects.Node(name="node11674.example.com", ndparams={ + constants.ND_SPINDLE_COUNT: 3, + constants.ND_EXCLUSIVE_STORAGE: False, + }) + self.assertTrue(constants.ND_EXCLUSIVE_STORAGE in node1.ndparams) + node1.UpgradeConfig() + self.assertFalse(constants.ND_EXCLUSIVE_STORAGE in node1.ndparams) + self.assertTrue(constants.ND_EXCLUSIVE_STORAGE in node2.ndparams) + self.assertTrue(constants.ND_SPINDLE_COUNT in node2.ndparams) + node2.UpgradeConfig() + self.assertFalse(constants.ND_EXCLUSIVE_STORAGE in node2.ndparams) + self.assertTrue(constants.ND_SPINDLE_COUNT in node2.ndparams) + if __name__ == "__main__": testutils.GanetiTestProgram() diff --git a/tools/cfgupgrade b/tools/cfgupgrade index 8d97f5c436aca5bafc5cbc7278d5af6a0988b5a1..bccb34e4a80fefdab809f44a9cd174fd149413f2 100755 --- a/tools/cfgupgrade +++ b/tools/cfgupgrade @@ -52,7 +52,7 @@ args = None #: Target major version we will upgrade to TARGET_MAJOR = 2 #: Target minor version we will upgrade to -TARGET_MINOR = 6 +TARGET_MINOR = 7 class Error(Exception): @@ -204,8 +204,8 @@ def main(): raise Error("Inconsistent configuration: found config_version in" " configuration file") - # Upgrade from 2.0/2.1/2.2/2.3 to 2.4 - if config_major == 2 and config_minor in (0, 1, 2, 3, 4, 5): + # Upgrade from 2.{0..6} to 2.7 + if config_major == 2 and config_minor in (0, 1, 2, 3, 4, 5, 6): if config_revision != 0: logging.warning("Config revision is %s, not 0", config_revision)