github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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/juju/constraints" 11 "github.com/juju/juju/environs/imagemetadata" 12 "github.com/juju/juju/juju/arch" 13 ) 14 15 // InstanceConstraint constrains the possible instances that may be 16 // chosen by the environment provider. 17 type InstanceConstraint struct { 18 Region string 19 Series string 20 Arches []string 21 Constraints constraints.Value 22 // Optional filtering criteria not supported by all providers. These attributes are not specified 23 // by the user as a constraint but rather passed in by the provider implementation to restrict the 24 // choice of available images. 25 Storage *string 26 } 27 28 // String returns a human readable form of this InstanceConstaint. 29 func (ic *InstanceConstraint) String() string { 30 storage := "none" 31 if ic.Storage != nil { 32 storage = *ic.Storage 33 } 34 return fmt.Sprintf( 35 "{region: %s, series: %s, arches: %s, constraints: %s, storage: %s}", 36 ic.Region, 37 ic.Series, 38 ic.Arches, 39 ic.Constraints, 40 storage, 41 ) 42 } 43 44 // InstanceSpec holds an instance type name and the chosen image info. 45 type InstanceSpec struct { 46 InstanceType InstanceType 47 Image Image 48 // order is used to sort InstanceSpec based on the input InstanceTypes. 49 order int 50 } 51 52 // FindInstanceSpec returns an InstanceSpec satisfying the supplied InstanceConstraint. 53 // possibleImages contains a list of images matching the InstanceConstraint. 54 // allInstanceTypes provides information on every known available instance type (name, memory, cpu cores etc) on 55 // which instances can be run. The InstanceConstraint is used to filter allInstanceTypes and then a suitable image 56 // compatible with the matching instance types is returned. 57 func FindInstanceSpec(possibleImages []Image, ic *InstanceConstraint, allInstanceTypes []InstanceType) (*InstanceSpec, error) { 58 if len(possibleImages) == 0 { 59 return nil, fmt.Errorf("no %q images in %s with arches %s", 60 ic.Series, ic.Region, ic.Arches) 61 } 62 63 var matchingTypes []InstanceType 64 if ic.Constraints.HasInstanceType() { 65 for _, itype := range allInstanceTypes { 66 if itype.Name == *ic.Constraints.InstanceType { 67 matchingTypes = append(matchingTypes, itype) 68 break 69 } 70 } 71 if len(matchingTypes) == 0 { 72 return nil, fmt.Errorf("invalid instance type %q", *ic.Constraints.InstanceType) 73 } 74 } else { 75 var err error 76 matchingTypes, err = getMatchingInstanceTypes(ic, allInstanceTypes) 77 if err != nil { 78 return nil, err 79 } 80 } 81 if len(matchingTypes) == 0 { 82 return nil, fmt.Errorf("no instance types found matching constraint: %s", ic) 83 } 84 85 specs := []*InstanceSpec{} 86 for _, itype := range matchingTypes { 87 for _, image := range possibleImages { 88 if image.match(itype) { 89 specs = append(specs, &InstanceSpec{ 90 InstanceType: itype, 91 Image: image, 92 order: len(specs), 93 }) 94 } 95 } 96 } 97 if len(specs) > 0 { 98 sort.Sort(byArch(specs)) 99 return specs[0], nil 100 } 101 102 names := make([]string, len(matchingTypes)) 103 for i, itype := range matchingTypes { 104 names[i] = itype.Name 105 } 106 return nil, fmt.Errorf("no %q images in %s matching instance types %v", ic.Series, ic.Region, names) 107 } 108 109 // byArch sorts InstanceSpecs first by descending word-size, then 110 // alphabetically by name, and choose the first spec in the sequence. 111 type byArch []*InstanceSpec 112 113 func (a byArch) Len() int { 114 return len(a) 115 } 116 117 func (a byArch) Less(i, j int) bool { 118 iArchName := a[i].Image.Arch 119 jArchName := a[j].Image.Arch 120 iArch := arch.Info[iArchName] 121 jArch := arch.Info[jArchName] 122 // Wider word-size first. 123 switch { 124 case iArch.WordSize > jArch.WordSize: 125 return true 126 case iArch.WordSize < jArch.WordSize: 127 return false 128 } 129 // Alphabetically by arch name. 130 switch { 131 case iArchName < jArchName: 132 return true 133 case iArchName > jArchName: 134 return false 135 } 136 // If word-size and name the same, keep stable. 137 return a[i].order < a[j].order 138 } 139 140 func (a byArch) Swap(i, j int) { 141 a[i], a[j] = a[j], a[i] 142 } 143 144 // Image holds the attributes that vary amongst relevant images for 145 // a given series in a given region. 146 type Image struct { 147 Id string 148 Arch string 149 // The type of virtualisation supported by this image. 150 VirtType string 151 } 152 153 // match returns true if the image can run on the supplied instance type. 154 func (image Image) match(itype InstanceType) bool { 155 // The virtualisation type is optional. 156 if itype.VirtType != nil && image.VirtType != *itype.VirtType { 157 return false 158 } 159 for _, arch := range itype.Arches { 160 if arch == image.Arch { 161 return true 162 } 163 } 164 return false 165 } 166 167 // ImageMetadataToImages converts an array of ImageMetadata pointers (as 168 // returned by imagemetadata.Fetch) to an array of Image objects (as required 169 // by instances.FindInstanceSpec). 170 func ImageMetadataToImages(inputs []*imagemetadata.ImageMetadata) []Image { 171 result := make([]Image, len(inputs)) 172 for index, input := range inputs { 173 result[index] = Image{ 174 Id: input.Id, 175 VirtType: input.VirtType, 176 Arch: input.Arch, 177 } 178 } 179 return result 180 }