github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/utils/set" 12 "launchpad.net/gwacl" 13 14 "github.com/juju/juju/constraints" 15 "github.com/juju/juju/environs" 16 "github.com/juju/juju/environs/imagemetadata" 17 "github.com/juju/juju/environs/instances" 18 "github.com/juju/juju/environs/simplestreams" 19 ) 20 21 const defaultMem = 1 * gwacl.GB 22 23 var ( 24 roleSizeCost = gwacl.RoleSizeCost 25 getAvailableRoleSizes = (*azureEnviron).getAvailableRoleSizes 26 getVirtualNetwork = (*azureEnviron).getVirtualNetwork 27 ) 28 29 // If you specify no constraints at all, you're going to get the smallest 30 // instance type available. In practice that one's a bit small. So unless 31 // the constraints are deliberately set lower, this gives you a set of 32 // baseline constraints that are just slightly more ambitious than that. 33 func defaultToBaselineSpec(constraint constraints.Value) constraints.Value { 34 result := constraint 35 if !result.HasInstanceType() && result.Mem == nil { 36 var value uint64 = defaultMem 37 result.Mem = &value 38 } 39 return result 40 } 41 42 // selectMachineType returns the Azure machine type that best matches the 43 // supplied instanceConstraint. 44 func selectMachineType(env *azureEnviron, cons constraints.Value) (*instances.InstanceType, error) { 45 instanceTypes, err := listInstanceTypes(env) 46 if err != nil { 47 return nil, err 48 } 49 region := env.getSnapshot().ecfg.location() 50 instanceTypes, err = instances.MatchingInstanceTypes(instanceTypes, region, cons) 51 if err != nil { 52 return nil, err 53 } 54 return &instanceTypes[0], nil 55 } 56 57 // getEndpoint returns the simplestreams endpoint to use for the given Azure 58 // location (e.g. West Europe or China North). 59 func getEndpoint(location string) string { 60 // Simplestreams uses the management-API endpoint for the image, not 61 // the base managent API URL. 62 return gwacl.GetEndpoint(location).ManagementAPI() 63 } 64 65 // As long as this code only supports the default simplestreams 66 // database, which is always signed, there is no point in accepting 67 // unsigned metadata. 68 // 69 // For tests, however, unsigned data is more convenient. They can override 70 // this setting. 71 var signedImageDataOnly = true 72 73 // findMatchingImages queries simplestreams for OS images that match the given 74 // requirements. 75 // 76 // If it finds no matching images, that's an error. 77 func findMatchingImages(e *azureEnviron, location, series string, arches []string) ([]*imagemetadata.ImageMetadata, error) { 78 endpoint := getEndpoint(location) 79 constraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 80 CloudSpec: simplestreams.CloudSpec{location, endpoint}, 81 Series: []string{series}, 82 Arches: arches, 83 Stream: e.Config().ImageStream(), 84 }) 85 sources, err := environs.ImageMetadataSources(e) 86 if err != nil { 87 return nil, err 88 } 89 images, _, err := imagemetadata.Fetch(sources, constraint, signedImageDataOnly) 90 if len(images) == 0 || errors.IsNotFound(err) { 91 return nil, fmt.Errorf("no OS images found for location %q, series %q, architectures %q (and endpoint: %q)", location, series, arches, endpoint) 92 } else if err != nil { 93 return nil, err 94 } 95 return images, nil 96 } 97 98 // newInstanceType creates an InstanceType based on a gwacl.RoleSize. 99 func newInstanceType(roleSize gwacl.RoleSize, region string) (instances.InstanceType, error) { 100 cost, err := roleSizeCost(region, roleSize.Name) 101 if err != nil { 102 return instances.InstanceType{}, err 103 } 104 105 vtype := "Hyper-V" 106 return instances.InstanceType{ 107 Id: roleSize.Name, 108 Name: roleSize.Name, 109 CpuCores: roleSize.CpuCores, 110 Mem: roleSize.Mem, 111 RootDisk: roleSize.OSDiskSpace, 112 Cost: cost, 113 VirtType: &vtype, 114 // tags are not currently supported by azure 115 }, nil 116 } 117 118 // listInstanceTypes describes the available instance types based on a 119 // description in gwacl's terms. 120 func listInstanceTypes(env *azureEnviron) ([]instances.InstanceType, error) { 121 // Not all locations are made equal: some role sizes are only available 122 // in some locations. 123 availableRoleSizes, err := getAvailableRoleSizes(env) 124 if err != nil { 125 return nil, errors.Trace(err) 126 } 127 128 // If the virtual network is tied to an affinity group, then the 129 // role sizes that are useable are limited. 130 vnet, err := getVirtualNetwork(env) 131 if err != nil && errors.IsNotFound(err) { 132 // Virtual network doesn't exist yet; we'll create it with a 133 // location, so we're not limited. 134 } else if err != nil { 135 return nil, errors.Annotate(err, "cannot get virtual network details to filter instance types") 136 } 137 limitedTypes := make(set.Strings) 138 139 region := env.getSnapshot().ecfg.location() 140 arches, err := env.SupportedArchitectures() 141 if err != nil { 142 return nil, err 143 } 144 types := make([]instances.InstanceType, 0, len(gwacl.RoleSizes)) 145 for _, roleSize := range gwacl.RoleSizes { 146 if !availableRoleSizes.Contains(roleSize.Name) { 147 logger.Debugf("role size %q is unavailable in location %q", roleSize.Name, region) 148 continue 149 } 150 // TODO(axw) 2014-06-23 #1324666 151 // Support basic instance types. We need to not default 152 // to them as they do not support load balancing. 153 if strings.HasPrefix(roleSize.Name, "Basic_") { 154 logger.Debugf("role size %q is unsupported", roleSize.Name) 155 continue 156 } 157 if vnet != nil && vnet.AffinityGroup != "" && isLimitedRoleSize(roleSize.Name) { 158 limitedTypes.Add(roleSize.Name) 159 continue 160 } 161 instanceType, err := newInstanceType(roleSize, region) 162 if err != nil { 163 return nil, err 164 } 165 types = append(types, instanceType) 166 } 167 for i := range types { 168 types[i].Arches = arches 169 } 170 if len(limitedTypes) > 0 { 171 logger.Warningf( 172 "virtual network %q has an affinity group: disabling instance types %s", 173 vnet.Name, limitedTypes.SortedValues(), 174 ) 175 } 176 return types, nil 177 } 178 179 // isLimitedRoleSize reports whether the named role size is limited to some 180 // physical hosts only. 181 func isLimitedRoleSize(name string) bool { 182 switch name { 183 case "ExtraSmall", "Small", "Medium", "Large", "ExtraLarge": 184 // At the time of writing, only the original role sizes are not limited. 185 return false 186 case "A5", "A6", "A7", "A8", "A9": 187 // We never used to filter out A5-A9 role sizes, so leave them in 188 // case users have been relying on them. It is *possible* that A-series 189 // role sizes are available, but we cannot automatically use them as 190 // they *may* not be. 191 return false 192 } 193 return true 194 } 195 196 // findInstanceSpec returns the InstanceSpec that best satisfies the supplied 197 // InstanceConstraint. 198 func findInstanceSpec(env *azureEnviron, constraint *instances.InstanceConstraint) (*instances.InstanceSpec, error) { 199 constraint.Constraints = defaultToBaselineSpec(constraint.Constraints) 200 imageData, err := findMatchingImages(env, constraint.Region, constraint.Series, constraint.Arches) 201 if err != nil { 202 return nil, err 203 } 204 images := instances.ImageMetadataToImages(imageData) 205 instanceTypes, err := listInstanceTypes(env) 206 if err != nil { 207 return nil, err 208 } 209 return instances.FindInstanceSpec(images, constraint, instanceTypes) 210 }