Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

demo: capability matching on vm sizes #12

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions const.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ const (
// Generation 2.
HyperVGeneration2 = "V2"
)

const (
base10 = 10
base64 = 64
)
11 changes: 11 additions & 0 deletions data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/sanity-io/litter"
)

var (
Expand Down Expand Up @@ -91,6 +92,16 @@ func Test_Data(t *testing.T) {
t.Error(err)
}
t.Run("virtual machines", func(t *testing.T) {
t.Run("match capability logic", func(t *testing.T) {
sku, err := cache.Get(ctx, "standard_d4s_v3", VirtualMachines, "eastus")
if err != nil {
t.Errorf("expected to find virtual machine sku standard_d4s_v3")
}

match := Match(ctx, cache, &sku, "eastus")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this currently returns zero matches because == is too strict on capabilities, but you get the idea..

litter.Dump(match)
})

t.Run("expect 377 virtual machine skus", func(t *testing.T) {
if len(cache.GetVirtualMachines(ctx)) != expectedVirtualMachinesCount {
t.Errorf("expected %d virtual machine skus but found %d", expectedVirtualMachinesCount, len(cache.GetVirtualMachines(ctx)))
Expand Down
4 changes: 2 additions & 2 deletions fakes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package skewer
import (
"context"
"encoding/json"
"io/ioutil"
"os"

"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute"
)
Expand All @@ -16,7 +16,7 @@ type dataWrapper struct {
// newDataWrapper takes a path to a list of compute skus and parses them
// to a dataWrapper for use in fake clients
func newDataWrapper(path string) (*dataWrapper, error) {
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
Expand Down
18 changes: 14 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
module github.com/Azure/skewer

go 1.13
go 1.18

require (
github.com/Azure/azure-sdk-for-go v46.0.0+incompatible
github.com/Azure/go-autorest/autorest v0.11.4 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.2 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/Azure/go-autorest/autorest/validation v0.3.0 // indirect
github.com/google/go-cmp v0.5.1
github.com/pkg/errors v0.9.1
github.com/sanity-io/litter v1.5.5
)

require (
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.4 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.2 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.0 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
)
6 changes: 5 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
Expand All @@ -28,13 +29,16 @@ github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand Down
72 changes: 72 additions & 0 deletions match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package skewer

import (
"context"
"fmt"
)

func Match(ctx context.Context, cache *Cache, sku *SKU, location string) *SKU {
sizes := cache.List(ctx, ResourceTypeFilter(sku.GetResourceType()), LocationFilter(normalizeLocation(location)))

capabilities := map[string]string{}
for _, capability := range *sku.Capabilities {
if capability.Name != nil {
if capability.Value != nil {
capabilities[*capability.Name] = *capability.Value
} else {
capabilities[*capability.Name] = ""
}
}
}

for i := range sizes {
candidate := &sizes[i]
if candidate.GetName() == sku.GetName() {
continue
}
if allCapabilitiesMatch(candidate, capabilities) {
return candidate
}
}

return nil
}

func allCapabilitiesMatch(sku *SKU, capabilities map[string]string) bool {
matched := 0
desired := len(capabilities)
for _, capability := range *sku.Capabilities {
if capability.Name != nil {
// TODO(ace): this is not actually accurate, really for each
// capability, we should decide whether you need subset, exact match,
// or numerically greater/less than.
if capabilitiesToIgnore[*capability.Name] {
continue
}
if capability.Value != nil {
// TODO(ace): this is far too strict and results in basically zero matches.
if capabilities[*capability.Name] != *capability.Value {
fmt.Printf("failed on capability %s=%s\n", *capability.Name, *capability.Value)
return false
}
matched++
} else {
val, ok := capabilities[*capability.Name]
if !ok || val != "" {
fmt.Printf("failed on capability %s with no value\n", *capability.Name)
return false
}
matched++
}
}
}

if matched != desired {
fmt.Printf("failed to find all desired capabilities want %d got %d\n", desired, matched)
}
return matched == desired
}

var capabilitiesToIgnore = map[string]bool{
MaxResourceVolumeMB: true,
Copy link
Contributor Author

@alexeldeib alexeldeib Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tried ignoring some, just fails matching different capabilities :D

}
10 changes: 5 additions & 5 deletions sku.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (s *SKU) IsUltraSSDAvailableInAvailabilityZone(zone string) bool {
// IsUltraSSDAvailable returns true when a VM size has ultra SSD enabled
// in at least 1 unrestricted zone.
//
// Deprecated. Use either IsUltraSSDAvailableWithoutAvailabilityZone or IsUltraSSDAvailableInAvailabilityZone
// Deprecated: Use either IsUltraSSDAvailableWithoutAvailabilityZone or IsUltraSSDAvailableInAvailabilityZone
func (s *SKU) IsUltraSSDAvailable() bool {
return s.HasZonalCapability(UltraSSDAvailable)
}
Expand Down Expand Up @@ -128,7 +128,7 @@ func (s *SKU) GetCapabilityIntegerQuantity(name string) (int64, error) {
for _, capability := range *s.Capabilities {
if capability.Name != nil && *capability.Name == name {
if capability.Value != nil {
intVal, err := strconv.ParseInt(*capability.Value, 10, 64)
intVal, err := strconv.ParseInt(*capability.Value, base10, base64)
if err != nil {
return -1, &ErrCapabilityValueParse{name, *capability.Value, err}
}
Expand All @@ -151,7 +151,7 @@ func (s *SKU) GetCapabilityFloatQuantity(name string) (float64, error) {
for _, capability := range *s.Capabilities {
if capability.Name != nil && *capability.Name == name {
if capability.Value != nil {
intVal, err := strconv.ParseFloat(*capability.Value, 64)
intVal, err := strconv.ParseFloat(*capability.Value, base64)
if err != nil {
return -1, &ErrCapabilityValueParse{name, *capability.Value, err}
}
Expand Down Expand Up @@ -213,7 +213,7 @@ func (s *SKU) HasZonalCapability(name string) bool {

// HasCapabilityInZone return true if the specified capability name is supported in the
// specified zone.
func (s *SKU) HasCapabilityInZone(name string, zone string) bool {
func (s *SKU) HasCapabilityInZone(name, zone string) bool {
if s.LocationInfo == nil {
return false
}
Expand Down Expand Up @@ -281,7 +281,7 @@ func (s *SKU) HasCapabilityWithMinCapacity(name string, value int64) (bool, erro
for _, capability := range *s.Capabilities {
if capability.Name != nil && strings.EqualFold(*capability.Name, name) {
if capability.Value != nil {
intVal, err := strconv.ParseInt(*capability.Value, 10, 64)
intVal, err := strconv.ParseInt(*capability.Value, base10, base64)
if err != nil {
return false, errors.Wrapf(err, "failed to parse string '%s' as int64", *capability.Value)
}
Expand Down
1 change: 1 addition & 0 deletions sku_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ func Test_SKU_GetLocation(t *testing.T) {

func Test_SKU_AvailabilityZones(t *testing.T) {}

//nolint:funlen
func Test_SKU_HasCapabilityInZone(t *testing.T) {
cases := map[string]struct {
sku compute.ResourceSku
Expand Down