Skip to content

Commit

Permalink
feat: allow wildcard on attributes in deployment messages (#60)
Browse files Browse the repository at this point in the history
Signed-off-by: Artur Troian <[email protected]>
  • Loading branch information
troian authored Jun 7, 2023
1 parent 659f29c commit d879c24
Show file tree
Hide file tree
Showing 9 changed files with 515 additions and 83 deletions.
2 changes: 1 addition & 1 deletion go/node/deployment/v1beta3/groupspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
266 changes: 266 additions & 0 deletions go/node/deployment/v1beta3/resource_list_validation_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
111 changes: 108 additions & 3 deletions go/node/deployment/v1beta3/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit d879c24

Please sign in to comment.