From 224ff0f75e06993305bcce079e47ccdd15b02f90 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Tue, 23 Oct 2012 20:16:25 +0200 Subject: [PATCH] gnt-node add: Use prepare-node-join MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch changes βgnt-node addβ to use the newly added βprepare-node-joinβ tool. Hereby Paramiko is no longer a hard dependency for setting up SSH on nodes. In βgnt_cluster.pyβ, a positional parameter is no longer passed as a keyword parameter. Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Iustin Pop <iustin@google.com> --- NEWS | 4 ++ lib/client/gnt_cluster.py | 2 +- lib/client/gnt_node.py | 119 +++++++++++++++++++++++++++++++------- lib/pathutils.py | 1 + man/gnt-node.rst | 4 -- tools/cluster-merge | 2 +- 6 files changed, 105 insertions(+), 27 deletions(-) diff --git a/NEWS b/NEWS index 7571ff49f..b411cb1bb 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,10 @@ Version 2.7.0 beta1 - The parsing of the variants file for OSes (see :manpage:`ganeti-os-interface(8)` has been slightly changed: now empty lines and comment lines are ignored for better readability. +- The ``setup-ssh`` tool added in Ganeti 2.2 has been replaced. + ``gnt-node add`` now invokes a new tool on the destination node, named + ``prepare-node-join``, to configure the SSH daemon. Paramiko is no + longer necessary to configure nodes' SSH daemons via ``gnt-node add``. Version 2.6.1 diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py index b71f5ac56..06bdaf6e1 100644 --- a/lib/client/gnt_cluster.py +++ b/lib/client/gnt_cluster.py @@ -503,7 +503,7 @@ def ClusterCopyFile(opts, args): secondary_ips=opts.use_replication_network, nodegroup=opts.nodegroup) - srun = ssh.SshRunner(cluster_name=cluster_name) + srun = ssh.SshRunner(cluster_name) for node in results: if not srun.CopyFileToNode(node, filename): ToStderr("Copy of file %s to node %s failed", filename, node) diff --git a/lib/client/gnt_node.py b/lib/client/gnt_node.py index 86cdf0ada..7c8add06e 100644 --- a/lib/client/gnt_node.py +++ b/lib/client/gnt_node.py @@ -27,6 +27,8 @@ # C0103: Invalid name gnt-node import itertools +import errno +import tempfile from ganeti.cli import * from ganeti import cli @@ -37,6 +39,8 @@ from ganeti import constants from ganeti import errors from ganeti import netutils from ganeti import pathutils +from ganeti import serializer +from ganeti import ssh from cStringIO import StringIO from ganeti import confd @@ -134,37 +138,111 @@ def ConvertStorageType(user_storage_type): errors.ECODE_INVAL) -def _RunSetupSSH(options, nodes): - """Wrapper around utils.RunCmd to call setup-ssh +def _TryReadFile(path): + """Tries to read a file. - @param options: The command line options - @param nodes: The nodes to setup + If the file is not found, C{None} is returned. + + @type path: string + @param path: Filename + @rtype: None or string + @todo: Consider adding a generic ENOENT wrapper """ + try: + return utils.ReadFile(path) + except EnvironmentError, err: + if err.errno == errno.ENOENT: + return None + else: + raise + - assert nodes, "Empty node list" +def _ReadSshKeys(keyfiles, _tostderr_fn=ToStderr): + """Reads SSH keys according to C{keyfiles}. + + @type keyfiles: dict + @param keyfiles: Dictionary with keys of L{constants.SSHK_ALL} and two-values + tuples (private and public key file) + @rtype: list + @return: List of three-values tuples (L{constants.SSHK_ALL}, private and + public key as strings) + + """ + result = [] - cmd = [pathutils.SETUP_SSH] + for (kind, (private_file, public_file)) in keyfiles.items(): + private_key = _TryReadFile(private_file) + public_key = _TryReadFile(public_file) - # Pass --debug|--verbose to the external script if set on our invocation - # --debug overrides --verbose + if public_key and private_key: + result.append((kind, private_key, public_key)) + elif public_key or private_key: + _tostderr_fn("Couldn't find a complete set of keys for kind '%s'; files" + " '%s' and '%s'", kind, private_file, public_file) + + return result + + +def _SetupSSH(options, cluster_name, node): + """Configures a destination node's SSH daemon. + + @param options: Command line options + @type cluster_name + @param cluster_name: Cluster name + @type node: string + @param node: Destination node name + + """ + if options.force_join: + ToStderr("The \"--force-join\" option is no longer supported and will be" + " ignored.") + + cmd = [pathutils.PREPARE_NODE_JOIN] + + # Pass --debug/--verbose to the external script if set on our invocation if options.debug: cmd.append("--debug") - elif options.verbose: + + if options.verbose: cmd.append("--verbose") - if not options.ssh_key_check: - cmd.append("--no-ssh-key-check") - if options.force_join: - cmd.append("--force-join") - cmd.extend(nodes) + host_keys = _ReadSshKeys(constants.SSH_DAEMON_KEYFILES) + + (_, root_keyfiles) = \ + ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False) + + root_keys = _ReadSshKeys(root_keyfiles) + + (_, cert_pem) = \ + utils.ExtractX509Certificate(utils.ReadFile(pathutils.NODED_CERT_FILE)) + + data = { + constants.SSHS_CLUSTER_NAME: cluster_name, + constants.SSHS_NODE_DAEMON_CERTIFICATE: cert_pem, + constants.SSHS_SSH_HOST_KEY: host_keys, + constants.SSHS_SSH_ROOT_KEY: root_keys, + } + + srun = ssh.SshRunner(cluster_name) + scmd = srun.BuildCmd(node, constants.SSH_LOGIN_USER, + utils.ShellQuoteArgs(cmd), + batch=False, ask_key=options.ssh_key_check, + strict_host_check=options.ssh_key_check, quiet=False, + use_cluster_key=False) + + tempfh = tempfile.TemporaryFile() + try: + tempfh.write(serializer.DumpJson(data)) + tempfh.seek(0) - result = utils.RunCmd(cmd, interactive=True) + result = utils.RunCmd(scmd, interactive=True, input_fd=tempfh) + finally: + tempfh.close() if result.failed: - errmsg = ("Command '%s' failed with exit code %s; output %r" % - (result.cmd, result.exit_code, result.output)) - raise errors.OpExecError(errmsg) + raise errors.OpExecError("Command '%s' failed: %s" % + (result.cmd, result.fail_reason)) @UsesRPC @@ -206,8 +284,7 @@ def AddNode(opts, args): sip = opts.secondary_ip # read the cluster name from the master - output = cl.QueryConfigValues(["cluster_name"]) - cluster_name = output[0] + (cluster_name, ) = cl.QueryConfigValues(["cluster_name"]) if not readd and opts.node_setup: ToStderr("-- WARNING -- \n" @@ -218,7 +295,7 @@ def AddNode(opts, args): "and grant full intra-cluster ssh root access to/from it\n", node) if opts.node_setup: - _RunSetupSSH(opts, [node]) + _SetupSSH(opts, cluster_name, node) bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check) diff --git a/lib/pathutils.py b/lib/pathutils.py index bb1371aa9..fba77d83f 100644 --- a/lib/pathutils.py +++ b/lib/pathutils.py @@ -44,6 +44,7 @@ IMPORT_EXPORT_DAEMON = _autoconf.PKGLIBDIR + "/import-export" KVM_CONSOLE_WRAPPER = _autoconf.PKGLIBDIR + "/tools/kvm-console-wrapper" KVM_IFUP = _autoconf.PKGLIBDIR + "/kvm-ifup" SETUP_SSH = _autoconf.TOOLSDIR + "/setup-ssh" +PREPARE_NODE_JOIN = _autoconf.PKGLIBDIR + "/prepare-node-join" XM_CONSOLE_WRAPPER = _autoconf.PKGLIBDIR + "/tools/xm-console-wrapper" ETC_HOSTS = vcluster.ETC_HOSTS diff --git a/man/gnt-node.rst b/man/gnt-node.rst index 9d0583608..5e18c2678 100644 --- a/man/gnt-node.rst +++ b/man/gnt-node.rst @@ -52,10 +52,6 @@ secondary IP again, it will reused from the cluster. Also, the drained and offline flags of the node will be cleared before re-adding it. -The ``--force-join`` option is to proceed with adding a node even if it already -appears to belong to another cluster. This is used during cluster merging, for -example. - The ``-g (--node-group)`` option is used to add the new node into a specific node group, specified by UUID or name. If only one node group exists you can skip this option, otherwise it's mandatory. diff --git a/tools/cluster-merge b/tools/cluster-merge index 1ff4b64e6..d45d38148 100755 --- a/tools/cluster-merge +++ b/tools/cluster-merge @@ -651,7 +651,7 @@ class Merger(object): for node in data.nodes: logging.info("Readding node %s", node) result = utils.RunCmd(["gnt-node", "add", "--readd", - "--no-ssh-key-check", "--force-join", node]) + "--no-ssh-key-check", node]) if result.failed: logging.error("%s failed to be readded. Reason: %s, output: %s", node, result.fail_reason, result.output) -- GitLab