@Override public void redeployAll() { Map<String, String> wars = MutableMap.copyOf(getConfig(WARS_BY_CONTEXT)); String redeployPrefix = "Redeploy all WARs (count " + wars.size() + ")"; log.debug("Redeplying all WARs across cluster " + this + ": " + getConfig(WARS_BY_CONTEXT)); Iterable<CanDeployAndUndeploy> targetEntities = Iterables.filter(getChildren(), CanDeployAndUndeploy.class); TaskBuilder<Void> tb = Tasks.<Void>builder() .parallel(true) .name(redeployPrefix + " across cluster (size " + Iterables.size(targetEntities) + ")"); for (Entity targetEntity : targetEntities) { TaskBuilder<Void> redeployAllToTarget = Tasks.<Void>builder() .name(redeployPrefix + " at " + targetEntity + " (after ready check)"); for (String warContextPath : wars.keySet()) { redeployAllToTarget.add( Effectors.invocation( targetEntity, DEPLOY, MutableMap.of("url", wars.get(warContextPath), "targetName", warContextPath))); } tb.add( whenServiceUp( targetEntity, redeployAllToTarget.build(), redeployPrefix + " at " + targetEntity + " when ready")); } DynamicTasks.queueIfPossible(tb.build()).orSubmitAsync(this).asTask().getUnchecked(); }
@Override public void undeploy(String targetName) { checkNotNull(targetName, "targetName"); targetName = FILENAME_TO_WEB_CONTEXT_MAPPER.convertDeploymentTargetNameToContext(targetName); // set it up so future nodes get the right wars if (!removeFromWarsByContext(this, targetName)) { DynamicTasks.submit( Tasks.warning( "Context " + targetName + " not known at " + this + "; attempting to undeploy regardless", null), this); } log.debug( "Undeploying " + targetName + " across cluster " + this + "; WARs now " + getConfig(WARS_BY_CONTEXT)); Iterable<CanDeployAndUndeploy> targets = Iterables.filter(getChildren(), CanDeployAndUndeploy.class); TaskBuilder<Void> tb = Tasks.<Void>builder() .parallel(true) .name( "Undeploy " + targetName + " across cluster (size " + Iterables.size(targets) + ")"); for (Entity target : targets) { tb.add( whenServiceUp( target, Effectors.invocation(target, UNDEPLOY, MutableMap.of("targetName", targetName)), "Undeploy " + targetName + " at " + target + " when ready")); } DynamicTasks.queueIfPossible(tb.build()).orSubmitAsync(this).asTask().getUnchecked(); // Update attribute Set<String> deployedWars = MutableSet.copyOf(getAttribute(DEPLOYED_WARS)); deployedWars.remove( FILENAME_TO_WEB_CONTEXT_MAPPER.convertDeploymentTargetNameToContext(targetName)); setAttribute(DEPLOYED_WARS, deployedWars); }
@Test public void testComplex() throws InterruptedException, ExecutionException { Task<List<?>> t = Tasks.sequential( sayTask("1"), sayTask("2"), Tasks.parallel(sayTask("4"), sayTask("3")), sayTask("5")); ec.submit(t); Assert.assertEquals(t.get().size(), 4); Asserts.assertEqualsIgnoringOrder((List<?>) t.get().get(2), ImmutableSet.of("3", "4")); Assert.assertTrue( messages.equals(Arrays.asList("1", "2", "3", "4", "5")) || messages.equals(Arrays.asList("1", "2", "4", "3", "5")), "messages=" + messages); }
@Test public void testInessentialChildrenFailureDoesNotAbortSecondaryOrFailPrimary() { Task<String> t1 = monitorableTask(null, "1", new FailCallable()); TaskTags.markInessential(t1); Task<String> t = Tasks.<String>builder() .dynamic(true) .body(monitorableJob("main")) .add(t1) .add(monitorableTask("2")) .build(); ec.submit(t); releaseAndWaitForMonitorableJob("1"); Assert.assertFalse(t.blockUntilEnded(TINY_TIME)); releaseAndWaitForMonitorableJob("2"); Assert.assertFalse(t.blockUntilEnded(TINY_TIME)); releaseMonitorableJob("main"); Assert.assertTrue(t.blockUntilEnded(TIMEOUT)); Assert.assertEquals(messages, MutableList.of("1", "2", "main")); Assert.assertTrue( stopwatch.elapsed(TimeUnit.MILLISECONDS) < TIMEOUT.toMilliseconds(), "took too long: " + stopwatch); Assert.assertFalse(t.isError()); Assert.assertTrue(t1.isError()); }
@Test public void testCancelled() throws InterruptedException, ExecutionException { Task<List<?>> t = Tasks.sequential(sayTask("1"), sayTask("2a", Duration.THIRTY_SECONDS, "2b"), sayTask("3")); ec.submit(t); synchronized (messages) { while (messages.size() <= 1) messages.wait(); } Assert.assertEquals(messages, Arrays.asList("1", "2a")); Time.sleep(Duration.millis(50)); t.cancel(true); Assert.assertTrue(t.isDone()); // 2 should get cancelled, and invoke the cancellation semaphore // 3 should get cancelled and not run at all Assert.assertEquals(messages, Arrays.asList("1", "2a")); // Need to ensure that 2 has been started; race where we might cancel it before its run method // is even begun. Hence doing "2a; pause; 2b" where nothing is interruptable before pause. Assert.assertTrue(cancellations.tryAcquire(10, TimeUnit.SECONDS)); Iterator<Task<?>> ci = ((HasTaskChildren) t).getChildren().iterator(); Assert.assertEquals(ci.next().get(), "1"); Task<?> task2 = ci.next(); Assert.assertTrue(task2.isBegun()); Assert.assertTrue(task2.isDone()); Assert.assertTrue(task2.isCancelled()); Task<?> task3 = ci.next(); Assert.assertFalse(task3.isBegun()); Assert.assertTrue(task2.isDone()); Assert.assertTrue(task2.isCancelled()); // but we do _not_ get a mutex from task3 as it does not run (is not interrupted) Assert.assertEquals(cancellations.availablePermits(), 0); }
/** * attempts to resolve hostnameTarget from origin * * @return null if it definitively can't be resolved, best-effort IP address if possible, or blank * if we could not run ssh or make sense of the output */ public static String getResolvedAddress( Entity entity, SshMachineLocation origin, String hostnameTarget) { ProcessTaskWrapper<Integer> task = SshTasks.newSshExecTaskFactory(origin, "ping -c 1 -t 1 " + hostnameTarget) .summary("checking resolution of " + hostnameTarget) .allowingNonZeroExitCode() .newTask(); DynamicTasks.queueIfPossible(task).orSubmitAndBlock(entity).asTask().blockUntilEnded(); if (task.asTask().isError()) { log.warn( "ping could not be run, at " + entity + " / " + origin + ": " + Tasks.getError(task.asTask())); return ""; } if (task.getExitCode() == null || task.getExitCode() != 0) { if (task.getExitCode() != null && task.getExitCode() < 10) { // small number means ping failed to resolve or ping the hostname log.debug( "not able to resolve " + hostnameTarget + " from " + origin + " for " + entity + " because exit code was " + task.getExitCode()); return null; } // large number means ping probably did not run log.warn( "ping not run as expected, at " + entity + " / " + origin + " (code " + task.getExitCode() + "):\n" + task.getStdout().trim() + " --- " + task.getStderr().trim()); return ""; } String out = task.getStdout(); try { String line1 = Strings.getFirstLine(out); String ip = Strings.getFragmentBetween(line1, "(", ")"); if (Strings.isNonBlank(ip)) return ip; } catch (Exception e) { Exceptions.propagateIfFatal(e); /* ignore non-parseable output */ } if (out.contains("127.0.0.1")) return "127.0.0.1"; return ""; }
@SuppressWarnings({"unchecked"}) public void start() { // TODO Previous incarnation of this logged this logged polledSensors.keySet(), but we don't // know that anymore // Is that ok, are can we do better? if (log.isDebugEnabled()) log.debug("Starting poll for {} (using {})", new Object[] {entity, this}); if (running) { throw new IllegalStateException( String.format( "Attempt to start poller %s of entity %s when already running", this, entity)); } running = true; for (final Callable<?> oneOffJob : oneOffJobs) { Task<?> task = Tasks.builder() .dynamic(false) .body((Callable<Object>) oneOffJob) .name("Poll") .description("One-time poll job " + oneOffJob) .build(); oneOffTasks.add(((EntityInternal) entity).getExecutionContext().submit(task)); } for (final PollJob<V> pollJob : pollJobs) { final String scheduleName = pollJob.handler.getDescription(); if (pollJob.pollPeriod.compareTo(Duration.ZERO) > 0) { Callable<Task<?>> pollingTaskFactory = new Callable<Task<?>>() { public Task<?> call() { DynamicSequentialTask<Void> task = new DynamicSequentialTask<Void>( MutableMap.of("displayName", scheduleName, "entity", entity), new Callable<Void>() { public Void call() { pollJob.wrappedJob.run(); return null; } }); BrooklynTaskTags.setTransient(task); return task; } }; ScheduledTask task = new ScheduledTask(MutableMap.of("period", pollJob.pollPeriod), pollingTaskFactory); tasks.add((ScheduledTask) Entities.submit(entity, task)); } else { if (log.isDebugEnabled()) log.debug( "Activating poll (but leaving off, as period {}) for {} (using {})", new Object[] {pollJob.pollPeriod, entity, this}); } } }
@Test public void testTaskBuilderUsingAddAllChildren() { Task<String> t = Tasks.<String>builder() .dynamic(true) .body(monitorableJob("main")) .addAll(ImmutableList.of(monitorableTask("1"), monitorableTask("2"))) .build(); ec.submit(t); releaseAndWaitForMonitorableJob("1"); releaseAndWaitForMonitorableJob("2"); releaseAndWaitForMonitorableJob("main"); Assert.assertEquals(messages, MutableList.of("1", "2", "main")); }
protected void beforeSubmit(Map<?, ?> flags, Task<?> task) { incompleteTaskCount.incrementAndGet(); Task<?> currentTask = Tasks.current(); if (currentTask != null) ((TaskInternal<?>) task).setSubmittedByTask(currentTask); ((TaskInternal<?>) task).setSubmitTimeUtc(System.currentTimeMillis()); if (flags.get("tag") != null) ((TaskInternal<?>) task).getMutableTags().add(flags.remove("tag")); if (flags.get("tags") != null) ((TaskInternal<?>) task).getMutableTags().addAll((Collection<?>) flags.remove("tags")); for (Object tag : ((TaskInternal<?>) task).getTags()) { getMutableTasksWithTag(tag).add(task); } }
/** * Waits for the given target to report service up, then runs the given task (often an invocation * on that entity), with the given name. If the target goes away, this task marks itself * inessential before failing so as not to cause a parent task to fail. */ static <T> Task<T> whenServiceUp(final Entity target, final TaskAdaptable<T> task, String name) { return Tasks.<T>builder() .name(name) .dynamic(true) .body( new Callable<T>() { @Override public T call() { try { while (true) { if (!Entities.isManaged(target)) { Tasks.markInessential(); throw new IllegalStateException("Target " + target + " is no longer managed"); } if (Boolean.TRUE.equals(target.getAttribute(Attributes.SERVICE_UP))) { Tasks.resetBlockingDetails(); TaskTags.markInessential(task); DynamicTasks.queue(task); try { return task.asTask().getUnchecked(); } catch (Exception e) { if (Entities.isManaged(target)) { throw Exceptions.propagate(e); } else { Tasks.markInessential(); throw new IllegalStateException( "Target " + target + " is no longer managed", e); } } } else { Tasks.setBlockingDetails("Waiting on " + target + " to be ready"); } // TODO replace with subscription? Time.sleep(Duration.ONE_SECOND); } } finally { Tasks.resetBlockingDetails(); } } }) .build(); }
@Override public void deploy(String url, String targetName) { checkNotNull(url, "url"); checkNotNull(targetName, "targetName"); targetName = FILENAME_TO_WEB_CONTEXT_MAPPER.convertDeploymentTargetNameToContext(targetName); // set it up so future nodes get the right wars addToWarsByContext(this, url, targetName); log.debug( "Deploying " + targetName + "->" + url + " across cluster " + this + "; WARs now " + getConfig(WARS_BY_CONTEXT)); Iterable<CanDeployAndUndeploy> targets = Iterables.filter(getChildren(), CanDeployAndUndeploy.class); TaskBuilder<Void> tb = Tasks.<Void>builder() .parallel(true) .name("Deploy " + targetName + " to cluster (size " + Iterables.size(targets) + ")"); for (Entity target : targets) { tb.add( whenServiceUp( target, Effectors.invocation( target, DEPLOY, MutableMap.of("url", url, "targetName", targetName)), "Deploy " + targetName + " to " + target + " when ready")); } DynamicTasks.queueIfPossible(tb.build()).orSubmitAsync(this).asTask().getUnchecked(); // Update attribute // TODO support for atomic sensor update (should be part of standard tooling; NB there is some // work towards this, according to @aledsage) Set<String> deployedWars = MutableSet.copyOf(getAttribute(DEPLOYED_WARS)); deployedWars.add(targetName); setAttribute(DEPLOYED_WARS, deployedWars); }
@Test public void testChildrenRunConcurrentlyWithPrimary() { Task<String> t = Tasks.<String>builder() .dynamic(true) .body(monitorableJob("main")) .add(monitorableTask("1")) .add(monitorableTask("2")) .build(); ec.submit(t); releaseAndWaitForMonitorableJob("1"); releaseAndWaitForMonitorableJob("main"); Assert.assertFalse(t.blockUntilEnded(TINY_TIME)); releaseMonitorableJob("2"); Assert.assertTrue(t.blockUntilEnded(TIMEOUT)); Assert.assertEquals(messages, MutableList.of("1", "main", "2")); Assert.assertTrue( stopwatch.elapsed(TimeUnit.MILLISECONDS) < TIMEOUT.toMilliseconds(), "took too long: " + stopwatch); Assert.assertFalse(t.isError()); }
public Task<String> sayTask(String message, Duration duration, String message2) { return Tasks.<String>builder().body(sayCallable(message, duration, message2)).build(); }
/** * convenience for setting "blocking details" on any task where the current thread is running, * while the passed code is executed; often used from groovy as <code> * withBlockingDetails("sleeping 5s") { Thread.sleep(5000); } </code> * * @deprecated in 0.4.0, use Tasks.withBlockingDetails */ public static Object withBlockingDetails(String description, Callable code) throws Exception { return Tasks.withBlockingDetails(description, code); }
/** * convenience for setting "blocking details" on any task where the current thread is running; * typically invoked prior to a wait, for transparency to a user; then invoked with 'null' just * after the wait * * @deprecated in 0.4.0, use Tasks.setBlockingDetails */ public static void setBlockingDetails(String description) { Tasks.setBlockingDetails(description); }
/** @deprecated in 0.4.0, use Tasks.current() */ public static Task getCurrentTask() { return Tasks.current(); }
protected Task<String> monitorableTask( final Runnable pre, final String id, final Callable<String> post) { Task<String> t = Tasks.<String>builder().body(monitorableJob(pre, id, post)).build(); monitorableTasksMap.put(id, t); return t; }
@Override public void install() { // will fail later if can't sudo (if sudo is required) DynamicTasks.queueIfPossible(SshTasks.dontRequireTtyForSudo(getMachine(), false)) .orSubmitAndBlock(); DownloadResolver nginxResolver = mgmt().getEntityDownloadsManager().newDownloader(this); List<String> nginxUrls = nginxResolver.getTargets(); String nginxSaveAs = nginxResolver.getFilename(); setExpandedInstallDir( getInstallDir() + "/" + nginxResolver.getUnpackedDirectoryName(format("nginx-%s", getVersion()))); boolean sticky = ((NginxController) entity).isSticky(); boolean isMac = getMachine().getOsDetails().isMac(); MutableMap<String, String> installGccPackageFlags = MutableMap.of( "onlyifmissing", "gcc", "yum", "gcc", "apt", "gcc", "zypper", "gcc", "port", null); MutableMap<String, String> installMakePackageFlags = MutableMap.of( "onlyifmissing", "make", "yum", "make", "apt", "make", "zypper", "make", "port", null); MutableMap<String, String> installPackageFlags = MutableMap.of( "yum", "openssl-devel pcre-devel", "apt", "libssl-dev zlib1g-dev libpcre3-dev", "zypper", "libopenssl-devel pcre-devel", "port", null); String stickyModuleVersion = entity.getConfig(NginxController.STICKY_VERSION); DownloadResolver stickyModuleResolver = mgmt() .getEntityDownloadsManager() .newDownloader( this, "stickymodule", ImmutableMap.of("addonversion", stickyModuleVersion)); List<String> stickyModuleUrls = stickyModuleResolver.getTargets(); String stickyModuleSaveAs = stickyModuleResolver.getFilename(); String stickyModuleExpandedInstallDir = String.format( "%s/src/%s", getExpandedInstallDir(), stickyModuleResolver.getUnpackedDirectoryName( "nginx-sticky-module-" + stickyModuleVersion)); List<String> cmds = Lists.newArrayList(); cmds.add(BashCommands.INSTALL_TAR); cmds.add( BashCommands.alternatives( BashCommands.ifExecutableElse0( "apt-get", BashCommands.installPackage("build-essential")), BashCommands.ifExecutableElse0( "yum", BashCommands.sudo("yum -y --nogpgcheck groupinstall \"Development Tools\"")))); cmds.add(BashCommands.installPackage(installGccPackageFlags, "nginx-prerequisites-gcc")); cmds.add(BashCommands.installPackage(installMakePackageFlags, "nginx-prerequisites-make")); cmds.add(BashCommands.installPackage(installPackageFlags, "nginx-prerequisites")); cmds.addAll(BashCommands.commandsToDownloadUrlsAs(nginxUrls, nginxSaveAs)); String pcreExpandedInstallDirname = ""; if (isMac) { String pcreVersion = entity.getConfig(NginxController.PCRE_VERSION); DownloadResolver pcreResolver = mgmt() .getEntityDownloadsManager() .newDownloader(this, "pcre", ImmutableMap.of("addonversion", pcreVersion)); List<String> pcreUrls = pcreResolver.getTargets(); String pcreSaveAs = pcreResolver.getFilename(); pcreExpandedInstallDirname = pcreResolver.getUnpackedDirectoryName("pcre-" + pcreVersion); // Install PCRE cmds.addAll(BashCommands.commandsToDownloadUrlsAs(pcreUrls, pcreSaveAs)); cmds.add(format("mkdir -p %s/pcre-dist", getInstallDir())); cmds.add(format("tar xvzf %s", pcreSaveAs)); cmds.add(format("cd %s", pcreExpandedInstallDirname)); cmds.add(format("./configure --prefix=%s/pcre-dist", getInstallDir())); cmds.add("make"); cmds.add("make install"); cmds.add("cd .."); } cmds.add(format("tar xvzf %s", nginxSaveAs)); cmds.add(format("cd %s", getExpandedInstallDir())); if (sticky) { cmds.add("cd src"); cmds.addAll(BashCommands.commandsToDownloadUrlsAs(stickyModuleUrls, stickyModuleSaveAs)); cmds.add(format("tar xvzf %s", stickyModuleSaveAs)); cmds.add("cd .."); } // Note that for OS X, not including space after "-L" because broken in 10.6.8 (but fixed in // 10.7.x) // see http://trac.nginx.org/nginx/ticket/227 String withLdOpt = entity.getConfig(NginxController.WITH_LD_OPT); if (isMac) withLdOpt = format("-L%s/pcre-dist/lib", getInstallDir()) + (Strings.isBlank(withLdOpt) ? "" : " " + withLdOpt); String withCcOpt = entity.getConfig(NginxController.WITH_CC_OPT); StringBuilder configureCommand = new StringBuilder("./configure") .append(format(" --prefix=%s/dist", getExpandedInstallDir())) .append(" --with-http_ssl_module") .append(sticky ? format(" --add-module=%s ", stickyModuleExpandedInstallDir) : "") .append(!Strings.isBlank(withLdOpt) ? format(" --with-ld-opt=\"%s\"", withLdOpt) : "") .append(!Strings.isBlank(withCcOpt) ? format(" --with-cc-opt=\"%s\"", withCcOpt) : ""); if (isMac) { configureCommand .append(" --with-pcre=") .append(getInstallDir()) .append("/") .append(pcreExpandedInstallDirname); } cmds.addAll(ImmutableList.of("mkdir -p dist", configureCommand.toString(), "make install")); ScriptHelper script = newScript(INSTALLING) .body .append(cmds) .header .prepend("set -x") .gatherOutput() .failOnNonZeroResultCode(false); int result = script.execute(); if (result != 0) { String notes = "likely an error building nginx. consult the brooklyn log ssh output for further details.\n" + "note that this Brooklyn nginx driver compiles nginx from source. " + "it attempts to install common prerequisites but this does not always succeed.\n"; OsDetails os = getMachine().getOsDetails(); if (os.isMac()) { notes += "deploying to Mac OS X, you will require Xcode and Xcode command-line tools, and on " + "some versions the pcre library (e.g. using macports, sudo port install pcre).\n"; } if (os.isWindows()) { notes += "this nginx driver is not designed for windows, unless cygwin is installed, and you are patient.\n"; } if (getEntity().getApplication().getClass().getCanonicalName().startsWith("brooklyn.demo.")) { // this is maybe naughty ... but since we use nginx in the first demo example, // and since it's actually pretty complicated, let's give a little extra hand-holding notes += "if debugging this is all a bit much and you just want to run a demo, " + "you have two fairly friendly options.\n" + "1. you can use a well known cloud, like AWS or Rackspace, where this should run " + "in a tried-and-tested Ubuntu or CentOS environment, without any problems " + "(and if it does let us know and we'll fix it!).\n" + "2. or you can just use the demo without nginx, instead access the appserver instances directly.\n"; } if (!script.getResultStderr().isEmpty()) { notes += "\n" + "STDERR\n" + script.getResultStderr() + "\n"; Streams.logStreamTail( log, "STDERR of problem in " + Tasks.current(), Streams.byteArrayOfString(script.getResultStderr()), 1024); } if (!script.getResultStdout().isEmpty()) { notes += "\n" + "STDOUT\n" + script.getResultStdout() + "\n"; Streams.logStreamTail( log, "STDOUT of problem in " + Tasks.current(), Streams.byteArrayOfString(script.getResultStdout()), 1024); } Tasks.setExtraStatusDetails(notes.trim()); throw new IllegalStateException( "Installation of nginx failed (shell returned non-zero result " + result + ")"); } }