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.domain.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.collect.Iterables.filter;
24  import static com.google.common.collect.Lists.newArrayList;
25  import static org.jclouds.compute.util.ComputeServiceUtils.getCores;
26  import static org.jclouds.compute.util.ComputeServiceUtils.getCoresAndSpeed;
27  import static org.jclouds.compute.util.ComputeServiceUtils.getSpace;
28  
29  import java.util.Arrays;
30  import java.util.List;
31  import java.util.NoSuchElementException;
32  import java.util.Set;
33  
34  import javax.annotation.Resource;
35  import javax.inject.Inject;
36  import javax.inject.Named;
37  import javax.inject.Provider;
38  
39  import org.jclouds.collect.Memoized;
40  import org.jclouds.compute.domain.ComputeMetadata;
41  import org.jclouds.compute.domain.Hardware;
42  import org.jclouds.compute.domain.Image;
43  import org.jclouds.compute.domain.OperatingSystem;
44  import org.jclouds.compute.domain.OsFamily;
45  import org.jclouds.compute.domain.Template;
46  import org.jclouds.compute.domain.TemplateBuilder;
47  import org.jclouds.compute.options.TemplateOptions;
48  import org.jclouds.compute.reference.ComputeServiceConstants;
49  import org.jclouds.domain.Location;
50  import org.jclouds.logging.Logger;
51  import org.jclouds.util.Lists2;
52  
53  import com.google.common.annotations.VisibleForTesting;
54  import com.google.common.base.Predicate;
55  import com.google.common.base.Predicates;
56  import com.google.common.base.Supplier;
57  import com.google.common.collect.ComparisonChain;
58  import com.google.common.collect.ImmutableList;
59  import com.google.common.collect.Iterables;
60  import com.google.common.collect.Ordering;
61  import com.google.common.primitives.Doubles;
62  
63  /**
64   * 
65   * @author Adrian Cole
66   */
67  @SuppressWarnings("unchecked")
68  public class TemplateBuilderImpl implements TemplateBuilder {
69  
70     @Resource
71     @Named(ComputeServiceConstants.COMPUTE_LOGGER)
72     protected Logger logger = Logger.NULL;
73  
74     protected final Supplier<Set<? extends Image>> images;
75     protected final Supplier<Set<? extends Hardware>> hardwares;
76     protected final Supplier<Set<? extends Location>> locations;
77     protected final Supplier<Location> defaultLocation;
78     protected final Provider<TemplateOptions> optionsProvider;
79     protected final Provider<TemplateBuilder> defaultTemplateProvider;
80  
81     @VisibleForTesting
82     protected Location location;
83     @VisibleForTesting
84     protected String imageId;
85     @VisibleForTesting
86     protected String hardwareId;
87     @VisibleForTesting
88     protected String imageVersion;
89     @VisibleForTesting
90     protected OsFamily osFamily;
91     @VisibleForTesting
92     protected String osVersion;
93     @VisibleForTesting
94     protected Boolean os64Bit;
95     @VisibleForTesting
96     protected String osName;
97     @VisibleForTesting
98     protected String osDescription;
99     @VisibleForTesting
100    protected String osArch;
101    @VisibleForTesting
102    protected String imageName;
103    @VisibleForTesting
104    protected String imageDescription;
105    @VisibleForTesting
106    protected double minCores;
107    @VisibleForTesting
108    protected int minRam;
109    @VisibleForTesting
110    protected boolean biggest;
111    @VisibleForTesting
112    protected boolean fastest;
113    @VisibleForTesting
114    protected TemplateOptions options;
115 
116    @Inject
117    protected TemplateBuilderImpl(@Memoized Supplier<Set<? extends Location>> locations,
118             @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> hardwares,
119             Supplier<Location> defaultLocation2, Provider<TemplateOptions> optionsProvider,
120             @Named("DEFAULT") Provider<TemplateBuilder> defaultTemplateProvider) {
121       this.locations = locations;
122       this.images = images;
123       this.hardwares = hardwares;
124       this.defaultLocation = defaultLocation2;
125       this.optionsProvider = optionsProvider;
126       this.defaultTemplateProvider = defaultTemplateProvider;
127    }
128 
129    /**
130     * If the current location id is null, then we don't care where to launch a node.
131     * 
132     * If the input location is null, then the data isn't location sensitive
133     * 
134     * If the input location is a parent of the specified location, then we are ok.
135     */
136    private final Predicate<ComputeMetadata> locationPredicate = new Predicate<ComputeMetadata>() {
137       @Override
138       public boolean apply(ComputeMetadata input) {
139          boolean returnVal = true;
140          if (location != null && input.getLocation() != null)
141             returnVal = location.equals(input.getLocation()) || location.getParent() != null
142                      && location.getParent().equals(input.getLocation()) || location.getParent().getParent() != null
143                      && location.getParent().getParent().equals(input.getLocation());
144          return returnVal;
145       }
146 
147       @Override
148       public String toString() {
149          return "location(" + location + ")";
150       }
151    };
152 
153    private final Predicate<Image> idPredicate = new Predicate<Image>() {
154       @Override
155       public boolean apply(Image input) {
156          boolean returnVal = true;
157          if (imageId != null) {
158             returnVal = imageId.equals(input.getId());
159             // match our input params so that the later predicates pass.
160             if (returnVal) {
161                fromImage(input);
162             }
163          }
164          return returnVal;
165       }
166 
167       @Override
168       public String toString() {
169          return "imageId(" + imageId + ")";
170       }
171    };
172 
173    private final Predicate<OperatingSystem> osFamilyPredicate = new Predicate<OperatingSystem>() {
174 
175       @Override
176       public boolean apply(OperatingSystem input) {
177          boolean returnVal = true;
178          if (osFamily != null)
179             returnVal = osFamily.equals(input.getFamily());
180          return returnVal;
181       }
182 
183       @Override
184       public String toString() {
185          return "osFamily(" + osFamily + ")";
186       }
187    };
188 
189    private final Predicate<OperatingSystem> osNamePredicate = new Predicate<OperatingSystem>() {
190       @Override
191       public boolean apply(OperatingSystem input) {
192          boolean returnVal = true;
193          if (osName != null) {
194             if (input.getName() == null)
195                returnVal = false;
196             else
197                returnVal = input.getName().contains(osName) || input.getName().matches(osName);
198          }
199          return returnVal;
200       }
201 
202       @Override
203       public String toString() {
204          return "osName(" + osName + ")";
205       }
206    };
207 
208    private final Predicate<OperatingSystem> osDescriptionPredicate = new Predicate<OperatingSystem>() {
209       @Override
210       public boolean apply(OperatingSystem input) {
211          boolean returnVal = true;
212          if (osDescription != null) {
213             if (input.getDescription() == null)
214                returnVal = false;
215             else
216                returnVal = input.getDescription().contains(osDescription)
217                         || input.getDescription().matches(osDescription);
218          }
219          return returnVal;
220       }
221 
222       @Override
223       public String toString() {
224          return "osDescription(" + osDescription + ")";
225       }
226    };
227 
228    private final Predicate<OperatingSystem> osVersionPredicate = new Predicate<OperatingSystem>() {
229       @Override
230       public boolean apply(OperatingSystem input) {
231          boolean returnVal = true;
232          if (osVersion != null) {
233             if (input.getVersion() == null)
234                returnVal = false;
235             else
236                returnVal = input.getVersion().contains(osVersion) || input.getVersion().matches(osVersion);
237          }
238          return returnVal;
239       }
240 
241       @Override
242       public String toString() {
243          return "osVersion(" + osVersion + ")";
244       }
245    };
246 
247    private final Predicate<OperatingSystem> os64BitPredicate = new Predicate<OperatingSystem>() {
248       @Override
249       public boolean apply(OperatingSystem input) {
250          boolean returnVal = true;
251          if (os64Bit != null) {
252             if (os64Bit)
253                return input.is64Bit();
254             else
255                return !input.is64Bit();
256          }
257          return returnVal;
258       }
259 
260       @Override
261       public String toString() {
262          return "os64Bit(" + os64Bit + ")";
263       }
264    };
265 
266    private final Predicate<OperatingSystem> osArchPredicate = new Predicate<OperatingSystem>() {
267       @Override
268       public boolean apply(OperatingSystem input) {
269          boolean returnVal = true;
270          if (osArch != null) {
271             if (input.getArch() == null)
272                returnVal = false;
273             else
274                returnVal = input.getArch().contains(osArch) || input.getArch().matches(osArch);
275          }
276          return returnVal;
277       }
278 
279       @Override
280       public String toString() {
281          return "osArch(" + osArch + ")";
282       }
283    };
284 
285    private final Predicate<Image> imageVersionPredicate = new Predicate<Image>() {
286       @Override
287       public boolean apply(Image input) {
288          boolean returnVal = true;
289          if (imageVersion != null) {
290             if (input.getVersion() == null)
291                returnVal = false;
292             else
293                returnVal = input.getVersion().contains(imageVersion) || input.getVersion().matches(imageVersion);
294          }
295          return returnVal;
296       }
297 
298       @Override
299       public String toString() {
300          return "imageVersion(" + imageVersion + ")";
301       }
302    };
303 
304    private final Predicate<Image> imageNamePredicate = new Predicate<Image>() {
305       @Override
306       public boolean apply(Image input) {
307          boolean returnVal = true;
308          if (imageName != null) {
309             if (input.getName() == null)
310                returnVal = false;
311             else
312                returnVal = input.getName().contains(imageName) || input.getName().matches(imageName);
313          }
314          return returnVal;
315       }
316 
317       @Override
318       public String toString() {
319          return "imageName(" + imageName + ")";
320       }
321    };
322    private final Predicate<Image> imageDescriptionPredicate = new Predicate<Image>() {
323       @Override
324       public boolean apply(Image input) {
325          boolean returnVal = true;
326          if (imageDescription != null) {
327             if (input.getDescription() == null)
328                returnVal = false;
329             else
330                returnVal = input.getDescription().equals(imageDescription)
331                         || input.getDescription().contains(imageDescription)
332                         || input.getDescription().matches(imageDescription);
333          }
334          return returnVal;
335       }
336 
337       @Override
338       public String toString() {
339          return "imageDescription(" + imageDescription + ")";
340       }
341    };
342    private final Predicate<Hardware> hardwareIdPredicate = new Predicate<Hardware>() {
343       @Override
344       public boolean apply(Hardware input) {
345          boolean returnVal = true;
346          if (hardwareId != null) {
347             returnVal = hardwareId.equals(input.getId());
348             // match our input params so that the later predicates pass.
349             if (returnVal) {
350                fromHardware(input);
351             }
352          }
353          return returnVal;
354       }
355 
356       @Override
357       public String toString() {
358          return "hardwareId(" + hardwareId + ")";
359       }
360    };
361 
362    private final Predicate<Hardware> hardwareCoresPredicate = new Predicate<Hardware>() {
363       @Override
364       public boolean apply(Hardware input) {
365          double cores = getCores(input);
366          return cores >= TemplateBuilderImpl.this.minCores;
367       }
368 
369       @Override
370       public String toString() {
371          return "minCores(" + minCores + ")";
372       }
373    };
374 
375    private final Predicate<Hardware> hardwareRamPredicate = new Predicate<Hardware>() {
376       @Override
377       public boolean apply(Hardware input) {
378          return input.getRam() >= TemplateBuilderImpl.this.minRam;
379       }
380 
381       @Override
382       public String toString() {
383          return "minRam(" + minRam + ")";
384       }
385    };
386    private final Predicate<Hardware> hardwarePredicate = and(hardwareIdPredicate, locationPredicate,
387             hardwareCoresPredicate, hardwareRamPredicate);
388 
389    static final Ordering<Hardware> DEFAULT_SIZE_ORDERING = new Ordering<Hardware>() {
390       public int compare(Hardware left, Hardware right) {
391          return ComparisonChain.start().compare(getCores(left), getCores(right)).compare(left.getRam(), right.getRam())
392                   .compare(getSpace(left), getSpace(right)).result();
393       }
394    };
395    static final Ordering<Hardware> BY_CORES_ORDERING = new Ordering<Hardware>() {
396       public int compare(Hardware left, Hardware right) {
397          return Doubles.compare(getCoresAndSpeed(left), getCoresAndSpeed(right));
398       }
399    };
400    static final Ordering<Image> DEFAULT_IMAGE_ORDERING = new Ordering<Image>() {
401       public int compare(Image left, Image right) {
402          return ComparisonChain.start().compare(left.getName(), right.getName(),
403                   Ordering.<String> natural().nullsLast()).compare(left.getVersion(), right.getVersion(),
404                   Ordering.<String> natural().nullsLast()).compare(left.getOperatingSystem().getName(),
405                   right.getOperatingSystem().getName(),//
406                   Ordering.<String> natural().nullsLast()).compare(left.getOperatingSystem().getVersion(),
407                   right.getOperatingSystem().getVersion(),//
408                   Ordering.<String> natural().nullsLast()).compare(left.getOperatingSystem().getDescription(),
409                   right.getOperatingSystem().getDescription(),//
410                   Ordering.<String> natural().nullsLast()).compare(left.getOperatingSystem().getArch(),
411                   right.getOperatingSystem().getArch()).result();
412       }
413    };
414 
415    /**
416     * {@inheritDoc}
417     */
418    @Override
419    public TemplateBuilder fromTemplate(Template template) {
420       fromHardware(template.getHardware());
421       fromImage(template.getImage());
422       return this;
423    }
424 
425    /**
426     * {@inheritDoc}
427     */
428    @Override
429    public TemplateBuilder fromHardware(Hardware hardware) {
430       if (currentLocationWiderThan(hardware.getLocation()))
431          this.location = hardware.getLocation();
432       this.minCores = getCores(hardware);
433       this.minRam = hardware.getRam();
434       return this;
435    }
436 
437    private boolean currentLocationWiderThan(Location location) {
438       return this.location == null || (location != null && this.location.getScope().compareTo(location.getScope()) < 0);
439    }
440 
441    /**
442     * {@inheritDoc}
443     */
444    @Override
445    public TemplateBuilder fromImage(Image image) {
446       if (currentLocationWiderThan(image.getLocation()))
447          this.location = image.getLocation();
448       if (image.getOperatingSystem().getFamily() != null)
449          this.osFamily = image.getOperatingSystem().getFamily();
450       if (image.getName() != null)
451          this.imageName = image.getName();
452       if (image.getDescription() != null)
453          this.imageDescription = image.getDescription();
454       if (image.getOperatingSystem().getName() != null)
455          this.osName = image.getOperatingSystem().getName();
456       if (image.getOperatingSystem().getDescription() != null)
457          this.osDescription = image.getOperatingSystem().getDescription();
458       if (image.getVersion() != null)
459          this.imageVersion = image.getVersion();
460       if (image.getOperatingSystem().getVersion() != null)
461          this.osVersion = image.getOperatingSystem().getVersion();
462       this.os64Bit = image.getOperatingSystem().is64Bit();
463       if (image.getOperatingSystem().getArch() != null)
464          this.osArch = image.getOperatingSystem().getArch();
465       return this;
466    }
467 
468    /**
469     * {@inheritDoc}
470     */
471    @Override
472    public TemplateBuilder smallest() {
473       this.biggest = false;
474       return this;
475    }
476 
477    /**
478     * {@inheritDoc}
479     */
480    @Override
481    public TemplateBuilder biggest() {
482       this.biggest = true;
483       return this;
484    }
485 
486    /**
487     * {@inheritDoc}
488     */
489    @Override
490    public TemplateBuilder fastest() {
491       this.fastest = true;
492       return this;
493    }
494 
495    /**
496     * {@inheritDoc}
497     */
498    @Override
499    public TemplateBuilder locationId(final String locationId) {
500       Set<? extends Location> locations = this.locations.get();
501       try {
502          this.location = Iterables.find(locations, new Predicate<Location>() {
503 
504             @Override
505             public boolean apply(Location input) {
506                return input.getId().equals(locationId);
507             }
508 
509             @Override
510             public String toString() {
511                return "locationId(" + locationId + ")";
512             }
513 
514          });
515       } catch (NoSuchElementException e) {
516          throw new NoSuchElementException(String.format("location id %s not found in: %s", locationId, locations));
517       }
518       return this;
519    }
520 
521    /**
522     * {@inheritDoc}
523     */
524    @Override
525    public TemplateBuilder osFamily(OsFamily os) {
526       this.osFamily = os;
527       return this;
528    }
529 
530    /**
531     * {@inheritDoc}
532     */
533    @Override
534    public Template build() {
535       if (nothingChangedExceptOptions()) {
536          TemplateBuilder defaultTemplate = defaultTemplateProvider.get();
537          if (options != null)
538             defaultTemplate.options(options);
539          return defaultTemplate.build();
540       }
541       if (location == null)
542          location = defaultLocation.get();
543       if (options == null)
544          options = optionsProvider.get();
545       logger.debug(">> searching params(%s)", this);
546       Set<? extends Image> images = getImages();
547       Predicate<Image> imagePredicate = buildImagePredicate();
548       Iterable<? extends Image> supportedImages = filter(images, buildImagePredicate());
549       if (Iterables.size(supportedImages) == 0)
550          throw new NoSuchElementException(String.format(
551                   "no image matched predicate %s images that didn't match below:\n%s", imagePredicate, images));
552       Hardware hardware = resolveSize(hardwareSorter(), supportedImages);
553       Image image = resolveImage(hardware, supportedImages);
554       logger.debug("<<   matched image(%s)", image);
555 
556       return new TemplateImpl(image, hardware, location, options);
557    }
558 
559    protected Hardware resolveSize(Ordering<Hardware> hardwareOrdering, final Iterable<? extends Image> images) {
560       Set<? extends Hardware> hardwaresl = hardwares.get();
561       Hardware hardware;
562       try {
563          Iterable<? extends Hardware> hardwaresThatAreCompatibleWithOurImages = filter(hardwaresl,
564                   new Predicate<Hardware>() {
565                      @Override
566                      public boolean apply(final Hardware hardware) {
567                         return Iterables.any(images, new Predicate<Image>() {
568 
569                            @Override
570                            public boolean apply(Image input) {
571                               return hardware.supportsImage().apply(input);
572                            }
573 
574                            @Override
575                            public String toString() {
576                               return "hardware(" + hardware + ").supportsImage()";
577                            }
578 
579                         });
580 
581                      }
582                   });
583          hardware = hardwareOrdering.max(filter(hardwaresThatAreCompatibleWithOurImages, hardwarePredicate));
584       } catch (NoSuchElementException exception) {
585          throw new NoSuchElementException("hardwares don't support any images: " + toString() + "\n" + hardwaresl
586                   + "\n" + images);
587       }
588       logger.debug("<<   matched hardware(%s)", hardware);
589       return hardware;
590    }
591 
592    protected Ordering<Hardware> hardwareSorter() {
593       Ordering<Hardware> hardwareOrdering = DEFAULT_SIZE_ORDERING;
594       if (!biggest)
595          hardwareOrdering = hardwareOrdering.reverse();
596       if (fastest)
597          hardwareOrdering = Ordering.compound(ImmutableList.of(BY_CORES_ORDERING, hardwareOrdering));
598       return hardwareOrdering;
599    }
600 
601    /**
602     * 
603     * @param hardware
604     * @param supportedImages
605     * @throws NoSuchElementException
606     *            if there's no image that matches the predicate
607     */
608    protected Image resolveImage(final Hardware hardware, Iterable<? extends Image> supportedImages) {
609       Predicate<Image> imagePredicate = new Predicate<Image>() {
610 
611          @Override
612          public boolean apply(Image arg0) {
613             return hardware.supportsImage().apply(arg0);
614          }
615 
616          @Override
617          public String toString() {
618             return "hardware(" + hardware + ").supportsImage()";
619          }
620       };
621       try {
622          Iterable<? extends Image> matchingImages = filter(supportedImages, imagePredicate);
623          if (logger.isTraceEnabled())
624             logger.trace("<<   matched images(%s)", matchingImages);
625          List<? extends Image> maxImages = Lists2.multiMax(DEFAULT_IMAGE_ORDERING, matchingImages);
626          if (logger.isTraceEnabled())
627             logger.trace("<<   best images(%s)", maxImages);
628          return maxImages.get(maxImages.size() - 1);
629       } catch (NoSuchElementException exception) {
630          throw new NoSuchElementException("image didn't match: " + toString() + "\n" + supportedImages);
631       }
632    }
633 
634    protected Set<? extends Image> getImages() {
635       return images.get();
636    }
637 
638    private Predicate<Image> buildImagePredicate() {
639       List<Predicate<Image>> predicates = newArrayList();
640       if (imageId != null) {
641          predicates.add(idPredicate);
642       } else {
643          if (location != null)
644             predicates.add(new Predicate<Image>() {
645 
646                @Override
647                public boolean apply(Image input) {
648                   return locationPredicate.apply(input);
649                }
650 
651                @Override
652                public String toString() {
653                   return "location(" + location + ")";
654                }
655             });
656 
657          final List<Predicate<OperatingSystem>> osPredicates = newArrayList();
658          if (osFamily != null)
659             osPredicates.add(osFamilyPredicate);
660          if (osName != null)
661             osPredicates.add(osNamePredicate);
662          if (osDescription != null)
663             osPredicates.add(osDescriptionPredicate);
664          if (osVersion != null)
665             osPredicates.add(osVersionPredicate);
666          if (os64Bit != null)
667             osPredicates.add(os64BitPredicate);
668          if (osArch != null)
669             osPredicates.add(osArchPredicate);
670          predicates.add(new Predicate<Image>() {
671 
672             @Override
673             public boolean apply(Image input) {
674                return Predicates.and(osPredicates).apply(input.getOperatingSystem());
675             }
676 
677             @Override
678             public String toString() {
679                return Predicates.and(osPredicates).toString();
680             }
681 
682          });
683          if (imageVersion != null)
684             predicates.add(imageVersionPredicate);
685          if (imageName != null)
686             predicates.add(imageNamePredicate);
687          if (imageDescription != null)
688             predicates.add(imageDescriptionPredicate);
689       }
690 
691       // looks verbose, but explicit <Image> type needed for this to compile
692       // properly
693       Predicate<Image> imagePredicate = predicates.size() == 1 ? Iterables.<Predicate<Image>> get(predicates, 0)
694                : Predicates.<Image> and(predicates);
695       return imagePredicate;
696    }
697 
698    /**
699     * {@inheritDoc}
700     */
701    @Override
702    public TemplateBuilder imageId(String imageId) {
703       this.imageId = imageId;
704       this.imageName = null;
705       this.imageDescription = null;
706       this.imageVersion = null;
707       this.osFamily = null;
708       this.osName = null;
709       this.osDescription = null;
710       this.osVersion = null;
711       this.os64Bit = null;
712       this.osArch = null;
713       return this;
714    }
715 
716    /**
717     * {@inheritDoc}
718     */
719    @Override
720    public TemplateBuilder imageNameMatches(String nameRegex) {
721       this.imageName = nameRegex;
722       return this;
723    }
724 
725    /**
726     * {@inheritDoc}
727     */
728    @Override
729    public TemplateBuilder imageDescriptionMatches(String descriptionRegex) {
730       this.imageDescription = descriptionRegex;
731       return this;
732    }
733 
734    /**
735     * {@inheritDoc}
736     */
737    @Override
738    public TemplateBuilder imageVersionMatches(String imageVersionRegex) {
739       this.imageVersion = imageVersionRegex;
740       return this;
741    }
742 
743    /**
744     * {@inheritDoc}
745     */
746    @Override
747    public TemplateBuilder osVersionMatches(String osVersionRegex) {
748       this.osVersion = osVersionRegex;
749       return this;
750    }
751 
752    /**
753     * {@inheritDoc}
754     */
755    @Override
756    public TemplateBuilder osArchMatches(String osArchitectureRegex) {
757       this.osArch = osArchitectureRegex;
758       return this;
759    }
760 
761    /**
762     * {@inheritDoc}
763     */
764    @Override
765    public TemplateBuilder minCores(double minCores) {
766       this.minCores = minCores;
767       return this;
768    }
769 
770    /**
771     * {@inheritDoc}
772     */
773    @Override
774    public TemplateBuilder minRam(int megabytes) {
775       this.minRam = megabytes;
776       return this;
777    }
778 
779    /**
780     * {@inheritDoc}
781     */
782    @Override
783    public TemplateBuilder osNameMatches(String osNameRegex) {
784       this.osName = osNameRegex;
785       return this;
786    }
787 
788    /**
789     * {@inheritDoc}
790     */
791    @Override
792    public TemplateBuilder osDescriptionMatches(String osDescriptionRegex) {
793       this.osDescription = osDescriptionRegex;
794       return this;
795    }
796 
797    /**
798     * {@inheritDoc}
799     */
800    @Override
801    public TemplateBuilder hardwareId(String hardwareId) {
802       this.hardwareId = hardwareId;
803       return this;
804    }
805 
806    /**
807     * {@inheritDoc}
808     */
809    @Override
810    public TemplateBuilder options(TemplateOptions options) {
811       this.options = optionsProvider.get();
812       copyTemplateOptions(checkNotNull(options, "options"), this.options);
813       return this;
814    }
815 
816    protected void copyTemplateOptions(TemplateOptions from, TemplateOptions to) {
817       if (!Arrays.equals(to.getInboundPorts(), from.getInboundPorts()))
818          to.inboundPorts(from.getInboundPorts());
819       if (from.getRunScript() != null)
820          to.runScript(from.getRunScript());
821       if (from.getPrivateKey() != null)
822          to.installPrivateKey(from.getPrivateKey());
823       if (from.getPublicKey() != null)
824          to.authorizePublicKey(from.getPublicKey());
825       if (from.getPort() != -1)
826          to.blockOnPort(from.getPort(), from.getSeconds());
827       if (from.isIncludeMetadata())
828          to.withMetadata();
829       if (!from.shouldBlockUntilRunning())
830          to.blockUntilRunning(false);
831       if (!from.shouldBlockOnComplete())
832          to.blockOnComplete(false);
833       if (from.getOverrideCredentials() != null)
834          to.withOverridingCredentials(from.getOverrideCredentials());
835       if (from.getTaskName() != null)
836          to.nameTask(from.getTaskName());
837    }
838 
839    @VisibleForTesting
840    boolean nothingChangedExceptOptions() {
841       return osFamily == null && location == null && imageId == null && hardwareId == null && osName == null
842                && osDescription == null && imageVersion == null && osVersion == null && osArch == null
843                && os64Bit == null && imageName == null && imageDescription == null && minCores == 0 && minRam == 0
844                && !biggest && !fastest;
845    }
846 
847    /**
848     * {@inheritDoc}
849     */
850    @Override
851    public TemplateBuilder any() {
852       return defaultTemplateProvider.get();
853    }
854 
855    @Override
856    public String toString() {
857       return "[biggest=" + biggest + ", fastest=" + fastest + ", imageName=" + imageName + ", imageDescription="
858                + imageDescription + ", imageId=" + imageId + ", imageVersion=" + imageVersion + ", location="
859                + location + ", minCores=" + minCores + ", minRam=" + minRam + ", osFamily=" + osFamily + ", osName="
860                + osName + ", osDescription=" + osDescription + ", osVersion=" + osVersion + ", osArch=" + osArch
861                + ", os64Bit=" + os64Bit + ", hardwareId=" + hardwareId + "]";
862    }
863 
864    @Override
865    public TemplateBuilder os64Bit(boolean is64Bit) {
866       this.os64Bit = is64Bit;
867       return this;
868    }
869 
870 }