github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/provider/azure/instancetype.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure 5 6 import ( 7 "fmt" 8 "sort" 9 10 "github.com/juju/errors" 11 "launchpad.net/gwacl" 12 13 "github.com/juju/juju/constraints" 14 "github.com/juju/juju/environs/imagemetadata" 15 "github.com/juju/juju/environs/instances" 16 "github.com/juju/juju/environs/simplestreams" 17 ) 18 19 // preferredTypes is a list of machine types, in order of preference so that 20 // the first type that matches a set of hardware constraints is also the best 21 // (cheapest) fit for those constraints. Or if your constraint is a maximum 22 // price you're willing to pay, your best match is the last type that falls 23 // within your threshold price. 24 type preferredTypes []*gwacl.RoleSize 25 26 // preferredTypes implements sort.Interface. 27 var _ sort.Interface = (*preferredTypes)(nil) 28 29 // newPreferredTypes creates a preferredTypes based on the given slice of 30 // RoleSize objects. It will hold pointers to the elements of that slice. 31 func newPreferredTypes(availableTypes []gwacl.RoleSize) preferredTypes { 32 types := make(preferredTypes, len(availableTypes)) 33 for index := range availableTypes { 34 types[index] = &availableTypes[index] 35 } 36 sort.Sort(&types) 37 return types 38 } 39 40 // Len is specified in sort.Interface. 41 func (types *preferredTypes) Len() int { 42 return len(*types) 43 } 44 45 // Less is specified in sort.Interface. 46 func (types *preferredTypes) Less(i, j int) bool { 47 // All we care about for now is cost. If at some point Azure offers 48 // different tradeoffs for the same price, we may need a tie-breaker. 49 return (*types)[i].Cost < (*types)[j].Cost 50 } 51 52 // Swap is specified in sort.Interface. 53 func (types *preferredTypes) Swap(i, j int) { 54 firstPtr := &(*types)[i] 55 secondPtr := &(*types)[j] 56 *secondPtr, *firstPtr = *firstPtr, *secondPtr 57 } 58 59 // suffices returns whether the given value is high enough to meet the 60 // required value, if any. If "required" is nil, there is no requirement 61 // and so any given value will do. 62 // 63 // This is a method only to limit namespace pollution. It ignores the receiver. 64 func (*preferredTypes) suffices(given uint64, required *uint64) bool { 65 return required == nil || given >= *required 66 } 67 68 // satisfies returns whether the given machine type is enough to satisfy 69 // the given constraints. (It doesn't matter if it's overkill; all that 70 // matters here is whether the machine type is good enough.) 71 // 72 // This is a method only to limit namespace pollution. It ignores the receiver. 73 func (types *preferredTypes) satisfies(machineType *gwacl.RoleSize, constraint constraints.Value) bool { 74 // gwacl does not model CPU power yet, although Azure does have the 75 // option of a shared core (for ExtraSmall instances). For now we 76 // just pretend that's a full-fledged core. 77 return types.suffices(machineType.CpuCores, constraint.CpuCores) && 78 types.suffices(machineType.Mem, constraint.Mem) 79 } 80 81 const defaultMem = 1 * gwacl.GB 82 83 // If you specify no constraints at all, you're going to get the smallest 84 // instance type available. In practice that one's a bit small. So unless 85 // the constraints are deliberately set lower, this gives you a set of 86 // baseline constraints that are just slightly more ambitious than that. 87 func defaultToBaselineSpec(constraint constraints.Value) constraints.Value { 88 result := constraint 89 if result.Mem == nil { 90 var value uint64 = defaultMem 91 result.Mem = &value 92 } 93 return result 94 } 95 96 // selectMachineType returns the Azure machine type that best matches the 97 // supplied instanceConstraint. 98 func selectMachineType(availableTypes []gwacl.RoleSize, constraint constraints.Value) (*gwacl.RoleSize, error) { 99 types := newPreferredTypes(availableTypes) 100 for _, machineType := range types { 101 if types.satisfies(machineType, constraint) { 102 return machineType, nil 103 } 104 } 105 return nil, fmt.Errorf("no machine type matches constraints %v", constraint) 106 } 107 108 // getEndpoint returns the simplestreams endpoint to use for the given Azure 109 // location (e.g. West Europe or China North). 110 func getEndpoint(location string) string { 111 // Simplestreams uses the management-API endpoint for the image, not 112 // the base managent API URL. 113 return gwacl.GetEndpoint(location).ManagementAPI() 114 } 115 116 // As long as this code only supports the default simplestreams 117 // database, which is always signed, there is no point in accepting 118 // unsigned metadata. 119 // 120 // For tests, however, unsigned data is more convenient. They can override 121 // this setting. 122 var signedImageDataOnly = true 123 124 // findMatchingImages queries simplestreams for OS images that match the given 125 // requirements. 126 // 127 // If it finds no matching images, that's an error. 128 func findMatchingImages(e *azureEnviron, location, series string, arches []string) ([]*imagemetadata.ImageMetadata, error) { 129 endpoint := getEndpoint(location) 130 constraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 131 CloudSpec: simplestreams.CloudSpec{location, endpoint}, 132 Series: []string{series}, 133 Arches: arches, 134 Stream: e.Config().ImageStream(), 135 }) 136 sources, err := imagemetadata.GetMetadataSources(e) 137 if err != nil { 138 return nil, err 139 } 140 indexPath := simplestreams.DefaultIndexPath 141 images, _, err := imagemetadata.Fetch(sources, indexPath, constraint, signedImageDataOnly) 142 if len(images) == 0 || errors.IsNotFound(err) { 143 return nil, fmt.Errorf("no OS images found for location %q, series %q, architectures %q (and endpoint: %q)", location, series, arches, endpoint) 144 } else if err != nil { 145 return nil, err 146 } 147 return images, nil 148 } 149 150 // newInstanceType creates an InstanceType based on a gwacl.RoleSize. 151 func newInstanceType(roleSize gwacl.RoleSize) instances.InstanceType { 152 vtype := "Hyper-V" 153 // Actually Azure has shared and dedicated CPUs, but gwacl doesn't 154 // model that distinction yet. 155 var cpuPower uint64 = 100 156 157 return instances.InstanceType{ 158 Id: roleSize.Name, 159 Name: roleSize.Name, 160 CpuCores: roleSize.CpuCores, 161 Mem: roleSize.Mem, 162 RootDisk: roleSize.OSDiskSpaceVirt, 163 Cost: roleSize.Cost, 164 VirtType: &vtype, 165 CpuPower: &cpuPower, 166 // tags are not currently supported by azure 167 } 168 } 169 170 // listInstanceTypes describes the available instance types based on a 171 // description in gwacl's terms. 172 func listInstanceTypes(env *azureEnviron, roleSizes []gwacl.RoleSize) ([]instances.InstanceType, error) { 173 arches, err := env.SupportedArchitectures() 174 if err != nil { 175 return nil, err 176 } 177 types := make([]instances.InstanceType, len(roleSizes)) 178 for index, roleSize := range roleSizes { 179 types[index] = newInstanceType(roleSize) 180 types[index].Arches = arches 181 } 182 return types, nil 183 } 184 185 // findInstanceSpec returns the InstanceSpec that best satisfies the supplied 186 // InstanceConstraint. 187 func findInstanceSpec(env *azureEnviron, constraint *instances.InstanceConstraint) (*instances.InstanceSpec, error) { 188 constraint.Constraints = defaultToBaselineSpec(constraint.Constraints) 189 imageData, err := findMatchingImages(env, constraint.Region, constraint.Series, constraint.Arches) 190 if err != nil { 191 return nil, err 192 } 193 images := instances.ImageMetadataToImages(imageData) 194 instanceTypes, err := listInstanceTypes(env, gwacl.RoleSizes) 195 if err != nil { 196 return nil, err 197 } 198 return instances.FindInstanceSpec(images, constraint, instanceTypes) 199 }