View Javadoc

1   /**
2    *
3    * Copyright (C) 2011 Cloud Conscious, LLC. <info@cloudconscious.com>
4    *
5    * ====================================================================
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * 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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   * ====================================================================
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.concat;
26  import static com.google.common.collect.Iterables.filter;
27  import static com.google.common.collect.Maps.newLinkedHashMap;
28  import static com.google.common.collect.Sets.filter;
29  import static com.google.common.collect.Sets.newLinkedHashSet;
30  import static com.google.common.util.concurrent.Futures.immediateFuture;
31  import static org.jclouds.compute.predicates.NodePredicates.TERMINATED;
32  import static org.jclouds.compute.predicates.NodePredicates.all;
33  import static org.jclouds.concurrent.FutureIterables.awaitCompletion;
34  import static org.jclouds.concurrent.FutureIterables.transformParallel;
35  
36  import java.io.IOException;
37  import java.util.Map;
38  import java.util.NoSuchElementException;
39  import java.util.Set;
40  import java.util.concurrent.Callable;
41  import java.util.concurrent.ExecutorService;
42  import java.util.concurrent.Future;
43  import java.util.concurrent.TimeUnit;
44  import java.util.concurrent.atomic.AtomicReference;
45  
46  import javax.annotation.Resource;
47  import javax.inject.Inject;
48  import javax.inject.Named;
49  import javax.inject.Provider;
50  import javax.inject.Singleton;
51  
52  import org.jclouds.Constants;
53  import org.jclouds.collect.Memoized;
54  import org.jclouds.compute.ComputeService;
55  import org.jclouds.compute.ComputeServiceContext;
56  import org.jclouds.compute.RunNodesException;
57  import org.jclouds.compute.RunScriptOnNodesException;
58  import org.jclouds.compute.callables.RunScriptOnNode;
59  import org.jclouds.compute.config.CustomizationResponse;
60  import org.jclouds.compute.domain.ComputeMetadata;
61  import org.jclouds.compute.domain.ExecResponse;
62  import org.jclouds.compute.domain.Hardware;
63  import org.jclouds.compute.domain.Image;
64  import org.jclouds.compute.domain.NodeMetadata;
65  import org.jclouds.compute.domain.NodeMetadataBuilder;
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.DestroyNodeStrategy;
73  import org.jclouds.compute.strategy.GetNodeMetadataStrategy;
74  import org.jclouds.compute.strategy.InitializeRunScriptOnNodeOrPlaceInBadMap;
75  import org.jclouds.compute.strategy.ListNodesStrategy;
76  import org.jclouds.compute.strategy.RebootNodeStrategy;
77  import org.jclouds.compute.strategy.ResumeNodeStrategy;
78  import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
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.io.Payload;
84  import org.jclouds.logging.Logger;
85  import org.jclouds.predicates.RetryablePredicate;
86  import org.jclouds.scriptbuilder.domain.Statement;
87  import org.jclouds.scriptbuilder.domain.Statements;
88  import org.jclouds.util.Strings2;
89  
90  import com.google.common.base.Function;
91  import com.google.common.base.Predicate;
92  import com.google.common.base.Supplier;
93  import com.google.common.base.Throwables;
94  import com.google.common.collect.ImmutableMap;
95  import com.google.common.collect.Iterables;
96  import com.google.common.collect.LinkedHashMultimap;
97  import com.google.common.collect.Multimap;
98  
99  /**
100  * 
101  * @author Adrian Cole
102  */
103 @Singleton
104 public class BaseComputeService implements ComputeService {
105 
106    @Resource
107    @Named(ComputeServiceConstants.COMPUTE_LOGGER)
108    protected Logger logger = Logger.NULL;
109 
110    protected final ComputeServiceContext context;
111    protected final Map<String, Credentials> credentialStore;
112 
113    private final Supplier<Set<? extends Image>> images;
114    private final Supplier<Set<? extends Hardware>> hardwareProfiles;
115    private final Supplier<Set<? extends Location>> locations;
116    private final ListNodesStrategy listNodesStrategy;
117    private final GetNodeMetadataStrategy getNodeMetadataStrategy;
118    private final CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy;
119    private final RebootNodeStrategy rebootNodeStrategy;
120    private final DestroyNodeStrategy destroyNodeStrategy;
121    private final ResumeNodeStrategy resumeNodeStrategy;
122    private final SuspendNodeStrategy suspendNodeStrategy;
123    private final Provider<TemplateBuilder> templateBuilderProvider;
124    private final Provider<TemplateOptions> templateOptionsProvider;
125    private final Predicate<NodeMetadata> nodeRunning;
126    private final Predicate<NodeMetadata> nodeTerminated;
127    private final Predicate<NodeMetadata> nodeSuspended;
128    private final InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory;
129    private final Timeouts timeouts;
130    private final ExecutorService executor;
131 
132    @Inject
133    protected BaseComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
134             @Memoized Supplier<Set<? extends Image>> images,
135             @Memoized Supplier<Set<? extends Hardware>> hardwareProfiles,
136             @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy,
137             GetNodeMetadataStrategy getNodeMetadataStrategy, CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy,
138             RebootNodeStrategy rebootNodeStrategy, DestroyNodeStrategy destroyNodeStrategy,
139             ResumeNodeStrategy resumeNodeStrategy, SuspendNodeStrategy suspendNodeStrategy,
140             Provider<TemplateBuilder> templateBuilderProvider, Provider<TemplateOptions> templateOptionsProvider,
141             @Named("NODE_RUNNING") Predicate<NodeMetadata> nodeRunning,
142             @Named("NODE_TERMINATED") Predicate<NodeMetadata> nodeTerminated,
143             @Named("NODE_SUSPENDED") Predicate<NodeMetadata> nodeSuspended,
144             InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, Timeouts timeouts,
145             @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor) {
146       this.context = checkNotNull(context, "context");
147       this.credentialStore = checkNotNull(credentialStore, "credentialStore");
148       this.images = checkNotNull(images, "images");
149       this.hardwareProfiles = checkNotNull(hardwareProfiles, "hardwareProfiles");
150       this.locations = checkNotNull(locations, "locations");
151       this.listNodesStrategy = checkNotNull(listNodesStrategy, "listNodesStrategy");
152       this.getNodeMetadataStrategy = checkNotNull(getNodeMetadataStrategy, "getNodeMetadataStrategy");
153       this.runNodesAndAddToSetStrategy = checkNotNull(runNodesAndAddToSetStrategy, "runNodesAndAddToSetStrategy");
154       this.rebootNodeStrategy = checkNotNull(rebootNodeStrategy, "rebootNodeStrategy");
155       this.resumeNodeStrategy = checkNotNull(resumeNodeStrategy, "resumeNodeStrategy");
156       this.suspendNodeStrategy = checkNotNull(suspendNodeStrategy, "suspendNodeStrategy");
157       this.destroyNodeStrategy = checkNotNull(destroyNodeStrategy, "destroyNodeStrategy");
158       this.templateBuilderProvider = checkNotNull(templateBuilderProvider, "templateBuilderProvider");
159       this.templateOptionsProvider = checkNotNull(templateOptionsProvider, "templateOptionsProvider");
160       this.nodeRunning = checkNotNull(nodeRunning, "nodeRunning");
161       this.nodeTerminated = checkNotNull(nodeTerminated, "nodeTerminated");
162       this.nodeSuspended = checkNotNull(nodeSuspended, "nodeSuspended");
163       this.initScriptRunnerFactory = checkNotNull(initScriptRunnerFactory, "initScriptRunnerFactory");
164       this.timeouts = checkNotNull(timeouts, "timeouts");
165       this.executor = checkNotNull(executor, "executor");
166    }
167 
168    /**
169     * {@inheritDoc}
170     */
171    @Override
172    public ComputeServiceContext getContext() {
173       return context;
174    }
175 
176    /**
177     * {@inheritDoc}
178     */
179    @Override
180    public Set<? extends NodeMetadata> runNodesWithTag(String group, int count, Template template)
181             throws RunNodesException {
182       return createNodesInGroup(group, count, template);
183    }
184 
185    /**
186     * {@inheritDoc}
187     */
188    @Override
189    public Set<? extends NodeMetadata> runNodesWithTag(String group, int count, TemplateOptions templateOptions)
190             throws RunNodesException {
191       return createNodesInGroup(group, count, templateBuilder().any().options(templateOptions).build());
192    }
193 
194    /**
195     * {@inheritDoc}
196     */
197    @Override
198    public Set<? extends NodeMetadata> runNodesWithTag(String group, int count) throws RunNodesException {
199       return createNodesInGroup(group, count, templateOptions());
200    }
201 
202    @Override
203    public Set<? extends NodeMetadata> createNodesInGroup(String group, int count, Template template)
204             throws RunNodesException {
205       checkNotNull(group, "group cannot be null");
206       checkNotNull(template.getLocation(), "location");
207       logger.debug(">> running %d node%s group(%s) location(%s) image(%s) hardwareProfile(%s) options(%s)", count,
208                count > 1 ? "s" : "", group, template.getLocation().getId(), template.getImage().getId(), template
209                         .getHardware().getId(), template.getOptions());
210       Set<NodeMetadata> goodNodes = newLinkedHashSet();
211       Map<NodeMetadata, Exception> badNodes = newLinkedHashMap();
212       Multimap<NodeMetadata, CustomizationResponse> customizationResponses = LinkedHashMultimap.create();
213 
214       Map<?, Future<Void>> responses = runNodesAndAddToSetStrategy.execute(group, count, template, goodNodes, badNodes,
215                customizationResponses);
216       Map<?, Exception> executionExceptions = awaitCompletion(responses, executor, null, logger, "runNodesWithTag("
217                + group + ")");
218       for (NodeMetadata node : concat(goodNodes, badNodes.keySet()))
219          if (node.getCredentials() != null)
220             credentialStore.put("node#" + node.getId(), node.getCredentials());
221       if (executionExceptions.size() > 0 || badNodes.size() > 0) {
222          throw new RunNodesException(group, count, template, goodNodes, executionExceptions, badNodes);
223       }
224       return goodNodes;
225    }
226 
227    @Override
228    public Set<? extends NodeMetadata> createNodesInGroup(String group, int count, TemplateOptions templateOptions)
229             throws RunNodesException {
230       return createNodesInGroup(group, count, templateBuilder().any().options(templateOptions).build());
231 
232    }
233 
234    @Override
235    public Set<? extends NodeMetadata> createNodesInGroup(String group, int count) throws RunNodesException {
236       return createNodesInGroup(group, count, templateOptions());
237    }
238 
239    /**
240     * {@inheritDoc}
241     */
242    @Override
243    public void destroyNode(final String id) {
244       checkNotNull(id, "id");
245       logger.debug(">> destroying node(%s)", id);
246       final AtomicReference<NodeMetadata> node = new AtomicReference<NodeMetadata>();
247       RetryablePredicate<String> tester = new RetryablePredicate<String>(new Predicate<String>() {
248 
249          @Override
250          public boolean apply(String input) {
251             try {
252                NodeMetadata md = destroyNodeStrategy.destroyNode(id);
253                if (md != null)
254                   node.set(md);
255                return true;
256             } catch (IllegalStateException e) {
257                logger.warn("<< illegal state destroying node(%s)", id);
258                return false;
259             }
260          }
261 
262       }, timeouts.nodeRunning, 1000, TimeUnit.MILLISECONDS);
263       boolean successful = tester.apply(id) && (node.get() == null || nodeTerminated.apply(node.get()));
264       if (successful)
265          credentialStore.remove("node#" + id);
266       logger.debug("<< destroyed node(%s) success(%s)", id, successful);
267    }
268 
269    /**
270     * {@inheritDoc}
271     */
272    @Override
273    public Set<? extends NodeMetadata> destroyNodesMatching(Predicate<NodeMetadata> filter) {
274       logger.debug(">> destroying nodes matching(%s)", filter);
275       Set<NodeMetadata> set = newLinkedHashSet(transformParallel(nodesMatchingFilterAndNotTerminated(filter),
276                new Function<NodeMetadata, Future<NodeMetadata>>() {
277 
278                   // TODO make an async interface instead of re-wrapping
279                   @Override
280                   public Future<NodeMetadata> apply(final NodeMetadata from) {
281                      return executor.submit(new Callable<NodeMetadata>() {
282 
283                         @Override
284                         public NodeMetadata call() throws Exception {
285                            destroyNode(from.getId());
286                            return from;
287                         }
288 
289                      });
290                   }
291 
292                }, executor, null, logger, "destroyNodesMatching(" + filter + ")"));
293       logger.debug("<< destroyed(%d)", set.size());
294       return set;
295    }
296 
297    Iterable<? extends NodeMetadata> nodesMatchingFilterAndNotTerminated(Predicate<NodeMetadata> filter) {
298       return filter(detailsOnAllNodes(), and(checkNotNull(filter, "filter"), not(TERMINATED)));
299    }
300 
301    /**
302     * @throws NoSuchElementException
303     *            if none found
304     */
305    Iterable<? extends NodeMetadata> nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(
306             Predicate<NodeMetadata> filter) {
307       Iterable<? extends NodeMetadata> nodes = nodesMatchingFilterAndNotTerminated(filter);
308       if (Iterables.size(nodes) == 0)
309          throw new NoSuchElementException("no nodes matched filter: " + filter);
310       return nodes;
311    }
312 
313    /**
314     * {@inheritDoc}
315     */
316    @Override
317    public Set<ComputeMetadata> listNodes() {
318       logger.debug(">> listing nodes");
319       Set<ComputeMetadata> set = newLinkedHashSet(listNodesStrategy.listNodes());
320       logger.debug("<< list(%d)", set.size());
321       return set;
322    }
323 
324    /**
325     * {@inheritDoc}
326     */
327    @Override
328    public Set<? extends NodeMetadata> listNodesDetailsMatching(Predicate<ComputeMetadata> filter) {
329       checkNotNull(filter, "filter");
330       logger.debug(">> listing node details matching(%s)", filter);
331       Set<NodeMetadata> set = newLinkedHashSet(listNodesStrategy.listDetailsOnNodesMatching(filter));
332       logger.debug("<< list(%d)", set.size());
333       return set;
334    }
335 
336    /**
337     * {@inheritDoc}
338     */
339    @Override
340    public Set<? extends Hardware> listHardwareProfiles() {
341       return hardwareProfiles.get();
342    }
343 
344    /**
345     * {@inheritDoc}
346     */
347    @Override
348    public Set<? extends Image> listImages() {
349       return images.get();
350    }
351 
352    /**
353     * {@inheritDoc}
354     */
355    @Override
356    public Set<? extends Location> listAssignableLocations() {
357       return locations.get();
358    }
359 
360    /**
361     * {@inheritDoc}
362     */
363    @Override
364    public TemplateBuilder templateBuilder() {
365       return templateBuilderProvider.get();
366    }
367 
368    /**
369     * {@inheritDoc}
370     */
371    @Override
372    public NodeMetadata getNodeMetadata(String id) {
373       checkNotNull(id, "id");
374       return getNodeMetadataStrategy.getNode(id);
375    }
376 
377    /**
378     * {@inheritDoc}
379     */
380    @Override
381    public void rebootNode(String id) {
382       checkNotNull(id, "id");
383       logger.debug(">> rebooting node(%s)", id);
384       NodeMetadata node = rebootNodeStrategy.rebootNode(id);
385       boolean successful = nodeRunning.apply(node);
386       logger.debug("<< rebooted node(%s) success(%s)", id, successful);
387    }
388 
389    /**
390     * {@inheritDoc}
391     */
392    @Override
393    public void rebootNodesMatching(Predicate<NodeMetadata> filter) {
394       logger.debug(">> rebooting nodes matching(%s)", filter);
395       transformParallel(nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(filter),
396                new Function<NodeMetadata, Future<Void>>() {
397                   // TODO use native async
398                   @Override
399                   public Future<Void> apply(NodeMetadata from) {
400                      rebootNode(from.getId());
401                      return immediateFuture(null);
402                   }
403 
404                }, executor, null, logger, "rebootNodesMatching(" + filter + ")");
405       logger.debug("<< rebooted");
406    }
407 
408    /**
409     * {@inheritDoc}
410     */
411    @Override
412    public void resumeNode(String id) {
413       checkNotNull(id, "id");
414       logger.debug(">> resuming node(%s)", id);
415       NodeMetadata node = resumeNodeStrategy.resumeNode(id);
416       boolean successful = nodeRunning.apply(node);
417       logger.debug("<< resumed node(%s) success(%s)", id, successful);
418    }
419 
420    /**
421     * {@inheritDoc}
422     */
423    @Override
424    public void resumeNodesMatching(Predicate<NodeMetadata> filter) {
425       logger.debug(">> resuming nodes matching(%s)", filter);
426       transformParallel(nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(filter),
427                new Function<NodeMetadata, Future<Void>>() {
428                   // TODO use native async
429                   @Override
430                   public Future<Void> apply(NodeMetadata from) {
431                      resumeNode(from.getId());
432                      return immediateFuture(null);
433                   }
434 
435                }, executor, null, logger, "resumeNodesMatching(" + filter + ")");
436       logger.debug("<< resumed");
437    }
438 
439    /**
440     * {@inheritDoc}
441     */
442    @Override
443    public void suspendNode(String id) {
444       checkNotNull(id, "id");
445       logger.debug(">> suspending node(%s)", id);
446       NodeMetadata node = suspendNodeStrategy.suspendNode(id);
447       boolean successful = nodeSuspended.apply(node);
448       logger.debug("<< suspended node(%s) success(%s)", id, successful);
449    }
450 
451    /**
452     * {@inheritDoc}
453     */
454    @Override
455    public void suspendNodesMatching(Predicate<NodeMetadata> filter) {
456       logger.debug(">> suspending nodes matching(%s)", filter);
457       transformParallel(nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(filter),
458                new Function<NodeMetadata, Future<Void>>() {
459                   // TODO use native async
460                   @Override
461                   public Future<Void> apply(NodeMetadata from) {
462                      suspendNode(from.getId());
463                      return immediateFuture(null);
464                   }
465 
466                }, executor, null, logger, "suspendNodesMatching(" + filter + ")");
467       logger.debug("<< suspended");
468    }
469 
470    /**
471     * {@inheritDoc}
472     */
473    @Override
474    public Map<NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter, Payload runScript)
475             throws RunScriptOnNodesException {
476       return runScriptOnNodesMatching(filter, runScript, RunScriptOptions.NONE);
477    }
478 
479    /**
480     * {@inheritDoc}
481     */
482    @Override
483    public Map<NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter, Payload runScript,
484             RunScriptOptions options) throws RunScriptOnNodesException {
485       try {
486          return runScriptOnNodesMatching(filter, Statements.exec(Strings2.toStringAndClose(checkNotNull(runScript,
487                   "runScript").getInput())), options);
488       } catch (IOException e) {
489          Throwables.propagate(e);
490          return null;
491       }
492    }
493 
494    /**
495     * {@inheritDoc}
496     */
497    @Override
498    public Map<NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter, String runScript)
499             throws RunScriptOnNodesException {
500       return runScriptOnNodesMatching(filter, Statements.exec(checkNotNull(runScript, "runScript")));
501    }
502 
503    /**
504     * {@inheritDoc}
505     */
506    @Override
507    public Map<NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter, Statement runScript)
508             throws RunScriptOnNodesException {
509       return runScriptOnNodesMatching(filter, runScript, RunScriptOptions.NONE);
510    }
511 
512    @Override
513    public Map<? extends NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter,
514             String runScript, RunScriptOptions options) throws RunScriptOnNodesException {
515       return runScriptOnNodesMatching(filter, Statements.exec(checkNotNull(runScript, "runScript")),
516                RunScriptOptions.NONE);
517    }
518 
519    /**
520     * {@inheritDoc}
521     */
522    @Override
523    public Map<NodeMetadata, ExecResponse> runScriptOnNodesMatching(Predicate<NodeMetadata> filter, Statement runScript,
524             RunScriptOptions options) throws RunScriptOnNodesException {
525 
526       checkNotNull(filter, "filter");
527       checkNotNull(runScript, "runScript");
528       checkNotNull(options, "options");
529 
530       Map<NodeMetadata, ExecResponse> goodNodes = newLinkedHashMap();
531       Map<NodeMetadata, Exception> badNodes = newLinkedHashMap();
532       Map<NodeMetadata, Future<ExecResponse>> responses = newLinkedHashMap();
533       Map<?, Exception> exceptions = ImmutableMap.<Object, Exception> of();
534 
535       Iterable<? extends RunScriptOnNode> scriptRunners = transformNodesIntoInitializedScriptRunners(
536                nodesMatchingFilterAndNotTerminatedExceptionIfNotFound(filter), runScript, options, badNodes);
537       if (Iterables.size(scriptRunners) > 0) {
538          for (RunScriptOnNode runner : scriptRunners) {
539             responses.put(runner.getNode(), executor.submit(new RunScriptOnNodeAndAddToGoodMapOrPutExceptionIntoBadMap(
540                      runner, goodNodes, badNodes)));
541          }
542          exceptions = awaitCompletion(responses, executor, null, logger, "runScriptOnNodesMatching(" + filter + ")");
543       }
544 
545       if (exceptions.size() > 0 || badNodes.size() > 0) {
546          throw new RunScriptOnNodesException(runScript, options, goodNodes, exceptions, badNodes);
547       }
548       return goodNodes;
549    }
550 
551    private Iterable<? extends RunScriptOnNode> transformNodesIntoInitializedScriptRunners(
552             Iterable<? extends NodeMetadata> nodes, Statement script, RunScriptOptions options,
553             Map<NodeMetadata, Exception> badNodes) {
554       return filter(transformParallel(nodes, new TransformNodesIntoInitializedScriptRunners(script, options, badNodes),
555                executor, null, logger, "initialize script runners"), notNull());
556    }
557 
558    private Set<? extends NodeMetadata> detailsOnAllNodes() {
559       return newLinkedHashSet(listNodesStrategy.listDetailsOnNodesMatching(all()));
560    }
561 
562    @Override
563    public TemplateOptions templateOptions() {
564       return templateOptionsProvider.get();
565    }
566 
567    private final class TransformNodesIntoInitializedScriptRunners implements
568             Function<NodeMetadata, Future<RunScriptOnNode>> {
569       private final Map<NodeMetadata, Exception> badNodes;
570       private final Statement script;
571       private final RunScriptOptions options;
572 
573       private TransformNodesIntoInitializedScriptRunners(Statement script, RunScriptOptions options,
574                Map<NodeMetadata, Exception> badNodes) {
575          this.badNodes = checkNotNull(badNodes, "badNodes");
576          this.script = checkNotNull(script, "script");
577          this.options = checkNotNull(options, "options");
578       }
579 
580       @Override
581       public Future<RunScriptOnNode> apply(NodeMetadata node) {
582          checkNotNull(node, "node");
583          if (options.getOverrideCredentials() != null) {
584             node = NodeMetadataBuilder.fromNodeMetadata(node).credentials(options.getOverrideCredentials()).build();
585          }
586          return executor.submit(initScriptRunnerFactory.create(node, script, options, badNodes));
587       }
588    }
589 
590 }