diff --git a/lib/backend.py b/lib/backend.py index 5b77dae6534a1d34ead20d3411b30b07afc90a65..260e02005cd7a7fd0eb5bad1ddc8967c99734981 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -231,7 +231,8 @@ def GetMasterInfo(): for consumption here or from the node daemon. @rtype: tuple - @return: master_netdev, master_ip, master_name, primary_ip_family + @return: master_netdev, master_ip, master_netmask, master_name, + primary_ip_family @raise RPCFail: in case of errors """ @@ -239,11 +240,13 @@ def GetMasterInfo(): cfg = _GetConfig() master_netdev = cfg.GetMasterNetdev() master_ip = cfg.GetMasterIP() + master_netmask = cfg.GetMasterNetmask() master_node = cfg.GetMasterNode() primary_ip_family = cfg.GetPrimaryIPFamily() except errors.ConfigurationError, err: _Fail("Cluster configuration incomplete: %s", err, exc=True) - return (master_netdev, master_ip, master_node, primary_ip_family) + return (master_netdev, master_ip, master_netmask, master_node, + primary_ip_family) def ActivateMasterIp(): @@ -251,7 +254,7 @@ def ActivateMasterIp(): """ # GetMasterInfo will raise an exception if not able to return data - master_netdev, master_ip, _, family = GetMasterInfo() + master_netdev, master_ip, master_netmask, _, family = GetMasterInfo() err_msg = None if netutils.TcpPing(master_ip, constants.DEFAULT_NODED_PORT): @@ -267,7 +270,7 @@ def ActivateMasterIp(): ipcls = netutils.IP6Address result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "add", - "%s/%d" % (master_ip, ipcls.iplen), + "%s/%s" % (master_ip, master_netmask), "dev", master_netdev, "label", "%s:0" % master_netdev]) if result.failed: @@ -325,14 +328,10 @@ def DeactivateMasterIp(): # need to decide in which case we fail the RPC for this # GetMasterInfo will raise an exception if not able to return data - master_netdev, master_ip, _, family = GetMasterInfo() - - ipcls = netutils.IP4Address - if family == netutils.IP6Address.family: - ipcls = netutils.IP6Address + master_netdev, master_ip, master_netmask, _, _ = GetMasterInfo() result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "del", - "%s/%d" % (master_ip, ipcls.iplen), + "%s/%s" % (master_ip, master_netmask), "dev", master_netdev]) if result.failed: logging.error("Can't remove the master IP, error: %s", result.output) @@ -357,6 +356,29 @@ def StopMasterDaemons(): result.cmd, result.exit_code, result.output) +def ChangeMasterNetmask(netmask): + """Change the netmask of the master IP. + + """ + master_netdev, master_ip, old_netmask, _, _ = GetMasterInfo() + if old_netmask == netmask: + return + + result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "add", + "%s/%s" % (master_ip, netmask), + "dev", master_netdev, "label", + "%s:0" % master_netdev]) + if result.failed: + _Fail("Could not change the master IP netmask") + + result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "del", + "%s/%s" % (master_ip, old_netmask), + "dev", master_netdev, "label", + "%s:0" % master_netdev]) + if result.failed: + _Fail("Could not change the master IP netmask") + + def EtcHostsModify(mode, host, ip): """Modify a host entry in /etc/hosts. diff --git a/lib/bootstrap.py b/lib/bootstrap.py index 5893cb67c0107189cd95a2b7eb271605fc75e5d2..9f79eccb62410e2532f523ffec41738c462fb7cb 100644 --- a/lib/bootstrap.py +++ b/lib/bootstrap.py @@ -283,10 +283,10 @@ def _InitFileStorage(file_storage_dir): def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913 - master_netdev, file_storage_dir, shared_file_storage_dir, - candidate_pool_size, secondary_ip=None, vg_name=None, - beparams=None, nicparams=None, ndparams=None, hvparams=None, - enabled_hypervisors=None, modify_etc_hosts=True, + master_netmask, master_netdev, file_storage_dir, + shared_file_storage_dir, candidate_pool_size, secondary_ip=None, + vg_name=None, beparams=None, nicparams=None, ndparams=None, + hvparams=None, enabled_hypervisors=None, modify_etc_hosts=True, modify_ssh_setup=True, maintain_node_health=False, drbd_helper=None, uid_pool=None, default_iallocator=None, primary_ip_version=None, prealloc_wipe_disks=False): @@ -310,12 +310,9 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913 " entries: %s" % invalid_hvs, errors.ECODE_INVAL) - ipcls = None - if primary_ip_version == constants.IP4_VERSION: - ipcls = netutils.IP4Address - elif primary_ip_version == constants.IP6_VERSION: - ipcls = netutils.IP6Address - else: + try: + ipcls = netutils.IPAddress.GetClassFromIpVersion(primary_ip_version) + except errors.ProgrammerError: raise errors.OpPrereqError("Invalid primary ip version: %d." % primary_ip_version) @@ -359,6 +356,13 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913 " but it does not belong to this host." % secondary_ip, errors.ECODE_ENVIRON) + if master_netmask is not None: + if not ipcls.ValidateNetmask(master_netmask): + raise errors.OpPrereqError("CIDR netmask (%s) not valid for IPv%s " % + (master_netmask, primary_ip_version)) + else: + master_netmask = ipcls.iplen + if vg_name is not None: # Check if volume group is valid vgstatus = utils.CheckVolumeGroupSize(utils.ListVolumeGroups(), vg_name, @@ -457,6 +461,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913 tcpudp_port_pool=set(), master_node=hostname.name, master_ip=clustername.ip, + master_netmask=master_netmask, master_netdev=master_netdev, cluster_name=clustername.name, file_storage_dir=file_storage_dir, diff --git a/lib/cli.py b/lib/cli.py index 2656d8896e00fcb0a01a52f782eda37ca845b27d..0e01980b4a38a55dc385f21720ae6e12125bb798 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -101,6 +101,7 @@ __all__ = [ "MAC_PREFIX_OPT", "MAINTAIN_NODE_HEALTH_OPT", "MASTER_NETDEV_OPT", + "MASTER_NETMASK_OPT", "MC_OPT", "MIGRATION_MODE_OPT", "NET_OPT", @@ -1008,6 +1009,11 @@ MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev", metavar="NETDEV", default=None) +MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask", + help="Specify the netmask of the master IP", + metavar="NETMASK", + default=None) + GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir", help="Specify the default directory (cluster-" "wide) for storing the file-based disks [%s]" % diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py index f3ada54864d92e09f677a079b6d73047279dd91e..cd0f719f5d505d5e98ca7553bed80895fd224bf2 100644 --- a/lib/client/gnt_cluster.py +++ b/lib/client/gnt_cluster.py @@ -139,10 +139,19 @@ def InitCluster(opts, args): ToStderr("Invalid primary ip version value: %s" % str(err)) return 1 + master_netmask = opts.master_netmask + try: + if master_netmask is not None: + master_netmask = int(master_netmask) + except (ValueError, TypeError), err: + ToStderr("Invalid master netmask value: %s" % str(err)) + return 1 + bootstrap.InitCluster(cluster_name=args[0], secondary_ip=opts.secondary_ip, vg_name=vg_name, mac_prefix=opts.mac_prefix, + master_netmask=master_netmask, master_netdev=master_netdev, file_storage_dir=opts.file_storage_dir, shared_file_storage_dir=opts.shared_file_storage_dir, @@ -371,6 +380,7 @@ def ShowClusterConfig(opts, args): compat.TryToRoman(result["candidate_pool_size"], convert=opts.roman_integers)) ToStdout(" - master netdev: %s", result["master_netdev"]) + ToStdout(" - master netmask: %s", result["master_netmask"]) ToStdout(" - lvm volume group: %s", result["volume_group_name"]) if result["reserved_lvs"]: reserved_lvs = utils.CommaJoin(result["reserved_lvs"]) @@ -867,6 +877,7 @@ def SetClusterParams(opts, args): opts.default_iallocator is not None or opts.reserved_lvs is not None or opts.master_netdev is not None or + opts.master_netmask is not None or opts.prealloc_wipe_disks is not None): ToStderr("Please give at least one of the parameters.") return 1 @@ -926,6 +937,13 @@ def SetClusterParams(opts, args): else: opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",") + if opts.master_netmask is not None: + try: + opts.master_netmask = int(opts.master_netmask) + except ValueError: + ToStderr("The --master-netmask option expects an int parameter.") + return 1 + op = opcodes.OpClusterSetParams(vg_name=vg_name, drbd_helper=drbd_helper, enabled_hypervisors=hvlist, @@ -942,6 +960,7 @@ def SetClusterParams(opts, args): default_iallocator=opts.default_iallocator, prealloc_wipe_disks=opts.prealloc_wipe_disks, master_netdev=opts.master_netdev, + master_netmask=opts.master_netmask, reserved_lvs=opts.reserved_lvs) SubmitOpCode(op, opts=opts) return 0 @@ -1339,10 +1358,10 @@ commands = { "init": ( InitCluster, [ArgHost(min=1, max=1)], [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT, - HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT, - NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT, - SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT, - UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT, + HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, MASTER_NETMASK_OPT, + NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, + NOMODIFY_SSH_SETUP_OPT, SECONDARY_IP_OPT, VG_NAME_OPT, + MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, PRIMARY_IP_VERSION_OPT, PREALLOC_WIPE_DISKS_OPT, NODE_PARAMS_OPT, GLOBAL_SHARED_FILEDIR_OPT], "[opts...] <cluster_name>", "Initialises a new cluster configuration"), @@ -1417,10 +1436,11 @@ commands = { "modify": ( SetClusterParams, ARGS_NONE, [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT, MASTER_NETDEV_OPT, - NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT, - UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, DRBD_HELPER_OPT, - NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, RESERVED_LVS_OPT, - DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT, NODE_PARAMS_OPT], + MASTER_NETMASK_OPT, NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, + MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, + DRBD_HELPER_OPT, NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, + RESERVED_LVS_OPT, DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT, + NODE_PARAMS_OPT], "[opts...]", "Alters the parameters of the cluster"), "renew-crypto": ( diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 921e984459d7651b5cabaf16470b247a4a3091fd..7ab74ac5985bd6d924b88f53fb0a94bb981eca55 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -3367,6 +3367,27 @@ class LUClusterRename(LogicalUnit): return clustername +def _ValidateNetmask(cfg, netmask): + """Checks if a netmask is valid. + + @type cfg: L{config.ConfigWriter} + @param cfg: The cluster configuration + @type netmask: int + @param netmask: the netmask to be verified + @raise errors.OpPrereqError: if the validation fails + + """ + ip_family = cfg.GetPrimaryIPFamily() + try: + ipcls = netutils.IPAddress.GetClassFromIpFamily(ip_family) + except errors.ProgrammerError: + raise errors.OpPrereqError("Invalid primary ip family: %s." % + ip_family) + if not ipcls.ValidateNetmask(netmask): + raise errors.OpPrereqError("CIDR netmask (%s) not valid" % + (netmask)) + + class LUClusterSetParams(LogicalUnit): """Change the parameters of the cluster. @@ -3388,6 +3409,9 @@ class LUClusterSetParams(LogicalUnit): if self.op.remove_uids: uidpool.CheckUidPool(self.op.remove_uids) + if self.op.master_netmask is not None: + _ValidateNetmask(self.cfg, self.op.master_netmask) + def ExpandNames(self): # FIXME: in the future maybe other cluster params won't require checking on # all nodes to be modified. @@ -3697,6 +3721,18 @@ class LUClusterSetParams(LogicalUnit): (self.cluster.master_netdev, self.op.master_netdev)) self.cluster.master_netdev = self.op.master_netdev + if self.op.master_netmask: + master = self.cfg.GetMasterNode() + feedback_fn("Changing master IP netmask to %s" % self.op.master_netmask) + result = self.rpc.call_node_change_master_netmask(master, + self.op.master_netmask) + if result.fail_msg: + msg = "Could not change the master IP netmask: %s" % result.fail_msg + self.LogWarning(msg) + feedback_fn(msg) + else: + self.cluster.master_netmask = self.op.master_netmask + self.cfg.Update(self.cluster, feedback_fn) if self.op.master_netdev: @@ -5518,6 +5554,7 @@ class LUClusterQuery(NoHooksLU): "ndparams": cluster.ndparams, "candidate_pool_size": cluster.candidate_pool_size, "master_netdev": cluster.master_netdev, + "master_netmask": cluster.master_netmask, "volume_group_name": cluster.volume_group_name, "drbd_usermode_helper": cluster.drbd_usermode_helper, "file_storage_dir": cluster.file_storage_dir, diff --git a/lib/config.py b/lib/config.py index fdd77c0d918dcf43ae3628c1326376b5380d5742..268304fdbadb8d06108bb2fff98eb743c4643a13 100644 --- a/lib/config.py +++ b/lib/config.py @@ -876,6 +876,13 @@ class ConfigWriter: """ return self._config_data.cluster.master_netdev + @locking.ssynchronized(_config_lock, shared=1) + def GetMasterNetmask(self): + """Get the netmask of the master node for this cluster. + + """ + return self._config_data.cluster.master_netmask + @locking.ssynchronized(_config_lock, shared=1) def GetFileStorageDir(self): """Get the file storage dir for this cluster. @@ -1839,6 +1846,7 @@ class ConfigWriter: constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data, constants.SS_MASTER_IP: cluster.master_ip, constants.SS_MASTER_NETDEV: cluster.master_netdev, + constants.SS_MASTER_NETMASK: str(cluster.master_netmask), constants.SS_MASTER_NODE: cluster.master_node, constants.SS_NODE_LIST: node_data, constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data, diff --git a/lib/constants.py b/lib/constants.py index 2b92284c57c5b1333a90f173fe6df3d4b76b1633..516996758945a1bc2caa50ac303dc273ad3b53f9 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -1343,6 +1343,7 @@ SS_MASTER_CANDIDATES = "master_candidates" SS_MASTER_CANDIDATES_IPS = "master_candidates_ips" SS_MASTER_IP = "master_ip" SS_MASTER_NETDEV = "master_netdev" +SS_MASTER_NETMASK = "master_netmask" SS_MASTER_NODE = "master_node" SS_NODE_LIST = "node_list" SS_NODE_PRIMARY_IPS = "node_primary_ips" diff --git a/lib/objects.py b/lib/objects.py index 29f03e05ddabeb9d6ee23fb61af55b78be1d2467..13167851174baa5d4205ac98e038a1de99e97bfd 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -1070,6 +1070,7 @@ class Cluster(TaggableObject): "master_node", "master_ip", "master_netdev", + "master_netmask", "cluster_name", "file_storage_dir", "shared_file_storage_dir", diff --git a/lib/opcodes.py b/lib/opcodes.py index 6b90ce6dde2a75293513367496da28c42f1bcee8..124fde0daf5ceda8004ff2f425283ed2aa591077 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -777,6 +777,8 @@ class OpClusterSetParams(OpCode): "Default iallocator for cluster"), ("master_netdev", None, ht.TOr(ht.TString, ht.TNone), "Master network device"), + ("master_netmask", None, ht.TOr(ht.TInt, ht.TNone), + "Netmask of the master IP"), ("reserved_lvs", None, ht.TOr(ht.TListOf(ht.TNonEmptyString), ht.TNone), "List of reserved LVs"), ("hidden_os", None, _TestClusterOsList, diff --git a/lib/rpc.py b/lib/rpc.py index 5052775a7f1280778cfc9c229d983d2f7ec34b98..8244215273c75e248db9f8b0edcf4c4f6005297b 100644 --- a/lib/rpc.py +++ b/lib/rpc.py @@ -983,6 +983,17 @@ class RpcRunner(object): """ return cls._StaticSingleNodeCall(node, "node_deactivate_master_ip", []) + @classmethod + @_RpcTimeout(_TMO_FAST) + def call_node_change_master_netmask(cls, node, netmask): + """Change master IP netmask. + + This is a single-node call. + + """ + return cls._StaticSingleNodeCall(node, "node_change_master_netmask", + [netmask]) + @classmethod @_RpcTimeout(_TMO_URGENT) def call_master_info(cls, node_list): diff --git a/lib/server/noded.py b/lib/server/noded.py index 1295b76861d1ae7d01bce940fd5a9a4c7355cfb1..4cdd870eed4984f0599a5437fe76398ae71e6f16 100644 --- a/lib/server/noded.py +++ b/lib/server/noded.py @@ -725,6 +725,13 @@ class NodeHttpServer(http.server.HttpServer): backend.DeactivateMasterIp() return backend.StopMasterDaemons() + @staticmethod + def perspective_node_change_master_netmask(params): + """Change the master IP netmask. + + """ + return backend.ChangeMasterNetmask(params[0]) + @staticmethod def perspective_node_leave_cluster(params): """Cleanup after leaving a cluster. diff --git a/lib/ssconf.py b/lib/ssconf.py index 9cc2a345022ff3f33e5962e09d96bf1a6f8991dc..67d903c17a37d5f6e6df6f4f60a23ffe6202b9cc 100644 --- a/lib/ssconf.py +++ b/lib/ssconf.py @@ -152,6 +152,9 @@ class SimpleConfigReader(object): def GetMasterNetdev(self): return self._config_data["cluster"]["master_netdev"] + def GetMasterNetmask(self): + return self._config_data["cluster"]["master_netmask"] + def GetFileStorageDir(self): return self._config_data["cluster"]["file_storage_dir"] @@ -280,6 +283,7 @@ class SimpleStore(object): constants.SS_MASTER_CANDIDATES_IPS, constants.SS_MASTER_IP, constants.SS_MASTER_NETDEV, + constants.SS_MASTER_NETMASK, constants.SS_MASTER_NODE, constants.SS_NODE_LIST, constants.SS_NODE_PRIMARY_IPS, @@ -408,6 +412,12 @@ class SimpleStore(object): """ return self._ReadFile(constants.SS_MASTER_NETDEV) + def GetMasterNetmask(self): + """Get the netdev to which we'll add the master ip. + + """ + return self._ReadFile(constants.SS_MASTER_NETMASK) + def GetMasterNode(self): """Get the hostname of the master node for this cluster. diff --git a/man/gnt-cluster.rst b/man/gnt-cluster.rst index b9b26c91db6c8258f73ee82b4084b492e6666a23..46372785ab0cb01ff0be336c622b2f589f57c5d2 100644 --- a/man/gnt-cluster.rst +++ b/man/gnt-cluster.rst @@ -166,6 +166,7 @@ INIT | [{-s|--secondary-ip} *secondary\_ip*] | [--vg-name *vg-name*] | [--master-netdev *interface-name*] +| [--master-netmask *netmask*] | [{-m|--mac-prefix} *mac-prefix*] | [--no-lvm-storage] | [--no-etc-hosts] @@ -222,6 +223,11 @@ interface on which the master will activate its IP address. It's important that all nodes have this interface because you'll need it for a master failover. +The ``--master-netmask`` option allows to specify a netmask for the +master IP. The netmask must be specified as an integer, and will be +interpreted as a CIDR netmask. The default value is 32 for an IPv4 +address and 128 for an IPv6 address. + The ``-m (--mac-prefix)`` option will let you specify a three byte prefix under which the virtual MAC addresses of your instances will be generated. The prefix must be specified in the format ``XX:XX:XX`` and @@ -431,6 +437,7 @@ MODIFY | [--reserved-lvs=*NAMES*] | [--node-parameters *ndparams*] | [--master-netdev *interface-name*] +| [--master-netmask *netmask*] Modify the options for the cluster. @@ -438,8 +445,8 @@ The ``--vg-name``, ``--no-lvm-storarge``, ``--enabled-hypervisors``, ``-H (--hypervisor-parameters)``, ``-B (--backend-parameters)``, ``--nic-parameters``, ``-C (--candidate-pool-size)``, ``--maintain-node-health``, ``--prealloc-wipe-disks``, ``--uid-pool``, -``--node-parameters``, ``--master-netdev`` options are described in -the **init** command. +``--node-parameters``, ``--master-netdev`` and ``--master-netmask`` +options are described in the **init** command. The ``--add-uids`` and ``--remove-uids`` options can be used to modify the user-id pool by adding/removing a list of user-ids or