Return-Path: X-Original-To: apmail-brooklyn-commits-archive@minotaur.apache.org Delivered-To: apmail-brooklyn-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id BE6BA18FE6 for ; Tue, 18 Aug 2015 15:03:44 +0000 (UTC) Received: (qmail 81061 invoked by uid 500); 18 Aug 2015 15:03:44 -0000 Delivered-To: apmail-brooklyn-commits-archive@brooklyn.apache.org Received: (qmail 81039 invoked by uid 500); 18 Aug 2015 15:03:44 -0000 Mailing-List: contact commits-help@brooklyn.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@brooklyn.incubator.apache.org Delivered-To: mailing list commits@brooklyn.incubator.apache.org Received: (qmail 81030 invoked by uid 99); 18 Aug 2015 15:03:44 -0000 Received: from Unknown (HELO spamd3-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 18 Aug 2015 15:03:44 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd3-us-west.apache.org (ASF Mail Server at spamd3-us-west.apache.org) with ESMTP id 170E1182343 for ; Tue, 18 Aug 2015 15:03:44 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd3-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 0.795 X-Spam-Level: X-Spam-Status: No, score=0.795 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, RP_MATCHES_RCVD=-0.006, URIBL_BLOCKED=0.001] autolearn=disabled Received: from mx1-eu-west.apache.org ([10.40.0.8]) by localhost (spamd3-us-west.apache.org [10.40.0.10]) (amavisd-new, port 10024) with ESMTP id v0z2jSq0tGs7 for ; Tue, 18 Aug 2015 15:03:28 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-eu-west.apache.org (ASF Mail Server at mx1-eu-west.apache.org) with SMTP id 7F57724F45 for ; Tue, 18 Aug 2015 15:03:12 +0000 (UTC) Received: (qmail 78433 invoked by uid 99); 18 Aug 2015 15:03:11 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 18 Aug 2015 15:03:11 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id EB3B4E04D7; Tue, 18 Aug 2015 15:03:10 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: hadrian@apache.org To: commits@brooklyn.incubator.apache.org Date: Tue, 18 Aug 2015 15:03:20 -0000 Message-Id: <05556e06ab7845f2934b76132fc92045@git.apache.org> In-Reply-To: <8a1c4e07828a430c8c0497ef97ea3865@git.apache.org> References: <8a1c4e07828a430c8c0497ef97ea3865@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [11/64] [abbrv] incubator-brooklyn git commit: BROOKLYN-162 - apply org.apache package prefix to utils-common http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java new file mode 100644 index 0000000..4019a67 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/BashCommands.java @@ -0,0 +1,711 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.ssh; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes; +import org.apache.brooklyn.util.time.Duration; + +import com.google.common.annotations.Beta; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + +public class BashCommands { + + /** + * Returns a string for checking whether the given executable is available, + * and installing it if necessary. + *

+ * Uses {@link #installPackage} and accepts the same flags e.g. for apt, yum, rpm. + */ + public static String installExecutable(Map flags, String executable) { + return onlyIfExecutableMissing(executable, installPackage(flags, executable)); + } + + public static String installExecutable(String executable) { + return installExecutable(MutableMap.of(), executable); + } + + /** + * Returns a command with all output redirected to /dev/null + */ + public static String quiet(String command) { + return format("(%s > /dev/null 2>&1)", command); + } + + /** + * Returns a command that always exits successfully + */ + public static String ok(String command) { + return String.format("(%s || true)", command); + } + + /** + * Returns a command for safely running as root, using {@code sudo}. + *

+ * Ensuring non-blocking if password not set by using + * {@code -n} which means to exit if password required + * (this is unsupported in Ubuntu 8 but all modern OS's seem okay with this!), + * and (perhaps unnecessarily ?) + * {@code -S} which reads from stdin (routed to {@code /dev/null}, it was claimed here previously, though I'm not sure?). + *

+ * Also specify {@code -E} to pass the parent environment in. + *

+ * If already root, simply runs the command, wrapped in brackets in case it is backgrounded. + *

+ * The command is not quoted or escaped in any ways. + * If you are doing privileged redirect you may need to pass e.g. "bash -c 'echo hi > file'". + *

+ * If null is supplied, it is returned (sometimes used to indicate no command desired). + */ + public static String sudo(String command) { + if (command.startsWith("( ") || command.endsWith(" &")) + return sudoNew(command); + else + return sudoOld(command); + } + + // TODO would like to move away from sudoOld -- but needs extensive testing! + + private static String sudoOld(String command) { + if (command==null) return null; + // some OS's (which?) fail if you try running sudo when you're already root (dumb but true) + return format("( if test \"$UID\" -eq 0; then ( %s ); else sudo -E -n -S -- %s; fi )", command, command); + } + private static String sudoNew(String command) { + if (command==null) return null; + // on some OS's e.g. Centos 6.5 in SL, sudo -- X tries to run X as a literal argument; + // in particular "( echo foo && echo bar )" fails when passed as an argument in that way, + // but works if passed to bash -c; + // but others e.g. OS X fail if you say sudo -- bash -c "( echo foo )" ... not liking the parentheses + // piping to sudo bash seems the most reliable way + return "( if test \"$UID\" -eq 0; then ( "+command+" ); else " + + "echo " + BashStringEscapes.wrapBash(command) + " | " + + "sudo -E -n -S -s -- bash" + + " ; fi )"; + } + + /** sudo to a given user and run the indicated command; + * @deprecated since 0.7.0 semantics of this are fiddly, e.g. whether user gets their environment */ + @Beta + public static String sudoAsUser(String user, String command) { + return sudoAsUserOld(user, command); + } + + private static String sudoAsUserOld(String user, String command) { + if (command == null) return null; + return format("{ sudo -E -n -u %s -s -- %s ; }", user, command); + } + // TODO would like to move away from sudoOld -- but needs extensive testing! +// private static String sudoAsUserNew(String user, String command) { +// if (command == null) return null; +// // no -E, run with permissions of this user +// // FIXME still doesn't always work e.g. doesn't have path of user +// // (Alex says: can't find any combinations which work reliably) +// return "{ sudo -n -S -i -u "+user+" -- "+BashStringEscapes.wrapBash(command)+" ; }"; +// } + + public static String addSbinPathCommand() { + return "export PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; + } + + /** executes a command, then as user tees the output to the given file. + * useful e.g. for appending to a file which is only writable by root or a priveleged user. */ + public static String executeCommandThenAsUserTeeOutputToFile(String commandWhoseOutputToWrite, String user, String file) { + return format("{ %s | sudo -E -n -u %s -s -- tee -a %s ; }", + commandWhoseOutputToWrite, user, file); + } + + /** some machines require a tty for sudo; brooklyn by default does not use a tty + * (so that it can get separate error+stdout streams); you can enable a tty as an + * option to every ssh command, or you can do it once and + * modify the machine so that a tty is not subsequently required. + *

+ * this command must be run with allocatePTY set as a flag to ssh. see SshTasks.dontRequireTtyForSudo which sets that up. + *

+ * (having a tty for sudo seems like another case of imaginary security which is just irritating. + * like water restrictions at airport security.) */ + public static String dontRequireTtyForSudo() { + return ifFileExistsElse0("/etc/sudoers", sudo("sed -i.brooklyn.bak 's/.*requiretty.*/#brooklyn-removed-require-tty/' /etc/sudoers")); + } + + /** generates ~/.ssh/id_rsa if that file does not exist */ + public static String generateKeyInDotSshIdRsaIfNotThere() { + return "[ -f ~/.ssh/id_rsa ] || ( mkdir -p ~/.ssh ; chmod 700 ~/.ssh ; ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa )"; + } + + // TODO a builder would be better than these ifBlahExistsElseBlah methods! + // (ideally formatting better also; though maybe SshTasks would be better?) + + /** + * Returns a command that runs only if the specified file (or link or directory) exists; + * if the command runs and fails that exit is preserved (but if the file does not exist exit code is zero). + * Executed as { { not-file-exists && ok ; } || command ; } for portability. + * ("if [ ... ] ; then xxx ; else xxx ; fi" syntax is not quite as portable, I seem to recall (not sure, Alex Aug 2013).) + */ + public static String ifFileExistsElse0(String path, String command) { + return alternativesGroup( + chainGroup(format("test ! -e %s", path), "true"), + command); + } + /** as {@link #ifFileExistsElse0(String, String)} but returns non-zero if the test fails (also returns non-zero if the command fails, + * so you can't tell the difference :( -- we need if ; then ; else ; fi semantics for that I think, but not sure how portable that is) */ + public static String ifFileExistsElse1(String path, String command) { + return chainGroup(format("test -e %s", path), command); + } + + /** + * Returns a command that runs only if the specified executable exists on the path (using `which`). + * if the command runs and fails that exit is preserved (but if the executable is not on the path exit code is zero). + * @see #ifFileExistsElse0(String, String) for implementation discussion, using { { test -z `which executable` && true ; } || command ; } + */ + public static String ifExecutableElse0(String executable, String command) { + return alternativesGroup( + chainGroup(format("test -z `which %s`", executable), "true"), + command); + } + + /** as {@link #ifExecutableElse0(String, String)} but returns 1 if the test fails (also returns non-zero if the command fails) */ + public static String ifExecutableElse1(String executable, String command) { + return chainGroup(format("which %s", executable), command); + } + + /** + * Returns a command which + * executes statement only if command is NOT found in $PATH + * + * @param command + * @param statement + * @return command + */ + public static String ifNotExecutable(String command, String statement) { + return String.format("{ { test ! -z `which %s`; } || { %s; } }", command, statement); + } + + /** + * Returns a command that runs only if the specified executable exists on the path (using `which`). + * if the command runs and fails that exit is preserved (but if the executable is not on the path exit code is zero). + * @see #ifFileExistsElse0(String, String) for implementation discussion, using { { test -z `which executable` && true ; } || command ; } + */ + public static String onlyIfExecutableMissing(String executable, String command) { + return alternativesGroup(format("which %s", executable), command); + } + + public static String ifExecutableElse(String command, String ifTrue, String otherwise) { + return com.google.common.base.Joiner.on('\n').join( + ifExecutableElse(command, ImmutableList.of(ifTrue), ImmutableList.of(otherwise))); + } + + public static ImmutableList ifExecutableElse(String command, List ifTrue, List otherwise) { + return ImmutableList.builder() + .add(String.format("if test -z `which %s`; then", command)) + .addAll(ifTrue) + .add("else") + .addAll(otherwise) + .add("fi") + .build(); + } + + /** + * Returns a sequence of chained commands that runs until one of them fails (i.e. joined by '&&') + * This currently runs as a subshell (so exits are swallowed) but behaviour may be changed imminently. + * (Use {@link #chainGroup(Collection)} or {@link #chainSubshell(Collection)} to be clear.) + */ + public static String chain(Collection commands) { + return "( " + Strings.join(commands, " && ") + " )"; + } + + /** Convenience for {@link #chain(Collection)} */ + public static String chain(String ...commands) { + return "( " + Strings.join(commands, " && ") + " )"; + } + + /** As {@link #chain(Collection)}, but explicitly using { } grouping characters + * to ensure exits are propagated. */ + public static String chainGroup(Collection commands) { + // spaces required around curly braces + return "{ " + Strings.join(commands, " && ") + " ; }"; + } + + /** As {@link #chainGroup(Collection)} */ + public static String chainGroup(String ...commands) { + return "{ " + Strings.join(commands, " && ") + " ; }"; + } + + /** As {@link #chain(Collection)}, but explicitly using ( ) grouping characters + * to ensure exits are caught. */ + public static String chainSubshell(Collection commands) { + // the spaces are not required, but it might be possible that a (( expr )) is interpreted differently + // (won't hurt to have the spaces in any case!) + return "( " + Strings.join(commands, " && ") + " )"; + } + + /** As {@link #chainSubshell(Collection)} */ + public static String chainSubshell(String ...commands) { + return "( " + Strings.join(commands, " && ") + " )"; + } + + /** + * Returns a sequence of chained commands that runs until one of them succeeds (i.e. joined by '||'). + * This currently runs as a subshell (so exits are swallowed) but behaviour may be changed imminently. + * (Use {@link #alternativesGroup(Collection)} or {@link #alternativesSubshell(Collection)} to be clear.) + */ + public static String alternatives(Collection commands) { + return "( " + Strings.join(commands, " || ") + " )"; + } + + /** As {@link #alternatives(Collection)} */ + public static String alternatives(String ...commands) { + return "( " + Strings.join(commands, " || ") + " )"; + } + + /** As {@link #alternatives(Collection)}, but explicitly using { } grouping characters + * to ensure exits are propagated. */ + public static String alternativesGroup(Collection commands) { + // spaces required around curly braces + return "{ " + Strings.join(commands, " || ") + " ; }"; + } + + /** As {@link #alternativesGroup(Collection)} */ + public static String alternativesGroup(String ...commands) { + return "{ " + Strings.join(commands, " || ") + " ; }"; + } + + /** As {@link #alternatives(Collection)}, but explicitly using ( ) grouping characters + * to ensure exits are caught. */ + public static String alternativesSubshell(Collection commands) { + // the spaces are not required, but it might be possible that a (( expr )) is interpreted differently + // (won't hurt to have the spaces in any case!) + return "( " + Strings.join(commands, " || ") + " )"; + } + + /** As {@link #alternativesSubshell(Collection)} */ + public static String alternativesSubshell(String ...commands) { + return "( " + Strings.join(commands, " || ") + " )"; + } + + /** returns the pattern formatted with the given arg if the arg is not null, otherwise returns null */ + public static String formatIfNotNull(String pattern, Object arg) { + if (arg==null) return null; + return format(pattern, arg); + } + + public static String installPackage(String packageDefaultName) { + return installPackage(MutableMap.of(), packageDefaultName); + } + /** + * Returns a command for installing the given package. + *

+ * Warns, but does not fail or return non-zero if it ultimately fails. + *

+ * Flags can contain common overrides for {@code apt}, {@code yum}, {@code port} and {@code brew} + * as the package names can be different for each of those. Setting the default package name to + * {@literal null} will use only the overridden package manager values. The {@code onlyifmissing} flag + * adds a check for an executable, and only attempts to install packages if it is not found. + *

+     * installPackage(ImmutableMap.of("yum", "openssl-devel", "apt", "openssl libssl-dev zlib1g-dev"), "libssl-devel");
+     * installPackage(ImmutableMap.of("apt", "libaio1"), null);
+     * installPackage(ImmutableMap.of("onlyifmissing", "curl"), "curl");
+     * 
+ */ + public static String installPackage(Map flags, String packageDefaultName) { + return installPackageOr(flags, packageDefaultName, null); + } + public static String installPackageOrFail(Map flags, String packageDefaultName) { + return installPackageOr(flags, packageDefaultName, "exit 9"); + } + public static String installPackageOr(Map flags, String packageDefaultName, String optionalCommandToRunIfNone) { + String ifMissing = (String) flags.get("onlyifmissing"); + String zypperInstall = formatIfNotNull("zypper --non-interactive --no-gpg-checks install %s", getFlag(flags, "zypper", packageDefaultName)); + String aptInstall = formatIfNotNull("apt-get install -y --allow-unauthenticated %s", getFlag(flags, "apt", packageDefaultName)); + String yumInstall = formatIfNotNull("yum -y --nogpgcheck install %s", getFlag(flags, "yum", packageDefaultName)); + String brewInstall = formatIfNotNull("brew install %s", getFlag(flags, "brew", packageDefaultName)); + String portInstall = formatIfNotNull("port install %s", getFlag(flags, "port", packageDefaultName)); + + List commands = new LinkedList(); + if (ifMissing != null) + commands.add(format("which %s", ifMissing)); + if (zypperInstall != null) + commands.add(ifExecutableElse1("zypper", + chainGroup( + "echo zypper exists, doing refresh", + ok(sudo("zypper --non-interactive --no-gpg-checks refresh")), + sudo(zypperInstall)))); + if (aptInstall != null) + commands.add(ifExecutableElse1("apt-get", + chainGroup( + "echo apt-get exists, doing update", + "export DEBIAN_FRONTEND=noninteractive", + ok(sudo("apt-get update")), + sudo(aptInstall)))); + if (yumInstall != null) + commands.add(ifExecutableElse1("yum", + chainGroup( + "echo yum exists, doing update", + ok(sudo("yum check-update")), + ok(sudo("yum -y install epel-release")), + sudo(yumInstall)))); + if (brewInstall != null) + commands.add(ifExecutableElse1("brew", brewInstall)); + if (portInstall != null) + commands.add(ifExecutableElse1("port", sudo(portInstall))); + + String lastCommand = ok(warn("WARNING: no known/successful package manager to install " + + (packageDefaultName!=null ? packageDefaultName : flags.toString()) + + ", may fail subsequently")); + if (optionalCommandToRunIfNone != null) + lastCommand = chain(lastCommand, optionalCommandToRunIfNone); + commands.add(lastCommand); + + return alternatives(commands); + } + + public static String warn(String message) { + return "( echo "+BashStringEscapes.wrapBash(message)+" | tee /dev/stderr )"; + } + + /** returns a command which logs a message to stdout and stderr then exits with the given error code */ + public static String fail(String message, int code) { + return chainGroup(warn(message), "exit "+code); + } + + /** requires the command to have a non-zero exit code; e.g. + * require("which foo", "Command foo must be found", 1) */ + public static String require(String command, String failureMessage, int exitCode) { + return alternativesGroup(command, fail(failureMessage, exitCode)); + } + + /** as {@link #require(String, String, int)} but returning the original exit code */ + public static String require(String command, String failureMessage) { + return alternativesGroup(command, chainGroup("EXIT_CODE=$?", warn(failureMessage), "exit $EXIT_CODE")); + } + + /** requires the test to pass, as valid bash `test` arguments; e.g. + * requireTest("-f /etc/hosts", "Hosts file must exist", 1) */ + public static String requireTest(String test, String failureMessage, int exitCode) { + return require("test "+test, failureMessage, exitCode); + } + + /** as {@link #requireTest(String, String, int)} but returning the original exit code */ + public static String requireTest(String test, String failureMessage) { + return require("test "+test, failureMessage); + } + + /** fails with nice error if the given file does not exist */ + public static String requireFile(String file) { + return requireTest("-f "+BashStringEscapes.wrapBash(file), "The required file \""+file+"\" does not exist"); + } + + /** fails with nice error if the given file does not exist */ + public static String requireExecutable(String command) { + return require("which "+BashStringEscapes.wrapBash(command), "The required executable \""+command+"\" does not exist"); + } + + public static String waitForFileContents(String file, String desiredContent, Duration timeout, boolean failOnTimeout) { + long secs = Math.max(timeout.toSeconds(), 1); + + List commands = ImmutableList.of( + "for i in {1.."+secs+"}; do", + " grep '"+desiredContent+"' "+file+" && result=0 || result=$?", + " [ \"$result\" == 0 ] && break", + " sleep 1", + "done", + "if test \"$result\" -ne 0; then", + " "+ (failOnTimeout ? + "echo \"Couldn't find "+desiredContent+" in "+file+"; aborting\" && exit 1" : + "echo \"Couldn't find "+desiredContent+" in "+file+"; continuing\""), + "fi"); + return Joiner.on("\n").join(commands); + } + + public static String waitForPortFree(int port, Duration timeout, boolean failOnTimeout) { + long secs = Math.max(timeout.toSeconds(), 1); + + // TODO How platform-dependent are the args + output format of netstat? + // TODO Not using sudo as wrapping either netstat call or sudo(alternativesGroup(...)) fails; parentheses too confusing! + String netstatCommand = alternativesGroup( + "sudo netstat -antp --tcp", // for Centos + "sudo netstat -antp TCP"); // for OS X + + // number could appear in an IP address or as a port; look for white space at end, and dot or colon before + String grepCommand = "grep -E '(:|\\.)"+port+"($|\\s)' > /dev/null"; + + List commands = ImmutableList.of( + "for i in {1.."+secs+"}; do", + " "+BashCommands.requireExecutable("netstat"), + " "+alternativesGroup( + chainGroup("which awk", "AWK_EXEC=awk"), + chainGroup("which gawk", "AWK_EXEC=gawk"), + chainGroup("which /usr/bin/awk", "AWK_EXEC=/usr/bin/awk"), + chainGroup("echo \"No awk to determine if Port "+port+" still in use; aborting\"", "exit 1")), + " "+netstatCommand+" | $AWK_EXEC '{print $4}' | "+grepCommand+" && result=0 || result=$?", + " [ \"$result\" != 0 ] && break", + " sleep 1", + "done", + "if test \"$result\" -eq 0; then", + " "+ (failOnTimeout ? + "echo \"Port "+port+" still in use (according to netstat); aborting\" && exit 1" : + "echo \"Port "+port+" still in use (according to netstat); continuing\""), + "fi"); + return Joiner.on("\n").join(commands); + } + + public static String unzip(String file, String targetDir) { + return "unzip " + file + (Strings.isNonBlank(targetDir) ? " -d "+targetDir : ""); + } + + public static final String INSTALL_TAR = installExecutable("tar"); + public static final String INSTALL_CURL = installExecutable("curl"); + public static final String INSTALL_WGET = installExecutable("wget"); + public static final String INSTALL_ZIP = installExecutable("zip"); + public static final String INSTALL_UNZIP = alternatives(installExecutable("unzip"), installExecutable("zip")); + public static final String INSTALL_SYSSTAT = installPackage(ImmutableMap.of("onlyifmissing", "iostat"), "sysstat"); + + /** + * Returns commands to download the URL, saving as the given file. Will try each URL in turn until one is successful + * (see `curl -f` documentation). + */ + public static List commandsToDownloadUrlsAs(List urls, String saveAs) { + return Arrays.asList(INSTALL_CURL, + require(simpleDownloadUrlAs(urls, saveAs), "Could not retrieve "+saveAs+". Tried: " + Joiner.on(", ").join(urls), 9)); + } + public static String commandToDownloadUrlsAs(List urls, String saveAs) { + return chain(INSTALL_CURL, + require(simpleDownloadUrlAs(urls, saveAs), "Could not retrieve "+saveAs+". Tried: " + Joiner.on(", ").join(urls), 9)); + } + public static String commandToDownloadUrlAs(String url, String saveAs) { + return chain(INSTALL_CURL, + require(simpleDownloadUrlAs(Arrays.asList(url), saveAs), "Could not retrieve "+saveAs+" from " + url, 9)); + } + + /** + * Returns command to download the URL, sending the output to stdout -- + * suitable for redirect by appending " | tar xvf". + * Will try each URL in turn until one is successful + */ + public static String downloadToStdout(List urls) { + return chain( + INSTALL_CURL + " > /dev/null", + require(simpleDownloadUrlAs(urls, null), + "Could not retrieve file. Tried: " + Joiner.on(", ").join(urls), 9)); + } + + /** as {@link #downloadToStdout(List)} but varargs for convenience */ + public static String downloadToStdout(String ...urls) { + return downloadToStdout(Arrays.asList(urls)); + } + + /** + * Same as {@link downloadUrlAs(List, String)}, except does not install curl, and does not exit on failure, + * and if saveAs is null it downloads it so stdout. + */ + public static String simpleDownloadUrlAs(List urls, String saveAs) { + return simpleDownloadUrlAs(urls, null, null, saveAs); + } + + public static String simpleDownloadUrlAs(List urls, String user, String password, String saveAs) { + if (urls.isEmpty()) throw new IllegalArgumentException("No URLs supplied to download "+saveAs); + + List commands = new ArrayList(); + for (String url : urls) { + String command = "curl -f -L -k --retry 10 --keepalive-time 30 --speed-time 30 "; + if (user!=null && password!=null) { + command = command + format("-u %s:%s ", user, password); + } + command = command + format("\"%s\"", url); + if (saveAs!=null) { + command = command + format(" -o %s", saveAs); + } + commands.add(command); + } + return alternatives(commands); + } + + private static Object getFlag(Map flags, String flagName, Object defaultValue) { + Object found = flags.get(flagName); + return found == null ? defaultValue : found; + } + + /** + * Install a particular Java runtime, fails if not possible. + *

+ * Note Java 8 is not yet supported on SUSE + * + * @return The command to install the given Java runtime. + * @see #installJava6OrFail() + * @see #installJava7Or6OrFail() + * @see #installJavaLatestOrFail() + */ + public static String installJava(int version) { + Preconditions.checkArgument(version == 6 || version == 7 || version == 8, "Supported Java versions are 6, 7, or 8"); + return installPackageOr(MutableMap.of("apt", "openjdk-" + version + "-jdk","yum", "java-1." + version + ".0-openjdk-devel"), null, + ifExecutableElse1("zypper", chainGroup( + ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/SLE_11_SP3 java_sles_11")), + ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/openSUSE_11.4 java_suse_11")), + ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/openSUSE_12.3 java_suse_12")), + ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/openSUSE_13.1 java_suse_13")), + alternatives(installPackageOrFail(MutableMap.of("zypper", "java-1_" + version + "_0-openjdk-devel"), null), + installPackageOrFail(MutableMap.of("zypper", "java-1_" + version + "_0-ibm"), null))))); + } + + public static String installJava6() { + return installJava(6); + } + public static String installJava7() { + return installJava(7); + } + public static String installJava8() { + return installJava(8); + } + + public static String installJava6IfPossible() { + return ok(installJava6()); + } + public static String installJava7IfPossible() { + return ok(installJava7()); + } + public static String installJava8IfPossible() { + return ok(installJava8()); + } + + public static String installJava6OrFail() { + return alternatives(installJava6(), fail("java 6 install failed", 9)); + } + public static String installJava7OrFail() { + return alternatives(installJava7(), fail("java 7 install failed", 9)); + } + public static String installJava7Or6OrFail() { + return alternatives(installJava7(), installJava6(), fail("java install failed", 9)); + } + public static String installJavaLatestOrFail() { + return alternatives(installJava8(), installJava7(), installJava6(), fail("java latest install failed", 9)); + } + + public static String installJavaLatestOrWarn() { + return alternatives(installJava8(), installJava7(), installJava6(), warn("java latest install failed, entity may subsequently fail")); + } + + /** cats the given text to the given command, using bash << multi-line input syntax */ + public static String pipeTextTo(String text, String command) { + String id = Identifiers.makeRandomId(8); + return "cat << EOF_"+id+" | "+command+"\n" + +text + +"\n"+"EOF_"+id+"\n"; + } + + public static String prependToEtcHosts(String ip, String... hostnames) { + String tempFileId = "bak"+Identifiers.makeRandomId(4); + return sudo(String.format("sed -i."+tempFileId+" -e '1i\\\n%s %s' /etc/hosts", ip, Joiner.on(" ").join(hostnames))); + } + + public static String appendToEtcHosts(String ip, String... hostnames) { + // Using sed rather than `echo ... >> /etc/hosts` because when embedded inside sudo, + // the redirect doesn't get executed by sudo. + String tempFileId = "bak"+Identifiers.makeRandomId(4); + return sudo(String.format("sed -i."+tempFileId+" -e '$a\\\n%s %s' /etc/hosts", ip, Joiner.on(" ").join(hostnames))); + } + + /** + * Sets the hostname, splitting the given hostname if it contains a dot to include the unqualified and fully qualified names. + * + * @see {@link #setHostname(String, String)} + */ + @Beta + public static List setHostname(String newHostname) { + // See http://www.dns-sd.org/trailingdotsindomainnames.html. + // If we are given "abcd." then let's pass that as-is to setHostname("abcd.", null) + + if (newHostname.indexOf(".") > 0) { + String hostPart = newHostname.substring(0, newHostname.indexOf(".")); + String domainPart = newHostname.substring(hostPart.length()+1); + return setHostname(hostPart, domainPart); + } else { + return setHostname(newHostname, null); + } + } + + /** + * Sets the hostname to {@code hostPart + "." + domainPart}, or if domainPart is null/empty then {code hostPart}. + * + * @param hostPart + * @param domainPart + * @return + */ + @Beta + public static List setHostname(String hostPart, String domainPart) { + // See: + // - http://www.rackspace.com/knowledge_center/article/centos-hostname-change + // - https://wiki.debian.org/HowTo/ChangeHostname + // - http://askubuntu.com/questions/9540/how-do-i-change-the-computer-name + // + // We prepend in /etc/hosts, to ensure the right fqn appears first. + // e.g. comment in http://askubuntu.com/questions/158957/how-to-set-the-fully-qualified-domain-name-in-12-04 + // says "It's important to note that the first domain in /etc/hosts should be your FQDN. " + // + // TODO Should we run `sudo service hostname restart` or `sudo /etc/init.d/hostname restart`? + // I don't think we need to because we've run `sudo hostname ` + // + // TODO What if /etc/sysconfig/network doesn't have a line for HOSTNAME=...? + // + // TODO What about hostPart ending in "." - see http://www.dns-sd.org/trailingdotsindomainnames.html + // for what that means in DNS. However, hostname is not the same as the DNS name (hostnames + // predate the invention of DNS! - although frequently the DNS name has the same first portion + // as the hostname) so the link you gave is not relevant. However despite searching Google and + // man pages I [Ricard] am unable to find a reference which clearly states what characters are + // relevant in a hostname. I think it's safest to assume that the hostname is just [a-z,0-9,-] + // and no dots at all. + + checkNotNull(hostPart, "hostPart"); + checkArgument(!hostPart.contains("."), "hostPart '%s' must not contain '.'", hostPart); + + String tempFileId = "bak"+Identifiers.makeRandomId(4); + + List allhostnames = Lists.newArrayList(); + String fqdn = hostPart; + if (Strings.isNonBlank(domainPart)) { + fqdn = hostPart+"."+domainPart; + allhostnames.add(fqdn); + } + allhostnames.add(hostPart); + allhostnames.add("localhost"); + + return ImmutableList.of( + sudo("sed -i."+tempFileId+" -e 's/^127.0.0.1/# Replaced by Brooklyn\\\n#127.0.0.1/' /etc/hosts"), + prependToEtcHosts("127.0.0.1", allhostnames.toArray(new String[allhostnames.size()])), + ifFileExistsElse0("/etc/sysconfig/network", sudo("sed -i."+tempFileId+" -e 's/^HOSTNAME=.*$/HOSTNAME="+hostPart+"/' /etc/sysconfig/network")), + ifFileExistsElse0("/etc/hostname", sudo("sed -i."+tempFileId+" -e 's/^[a-zA-Z_0-9].*$/"+hostPart+"/' /etc/hostname")), + sudo("hostname "+hostPart)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java new file mode 100644 index 0000000..2edc9bf --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/ssh/IptablesCommands.java @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.ssh; + +import static org.apache.brooklyn.util.ssh.BashCommands.alternatives; +import static org.apache.brooklyn.util.ssh.BashCommands.chain; +import static org.apache.brooklyn.util.ssh.BashCommands.installPackage; +import static org.apache.brooklyn.util.ssh.BashCommands.sudo; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; + +public class IptablesCommands { + + public enum Chain { + INPUT, FORWARD, OUTPUT + } + + public enum Policy { + ACCEPT, REJECT, DROP, LOG + } + + /*** @deprecated since 0.7; use {@link org.apache.brooklyn.util.net.Protocol} */ + @Deprecated + public enum Protocol { + TCP("tcp"), UDP("udp"), ALL("all"); + + final String protocol; + + private Protocol(String protocol) { + this.protocol = protocol; + } + + @Override + public String toString() { + return protocol; + } + + org.apache.brooklyn.util.net.Protocol convert() { + switch (this) { + case TCP: return org.apache.brooklyn.util.net.Protocol.TCP; + case UDP: return org.apache.brooklyn.util.net.Protocol.UDP; + case ALL: return org.apache.brooklyn.util.net.Protocol.ALL; + default: throw new IllegalStateException("Unexpected protocol "+this); + } + } + } + + @Beta // implementation not portable across distros + public static String iptablesService(String cmd) { + return sudo(alternatives( + BashCommands.ifExecutableElse1("service", "service iptables " + cmd), + "/sbin/service iptables " + cmd)); + } + + @Beta // implementation not portable across distros + public static String iptablesServiceStop() { + return iptablesService("stop"); + } + + @Beta // implementation not portable across distros + public static String iptablesServiceStart() { + return iptablesService("start"); + } + + @Beta // implementation not portable across distros + public static String iptablesServiceRestart() { + return iptablesService("restart"); + } + + @Beta // implementation not portable across distros + public static String iptablesServiceStatus() { + return iptablesService("status"); + } + + @Beta // implementation not portable across distros + public static String firewalldService(String cmd) { + return sudo(alternatives( + BashCommands.ifExecutableElse1("systemctl", "systemctl " + cmd + " firewalld"), + "/usr/bin/systemctl " + cmd + " firewalld")); + } + + @Beta // implementation not portable across distros + public static String firewalldServiceStop() { + return firewalldService("stop"); + } + + @Beta // implementation not portable across distros + public static String firewalldServiceStart() { + return firewalldService("start"); + } + + @Beta // implementation not portable across distros + public static String firewalldServiceRestart() { + return firewalldService("restart"); + } + + @Beta // implementation not portable across distros + public static String firewalldServiceStatus() { + return firewalldService("status"); + } + + @Beta // implementation not portable across distros + public static String firewalldServiceIsActive() { + return firewalldService("is-active"); + } + + /** + * Returns the command that saves iptables rules on file. + * + * @return Returns the command that saves iptables rules on file. + * + */ + public static String saveIptablesRules() { + return alternatives(sudo("service iptables save"), + chain(installPackage("iptables-persistent"), sudo("/etc/init.d/iptables-persistent save"))); + } + + /** + * Returns the command that cleans up iptables rules. + * + * @return Returns the command that cleans up iptables rules. + */ + public static String cleanUpIptablesRules() { + return sudo("/sbin/iptables -F"); + } + + /** + * Returns the iptables rules. + * + * @return Returns the command that list all the iptables rules. + */ + public static String listIptablesRule() { + return sudo("/sbin/iptables -L -v -n"); + } + + /** + * Returns the command that inserts a rule on top of the iptables' rules to all interfaces. + * + * @return Returns the command that inserts a rule on top of the iptables' + * rules. + */ + public static String insertIptablesRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) { + return addIptablesRule("-I", chain, Optional. absent(), protocol, port, policy); + } + + /** @deprecated since 0.7.0; use {@link #insertIptablesRule(Chain, org.apache.brooklyn.util.net.Protocol, int, Policy)} */ + @Deprecated + public static String insertIptablesRule(Chain chain, Protocol protocol, int port, Policy policy) { + return insertIptablesRule(chain, protocol.convert(), port, policy); + } + + /** + * Returns the command that inserts a rule on top of the iptables' rules. + * + * @return Returns the command that inserts a rule on top of the iptables' + * rules. + */ + public static String insertIptablesRule(Chain chain, String networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) { + return addIptablesRule("-I", chain, Optional.of(networkInterface), protocol, port, policy); + } + + /** @deprecated since 0.7.0; use {@link #insertIptablesRule(Chain, String, org.apache.brooklyn.util.net.Protocol, int, Policy)} */ + @Deprecated + public static String insertIptablesRule(Chain chain, String networkInterface, Protocol protocol, int port, Policy policy) { + return insertIptablesRule(chain, networkInterface, protocol.convert(), port, policy); + } + + /** + * Returns the command that appends a rule to iptables to all interfaces. + * + * @return Returns the command that appends a rule to iptables. + */ + public static String appendIptablesRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) { + return addIptablesRule("-A", chain, Optional. absent(), protocol, port, policy); + } + + /** @deprecated since 0.7.0; use {@link #appendIptablesRule(Chain, org.apache.brooklyn.util.net.Protocol, int, Policy)} */ + @Deprecated + public static String appendIptablesRule(Chain chain, Protocol protocol, int port, Policy policy) { + return appendIptablesRule(chain, protocol.convert(), port, policy); + } + + /** + * Returns the command that appends a rule to iptables. + * + * @return Returns the command that appends a rule to iptables. + */ + public static String appendIptablesRule(Chain chain, String networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) { + return addIptablesRule("-A", chain, Optional.of(networkInterface), protocol, port, policy); + } + + /** @deprecated since 0.7.0; use {@link #appendIptablesRule(Chain, String, org.apache.brooklyn.util.net.Protocol, int, Policy)} */ + @Deprecated + public static String appendIptablesRule(Chain chain, String networkInterface, Protocol protocol, int port, Policy policy) { + return appendIptablesRule(chain, networkInterface, protocol.convert(), port, policy); + } + + /** + * Returns the command that creates a rule to iptables. + * + * @return Returns the command that creates a rule for iptables. + */ + public static String addIptablesRule(String direction, Chain chain, Optional networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) { + String addIptablesRule; + if(networkInterface.isPresent()) { + addIptablesRule = String.format("/sbin/iptables %s %s -i %s -p %s --dport %d -j %s", direction, chain, networkInterface.get(), protocol, port, policy); + } else { + addIptablesRule = String.format("/sbin/iptables %s %s -p %s --dport %d -j %s", direction, chain, protocol, port, policy); + } + return sudo(addIptablesRule); + } + + /** @deprecated since 0.7.0; use {@link #addIptablesRule(String, Chain, Optional, org.apache.brooklyn.util.net.Protocol, int, Policy)} */ + @Deprecated + public static String addIptablesRule(String direction, Chain chain, Optional networkInterface, Protocol protocol, int port, Policy policy) { + return addIptablesRule(direction, chain, networkInterface, protocol.convert(), port, policy); + } + + /** + * Returns the command that adds firewalld direct rule. + * + * @return Returns the command that adds firewalld direct rule. + */ + public static String addFirewalldRule(Chain chain, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) { + return addFirewalldRule(chain, Optional.absent(), protocol, port, policy); + } + + /** + * Returns the command that adds firewalld direct rule. + * + * @return Returns the command that adds firewalld direct rule. + */ + public static String addFirewalldRule(Chain chain, Optional networkInterface, org.apache.brooklyn.util.net.Protocol protocol, int port, Policy policy) { + String command = new String("/usr/bin/firewall-cmd"); + String commandPermanent = new String("/usr/bin/firewall-cmd --permanent"); + + String interfaceParameter = String.format("%s", networkInterface.isPresent() ? " -i " + networkInterface.get() : ""); + + String commandParameters = String.format(" --direct --add-rule ipv4 filter %s 0 %s -p %s --dport %d -j %s", + chain, interfaceParameter, protocol, port, policy); + + return sudo(chain(command + commandParameters, commandPermanent + commandParameters)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/stream/DelegatingPrintStream.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/stream/DelegatingPrintStream.java b/utils/common/src/main/java/org/apache/brooklyn/util/stream/DelegatingPrintStream.java new file mode 100644 index 0000000..11728c4 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/stream/DelegatingPrintStream.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.stream; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.Locale; + +/** PrintStream which simply delegates to the implementation of getDelegate() */ +public abstract class DelegatingPrintStream extends PrintStream { + + public DelegatingPrintStream() { + super(new IllegalOutputStream()); + } + + protected abstract PrintStream getDelegate(); + + public int hashCode() { + return getDelegate().hashCode(); + } + + public void write(byte[] b) throws IOException { + getDelegate().write(b); + } + + public boolean equals(Object obj) { + return getDelegate().equals(obj); + } + + public String toString() { + return getDelegate().toString(); + } + + public void flush() { + getDelegate().flush(); + } + + public void close() { + getDelegate().close(); + } + + public boolean checkError() { + return getDelegate().checkError(); + } + + public void write(int b) { + getDelegate().write(b); + } + + public void write(byte[] buf, int off, int len) { + getDelegate().write(buf, off, len); + } + + public void print(boolean b) { + getDelegate().print(b); + } + + public void print(char c) { + getDelegate().print(c); + } + + public void print(int i) { + getDelegate().print(i); + } + + public void print(long l) { + getDelegate().print(l); + } + + public void print(float f) { + getDelegate().print(f); + } + + public void print(double d) { + getDelegate().print(d); + } + + public void print(char[] s) { + getDelegate().print(s); + } + + public void print(String s) { + getDelegate().print(s); + } + + public void print(Object obj) { + getDelegate().print(obj); + } + + public void println() { + getDelegate().println(); + } + + public void println(boolean x) { + getDelegate().println(x); + } + + public void println(char x) { + getDelegate().println(x); + } + + public void println(int x) { + getDelegate().println(x); + } + + public void println(long x) { + getDelegate().println(x); + } + + public void println(float x) { + getDelegate().println(x); + } + + public void println(double x) { + getDelegate().println(x); + } + + public void println(char[] x) { + getDelegate().println(x); + } + + public void println(String x) { + getDelegate().println(x); + } + + public void println(Object x) { + getDelegate().println(x); + } + + public PrintStream printf(String format, Object... args) { + return getDelegate().printf(format, args); + } + + public PrintStream printf(Locale l, String format, Object... args) { + return getDelegate().printf(l, format, args); + } + + public PrintStream format(String format, Object... args) { + return getDelegate().format(format, args); + } + + public PrintStream format(Locale l, String format, Object... args) { + return getDelegate().format(l, format, args); + } + + public PrintStream append(CharSequence csq) { + return getDelegate().append(csq); + } + + public PrintStream append(CharSequence csq, int start, int end) { + return getDelegate().append(csq, start, end); + } + + public PrintStream append(char c) { + return getDelegate().append(c); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/stream/IllegalOutputStream.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/stream/IllegalOutputStream.java b/utils/common/src/main/java/org/apache/brooklyn/util/stream/IllegalOutputStream.java new file mode 100644 index 0000000..1fcb1e8 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/stream/IllegalOutputStream.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.stream; + +import java.io.OutputStream; + +/** output stream which throws if anyone tries to write to it */ +public class IllegalOutputStream extends OutputStream { + @Override public void write(int b) { + throw new IllegalStateException("should not write to this output stream"); + } + @Override public void write(byte[] b, int off, int len) { + throw new IllegalStateException("should not write to this output stream"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/stream/InputStreamSupplier.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/stream/InputStreamSupplier.java b/utils/common/src/main/java/org/apache/brooklyn/util/stream/InputStreamSupplier.java new file mode 100644 index 0000000..e1832f1 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/stream/InputStreamSupplier.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.stream; + +import java.io.IOException; +import java.io.InputStream; + +import com.google.common.io.InputSupplier; + +public class InputStreamSupplier implements InputSupplier { + + private final InputStream target; + + /** @deprecated since 0.7.0; use {@link InputStreamSupplier#of(InputStream)} instead */ + @Deprecated + public InputStreamSupplier(InputStream target) { + this.target = target; + } + + @Override + public InputStream getInput() throws IOException { + return target; + } + + public static InputStreamSupplier of(InputStream target) { + return new InputStreamSupplier(target); + } + + public static InputStreamSupplier fromString(String input) { + return new InputStreamSupplier(Streams.newInputStreamWithContents(input)); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/stream/KnownSizeInputStream.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/stream/KnownSizeInputStream.java b/utils/common/src/main/java/org/apache/brooklyn/util/stream/KnownSizeInputStream.java new file mode 100644 index 0000000..2580bb2 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/stream/KnownSizeInputStream.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.stream; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** an input stream, whose size we know */ +public class KnownSizeInputStream extends InputStream { + + public static KnownSizeInputStream of(String contents) { + return of(contents.getBytes()); + } + + public static KnownSizeInputStream of(byte[] contents) { + return new KnownSizeInputStream(new ByteArrayInputStream(contents), contents.length); + } + + private final long length; + private final InputStream target; + + public KnownSizeInputStream(InputStream target, long length) { + this.target = checkNotNull(target, "target"); + this.length = length; + } + + public long length() { + return length; + } + + public InputStream getTarget() { + return target; + } + + @Override + public int read() throws IOException { + return target.read(); + } + + @Override + public int hashCode() { + return target.hashCode(); + } + + @Override + public int read(byte[] b) throws IOException { + return target.read(b); + } + + @Override + public boolean equals(Object obj) { + return target.equals(obj); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return target.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return target.skip(n); + } + + @Override + public int available() throws IOException { + return target.available(); + } + + @Override + public String toString() { + return target.toString(); + } + + @Override + public void close() throws IOException { + target.close(); + } + + @Override + public void mark(int readlimit) { + target.mark(readlimit); + } + + @Override + public void reset() throws IOException { + target.reset(); + } + + @Override + public boolean markSupported() { + return target.markSupported(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/stream/ReaderInputStream.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/stream/ReaderInputStream.java b/utils/common/src/main/java/org/apache/brooklyn/util/stream/ReaderInputStream.java new file mode 100644 index 0000000..a142052 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/stream/ReaderInputStream.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.stream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +public class ReaderInputStream extends InputStream { + + /** Source Reader */ + private Reader in; + + private String encoding = System.getProperty("file.encoding"); + + private byte[] slack; + + private int begin; + + /** + * Construct a <{@link ReaderInputStream} + * for the specified {@link Reader}. + * + * @param reader {@link Reader}; must not be {@code null}. + */ + public ReaderInputStream(Reader reader) { + in = reader; + } + + /** + * Construct a {@link ReaderInputStream} + * for the specified {@link Reader}, + * with the specified encoding. + * + * @param reader non-null {@link Reader}. + * @param encoding non-null {@link String} encoding. + */ + public ReaderInputStream(Reader reader, String encoding) { + this(reader); + if (encoding == null) { + throw new IllegalArgumentException("encoding must not be null"); + } else { + this.encoding = encoding; + } + } + + /** + * Reads from the {@link Reader}, returning the same value. + * + * @return the value of the next character in the {@link Reader}. + * + * @exception IOException if the original {@link Reader} fails to be read + */ + public synchronized int read() throws IOException { + if (in == null) { + throw new IOException("Stream Closed"); + } + + byte result; + if (slack != null && begin < slack.length) { + result = slack[begin]; + if (++begin == slack.length) { + slack = null; + } + } else { + byte[] buf = new byte[1]; + if (read(buf, 0, 1) <= 0) { + result = -1; + } + result = buf[0]; + } + + if (result < -1) { + result += 256; + } + + return result; + } + + /** + * Reads from the {@link Reader} into a byte array + * + * @param b the byte array to read into + * @param off the offset in the byte array + * @param len the length in the byte array to fill + * @return the actual number read into the byte array, -1 at + * the end of the stream + * @exception IOException if an error occurs + */ + public synchronized int read(byte[] b, int off, int len) + throws IOException { + if (in == null) { + throw new IOException("Stream Closed"); + } + + while (slack == null) { + char[] buf = new char[len]; // might read too much + int n = in.read(buf); + if (n == -1) { + return -1; + } + if (n > 0) { + slack = new String(buf, 0, n).getBytes(encoding); + begin = 0; + } + } + + if (len > slack.length - begin) { + len = slack.length - begin; + } + + System.arraycopy(slack, begin, b, off, len); + + if ((begin += len) >= slack.length) { + slack = null; + } + + return len; + } + + /** + * Marks the read limit of the StringReader. + * + * @param limit the maximum limit of bytes that can be read before the + * mark position becomes invalid + */ + public synchronized void mark(final int limit) { + try { + in.mark(limit); + } catch (IOException ioe) { + throw new RuntimeException(ioe.getMessage()); + } + } + + + /** + * @return the current number of bytes ready for reading + * @exception IOException if an error occurs + */ + public synchronized int available() throws IOException { + if (in == null) { + throw new IOException("Stream Closed"); + } + if (slack != null) { + return slack.length - begin; + } + if (in.ready()) { + return 1; + } else { + return 0; + } + } + + /** + * @return false - mark is not supported + */ + public boolean markSupported () { + return false; // would be imprecise + } + + /** + * Resets the StringReader. + * + * @exception IOException if the StringReader fails to be reset + */ + public synchronized void reset() throws IOException { + if (in == null) { + throw new IOException("Stream Closed"); + } + slack = null; + in.reset(); + } + + /** + * Closes the Stringreader. + * + * @exception IOException if the original StringReader fails to be closed + */ + public synchronized void close() throws IOException { + if (in != null) { + in.close(); + slack = null; + in = null; + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/stream/StreamGobbler.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/stream/StreamGobbler.java b/utils/common/src/main/java/org/apache/brooklyn/util/stream/StreamGobbler.java new file mode 100644 index 0000000..a440336 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/stream/StreamGobbler.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.stream; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; + +public class StreamGobbler extends Thread implements Closeable { + + protected final InputStream stream; + protected final PrintStream out; + protected final Logger log; + private final AtomicBoolean running = new AtomicBoolean(true); + + public StreamGobbler(InputStream stream, OutputStream out, Logger log) { + this(stream, out != null ? new PrintStream(out) : null, log); + } + + public StreamGobbler(InputStream stream, PrintStream out, Logger log) { + this.stream = stream; + this.out = out; + this.log = log; + } + + @Override + public void close() { + running.set(false); + interrupt(); + } + + /** + * @deprecate Use close() instead. + */ + @Deprecated + public void shutdown() { + close(); + } + + String logPrefix = ""; + String printPrefix = ""; + public StreamGobbler setPrefix(String prefix) { + setLogPrefix(prefix); + setPrintPrefix(prefix); + return this; + } + public StreamGobbler setPrintPrefix(String prefix) { + printPrefix = prefix; + return this; + } + public StreamGobbler setLogPrefix(String prefix) { + logPrefix = prefix; + return this; + } + + @Override + public void run() { + int c = -1; + try { + while (running.get() && (c=stream.read())>=0) { + onChar(c); + } + onClose(); + } catch (IOException e) { + onClose(); + //TODO parametrise log level, for this error, and for normal messages + if (log!=null && log.isTraceEnabled()) log.trace(logPrefix+"exception reading from stream ("+e+")"); + } + } + + private final StringBuilder lineSoFar = new StringBuilder(16); + public void onChar(int c) { + if (c=='\n' || c=='\r') { + if (lineSoFar.length()>0) + //suppress blank lines, so that we can treat either newline char as a line separator + //(eg to show curl updates frequently) + onLine(lineSoFar.toString()); + lineSoFar.setLength(0); + } else { + lineSoFar.append((char)c); + } + } + + public void onLine(String line) { + //right trim, in case there is \r or other funnies + while (line.length()>0 && Character.isWhitespace(line.charAt(line.length()-1))) + line = line.substring(0, line.length()-1); + //right trim, in case there is \r or other funnies + while (line.length()>0 && (line.charAt(0)=='\n' || line.charAt(0)=='\r')) + line = line.substring(1); + if (!line.isEmpty()) { + if (out!=null) out.println(printPrefix+line); + if (log!=null && log.isDebugEnabled()) log.debug(logPrefix+line); + } + } + + public void onClose() { + onLine(lineSoFar.toString()); + if (out!=null) out.flush(); + lineSoFar.setLength(0); + finished = true; + synchronized (this) { notifyAll(); } + } + + private volatile boolean finished = false; + + /** convenience -- equivalent to calling join() */ + public void blockUntilFinished() throws InterruptedException { + synchronized (this) { while (!finished) wait(); } + } + + /** convenience -- similar to !Thread.isAlive() */ + public boolean isFinished() { + return finished; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java b/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java new file mode 100644 index 0000000..6d262a9 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.stream; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.nio.charset.Charset; + +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Charsets; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; +import com.google.common.io.ByteStreams; +import com.google.common.io.CharStreams; + +/** + * Methods to manage byte and character streams. + * + * @see com.google.common.io.ByteStreams + * @see com.google.common.io.CharStreams + */ +public class Streams { + + private static final Logger log = LoggerFactory.getLogger(Streams.class); + + /** drop-in non-deprecated replacement for {@link Closeable}'s deprecated closeQuiety; + * we may wish to review usages, particularly as we drop support for java 1.6, + * but until then use this instead of the deprecated method */ + @Beta + public static void closeQuietly(Closeable x) { + try { + if (x!=null) + x.close(); + } catch (Exception e) { + if (log.isDebugEnabled()) + log.debug("Error closing (ignored) "+x+": "+e); + } + } + + /** @deprecated since 0.7.0 use {@link #newInputStreamWithContents(String)} */ @Deprecated + public static InputStream fromString(String contents) { + return newInputStreamWithContents(contents); + } + + public static InputStream newInputStreamWithContents(String contents) { + byte[] bytes = checkNotNull(contents, "contents").getBytes(Charsets.UTF_8); + return KnownSizeInputStream.of(bytes); + } + + public static Reader newReaderWithContents(String contents) { + return new StringReader(contents); + } + + public static Reader reader(InputStream stream) { + return new InputStreamReader(stream); + } + + public static Reader reader(InputStream stream, Charset charset) { + return new InputStreamReader(stream, charset); + } + + /** reads the input stream fully, returning a byte array; throws unchecked exception on failure; + * to get a string, use readFully(reader(is)) or readFullyString(is) */ + public static byte[] readFully(InputStream is) { + try { + return ByteStreams.toByteArray(is); + } catch (IOException ioe) { + throw Exceptions.propagate(ioe); + } + } + + public static String readFullyString(InputStream is) { + return readFully(reader(is)); + } + + public static String readFully(Reader is) { + try { + return CharStreams.toString(is); + } catch (IOException ioe) { + throw Exceptions.propagate(ioe); + } + } + + public static void copy(InputStream input, OutputStream output) { + try { + ByteStreams.copy(input, output); + output.flush(); + } catch (IOException ioe) { + throw Exceptions.propagate(ioe); + } + } + + public static void copy(Reader input, Writer output) { + try { + CharStreams.copy(input, output); + output.flush(); + } catch (IOException ioe) { + throw Exceptions.propagate(ioe); + } + } + + public static Supplier sizeSupplier(final ByteArrayOutputStream src) { + Preconditions.checkNotNull(src); + return new Supplier() { + @Override + public Integer get() { + return src.size(); + } + }; + } + + public static Function sizeFunction() { + return new Function() { + @Override + public Integer apply(ByteArrayOutputStream input) { + return input.size(); + } + }; + } + + public static ByteArrayOutputStream byteArrayOfString(String in) { + return byteArray(in.getBytes(Charsets.UTF_8)); + } + + public static ByteArrayOutputStream byteArray(byte[] in) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + stream.write(in); + } catch (IOException e) { + throw Exceptions.propagate(e); + } + return stream; + } + + public static boolean logStreamTail(Logger log, String message, ByteArrayOutputStream stream, int max) { + if (stream!=null && stream.size()>0) { + String streamS = stream.toString(); + if (max>=0 && streamS.length()>max) + streamS = "... "+streamS.substring(streamS.length()-max); + log.info(message+":\n"+streamS); + return true; + } + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/stream/ThreadLocalPrintStream.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/stream/ThreadLocalPrintStream.java b/utils/common/src/main/java/org/apache/brooklyn/util/stream/ThreadLocalPrintStream.java new file mode 100644 index 0000000..40633d1 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/stream/ThreadLocalPrintStream.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.stream; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; + +import org.apache.commons.io.output.TeeOutputStream; + +public class ThreadLocalPrintStream extends DelegatingPrintStream { + + protected PrintStream defaultPrintStream; + protected final ThreadLocal customStream = new ThreadLocal(); + + public ThreadLocalPrintStream(PrintStream defaultPrintStream) { + this.defaultPrintStream = defaultPrintStream; + } + + @Override + public PrintStream getDelegate() { + PrintStream delegate = customStream.get(); + if (delegate!=null) return delegate; + return defaultPrintStream; + } + + /** sets the PrintStream that callers from this thread should see; + * returns any previously custom PrintStream for this thread */ + public PrintStream setThreadLocalPrintStream(OutputStream stream) { + PrintStream old = customStream.get(); + if (!(stream instanceof PrintStream)) + stream = new PrintStream(stream); + customStream.set((PrintStream)stream); + return old; + } + + public PrintStream clearThreadLocalPrintStream() { + PrintStream old = customStream.get(); + customStream.remove(); + if (old!=null) old.flush(); + return old; + } + + /** creates a capturing context which eats the output to this stream, blocking the original target */ + public OutputCapturingContext capture() { + return new OutputCapturingContext(this); + } + + /** creates a capturing context which sees the output to this stream, without interrupting the original target */ + public OutputCapturingContext captureTee() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrintStream toRestore = setThreadLocalPrintStream(new TeeOutputStream(getDelegate(), out)); + return new OutputCapturingContext(this, out, toRestore); + } + + public static class OutputCapturingContext { + protected final ThreadLocalPrintStream stream; + protected final ByteArrayOutputStream out; + protected final OutputStream streamToRestore; + protected boolean finished = false; + /** constructor which installs a ByteArrayOutputStream to this stream */ + public OutputCapturingContext(ThreadLocalPrintStream stream) { + this.stream = stream; + this.out = new ByteArrayOutputStream(); + this.streamToRestore = stream.setThreadLocalPrintStream(out); + } + /** constructor for a capturing context which is already installed */ + public OutputCapturingContext(ThreadLocalPrintStream stream, ByteArrayOutputStream capturingStream, OutputStream optionalStreamToRestore) { + this.stream = stream; + this.out = capturingStream; + this.streamToRestore = optionalStreamToRestore; + } + public String getOutputSoFar() { + return out.toString(); + } + public String end() { + if (streamToRestore!=null) + stream.setThreadLocalPrintStream(streamToRestore); + else + stream.clearThreadLocalPrintStream(); + finished = true; + return out.toString(); + } + public boolean isActive() { + return !finished; + } + public ByteArrayOutputStream getOutputStream() { + return out; + } + @Override + public String toString() { + return getOutputSoFar(); + } + public boolean isEmpty() { + return out.size()==0; + } + } + + /** installs a thread local print stream to System.out if one is not already set; + * caller may then #capture and #captureTee on it. + * @return the ThreadLocalPrintStream which System.out is using */ + public synchronized static ThreadLocalPrintStream stdout() { + PrintStream oldOut = System.out; + if (oldOut instanceof ThreadLocalPrintStream) return (ThreadLocalPrintStream)oldOut; + ThreadLocalPrintStream newOut = new ThreadLocalPrintStream(System.out); + System.setOut(newOut); + return newOut; + } + + /** installs a thread local print stream to System.err if one is not already set; + * caller may then #capture and #captureTee on it. + * @return the ThreadLocalPrintStream which System.err is using */ + public synchronized static ThreadLocalPrintStream stderr() { + PrintStream oldErr = System.err; + if (oldErr instanceof ThreadLocalPrintStream) return (ThreadLocalPrintStream)oldErr; + ThreadLocalPrintStream newErr = new ThreadLocalPrintStream(System.err); + System.setErr(newErr); + return newErr; + } + +}