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  }