View Javadoc

1   /**
2    * Licensed to jclouds, Inc. (jclouds) under one or more
3    * contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  jclouds licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.jclouds.compute.internal;
20  
21  import static com.google.common.base.Preconditions.checkNotNull;
22  import static com.google.common.base.Predicates.and;
23  import static com.google.common.base.Predicates.not;
24  import static com.google.common.base.Predicates.notNull;
25  import static com.google.common.collect.Iterables.filter;
26  import static com.google.common.collect.Maps.newLinkedHashMap;
27  import static com.google.common.collect.Sets.filter;
28  import static com.google.common.collect.Sets.newLinkedHashSet;
29  import static com.google.common.util.concurrent.Futures.immediateFuture;
30  import static org.jclouds.compute.predicates.NodePredicates.TERMINATED;
31  import static org.jclouds.compute.predicates.NodePredicates.all;
32  import static org.jclouds.concurrent.FutureIterables.awaitCompletion;
33  import static org.jclouds.concurrent.FutureIterables.transformParallel;
34  
35  import java.io.IOException;
36  import java.util.Map;
37  import java.util.NoSuchElementException;
38  import java.util.Set;
39  import java.util.concurrent.Callable;
40  import java.util.concurrent.ExecutorService;
41  import java.util.concurrent.Future;
42  import java.util.concurrent.TimeUnit;
43  import java.util.concurrent.atomic.AtomicReference;
44  
45  import javax.annotation.Resource;
46  import javax.inject.Inject;
47  import javax.inject.Named;
48  import javax.inject.Provider;
49  import javax.inject.Singleton;
50  
51  import org.jclouds.Constants;
52  import org.jclouds.collect.Memoized;
53  import org.jclouds.compute.ComputeService;
54  import org.jclouds.compute.ComputeServiceContext;
55  import org.jclouds.compute.RunNodesException;
56  import org.jclouds.compute.RunScriptOnNodesException;
57  import org.jclouds.compute.callables.RunScriptOnNode;
58  import org.jclouds.compute.config.CustomizationResponse;
59  import org.jclouds.compute.domain.ComputeMetadata;
60  import org.jclouds.compute.domain.ExecResponse;
61  import org.jclouds.compute.domain.Hardware;
62  import org.jclouds.compute.domain.Image;
63  import org.jclouds.compute.domain.NodeMetadata;
64  import org.jclouds.compute.domain.NodeMetadataBuilder;
65  import org.jclouds.compute.domain.NodeState;
66  import org.jclouds.compute.domain.Template;
67  import org.jclouds.compute.domain.TemplateBuilder;
68  import org.jclouds.compute.options.RunScriptOptions;
69  import org.jclouds.compute.options.TemplateOptions;
70  import org.jclouds.compute.reference.ComputeServiceConstants;
71  import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts;
72  import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
73  import org.jclouds.compute.strategy.DestroyNodeStrategy;
74  import org.jclouds.compute.strategy.GetNodeMetadataStrategy;
75  import org.jclouds.compute.strategy.InitializeRunScriptOnNodeOrPlaceInBadMap;
76  import org.jclouds.compute.strategy.ListNodesStrategy;
77  import org.jclouds.compute.strategy.RebootNodeStrategy;
78  import org.jclouds.compute.strategy.ResumeNodeStrategy;
79  import org.jclouds.compute.strategy.RunScriptOnNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
80  import org.jclouds.compute.strategy.SuspendNodeStrategy;
81  import org.jclouds.domain.Credentials;
82  import org.jclouds.domain.Location;
83  import org.jclouds.domain.Credentials.Builder;
84  import org.jclouds.io.Payload;
85  import org.jclouds.logging.Logger;
86  import org.jclouds.predicates.RetryablePredicate;
87  import org.jclouds.scriptbuilder.domain.Statement;
88  import org.jclouds.scriptbuilder.domain.Statements;
89  import org.jclouds.scriptbuilder.functions.InitAdminAccess;
90  import org.jclouds.util.Maps2;
91  import org.jclouds.util.Strings2;
92  
93  import com.google.common.base.Function;
94  import com.google.common.base.Predicate;
95  import com.google.common.base.Supplier;
96  import com.google.common.base.Throwables;
97  import com.google.common.collect.ImmutableMap;
98  import com.google.common.collect.ImmutableSet;
99  import com.google.common.collect.Iterables;
100 import com.google.common.collect.LinkedHashMultimap;
101 import com.google.common.collect.Multimap;
102 import com.google.common.util.concurrent.ListenableFuture;
103 
104 /**
105  * 
106  * @author Adrian Cole
107  */
108 @Singleton
109 public class BaseComputeService implements ComputeService {
110 
111    @Resource
112    @Named(ComputeServiceConstants.COMPUTE_LOGGER)
113    protected Logger logger = Logger.NULL;
114 
115    protected final ComputeServiceContext context;
116    protected final Map<String, Credentials> credentialStore;
117 
118    private final Supplier<Set<? extends Image>> images;
119    private final Supplier<Set<? extends Hardware>> hardwareProfiles;
120    private final Supplier<Set<? extends Location>> locations;
121    private final ListNodesStrategy listNodesStrategy;
122    private final GetNodeMetadataStrategy getNodeMetadataStrategy;
123    private final CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy;
124    private final RebootNodeStrategy rebootNodeStrategy;
125    private final DestroyNodeStrategy destroyNodeStrategy;
126    private final ResumeNodeStrategy resumeNodeStrategy;
127    private final SuspendNodeStrategy suspendNodeStrategy;
128    private final Provider<TemplateBuilder> templateBuilderProvider;
129    private final Provider<TemplateOptions> templateOptionsProvider;
130    private final Predicate<NodeMetadata> nodeRunning;
131    private final Predicate<NodeMetadata> nodeTerminated;
132    private final Predicate<NodeMetadata> nodeSuspended;
133    private final InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory;
134    private final Timeouts timeouts;
135    private final InitAdminAccess initAdminAccess;
136    private final PersistNodeCredentials persistNodeCredentials;
137    private final RunScriptOnNode.Factory runScriptOnNodeFactory;
138    private final ExecutorService executor;
139 
140    @Inject
141    protected BaseComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
142             @Memoized Supplier<Set<? extends Image>> images,
143             @Memoized Supplier<Set<? extends Hardware>> hardwareProfiles,
144             @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy,
145             GetNodeMetadataStrategy getNodeMetadataStrategy,
146             CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy,
147             DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy resumeNodeStrategy,
148             SuspendNodeStrategy suspendNodeStrategy, Provider<TemplateBuilder> templateBuilderProvider,
149             Provider<TemplateOptions> templateOptionsProvider,
150             @Named("NODE_RUNNING") Predicate<NodeMetadata> nodeRunning,
151             @Named("NODE_TERMINATED") Predicate<NodeMetadata> nodeTerminated,
152             @Named("NODE_SUSPENDED") Predicate<NodeMetadata> nodeSuspended,
153             InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, InitAdminAccess initAdminAccess,
154             RunScriptOnNode.Factory runScriptOnNodeFactory, PersistNodeCredentials persistNodeCredentials,
155             Timeouts timeouts, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor) {
156       this.context = checkNotNull(context, "context");
157       this.credentialStore = checkNotNull(credentialStore, "credentialStore");
158       this.images = checkNotNull(images, "images");
159       this.hardwareProfiles = checkNotNull(hardwareProfiles, "hardwareProfiles");
160       this.locations = checkNotNull(locations, "locations");
161       this.listNodesStrategy = checkNotNull(listNodesStrategy, "listNodesStrategy");
162       this.getNodeMetadataStrategy = checkNotNull(getNodeMetadataStrategy, "getNodeMetadataStrategy");
163       this.runNodesAndAddToSetStrategy = checkNotNull(runNodesAndAddToSetStrategy, "runNodesAndAddToSetStrategy");
164       this.rebootNodeStrategy = checkNotNull(rebootNodeStrategy, "rebootNodeStrategy");
165       this.resumeNodeStrategy = checkNotNull(resumeNodeStrategy, "resumeNodeStrategy");
166       this.suspendNodeStrategy = checkNotNull(suspendNodeStrategy, "suspendNodeStrategy");
167       this.destroyNodeStrategy = checkNotNull(destroyNodeStrategy, "destroyNodeStrategy");
168       this.templateBuilderProvider = checkNotNull(templateBuilderProvider, "templateBuilderProvider");
169       this.templateOptionsProvider = checkNotNull(templateOptionsProvider, "templateOptionsProvider");
170       this.nodeRunning = checkNotNull(nodeRunning, "nodeRunning");
171       this.nodeTerminated = checkNotNull(nodeTerminated, "nodeTerminated");
172       this.nodeSuspended = checkNotNull(nodeSuspended, "nodeSuspended");
173       this.initScriptRunnerFactory = checkNotNull(initScriptRunnerFactory, "initScriptRunnerFactory");
174       this.timeouts = checkNotNull(timeouts, "timeouts");
175       this.initAdminAccess = checkNotNull(initAdminAccess, "initAdminAccess");
176       this.runScriptOnNodeFactory = checkNotNull(runScriptOnNodeFactory, "runScriptOnNodeFactory");
177       this.persistNodeCredentials = checkNotNull(persistNodeCredentials, "persistNodeCredentials");
178       this.executor = checkNotNull(executor, "executor");
179    }
180 
181    /**
182     * {@inheritDoc}
183     */
184    @Override
185    public ComputeServiceContext getContext() {
186       return context;
187    }
188 
189    /**
190     * {@inheritDoc}
191     */
192    @Override
193    public Set<? extends NodeMetadata> runNodesWithTag(String group, int count, Template template)
194             throws RunNodesException {
195       return createNodesInGroup(group, count, template);
196    }
197 
198    /**
199     * {@inheritDoc}
200     */
201    @Override
202    public Set<? extends NodeMetadata> runNodesWithTag(String group, int count, TemplateOptions templateOptions)
203             throws RunNodesException {
204       return createNodesInGroup(group, count, templateBuilder().any().options(templateOptions).build());
205    }
206 
207    /**
208     * {@inheritDoc}
209     */
210    @Override
211    public Set<? extends NodeMetadata> runNodesWithTag(String group, int count) throws RunNodesException {
212       return createNodesInGroup(group, count, templateOptions());
213    }
214 
215    @Override
216    public Set<? extends NodeMetadata> createNodesInGroup(String group, int count, Template template)
217             throws RunNodesException {
218       checkNotNull(group, "group cannot be null");
219       checkNotNull(template.getLocation(), "location");
220       logger.debug(">> running %d node%s group(%s) location(%s) image(%s) hardwareProfile(%s) options(%s)", count,
221                count > 1 ? "s" : "", group, template.getLocation().getId(), template.getImage().getId(), template
222                         .getHardware().getId(), template.getOptions());
223       Set<NodeMetadata> goodNodes = newLinkedHashSet();
224       Map<NodeMetadata, Exception> badNodes = newLinkedHashMap();
225       Multimap<NodeMetadata, CustomizationResponse> customizationResponses = LinkedHashMultimap.create();
226 
227       if (template.getOptions().getRunScript() != null)
228          initAdminAccess.visit(template.getOptions().getRunScript());
229 
230       Map<?, Future<Void>> responses = runNodesAndAddToSetStrategy.execute(group, count, template, goodNodes, badNodes,
231                customizationResponses);
232       Map<?, Exception> executionExceptions = awaitCompletion(responses, executor, null, logger, "runNodesWithTag("
233                + group + ")");
234       Function<NodeMetadata, NodeMetadata> fn = persistNodeCredentials.always(template.getOptions().getRunScript());
235       badNodes = Maps2.transformKeys(badNodes, fn);
236       goodNodes = ImmutableSet.copyOf(Iterables.transform(goodNodes, fn));
237       if (executionExceptions.size() > 0 || badNodes.size() > 0) {
238          throw new RunNodesException(group, count, template, goodNodes, executionExceptions, badNodes);
239       }
240       return goodNodes;
241    }
242 
243    @Override
244    public Set<? extends NodeMetadata> createNodesInGroup(String group, int count, TemplateOptions templateOptions)
245             throws RunNodesException {
246       return createNodesInGroup(group, count, templateBuilder().any().options(templateOptions).build());
247    }
248 
249    @Override
250    public Set<? extends NodeMetadata> createNodesInGroup(String group, int count) throws RunNodesException {
251       return createNodesInGroup(group, count, templateOptions());
252    }
253 
254    /**
255     * {@inheritDoc}
256     */
257    @Override
258    public void destroyNode(final String id) {
259       checkNotNull(id, "id");
260       logger.debug(">> destroying node(%s)", id);
261       final AtomicReference<NodeMetadata> node = new AtomicReference<NodeMetadata>();
262       RetryablePredicate<String> tester = new RetryablePredicate<String>(new Predicate<String>() {
263 
264          @Override
265          public boolean apply(String input) {
266             try {
267                NodeMetadata md = destroyNodeStrategy.destroyNode(id);
268                if (md != null)
269                   node.set(md);
270                return true;
271             } catch (IllegalStateException e) {
272                logger.warn("<< illegal state destroying node(%s)", id);
273                return false;
274             }
275          }
276 
277       }, timeouts.nodeRunning, 1000, TimeUnit.MILLISECONDS);
278       boolean successful = tester.apply(id) && (node.get() == null || nodeTerminated.apply(node.get()));
279       if (successful)
280          credentialStore.remove("node#" + id);
281       logger.debug("<< destroyed node(%s) success(%s)", id, successful);
282    }
283 
284    /**
285     * {@inheritDoc}
286     */
287    @Override
288    public Set<? extends NodeMetadata> destroyNodesMatching(Predicate<NodeMetadata> filter) {
289       logger.debug(">> destroying nodes matching(%s)", filter);
290       Set<NodeMetadata> set = newLinkedHashSet(transformParallel(nodesMatchingFilterAndNotTerminated(filter),
291                new Function<NodeMetadata, Future<NodeMetadata>>() {
292 
293                   // TODO make an async interface instead of re-wrapping
294                   @Override
295                   public Future<NodeMetadata> apply(final NodeMetadata from) {
296                      return executor.submit(new Callable<NodeMetadata>() {
297 
298                         @Override
299                         public NodeMetadata call() throws Exception {
300                            destroyNode(from.getId());
301                            return from;
302                         }
303 
304                         @Override
305                         public String toString() {
306                            return "destroyNode(" + from.getId() + ")";
307                         }
308                      });
309                   }
310 
311                }, executor, null, logger, "destroyNodesMatching(" + filter + ")"));
312       logger.debug("<< destroyed(%d)", set.size());
313       return set;
314    }
315 
316    Iterable<? extends NodeMetadata> nodesMatchingFilterAndNotTerminated(Predicate<NodeMetadata> filter) {
317       return filter(detailsOnAllNodes(), and(checkNotNull(filter, "filter"), not(TERMINATED)));
318    }
319 
320    /**
321     * @throws NoSuchElementException
322     *            if none found
323     */
324    Iterable<? extends NodeMetadata> nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(
325             Predicate<NodeMetadata> filter) {
326       Iterable<? extends NodeMetadata> nodes = nodesMatchingFilterAndNotTerminated(filter);
327       if (Iterables.size(nodes) == 0)
328          throw new NoSuchElementException("no nodes matched filter: " + filter);
329       return nodes;
330    }
331 
332    /**
333     * {@inheritDoc}
334     */
335    @Override
336    public Set<ComputeMetadata> listNodes() {
337       logger.debug(">> listing nodes");
338       Set<ComputeMetadata> set = newLinkedHashSet(listNodesStrategy.listNodes());
339       logger.debug("<< list(%d)", set.size());
340       return set;
341    }
342 
343    /**
344     * {@inheritDoc}
345     */
346    @Override
347    public Set<? extends NodeMetadata> listNodesDetailsMatching(Predicate<ComputeMetadata> filter) {
348       checkNotNull(filter, "filter");
349       logger.debug(">> listing node details matching(%s)", filter);
350       Set<NodeMetadata> set = newLinkedHashSet(listNodesStrategy.listDetailsOnNodesMatching(filter));
351       logger.debug("<< list(%d)", set.size());
352       return set;
353    }
354 
355    /**
356     * {@inheritDoc}
357     */
358    @Override
359    public Set<? extends Hardware> listHardwareProfiles() {
360       return hardwareProfiles.get();
361    }
362 
363    /**
364     * {@inheritDoc}
365     */
366    @Override
367    public Set<? extends Image> listImages() {
368       return images.get();
369    }
370 
371    /**
372     * {@inheritDoc}
373     */
374    @Override
375    public Set<? extends Location> listAssignableLocations() {
376       return locations.get();
377    }
378 
379    /**
380     * {@inheritDoc}
381     */
382    @Override
383    public TemplateBuilder templateBuilder() {
384       return templateBuilderProvider.get();
385    }
386 
387    /**
388     * {@inheritDoc}
389     */
390    @Override
391    public NodeMetadata getNodeMetadata(String id) {
392       checkNotNull(id, "id");
393       return getNodeMetadataStrategy.getNode(id);
394    }
395 
396    /**
397     * {@inheritDoc}
398     */
399    @Override
400    public void rebootNode(String id) {
401       checkNotNull(id, "id");
402       logger.debug(">> rebooting node(%s)", id);
403       NodeMetadata node = rebootNodeStrategy.rebootNode(id);
404       boolean successful = nodeRunning.apply(node);
405       logger.debug("<< rebooted node(%s) success(%s)", id, successful);
406    }
407 
408    /**
409     * {@inheritDoc}
410     */
411    @Override
412    public void rebootNodesMatching(Predicate<NodeMetadata> filter) {
413       logger.debug(">> rebooting nodes matching(%s)", filter);
414       transformParallel(nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(filter),
415                new Function<NodeMetadata, Future<Void>>() {
416                   // TODO use native async
417                   @Override
418                   public Future<Void> apply(NodeMetadata from) {
419                      rebootNode(from.getId());
420                      return immediateFuture(null);
421                   }
422 
423                }, executor, null, logger, "rebootNodesMatching(" + filter + ")");
424       logger.debug("<< rebooted");
425    }
426 
427    /**
428     * {@inheritDoc}
429     */
430    @Override
431    public void resumeNode(String id) {
432       checkNotNull(id, "id");
433       logger.debug(">> resuming node(%s)", id);
434       NodeMetadata node = resumeNodeStrategy.resumeNode(id);
435       boolean successful = nodeRunning.apply(node);
436       logger.debug("<< resumed node(%s) success(%s)", id, successful);
437    }
438 
439    /**
440     * {@inheritDoc}
441     */
442    @Override
443    public void resumeNodesMatching(Predicate<NodeMetadata> filter) {
444       logger.debug(">> resuming nodes matching(%s)", filter);
445       transformParallel(nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(filter),
446                new Function<NodeMetadata, Future<Void>>() {
447                   // TODO use native async
448                   @Override
449                   public Future<Void> apply(NodeMetadata from) {
450                      resumeNode(from.getId());
451                      return immediateFuture(null);
452                   }
453 
454                }, executor, null, logger, "resumeNodesMatching(" + filter + ")");
455       logger.debug("<< resumed");
456    }
457 
458    /**
459     * {@inheritDoc}
460     */
461    @Override
462    public void suspendNode(String id) {
463       checkNotNull(id, "id");
464       logger.debug(">> suspending node(%s)", id);
465       NodeMetadata node = suspendNodeStrategy.suspendNode(id);
466       boolean successful = nodeSuspended.apply(node);
467       logger.debug("<< suspended node(%s) success(%s)", id, successful);
468    }
469 
470    /**
471     * {@inheritDoc}
472     */
473    @Override
474    public void suspendNodesMatching(Predicate<NodeMetadata> filter) {
475       logger.debug(">> suspending nodes matching(%s)", filter);
476       transformParallel(nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(filter),
477                new Function<NodeMetadata, Future<Void>>() {
478                   // TODO use native async
479                   @Override
480                   public Future<Void> apply(NodeMetadata from) {
481                      suspendNode(from.getId());
482                      return immediateFuture(null);
483                   }
484 
485                }, executor, null, logger, "suspendNodesMatching(" + filter + ")");
486       logger.debug("<< suspended");
487    }
488 
489    /**
490     * {@inheritDoc}
491     */
492    @Override
493    public Map<NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter, Payload runScript)
494             throws RunScriptOnNodesException {
495       return runScriptOnNodesMatching(filter, runScript, RunScriptOptions.NONE);
496    }
497 
498    /**
499     * {@inheritDoc}
500     */
501    @Override
502    public Map<NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter, Payload runScript,
503             RunScriptOptions options) throws RunScriptOnNodesException {
504       try {
505          return runScriptOnNodesMatching(filter, Statements.exec(Strings2.toStringAndClose(checkNotNull(runScript,
506                   "runScript").getInput())), options);
507       } catch (IOException e) {
508          Throwables.propagate(e);
509          return null;
510       }
511    }
512 
513    /**
514     * {@inheritDoc}
515     */
516    @Override
517    public Map<NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter, String runScript)
518             throws RunScriptOnNodesException {
519       return runScriptOnNodesMatching(filter, Statements.exec(checkNotNull(runScript, "runScript")));
520    }
521 
522    /**
523     * {@inheritDoc}
524     */
525    @Override
526    public Map<NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter, Statement runScript)
527             throws RunScriptOnNodesException {
528       return runScriptOnNodesMatching(filter, runScript, RunScriptOptions.NONE);
529    }
530 
531    @Override
532    public Map<? extends NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter,
533             String runScript, RunScriptOptions options) throws RunScriptOnNodesException {
534       return runScriptOnNodesMatching(filter, Statements.exec(checkNotNull(runScript, "runScript")), options);
535    }
536 
537    /**
538     * {@inheritDoc}
539     */
540    @Override
541    public Map<NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter, Statement runScript,
542             RunScriptOptions options) throws RunScriptOnNodesException {
543 
544       checkNotNull(filter, "filter");
545       checkNotNull(runScript, "runScript");
546       checkNotNull(options, "options");
547 
548       Map<NodeMetadata, ExecResponse> goodNodes = newLinkedHashMap();
549       Map<NodeMetadata, Exception> badNodes = newLinkedHashMap();
550       Map<NodeMetadata, Future<ExecResponse>> responses = newLinkedHashMap();
551       Map<?, Exception> exceptions = ImmutableMap.<Object, Exception> of();
552 
553       initAdminAccess.visit(runScript);
554 
555       Iterable<? extends RunScriptOnNode> scriptRunners = transformNodesIntoInitializedScriptRunners(
556                nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(filter), runScript, options, badNodes);
557       if (Iterables.size(scriptRunners) > 0) {
558          for (RunScriptOnNode runner : scriptRunners) {
559             responses.put(runner.getNode(), executor.submit(new RunScriptOnNodeAndAddToGoodMapOrPutExceptionIntoBadMap(
560                      runner, goodNodes, badNodes)));
561          }
562          exceptions = awaitCompletion(responses, executor, null, logger, "runScriptOnNodesMatching(" + filter + ")");
563       }
564 
565       Function<NodeMetadata, NodeMetadata> fn = persistNodeCredentials.ifAdminAccess(runScript);
566       badNodes = Maps2.transformKeys(badNodes, fn);
567       goodNodes = Maps2.transformKeys(goodNodes, fn);
568 
569       if (exceptions.size() > 0 || badNodes.size() > 0) {
570          throw new RunScriptOnNodesException(runScript, options, goodNodes, exceptions, badNodes);
571       }
572       return goodNodes;
573    }
574 
575    /**
576     * {@inheritDoc}
577     */
578    @Override
579    public ExecResponse runScriptOnNode(String id, String runScript) {
580       return runScriptOnNode(id, runScript, RunScriptOptions.NONE);
581    }
582 
583    /**
584     * {@inheritDoc}
585     */
586    @Override
587    public ExecResponse runScriptOnNode(String id, String runScript, RunScriptOptions options) {
588       return runScriptOnNode(id, Statements.exec(checkNotNull(runScript, "runScript")), options);
589    }
590 
591    /**
592     * {@inheritDoc}
593     */
594    @Override
595    public ExecResponse runScriptOnNode(String id, Statement runScript) {
596       return runScriptOnNode(id, runScript, RunScriptOptions.NONE);
597    }
598 
599    /**
600     * {@inheritDoc}
601     */
602    @Override
603    public ExecResponse runScriptOnNode(String id, Statement runScript, RunScriptOptions options) {
604       NodeMetadata node = this.getNodeMetadata(id);
605       if (node == null)
606          throw new NoSuchElementException(id);
607       if (node.getState() != NodeState.RUNNING)
608          throw new IllegalStateException("node " + id
609                   + " needs to be running before executing a script on it. current state: " + node.getState());
610       initAdminAccess.visit(runScript);
611       node = updateNodeWithCredentialsIfPresent(node, options);
612       ExecResponse response = runScriptOnNodeFactory.create(node, runScript, options).init().call();
613       persistNodeCredentials.ifAdminAccess(runScript).apply(node);
614       return response;
615    }
616 
617    /**
618     * {@inheritDoc}
619     */
620    @Override
621    public ListenableFuture<ExecResponse> submitScriptOnNode(String id, final Statement runScript,
622             RunScriptOptions options) {
623       NodeMetadata node = this.getNodeMetadata(id);
624       if (node == null)
625          throw new NoSuchElementException(id);
626       if (node.getState() != NodeState.RUNNING)
627          throw new IllegalStateException("node " + id
628                   + " needs to be running before executing a script on it. current state: " + node.getState());
629       initAdminAccess.visit(runScript);
630       final NodeMetadata node1 = updateNodeWithCredentialsIfPresent(node, options);
631       ListenableFuture<ExecResponse> response = runScriptOnNodeFactory.submit(node1, runScript, options);
632       response.addListener(new Runnable() {
633 
634          @Override
635          public void run() {
636             persistNodeCredentials.ifAdminAccess(runScript).apply(node1);
637          }
638 
639       }, executor);
640       return response;
641    }
642 
643    private Iterable<? extends RunScriptOnNode> transformNodesIntoInitializedScriptRunners(
644             Iterable<? extends NodeMetadata> nodes, Statement script, RunScriptOptions options,
645             Map<NodeMetadata, Exception> badNodes) {
646       return filter(transformParallel(nodes, new TransformNodesIntoInitializedScriptRunners(script, options, badNodes),
647                executor, null, logger, "initialize script runners"), notNull());
648    }
649 
650    private Set<? extends NodeMetadata> detailsOnAllNodes() {
651       return newLinkedHashSet(listNodesStrategy.listDetailsOnNodesMatching(all()));
652    }
653 
654    @Override
655    public TemplateOptions templateOptions() {
656       return templateOptionsProvider.get();
657    }
658 
659    protected NodeMetadata updateNodeWithCredentialsIfPresent(NodeMetadata node, RunScriptOptions options) {
660       checkNotNull(node, "node");
661       if (options.getOverridingCredentials() != null) {
662          Builder<? extends Credentials> builder = node.getCredentials() != null ? node.getCredentials().toBuilder()
663                   : new Credentials.Builder<Credentials>();
664          if (options.getOverridingCredentials().identity != null)
665             builder.identity(options.getOverridingCredentials().identity);
666          if (options.getOverridingCredentials().credential != null) {
667             // custom credentials are related to the input
668             builder = options.getOverridingCredentials().toBuilder();
669             Credentials cred = builder.build();
670             builder.identity(cred.identity);
671             builder.credential(options.getOverridingCredentials().credential);
672          }
673          node = NodeMetadataBuilder.fromNodeMetadata(node).credentials(builder.build()).build();
674       }
675       return node;
676    }
677 
678    private final class TransformNodesIntoInitializedScriptRunners implements
679             Function<NodeMetadata, Future<RunScriptOnNode>> {
680       private final Map<NodeMetadata, Exception> badNodes;
681       private final Statement script;
682       private final RunScriptOptions options;
683 
684       private TransformNodesIntoInitializedScriptRunners(Statement script, RunScriptOptions options,
685                Map<NodeMetadata, Exception> badNodes) {
686          this.badNodes = checkNotNull(badNodes, "badNodes");
687          this.script = checkNotNull(script, "script");
688          this.options = checkNotNull(options, "options");
689       }
690 
691       @Override
692       public Future<RunScriptOnNode> apply(NodeMetadata node) {
693          node = updateNodeWithCredentialsIfPresent(node, options);
694          return executor.submit(initScriptRunnerFactory.create(node, script, options, badNodes));
695       }
696 
697    }
698 
699 }