def _GetMagicCommand(self): """Returns the command to read/write the magic value. """ if not self._opts.magic: return None # Prefix to ensure magic isn't interpreted as option to "echo" magic = "M=%s" % self._opts.magic cmd = StringIO() if self._mode == constants.IEM_IMPORT: cmd.write("{ ") cmd.write( utils.ShellQuoteArgs(["read", "-n", str(len(magic)), "magic"])) cmd.write(" && ") cmd.write("if test \"$magic\" != %s; then" % utils.ShellQuote(magic)) cmd.write(" echo %s >&2;" % utils.ShellQuote("Magic value mismatch")) cmd.write(" exit 1;") cmd.write("fi;") cmd.write(" }") elif self._mode == constants.IEM_EXPORT: cmd.write(utils.ShellQuoteArgs(["echo", "-E", "-n", magic])) else: raise errors.GenericError("Invalid mode '%s'" % self._mode) return cmd.getvalue()
def BuildCmd(self, hostname, user, command, batch=True, ask_key=False, tty=False, use_cluster_key=True, strict_host_check=True, private_key=None, quiet=True, port=None): """Build an ssh command to execute a command on a remote node. @param hostname: the target host, string @param user: user to auth as @param command: the command @param batch: if true, ssh will run in batch mode with no prompting @param ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that we can connect to an unknown host (not valid in batch mode) @param use_cluster_key: whether to expect and use the cluster-global SSH key @param strict_host_check: whether to check the host's SSH key at all @param private_key: use this private key instead of the default @param quiet: whether to enable -q to ssh @param port: the SSH port on which the node's daemon is running @return: the ssh call to run 'command' on the remote host. """ argv = [constants.SSH] argv.extend( self._BuildSshOptions(batch, ask_key, use_cluster_key, strict_host_check, private_key, quiet=quiet, port=port)) if tty: argv.extend(["-t", "-t"]) argv.append("%s@%s" % (user, hostname)) # Insert variables for virtual nodes argv.extend( "export %s=%s;" % (utils.ShellQuote(name), utils.ShellQuote(value)) for (name, value) in vcluster.EnvironmentForHost(hostname).items()) argv.append(command) return argv
def UploadData(node, data, mode=0o600, filename=None): """Uploads data to a node and returns the filename. Caller needs to remove the returned file on the node when it's not needed anymore. """ if filename: tmp = "tmp=%s" % utils.ShellQuote(filename) else: tmp = ('tmp=$(mktemp --tmpdir gnt.XXXXXX) && ' 'chmod %o "${tmp}"') % mode cmd = ("%s && " "[[ -f \"${tmp}\" ]] && " "cat > \"${tmp}\" && " "echo \"${tmp}\"") % tmp p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE) p.stdin.write(data) p.stdin.close() AssertEqual(p.wait(), 0) # Return temporary filename return _GetCommandStdout(p).strip()
def test(self): buf = StringIO() sw = utils.ShellWriter(buf) sw.Write("#!/bin/bash") sw.Write("if true; then") sw.IncIndent() try: sw.Write("echo true") sw.Write("for i in 1 2 3") sw.Write("do") sw.IncIndent() try: self.assertEqual(sw._indent, 2) sw.Write("date") finally: sw.DecIndent() sw.Write("done") finally: sw.DecIndent() sw.Write("echo %s", utils.ShellQuote("Hello World")) sw.Write("exit 0") self.assertEqual(sw._indent, 0) output = buf.getvalue() self.assertTrue(output.endswith("\n")) lines = output.splitlines() self.assertEqual(len(lines), 9) self.assertEqual(lines[0], "#!/bin/bash") self.assertTrue(re.match(r"^\s+date$", lines[5])) self.assertEqual(lines[7], "echo 'Hello World'")
def _CreateOobScriptStructure(): """Create a simple OOB handling script and its structure.""" master = qa_config.GetMasterNode() data_path = qa_utils.UploadData(master.primary, "") verify_path = qa_utils.UploadData(master.primary, "") exit_code_path = qa_utils.UploadData(master.primary, "") oob_script = (("#!/bin/bash\n" "echo \"$@\" > %s\n" "cat %s\n" "exit $(< %s)\n") % (utils.ShellQuote(verify_path), utils.ShellQuote(data_path), utils.ShellQuote(exit_code_path))) oob_path = qa_utils.UploadData(master.primary, oob_script, mode=0700) return [oob_path, verify_path, data_path, exit_code_path]
def RemoveFromEtcHosts(hostnames): """Remove hostnames from /etc/hosts. @param hostnames: List of hostnames first used A records, all other CNAMEs """ master = qa_config.GetMasterNode() tmp_hosts = UploadData(master.primary, "", mode=0644) quoted_tmp_hosts = utils.ShellQuote(tmp_hosts) sed_data = " ".join(hostnames) try: AssertCommand( (r"sed -e '/^\(::1\|127\.0\.0\.1\)\s\+%s/d' %s > %s" r" && mv %s %s") % (sed_data, utils.ShellQuote(pathutils.ETC_HOSTS), quoted_tmp_hosts, quoted_tmp_hosts, utils.ShellQuote(pathutils.ETC_HOSTS))) except Exception: AssertCommand(["rm", "-f", tmp_hosts]) raise
def BackupFile(node, path): """Creates a backup of a file on the node and returns the filename. Caller needs to remove the returned file on the node when it's not needed anymore. """ vpath = MakeNodePath(node, path) cmd = ("tmp=$(mktemp .gnt.XXXXXX --tmpdir=$(dirname %s)) && " "[[ -f \"$tmp\" ]] && " "cp %s $tmp && " "echo $tmp") % (utils.ShellQuote(vpath), utils.ShellQuote(vpath)) # Return temporary filename result = GetCommandOutput(node, cmd).strip() print "Backup filename: %s" % result return result
def AddToEtcHosts(hostnames): """Adds hostnames to /etc/hosts. @param hostnames: List of hostnames first used A records, all other CNAMEs """ master = qa_config.GetMasterNode() tmp_hosts = UploadData(master.primary, "", mode=0644) data = [] for localhost in ("::1", "127.0.0.1"): data.append("%s %s" % (localhost, " ".join(hostnames))) try: AssertCommand( "{ cat %s && echo -e '%s'; } > %s && mv %s %s" % (utils.ShellQuote(pathutils.ETC_HOSTS), "\\n".join(data), utils.ShellQuote(tmp_hosts), utils.ShellQuote(tmp_hosts), utils.ShellQuote(pathutils.ETC_HOSTS))) except Exception: AssertCommand(["rm", "-f", tmp_hosts]) raise
def AssertRedirectedCommand(cmd, fail=False, node=None, log_cmd=True): """Executes a command with redirected output. The log will go to the qa-output log file in the ganeti log directory on the node where the command is executed. The fail and node parameters are passed unchanged to AssertCommand. @param cmd: the command to be executed, as a list; a string is not supported """ if not isinstance(cmd, list): raise qa_error.Error("Non-list passed to AssertRedirectedCommand") ofile = utils.ShellQuote(_QA_OUTPUT) cmdstr = utils.ShellQuoteArgs(cmd) AssertCommand("echo ---- $(date) %s ---- >> %s" % (cmdstr, ofile), fail=False, node=node, log_cmd=False) return AssertCommand(cmdstr + " >> %s" % ofile, fail=fail, node=node, log_cmd=log_cmd)
def GetSSHCommand(node, cmd, strict=True, opts=None, tty=False, use_multiplexer=True): """Builds SSH command to be executed. @type node: string @param node: node the command should run on @type cmd: string @param cmd: command to be executed in the node; if None or empty string, no command will be executed @type strict: boolean @param strict: whether to enable strict host key checking @type opts: list @param opts: list of additional options @type tty: boolean or None @param tty: if we should use tty; if None, will be auto-detected @type use_multiplexer: boolean @param use_multiplexer: if the multiplexer for the node should be used """ args = ["ssh", "-oEscapeChar=none", "-oBatchMode=yes", "-lroot"] if tty is None: tty = sys.stdout.isatty() if tty: args.append("-t") if strict: tmp = "yes" else: tmp = "no" args.append("-oStrictHostKeyChecking=%s" % tmp) args.append("-oClearAllForwardings=yes") args.append("-oForwardAgent=yes") if opts: args.extend(opts) if node in _MULTIPLEXERS and use_multiplexer: spath = _MULTIPLEXERS[node][0] args.append("-oControlPath=%s" % spath) args.append("-oControlMaster=no") (vcluster_master, vcluster_basedir) = \ qa_config.GetVclusterSettings() if vcluster_master: args.append(vcluster_master) args.append("%s/%s/cmd" % (vcluster_basedir, node)) if cmd: # For virtual clusters the whole command must be wrapped using the "cmd" # script, as that script sets a number of environment variables. If the # command contains shell meta characters the whole command needs to be # quoted. args.append(utils.ShellQuote(cmd)) else: args.append(node) if cmd: args.append(cmd) return args
# Return temporary filename return _GetCommandStdout(p).strip() finally: f.close() def UploadData(node, data, mode=0600, filename=None): """Uploads data to a node and returns the filename. Caller needs to remove the returned file on the node when it's not needed anymore. """ if filename: tmp = "tmp=%s" % utils.ShellQuote(filename) else: tmp = ('tmp=$(mktemp --tmpdir gnt.XXXXXX) && ' 'chmod %o "${tmp}"') % mode cmd = ("%s && " "[[ -f \"${tmp}\" ]] && " "cat > \"${tmp}\" && " "echo \"${tmp}\"") % tmp p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE) p.stdin.write(data) p.stdin.close() AssertEqual(p.wait(), 0)
# Return temporary filename return _GetCommandStdout(p).strip() finally: f.close() def UploadData(node, data, mode=0600, filename=None): """Uploads data to a node and returns the filename. Caller needs to remove the returned file on the node when it's not needed anymore. """ if filename: quoted_filename = utils.ShellQuote(filename) directory = utils.ShellQuote(os.path.dirname(filename)) cmd = " && ".join([ "mkdir -p %s" % directory, "cat > %s" % quoted_filename, "chmod %o %s" % (mode, quoted_filename) ]) else: cmd = " && ".join([ 'tmp=$(mktemp --tmpdir gnt.XXXXXX)', 'chmod %o "${tmp}"' % mode, 'cat > "${tmp}"', 'echo "${tmp}"' ]) p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False,
def testShellQuote(self): self.assertEqual(utils.ShellQuote("abc"), "abc") self.assertEqual(utils.ShellQuote('ab"c'), "'ab\"c'") self.assertEqual(utils.ShellQuote("a'bc"), "'a'\\''bc'") self.assertEqual(utils.ShellQuote("a b c"), "'a b c'") self.assertEqual(utils.ShellQuote("a b\\ c"), "'a b\\ c'")