github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/environs/instances/image.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package instances
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/utils/arch"
    12  
    13  	"github.com/juju/juju/constraints"
    14  	"github.com/juju/juju/environs/imagemetadata"
    15  )
    16  
    17  var logger = loggo.GetLogger("juju.environs.instances")
    18  
    19  // InstanceConstraint constrains the possible instances that may be
    20  // chosen by the environment provider.
    21  type InstanceConstraint struct {
    22  	Region      string
    23  	Series      string
    24  	Arches      []string
    25  	Constraints constraints.Value
    26  
    27  	// Optional filtering criteria not supported by all providers. These
    28  	// attributes are not specified by the user as a constraint but rather
    29  	// passed in by the provider implementation to restrict the choice of
    30  	// available images.
    31  
    32  	// Storage specifies a list of storage types, in order of preference.
    33  	// eg ["ssd", "ebs"] means find images with ssd storage, but if none
    34  	// exist, find those with ebs instead.
    35  	Storage []string
    36  }
    37  
    38  // String returns a human readable form of this InstanceConstraint.
    39  func (ic *InstanceConstraint) String() string {
    40  	return fmt.Sprintf(
    41  		"{region: %s, series: %s, arches: %s, constraints: %s, storage: %s}",
    42  		ic.Region,
    43  		ic.Series,
    44  		ic.Arches,
    45  		ic.Constraints,
    46  		ic.Storage,
    47  	)
    48  }
    49  
    50  // InstanceSpec holds an instance type name and the chosen image info.
    51  type InstanceSpec struct {
    52  	InstanceType InstanceType
    53  	Image        Image
    54  	// order is used to sort InstanceSpec based on the input InstanceTypes.
    55  	order int
    56  }
    57  
    58  // FindInstanceSpec returns an InstanceSpec satisfying the supplied InstanceConstraint.
    59  // possibleImages contains a list of images matching the InstanceConstraint.
    60  // allInstanceTypes provides information on every known available instance type (name, memory, cpu cores etc) on
    61  // which instances can be run. The InstanceConstraint is used to filter allInstanceTypes and then a suitable image
    62  // compatible with the matching instance types is returned.
    63  func FindInstanceSpec(possibleImages []Image, ic *InstanceConstraint, allInstanceTypes []InstanceType) (*InstanceSpec, error) {
    64  	logger.Debugf("instance constraints %+v", ic)
    65  	if len(possibleImages) == 0 {
    66  		return nil, fmt.Errorf("no %q images in %s with arches %s",
    67  			ic.Series, ic.Region, ic.Arches)
    68  	}
    69  
    70  	logger.Debugf("matching constraints %v against possible image metadata %+v", ic, possibleImages)
    71  	matchingTypes, err := MatchingInstanceTypes(allInstanceTypes, ic.Region, ic.Constraints)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	if len(matchingTypes) == 0 {
    76  		return nil, fmt.Errorf("no instance types found matching constraint: %s", ic)
    77  	}
    78  
    79  	// We check for exact matches (all attributes matching), and also for
    80  	// partial matches (instance type specifies attribute, but image does
    81  	// not). Exact matches always take precedence.
    82  	var exactSpecs, partialSpecs []*InstanceSpec
    83  	for _, itype := range matchingTypes {
    84  		for _, image := range possibleImages {
    85  			specs := &partialSpecs
    86  			switch image.match(itype) {
    87  			case exactMatch:
    88  				specs = &exactSpecs
    89  				fallthrough
    90  			case partialMatch:
    91  				*specs = append(*specs, &InstanceSpec{
    92  					InstanceType: itype,
    93  					Image:        image,
    94  					order:        len(*specs),
    95  				})
    96  			}
    97  		}
    98  	}
    99  
   100  	specs := exactSpecs
   101  	if len(specs) == 0 {
   102  		specs = partialSpecs
   103  	}
   104  	if len(specs) > 0 {
   105  		sort.Sort(byArch(specs))
   106  		logger.Infof("find instance - using image with id: %v", specs[0].Image.Id)
   107  		return specs[0], nil
   108  	}
   109  
   110  	names := make([]string, len(matchingTypes))
   111  	for i, itype := range matchingTypes {
   112  		names[i] = itype.Name
   113  	}
   114  	return nil, fmt.Errorf("no %q images in %s matching instance types %v", ic.Series, ic.Region, names)
   115  }
   116  
   117  // byArch sorts InstanceSpecs first by descending word-size, then
   118  // alphabetically by name, and choose the first spec in the sequence.
   119  type byArch []*InstanceSpec
   120  
   121  func (a byArch) Len() int {
   122  	return len(a)
   123  }
   124  
   125  func (a byArch) Less(i, j int) bool {
   126  	iArchName := a[i].Image.Arch
   127  	jArchName := a[j].Image.Arch
   128  	iArch := arch.Info[iArchName]
   129  	jArch := arch.Info[jArchName]
   130  	// Wider word-size first.
   131  	switch {
   132  	case iArch.WordSize > jArch.WordSize:
   133  		return true
   134  	case iArch.WordSize < jArch.WordSize:
   135  		return false
   136  	}
   137  	// Alphabetically by arch name.
   138  	switch {
   139  	case iArchName < jArchName:
   140  		return true
   141  	case iArchName > jArchName:
   142  		return false
   143  	}
   144  	// If word-size and name the same, keep stable.
   145  	return a[i].order < a[j].order
   146  }
   147  
   148  func (a byArch) Swap(i, j int) {
   149  	a[i], a[j] = a[j], a[i]
   150  }
   151  
   152  // Image holds the attributes that vary amongst relevant images for
   153  // a given series in a given region.
   154  type Image struct {
   155  	Id   string
   156  	Arch string
   157  	// The type of virtualisation supported by this image.
   158  	VirtType string
   159  }
   160  
   161  type imageMatch int
   162  
   163  const (
   164  	nonMatch imageMatch = iota
   165  	exactMatch
   166  	partialMatch
   167  )
   168  
   169  // match returns true if the image can run on the supplied instance type.
   170  func (image Image) match(itype InstanceType) imageMatch {
   171  	if !image.matchArch(itype.Arches) {
   172  		return nonMatch
   173  	}
   174  	if itype.VirtType == nil || image.VirtType == *itype.VirtType {
   175  		return exactMatch
   176  	}
   177  	if image.VirtType == "" {
   178  		// Image doesn't specify virtualisation type. We allow it
   179  		// to match, but prefer exact matches.
   180  		return partialMatch
   181  	}
   182  	return nonMatch
   183  }
   184  
   185  func (image Image) matchArch(arches []string) bool {
   186  	for _, arch := range arches {
   187  		if arch == image.Arch {
   188  			return true
   189  		}
   190  	}
   191  	return false
   192  }
   193  
   194  // ImageMetadataToImages converts an array of ImageMetadata pointers (as
   195  // returned by imagemetadata.Fetch) to an array of Image objects (as required
   196  // by instances.FindInstanceSpec).
   197  func ImageMetadataToImages(inputs []*imagemetadata.ImageMetadata) []Image {
   198  	result := make([]Image, len(inputs))
   199  	for index, input := range inputs {
   200  		result[index] = Image{
   201  			Id:       input.Id,
   202  			VirtType: input.VirtType,
   203  			Arch:     input.Arch,
   204  		}
   205  	}
   206  	return result
   207  }