From d879c2454b7f16202b1391b7e164006ca3d627e5 Mon Sep 17 00:00:00 2001 From: Artur Troian Date: Wed, 7 Jun 2023 11:48:31 -0400 Subject: [PATCH] feat: allow wildcard on attributes in deployment messages (#60) Signed-off-by: Artur Troian --- go/node/deployment/v1beta3/groupspec.go | 2 +- .../v1beta3/resource_list_validation_test.go | 266 ++++++++++++++++++ go/node/deployment/v1beta3/types_test.go | 111 +++++++- go/node/provider/v1beta3/msgs.go | 8 +- go/node/provider/v1beta3/msgs_test.go | 13 + go/node/types/v1beta3/attribute.go | 55 +++- go/node/types/v1beta3/attribute_test.go | 92 ++++-- go/node/types/v1beta3/resourcevalue_test.go | 49 ---- go/testutil/v1beta3/base.go | 2 +- 9 files changed, 515 insertions(+), 83 deletions(-) create mode 100644 go/node/deployment/v1beta3/resource_list_validation_test.go delete mode 100644 go/node/types/v1beta3/resourcevalue_test.go diff --git a/go/node/deployment/v1beta3/groupspec.go b/go/node/deployment/v1beta3/groupspec.go index 3e73c47f..5b1594e9 100644 --- a/go/node/deployment/v1beta3/groupspec.go +++ b/go/node/deployment/v1beta3/groupspec.go @@ -62,7 +62,7 @@ func (g GroupSpec) MatchResourcesRequirements(pattr types.Attributes) bool { continue } - pgroup = pattr.GetCapabilitiesGroup("gpu") + pgroup = pattr.GetCapabilitiesMap("gpu") if !gpu.Attributes.IN(pgroup) { return false } diff --git a/go/node/deployment/v1beta3/resource_list_validation_test.go b/go/node/deployment/v1beta3/resource_list_validation_test.go new file mode 100644 index 00000000..d7d27746 --- /dev/null +++ b/go/node/deployment/v1beta3/resource_list_validation_test.go @@ -0,0 +1,266 @@ +package v1beta3 + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + types "github.com/akash-network/akash-api/go/node/types/v1beta3" +) + +func TestValidateCPUNil(t *testing.T) { + _, err := validateCPU(nil) + require.Error(t, err) +} + +func TestValidateGPUNil(t *testing.T) { + _, err := validateGPU(nil) + require.Error(t, err) +} + +func TestValidateMemoryNil(t *testing.T) { + _, err := validateMemory(nil) + require.Error(t, err) +} + +func TestValidateStorageNil(t *testing.T) { + _, err := validateStorage(nil) + require.Error(t, err) +} + +func TestValidateCPULimits(t *testing.T) { + _, err := validateCPU(&types.CPU{Units: types.NewResourceValue(uint64(validationConfig.MinUnitCPU - 1))}) + require.Error(t, err) + + _, err = validateCPU(&types.CPU{Units: types.NewResourceValue(uint64(validationConfig.MaxUnitCPU + 1))}) + require.Error(t, err) + + _, err = validateCPU(&types.CPU{Units: types.NewResourceValue(uint64(validationConfig.MinUnitCPU))}) + require.NoError(t, err) + + _, err = validateCPU(&types.CPU{Units: types.NewResourceValue(uint64(validationConfig.MaxUnitCPU))}) + require.NoError(t, err) +} + +func TestValidateGPULimits(t *testing.T) { + _, err := validateGPU(&types.GPU{Units: types.NewResourceValue(uint64(validationConfig.MinUnitGPU - 1))}) + require.Error(t, err) + + _, err = validateGPU(&types.GPU{Units: types.NewResourceValue(uint64(validationConfig.MaxUnitGPU + 1))}) + require.Error(t, err) + + _, err = validateGPU(&types.GPU{Units: types.NewResourceValue(uint64(validationConfig.MinUnitGPU))}) + require.NoError(t, err) + + _, err = validateGPU(&types.GPU{Units: types.NewResourceValue(uint64(validationConfig.MaxUnitGPU))}) + require.NoError(t, err) +} + +func TestValidateMemoryLimits(t *testing.T) { + _, err := validateMemory(&types.Memory{Quantity: types.NewResourceValue(uint64(validationConfig.MinUnitMemory - 1))}) + require.Error(t, err) + + _, err = validateMemory(&types.Memory{Quantity: types.NewResourceValue(uint64(validationConfig.MaxUnitMemory + 1))}) + require.Error(t, err) + + _, err = validateMemory(&types.Memory{Quantity: types.NewResourceValue(uint64(validationConfig.MinUnitMemory))}) + require.NoError(t, err) + + _, err = validateMemory(&types.Memory{Quantity: types.NewResourceValue(uint64(validationConfig.MaxUnitMemory))}) + require.NoError(t, err) +} + +func TestValidateStorageLimits(t *testing.T) { + _, err := validateStorage(types.Volumes{{Quantity: types.NewResourceValue(validationConfig.MinUnitStorage - 1)}}) + require.Error(t, err) + + _, err = validateStorage(types.Volumes{{Quantity: types.NewResourceValue(validationConfig.MaxUnitStorage + 1)}}) + require.Error(t, err) + + _, err = validateStorage(types.Volumes{{Quantity: types.NewResourceValue(validationConfig.MinUnitStorage)}}) + require.NoError(t, err) + + _, err = validateStorage(types.Volumes{{Quantity: types.NewResourceValue(validationConfig.MaxUnitStorage)}}) + require.NoError(t, err) +} + +type resourceListTest struct { + rlist types.ResourceGroup + shouldPass bool + expErr error + expErrString string +} + +func dummyResources(count int) []Resource { + return make([]Resource, count) +} + +func TestValidateResourceList(t *testing.T) { + tests := []resourceListTest{ + { + rlist: GroupSpec{}, + shouldPass: false, + expErr: ErrGroupEmptyName, + }, + { + rlist: GroupSpec{ + Name: "test", + Resources: dummyResources(validationConfig.MaxGroupUnits + 1), + }, + shouldPass: false, + expErrString: "group test: too many units", + }, + { + rlist: GroupSpec{ + Name: "test", + Resources: []Resource{ + { + Resources: types.ResourceUnits{}, + Count: 1, + }, + }, + }, + shouldPass: false, + expErrString: "error: invalid unit CPU", + }, + { + rlist: GroupSpec{ + Name: "test", + Resources: []Resource{ + { + Resources: types.ResourceUnits{ + CPU: &types.CPU{Units: types.NewResourceValue(1000)}, + }, + Count: 1, + }, + }, + }, + shouldPass: false, + expErrString: "error: invalid unit GPU", + }, + { + rlist: GroupSpec{ + Name: "test", + Resources: []Resource{ + { + Resources: types.ResourceUnits{ + CPU: &types.CPU{Units: types.NewResourceValue(1000)}, + GPU: &types.GPU{Units: types.NewResourceValue(0)}, + }, + Count: 1, + }, + }, + }, + shouldPass: false, + expErrString: "error: invalid unit memory", + }, + { + rlist: GroupSpec{ + Name: "test", + Resources: []Resource{ + { + Resources: types.ResourceUnits{ + CPU: &types.CPU{Units: types.NewResourceValue(1000)}, + GPU: &types.GPU{Units: types.NewResourceValue(0)}, + Memory: &types.Memory{Quantity: types.NewResourceValue(validationConfig.MinUnitMemory)}, + }, + Count: 1, + }, + }, + }, + shouldPass: false, + expErrString: "error: invalid unit storage", + }, + { + rlist: GroupSpec{ + Name: "test", + Resources: []Resource{ + { + Resources: types.ResourceUnits{ + CPU: &types.CPU{Units: types.NewResourceValue(1000)}, + GPU: &types.GPU{Units: types.NewResourceValue(0)}, + Memory: &types.Memory{Quantity: types.NewResourceValue(validationConfig.MinUnitMemory)}, + Storage: types.Volumes{}, + }, + Count: 1, + }, + }, + }, + shouldPass: true, + }, + { + rlist: GroupSpec{ + Name: "test", + Resources: []Resource{ + { + Resources: types.ResourceUnits{ + CPU: &types.CPU{Units: types.NewResourceValue(uint64(validationConfig.MaxUnitCPU))}, + GPU: &types.GPU{Units: types.NewResourceValue(0)}, + Memory: &types.Memory{Quantity: types.NewResourceValue(validationConfig.MinUnitMemory)}, + Storage: types.Volumes{}, + }, + Count: uint32(validationConfig.MaxGroupCPU/uint64(validationConfig.MaxUnitCPU)) + 1, + }, + }, + }, + shouldPass: false, + expErrString: "invalid total CPU", + }, + { + rlist: GroupSpec{ + Name: "test", + Resources: []Resource{ + { + Resources: types.ResourceUnits{ + CPU: &types.CPU{Units: types.NewResourceValue(1000)}, + GPU: &types.GPU{Units: types.NewResourceValue(uint64(validationConfig.MaxUnitGPU))}, + Memory: &types.Memory{Quantity: types.NewResourceValue(validationConfig.MinUnitMemory)}, + Storage: types.Volumes{}, + }, + Count: uint32(validationConfig.MaxGroupGPU/uint64(validationConfig.MaxUnitGPU)) + 1, + }, + }, + }, + shouldPass: false, + expErrString: "invalid total GPU", + }, + { + rlist: GroupSpec{ + Name: "test", + Resources: []Resource{ + { + Resources: types.ResourceUnits{ + CPU: &types.CPU{Units: types.NewResourceValue(uint64(validationConfig.MinUnitCPU))}, + GPU: &types.GPU{Units: types.NewResourceValue(0)}, + Memory: &types.Memory{Quantity: types.NewResourceValue(validationConfig.MaxUnitMemory)}, + Storage: types.Volumes{}, + }, + Count: uint32(validationConfig.MaxGroupMemory/validationConfig.MaxUnitMemory) + 1, + }, + }, + }, + shouldPass: false, + expErrString: "invalid total memory", + }, + } + + for _, test := range tests { + err := ValidateResourceList(test.rlist) + if test.shouldPass { + require.NoError(t, err) + } else { + require.Error(t, err) + if test.expErr != nil { + require.EqualError(t, err, test.expErr.Error()) + } else if test.expErrString != "" { + require.True(t, + strings.Contains(err.Error(), test.expErrString), + fmt.Sprintf("invalid error message: expected to contain (%s) != actual(%s)", test.expErrString, err.Error())) + } else { + require.Error(t, err) + } + } + } +} diff --git a/go/node/deployment/v1beta3/types_test.go b/go/node/deployment/v1beta3/types_test.go index 7d305265..ba92faa1 100644 --- a/go/node/deployment/v1beta3/types_test.go +++ b/go/node/deployment/v1beta3/types_test.go @@ -313,9 +313,114 @@ func TestGroupSpec_MatchResourcesAttributes(t *testing.T) { }, } - require.True(t, group.MatchResourcesRequirements(provAttributes)) - require.False(t, group.MatchResourcesRequirements(prov2Attributes)) - require.False(t, group.MatchResourcesRequirements(prov3Attributes)) + match := group.MatchResourcesRequirements(provAttributes) + require.True(t, match) + match = group.MatchResourcesRequirements(prov2Attributes) + require.False(t, match) + match = group.MatchResourcesRequirements(prov3Attributes) + require.False(t, match) +} + +func TestGroupSpec_MatchGPUAttributes(t *testing.T) { + group := types.GroupSpec{ + Name: "spec", + Requirements: testutil.PlacementRequirements(t), + Resources: testutil.Resources(t), + } + + group.Resources[0].Resources.GPU.Attributes = akashtypes.Attributes{ + { + Key: "vendor/nvidia/model/a100", + Value: "true", + }, + } + + provAttributes := akashtypes.Attributes{ + { + Key: "capabilities/storage/1/class", + Value: "default", + }, + { + Key: "capabilities/storage/1/persistent", + Value: "true", + }, + { + Key: "capabilities/gpu/vendor/nvidia/model/a100", + Value: "true", + }, + } + + prov2Attributes := akashtypes.Attributes{ + { + Key: "capabilities/storage/1/class", + Value: "default", + }, + } + + prov3Attributes := akashtypes.Attributes{ + { + Key: "capabilities/storage/1/class", + Value: "beta2", + }, + } + + match := group.MatchResourcesRequirements(provAttributes) + require.True(t, match) + match = group.MatchResourcesRequirements(prov2Attributes) + require.False(t, match) + match = group.MatchResourcesRequirements(prov3Attributes) + require.False(t, match) +} + +func TestGroupSpec_MatchGPUAttributesWildcard(t *testing.T) { + group := types.GroupSpec{ + Name: "spec", + Requirements: testutil.PlacementRequirements(t), + Resources: testutil.Resources(t), + } + + group.Resources[0].Resources.GPU.Attributes = akashtypes.Attributes{ + { + Key: "vendor/nvidia/model/*", + Value: "true", + }, + } + + provAttributes := akashtypes.Attributes{ + { + Key: "capabilities/storage/1/class", + Value: "default", + }, + { + Key: "capabilities/storage/1/persistent", + Value: "true", + }, + { + Key: "capabilities/gpu/vendor/nvidia/model/a100", + Value: "true", + }, + } + + prov2Attributes := akashtypes.Attributes{ + { + Key: "capabilities/storage/1/class", + Value: "default", + }, + } + + prov3Attributes := akashtypes.Attributes{ + { + Key: "capabilities/storage/1/class", + Value: "beta2", + }, + } + + match := group.MatchResourcesRequirements(provAttributes) + require.True(t, match) + match = group.MatchResourcesRequirements(prov2Attributes) + require.False(t, match) + match = group.MatchResourcesRequirements(prov3Attributes) + require.False(t, match) } func TestDepositDeploymentAuthorization_Accept(t *testing.T) { diff --git a/go/node/provider/v1beta3/msgs.go b/go/node/provider/v1beta3/msgs.go index dafb854b..e4b638d5 100644 --- a/go/node/provider/v1beta3/msgs.go +++ b/go/node/provider/v1beta3/msgs.go @@ -2,6 +2,7 @@ package v1beta3 import ( "net/url" + "regexp" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -17,7 +18,8 @@ const ( ) var ( - _, _, _ sdk.Msg = &MsgCreateProvider{}, &MsgUpdateProvider{}, &MsgDeleteProvider{} + _, _, _ sdk.Msg = &MsgCreateProvider{}, &MsgUpdateProvider{}, &MsgDeleteProvider{} + attributeNameRegexp = regexp.MustCompile(types.AttributeNameRegexpString) ) // NewMsgCreateProvider creates a new MsgCreateProvider instance @@ -43,7 +45,7 @@ func (msg MsgCreateProvider) ValidateBasic() error { if _, err := sdk.AccAddressFromBech32(msg.Owner); err != nil { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "MsgCreate: Invalid Provider Address") } - if err := msg.Attributes.Validate(); err != nil { + if err := msg.Attributes.ValidateWithRegex(attributeNameRegexp); err != nil { return err } if err := msg.Info.Validate(); err != nil { @@ -90,7 +92,7 @@ func (msg MsgUpdateProvider) ValidateBasic() error { if _, err := sdk.AccAddressFromBech32(msg.Owner); err != nil { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "MsgUpdate: Invalid Provider Address") } - if err := msg.Attributes.Validate(); err != nil { + if err := msg.Attributes.ValidateWithRegex(attributeNameRegexp); err != nil { return err } if err := msg.Info.Validate(); err != nil { diff --git a/go/node/provider/v1beta3/msgs_test.go b/go/node/provider/v1beta3/msgs_test.go index af2c7351..f20abea6 100644 --- a/go/node/provider/v1beta3/msgs_test.go +++ b/go/node/provider/v1beta3/msgs_test.go @@ -185,6 +185,19 @@ var msgCreateTests = []providerTestParams{ }, expErr: nil, }, + { + msg: Provider{ + Owner: sdk.AccAddress("hihi").String(), + HostURI: "https://localhost:3001", + Attributes: []types.Attribute{ + { + Key: "hihi*", + Value: "neh", + }, + }, + }, + expErr: types.ErrInvalidAttributeKey, + }, { msg: Provider{ Owner: sdk.AccAddress("").String(), diff --git a/go/node/types/v1beta3/attribute.go b/go/node/types/v1beta3/attribute.go index 7f518cdb..0e7833e8 100644 --- a/go/node/types/v1beta3/attribute.go +++ b/go/node/types/v1beta3/attribute.go @@ -1,6 +1,7 @@ package v1beta3 import ( + "path/filepath" "reflect" "regexp" "sort" @@ -12,8 +13,9 @@ import ( ) const ( - moduleName = "akash" - attributeNameRegexpString = `^([a-zA-Z][\w\/\.\-]{1,126}\w)$` + moduleName = "akash" + AttributeNameRegexpStringWildcard = `^([a-zA-Z][\w\/\.\-]{1,126}[\w\*]?)$` + AttributeNameRegexpString = `^([a-zA-Z][\w\/\.\-]{1,126})$` ) const ( @@ -27,7 +29,8 @@ var ( ) var ( - attributeNameRegexp = regexp.MustCompile(attributeNameRegexpString) + attributeNameRegexp = regexp.MustCompile(AttributeNameRegexpString) + attributeNameRegexpWildcard = regexp.MustCompile(AttributeNameRegexpStringWildcard) ) /* @@ -88,7 +91,7 @@ func (m *Attribute) Equal(rhs *Attribute) bool { } func (m Attribute) SubsetOf(rhs Attribute) bool { - if m.Key == rhs.Key && m.Value == rhs.Value { + if match, _ := filepath.Match(m.Key, rhs.Key); match && (m.Value == rhs.Value) { return true } @@ -102,10 +105,14 @@ func (attr Attributes) Sort() { } func (attr Attributes) Validate() error { + return attr.ValidateWithRegex(attributeNameRegexpWildcard) +} + +func (attr Attributes) ValidateWithRegex(r *regexp.Regexp) error { store := make(map[string]bool) for i := range attr { - if !attributeNameRegexp.MatchString(attr[i].Key) { + if !r.MatchString(attr[i].Key) { return ErrInvalidAttributeKey } @@ -272,6 +279,44 @@ func (attr Attributes) GetCapabilitiesGroup(prefix string) AttributesGroup { return res } +func (attr Attributes) GetCapabilitiesMap(prefix string) AttributesGroup { + res := make(AttributesGroup, 0, 1) + var groups Attributes + + for _, item := range attr { + if !strings.HasPrefix(item.Key, "capabilities/"+prefix) { + continue + } + + tokens := strings.Split(strings.TrimPrefix(item.Key, "capabilities/"), "/") + // skip malformed attributes + if len(tokens) < 3 { + continue + } + + // filter out prefix name + tokens = tokens[1:] + + var key string + for i, token := range tokens { + if i == 0 { + key = token + } else { + key += "/" + token + } + } + + groups = append(groups, Attribute{ + Key: key, + Value: item.Value, + }) + } + + res = append(res, groups) + + return res +} + // IN check if given attributes are in attributes group // AttributesGroup for storage // - persistent: true diff --git a/go/node/types/v1beta3/attribute_test.go b/go/node/types/v1beta3/attribute_test.go index 8a4d41c1..5e626794 100644 --- a/go/node/types/v1beta3/attribute_test.go +++ b/go/node/types/v1beta3/attribute_test.go @@ -1,78 +1,82 @@ -package v1beta3_test +package v1beta3 import ( "testing" "github.com/stretchr/testify/require" - - types "github.com/akash-network/akash-api/go/node/types/v1beta3" ) +type regexTest struct { + runName string + key string + shouldPass bool +} + func TestAttributes_Validate(t *testing.T) { - attr := types.Attributes{ + attr := Attributes{ {Key: "key"}, {Key: "key"}, } - require.EqualError(t, attr.Validate(), types.ErrAttributesDuplicateKeys.Error()) + require.EqualError(t, attr.Validate(), ErrAttributesDuplicateKeys.Error()) // unsupported key symbol - attr = types.Attributes{ + attr = Attributes{ {Key: "$"}, } - require.EqualError(t, attr.Validate(), types.ErrInvalidAttributeKey.Error()) + require.EqualError(t, attr.Validate(), ErrInvalidAttributeKey.Error()) // empty key - attr = types.Attributes{ + attr = Attributes{ {Key: ""}, } - require.EqualError(t, attr.Validate(), types.ErrInvalidAttributeKey.Error()) + require.EqualError(t, attr.Validate(), ErrInvalidAttributeKey.Error()) // to long key - attr = types.Attributes{ + attr = Attributes{ {Key: "sdgkhaeirugaeroigheirghseiargfs3ssdgkhaeirugaeroigheirghseiargfs3sdgkhaeirugaeroigheirghseiargfs3ssdgkhaeirugaeroigheirghseiargfs3"}, } - require.EqualError(t, attr.Validate(), types.ErrInvalidAttributeKey.Error()) + require.EqualError(t, attr.Validate(), ErrInvalidAttributeKey.Error()) } func TestAttribute_Equal(t *testing.T) { - attr1 := &types.Attribute{Key: "key1", Value: "val1"} - attr2 := &types.Attribute{Key: "key1", Value: "val1"} - attr3 := &types.Attribute{Key: "key1", Value: "val2"} + attr1 := &Attribute{Key: "key1", Value: "val1"} + attr2 := &Attribute{Key: "key1", Value: "val1"} + attr3 := &Attribute{Key: "key1", Value: "val2"} require.True(t, attr1.Equal(attr2)) require.False(t, attr1.Equal(attr3)) } func TestAttribute_SubsetOf(t *testing.T) { - attr1 := types.Attribute{Key: "key1", Value: "val1"} - attr2 := types.Attribute{Key: "key1", Value: "val1"} - attr3 := types.Attribute{Key: "key1", Value: "val2"} + attr1 := Attribute{Key: "key1", Value: "val1"} + attr2 := Attribute{Key: "key1", Value: "val1"} + attr3 := Attribute{Key: "key1", Value: "val2"} require.True(t, attr1.SubsetOf(attr2)) require.False(t, attr1.SubsetOf(attr3)) } func TestAttributes_SubsetOf(t *testing.T) { - attr1 := types.Attributes{ + attr1 := Attributes{ {Key: "key1", Value: "val1"}, } - attr2 := types.Attributes{ + attr2 := Attributes{ {Key: "key1", Value: "val1"}, {Key: "key2", Value: "val2"}, } - attr3 := types.Attributes{ + attr3 := Attributes{ {Key: "key1", Value: "val1"}, {Key: "key2", Value: "val2"}, {Key: "key3", Value: "val3"}, {Key: "key4", Value: "val4"}, } - attr4 := types.Attributes{ + attr4 := Attributes{ {Key: "key3", Value: "val3"}, {Key: "key4", Value: "val4"}, } @@ -81,3 +85,49 @@ func TestAttributes_SubsetOf(t *testing.T) { require.True(t, attr2.SubsetOf(attr3)) require.False(t, attr1.SubsetOf(attr4)) } + +func TestAttributeRegex(t *testing.T) { + tests := []regexTest{ + { + "arbitrary key", + "key1", + true, + }, + { + "allow trailing wildcard", + "key1*", + true, + }, + { + "allow trailing wildcard", + "key1/*", + true, + }, + { + "leading wildcard is not allowed", + "*key1", + false, + }, + { + "multiple wildcards are not allowed", + "key1**", + false, + }, + { + "wildcards in the middle of key are not allowed", + "key1*/", + false, + }, + { + "wildcards in the middle of key are not allowed", + "key1/*/", + false, + }, + } + + for _, test := range tests { + t.Run(test.runName, func(t *testing.T) { + require.Equal(t, test.shouldPass, attributeNameRegexpWildcard.MatchString(test.key)) + }) + } +} diff --git a/go/node/types/v1beta3/resourcevalue_test.go b/go/node/types/v1beta3/resourcevalue_test.go deleted file mode 100644 index 654cc290..00000000 --- a/go/node/types/v1beta3/resourcevalue_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package v1beta3 - -// func TestValidSum(t *testing.T) { -// val1 := NewResourceValue(1) -// val2 := NewResourceValue(1) -// -// res, err := val1.add(val2) -// require.NoError(t, err) -// require.Equal(t, uint64(2), res.Value()) -// } -// -// func TestSubToNegative(t *testing.T) { -// val1 := NewResourceValue(1) -// val2 := NewResourceValue(2) -// -// _, err := val1.sub(val2) -// require.Error(t, err) -// } - -// func TestResourceValueSubIsIdempotent(t *testing.T) { -// val1 := NewResourceValue(100) -// before := val1.String() -// val2 := NewResourceValue(1) -// -// _, err := val1.sub(val2) -// require.NoError(t, err) -// after := val1.String() -// -// require.Equal(t, before, after) -// } -// -// func TestCPUSubIsNotIdempotent(t *testing.T) { -// val1 := &CPU{ -// Units: NewResourceValue(100), -// Attributes: nil, -// } -// -// before := val1.String() -// val2 := &CPU{ -// Units: NewResourceValue(1), -// Attributes: nil, -// } -// -// err := val1.sub(val2) -// require.NoError(t, err) -// after := val1.String() -// -// require.NotEqual(t, before, after) -// } diff --git a/go/testutil/v1beta3/base.go b/go/testutil/v1beta3/base.go index 4dcc77fe..03c4c40f 100644 --- a/go/testutil/v1beta3/base.go +++ b/go/testutil/v1beta3/base.go @@ -79,7 +79,7 @@ func Resources(t testing.TB) []dtypes.Resource { Units: types.NewResourceValue(uint64(dtypes.GetValidationConfig().MinUnitCPU)), }, GPU: &types.GPU{ - Units: types.NewResourceValue(uint64(dtypes.GetValidationConfig().MinUnitGPU)), + Units: types.NewResourceValue(uint64(dtypes.GetValidationConfig().MinUnitGPU) + 1), }, Memory: &types.Memory{ Quantity: types.NewResourceValue(dtypes.GetValidationConfig().MinUnitMemory),