github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "regexp" 8 "strconv" 9 "strings" 10 11 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" 12 "github.com/juju/errors" 13 14 "github.com/juju/juju/core/arch" 15 "github.com/juju/juju/core/constraints" 16 "github.com/juju/juju/environs/context" 17 "github.com/juju/juju/environs/instances" 18 "github.com/juju/juju/provider/azure/internal/imageutils" 19 ) 20 21 const defaultMem = 1024 // 1GiB 22 23 var instSizeVersionRegexp = regexp.MustCompile(`^((?P<instType>(Standard|Basic))_)?(?P<name>[^_]*)(?:_v(?P<version>\d\d?))?(_Promo)?$`) 24 25 // The ordering here is for linux vm costs for the eastus region 26 // obtained from https://azureprice.net. This is used as a fallback 27 // where real costs are not available, to ensure we choose the right 28 // VMs given matching constraints. The assumption is that all regions 29 // have the same relative costs. 30 // 31 // xxxS is the same price as xxx, but is targeted at Premium Storage. 32 // We put the premium storage variants directly after their non-premium counterparts. 33 var machineSizeCost = []string{ 34 "A0", 35 "A1", 36 "F1", 37 "F1s", 38 "DS1", 39 "D1", 40 "D2as", 41 "A2", 42 "D2", 43 "D2s", 44 "D2a", 45 "DC1s", 46 "DC1ds", 47 "F2", 48 "F2s", 49 "D2ads", 50 "E2as", 51 "D2d", 52 "D2ds", 53 "A2m", 54 "E2", 55 "E2s", 56 "E2a", 57 "E2ads", 58 "DC2as", 59 "E2d", 60 "E2ds", 61 "E2bs", 62 "E2bds", 63 "DS2", 64 "DC2ads", 65 "EC2as", 66 "D11", 67 "DS11", 68 "DS11-1", 69 "A4", 70 "D4", 71 "D4a", 72 "D4s", 73 "DC2s", 74 "DC2ds", 75 "D4as", 76 "F4", 77 "F4s", 78 "D4ads", 79 "EC2ads", 80 "D4d", 81 "D4ds", 82 "NV4as", 83 "A4m", 84 "A3", 85 "A5", 86 "E4", 87 "E4s", 88 "E4as", 89 "E4-2s", 90 "E4a", 91 "E4ads", 92 "E4-2as", 93 "E4-2ads", 94 "DC4as", 95 "E4-2ds", 96 "E4d", 97 "E4ds", 98 "E4bs", 99 "E4bds", 100 "D3", 101 "DS3", 102 "DC4ads", 103 "EC4as", 104 "D12", 105 "DS12", 106 "DS12-1", 107 "DS12-2", 108 "FX4mds", 109 "D8", 110 "D8s", 111 "D8a", 112 "DC4s", 113 "DC4ds", 114 "D8as", 115 "F8", 116 "F8s", 117 "A8", 118 "D8ads", 119 "EC4ads", 120 "D8d", 121 "D8ds", 122 "NV8as", 123 "A8m", 124 "A6", 125 "E8", 126 "E8s", 127 "E8-2s", 128 "E8-4s", 129 "E8a", 130 "E8-4as", 131 "E8as", 132 "E8-2as", 133 "E8ads", 134 "E8-2ads", 135 "E8-4ads", 136 "NC4as_T4", 137 "DC8as", 138 "E8d", 139 "E8ds", 140 "E8bs", 141 "E8bds", 142 "E8-4ds", 143 "E8-2ds", 144 "DS4", 145 "L8s", 146 "DC8ads", 147 "EC8as", 148 "DS13-4", 149 "D13", 150 "DS13-2", 151 "NC8as_T4", 152 "D16", 153 "D16s", 154 "D16as", 155 "DC8", 156 "DC8s", 157 "DC8ds", 158 "D16a", 159 "DS13", 160 "F16", 161 "F16s", 162 "D16ads", 163 "PB6s", 164 "EC8ads", 165 "NC6", 166 "D16d", 167 "D16ds", 168 "H8", 169 "NV16as", 170 "A7", 171 "E16", 172 "E16s", 173 "E16-4s", 174 "E16-8s", 175 "E16a", 176 "E16-8as", 177 "E16as", 178 "E16-4as", 179 "E16-4ads", 180 "E16ads", 181 "E16-8ads", 182 "DC16s", 183 "DC16ds", 184 "DC16as", 185 "FX12mds", 186 "NV6", 187 "NV12s", 188 "E16d", 189 "E16ds", 190 "E16bs", 191 "E16bds", 192 "E16-4ds", 193 "E16-8ds", 194 "DS5", 195 "D5", 196 "NC16as_T4", 197 "H8m", 198 "L16s", 199 "E20", 200 "E20s", 201 "E20a", 202 "E20as", 203 "DC16ads", 204 "E20ads", 205 "F32s", 206 "EC16as", 207 "E20d", 208 "E20ds", 209 "D14", 210 "DS14", 211 "DS14-8", 212 "DS14-4", 213 "D32", 214 "D32s", 215 "D32as", 216 "D32a", 217 "M8-2ms", 218 "M8-4ms", 219 "M8ms", 220 "D32ads", 221 "NP10s", 222 "EC16ads", 223 "EC20as", 224 "NC12", 225 "H16", 226 "D32d", 227 "D32ds", 228 "D15", 229 "DS15", 230 "NV32as", 231 "H16r", 232 "E32", 233 "E32-8s", 234 "E32-16s", 235 "E32s", 236 "E32-8as", 237 "E32as", 238 "E32-16as", 239 "E32a", 240 "F48s", 241 "ND6s", 242 "NC6s", 243 "EC20ads", 244 "E32-16ads", 245 "E32ads", 246 "E32-8ads", 247 "DC24s", 248 "DC24ds", 249 "DC32s", 250 "DC32ds", 251 "DC32as", 252 "FX24mds", 253 "HB60-15rs", 254 "HB60-45rs", 255 "HB60-30rs", 256 "NV24s", 257 "HB60rs", 258 "NV12", 259 "E32-8ds", 260 "E32ds", 261 "D48", 262 "E32-16ds", 263 "E32d", 264 "E32bs", 265 "E32bds", 266 "D48s", 267 "D48a", 268 "H16m", 269 "D48as", 270 "D48ads", 271 "L32s", 272 "DC32ads", 273 "H16mr", 274 "F64s", 275 "M32ts", 276 "D48d", 277 "D48ds", 278 "EC32as", 279 "M32ls", 280 "E48", 281 "E48s", 282 "E48a", 283 "E48as", 284 "F72s", 285 "D64", 286 "D64s", 287 "D64as", 288 "D64a", 289 "M16ms", 290 "M16-8ms", 291 "M16-4ms", 292 "E48ads", 293 "HC44-16rs", 294 "HC44-32rs", 295 "HC44rs", 296 "DC48as", 297 "DC48s", 298 "DC48ds", 299 "D64ads", 300 "NP20s", 301 "EC32ads", 302 "FX36mds", 303 "E48d", 304 "E48ds", 305 "E48bs", 306 "E48bds", 307 "HB120-16rs", 308 "HB120-64rs", 309 "HB120rs", 310 "HB120-96rs", 311 "HB120-32rs", 312 "NC24", 313 "E64as", 314 "D64d", 315 "E64", 316 "E64s", 317 "D64ds", 318 "E64-16s", 319 "E64is", 320 "E64-32s", 321 "E64i", 322 "L48s", 323 "DC48ads", 324 "NC24r", 325 "E64-16as", 326 "E64-32as", 327 "E64a", 328 "ND12s", 329 "E64-32ads", 330 "E64ads", 331 "E64-16ads", 332 "EC48as", 333 "DC64as", 334 "NC64as_T4", 335 "FX48mds", 336 "NV24", 337 "NV48s", 338 "D96", 339 "D96s", 340 "E64-32ds", 341 "E64-16ds", 342 "E64d", 343 "E64ds", 344 "E64bs", 345 "E64bds", 346 "D96a", 347 "D96as", 348 "D96ads", 349 "EC48ads", 350 "L64s", 351 "E80is", 352 "DC64ads", 353 "M64ls", 354 "E96as", 355 "E96ias", 356 "D96d", 357 "D96ds", 358 "EC64as", 359 "E80ids", 360 "E96", 361 "E96-48s", 362 "E96-24s", 363 "E96s", 364 "E96-24as", 365 "E96a", 366 "E96-48as", 367 "NC12s", 368 "M32ms", 369 "M32-8ms", 370 "M32-16ms", 371 "M32dms", 372 "L80s", 373 "E96ads", 374 "E96-48ads", 375 "E96-24ads", 376 "M64", 377 "M64s", 378 "DC96as", 379 "NP40s", 380 "EC64ads", 381 "M64ds", 382 "E96ds", 383 "E96-48ds", 384 "E96d", 385 "E96-24ds", 386 "E112ias", 387 "E104i", 388 "E104is", 389 "DC96ads", 390 "E112iads", 391 "E104ids", 392 "E104id", 393 "NC24s", 394 "ND24s", 395 "EC96as", 396 "NC24rs", 397 "ND24rs", 398 "EC96ias", 399 "EC96ads", 400 "M64ms", 401 "M64-32ms", 402 "M64-16ms", 403 "M64m", 404 "M64dms", 405 "EC96iads", 406 "M128", 407 "M128s", 408 "M128ds", 409 "M192is", 410 "M192ids", 411 "ND40rs", 412 "M208s", 413 "M128m", 414 "M128ms", 415 "M128-64ms", 416 "M128-32ms", 417 "M128dms", 418 "ND96asr", 419 "M192ims", 420 "M192idms", 421 "ND96amsr_A100", 422 "M208ms", 423 "M416s", 424 "M416-208s", 425 "M416ms", 426 "M416-208ms", 427 428 // Burstable instances need to be opt in since you 429 // don't get 100% of the vCPU capacity all of the time 430 // and so we want to avoid surprises unless you ask for it. 431 "B1ls", 432 "B1s", 433 "B1ms", 434 "B2s", 435 "B2ms", 436 "B4ms", 437 "B8ms", 438 "B12ms", 439 "B16ms", 440 "B20ms", 441 } 442 443 // newInstanceType creates an InstanceType based on a VirtualMachineSize. 444 func newInstanceType(size armcompute.VirtualMachineSize) instances.InstanceType { 445 sizeName := toValue(size.Name) 446 // Actual instance type names often are suffixed with _v3, _v4 etc. We always 447 // prefer the highest version number. 448 namePart := instSizeVersionRegexp.ReplaceAllString(sizeName, "$name") 449 instType := instSizeVersionRegexp.ReplaceAllString(sizeName, "$instType") 450 isPromo := strings.HasSuffix(sizeName, "_Promo") 451 vers := 0 452 if namePart == "" || instType == "Basic" { 453 namePart = sizeName 454 } else { 455 versStr := instSizeVersionRegexp.ReplaceAllString(sizeName, "$version") 456 if versStr != "" { 457 vers, _ = strconv.Atoi(versStr) 458 } 459 } 460 461 var ( 462 cost int 463 found bool 464 ) 465 // We don't have proper cost info for promo instances, so don't try and rank them. 466 if !isPromo { 467 for i, name := range machineSizeCost { 468 if namePart == name { 469 // Space out the relative costs and make a small subtraction 470 // so the higher versions of the same instance have a lower cost. 471 cost = 100*i - vers 472 found = true 473 break 474 } 475 } 476 } 477 // Anything not in the list is more expensive that is in the list. 478 if !found { 479 if !isPromo && instType != "Basic" { 480 logger.Debugf("got VM for which we don't have relative cost data: %q", sizeName) 481 } 482 cost = 100 * len(machineSizeCost) 483 } 484 485 vtype := "Hyper-V" 486 return instances.InstanceType{ 487 Id: sizeName, 488 Name: sizeName, 489 // TODO(wallyworld) - add arm64 once supported 490 Arch: arch.AMD64, 491 CpuCores: uint64(toValue(size.NumberOfCores)), 492 Mem: uint64(toValue(size.MemoryInMB)), 493 // NOTE(axw) size.OsDiskSizeInMB is the *maximum* 494 // OS-disk size. When we create a VM, we can create 495 // one that is smaller. 496 RootDisk: mbToMib(uint64(toValue(size.OSDiskSizeInMB))), 497 Cost: uint64(cost), 498 VirtType: &vtype, 499 // tags are not currently supported by azure 500 } 501 } 502 503 func deleteInstanceFamily(instanceTypes map[string]instances.InstanceType, fullName string) { 504 toDeleteNamePart := instSizeVersionRegexp.ReplaceAllString(fullName, "$name") 505 for n := range instanceTypes { 506 namePart := instSizeVersionRegexp.ReplaceAllString(n, "$name") 507 if namePart != "" && namePart == toDeleteNamePart || n == toDeleteNamePart { 508 delete(instanceTypes, n) 509 } 510 } 511 } 512 513 func mbToMib(mb uint64) uint64 { 514 b := mb * 1000 * 1000 515 return uint64(float64(b) / 1024 / 1024) 516 } 517 518 // findInstanceSpec returns the InstanceSpec that best satisfies the supplied 519 // InstanceConstraint. 520 // 521 // NOTE(axw) for now we ignore simplestreams altogether, and go straight to 522 // Azure's image registry. 523 func (env *azureEnviron) findInstanceSpec( 524 ctx context.ProviderCallContext, 525 instanceTypesMap map[string]instances.InstanceType, 526 constraint *instances.InstanceConstraint, 527 imageStream string, 528 ) (*instances.InstanceSpec, error) { 529 530 if constraint.Arch != arch.AMD64 { 531 // Azure only supports AMD64. 532 return nil, errors.NotFoundf("%s in arch constraints", arch.AMD64) 533 } 534 535 client, err := env.imagesClient() 536 if err != nil { 537 return nil, errors.Trace(err) 538 } 539 image, err := imageutils.SeriesImage(ctx, constraint.Base, imageStream, constraint.Region, client) 540 if err != nil { 541 return nil, errors.Trace(err) 542 } 543 images := []instances.Image{*image} 544 545 instanceTypes := make([]instances.InstanceType, 0, len(instanceTypesMap)) 546 for _, instanceType := range instanceTypesMap { 547 instanceTypes = append(instanceTypes, instanceType) 548 } 549 constraint.Constraints = defaultToBaselineSpec(constraint.Constraints) 550 return instances.FindInstanceSpec(images, constraint, instanceTypes) 551 } 552 553 // If you specify no constraints at all, you're going to get the smallest 554 // instance type available. In practice that one's a bit small, so unless 555 // the constraints are deliberately set lower, this gives you a set of 556 // baseline constraints that are just slightly more ambitious than that. 557 func defaultToBaselineSpec(constraint constraints.Value) constraints.Value { 558 result := constraint 559 if !result.HasInstanceType() && result.Mem == nil { 560 var value uint64 = defaultMem 561 result.Mem = &value 562 } 563 return result 564 }