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 }