From 6588466136e3ee1bd0e34e07f740009581624ec8 Mon Sep 17 00:00:00 2001 From: Artur Troian Date: Wed, 19 Jul 2023 20:07:02 -0400 Subject: [PATCH] feat: implement resource id and refactor sdl parsing Signed-off-by: Artur Troian --- go.mod | 2 +- go.sum | 4 +- sdl/_testdata/service-mix.yaml | 80 ++++ sdl/_testdata/service-mix2.yaml | 69 ++++ sdl/_testdata/simple-with-ip.yaml | 50 +++ sdl/expose.go | 88 +++++ sdl/gpu.go | 4 +- sdl/resources.go | 54 +-- sdl/sdl.go | 49 +-- sdl/sdl_test.go | 12 +- sdl/util/util.go | 12 - sdl/util/util_test.go | 31 -- sdl/v2.go | 392 +++++++++---------- sdl/v2_ip_test.go | 86 +++-- sdl/v2_test.go | 479 ++++++++++++++++++++---- testutil/base.go | 9 +- testutil/sdk.go | 2 +- testutil/types.go | 5 +- upgrades/software/v0.24.0/deployment.go | 1 + x/deployment/client/cli/tx.go | 6 +- x/deployment/handler/handler_test.go | 2 +- x/deployment/keeper/grpc_query_test.go | 2 +- x/deployment/simulation/operations.go | 4 +- x/market/handler/handler_test.go | 4 +- 24 files changed, 991 insertions(+), 456 deletions(-) create mode 100644 sdl/_testdata/service-mix.yaml create mode 100644 sdl/_testdata/service-mix2.yaml create mode 100644 sdl/_testdata/simple-with-ip.yaml diff --git a/go.mod b/go.mod index fc32c6e0af..aa343f2c01 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/akash-network/node go 1.20 require ( - github.com/akash-network/akash-api v0.0.23 + github.com/akash-network/akash-api v0.0.24 github.com/blang/semver/v4 v4.0.0 github.com/boz/go-lifecycle v0.1.1 github.com/cosmos/cosmos-sdk v0.45.16 diff --git a/go.sum b/go.sum index 1bf77e6f06..ab93bbce01 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,8 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/akash-network/akash-api v0.0.23 h1:zsu5ARReF7h8pD5jlBci3RpyJaahcvwI56IvNpR9+gE= -github.com/akash-network/akash-api v0.0.23/go.mod h1:9/uYusyBcZecBQCgZWUbXRu0i1tyxj4/ze45XB2oLIU= +github.com/akash-network/akash-api v0.0.24 h1:nIuftXhNI6w5oLjfncbb2BLxLEZvkwfxknbjYuFBI5M= +github.com/akash-network/akash-api v0.0.24/go.mod h1:9/uYusyBcZecBQCgZWUbXRu0i1tyxj4/ze45XB2oLIU= github.com/akash-network/cometbft v0.34.27-akash h1:V1dApDOr8Ee7BJzYyQ7Z9VBtrAul4+baMeA6C49dje0= github.com/akash-network/cometbft v0.34.27-akash/go.mod h1:BcCbhKv7ieM0KEddnYXvQZR+pZykTKReJJYf7YC7qhw= github.com/akash-network/ledger-go v0.14.3 h1:LCEFkTfgGA2xFMN2CtiKvXKE7dh0QSM77PJHCpSkaAo= diff --git a/sdl/_testdata/service-mix.yaml b/sdl/_testdata/service-mix.yaml new file mode 100644 index 0000000000..51a21a01ed --- /dev/null +++ b/sdl/_testdata/service-mix.yaml @@ -0,0 +1,80 @@ +--- +version: "2.0" +services: + svca: + image: nginx + expose: + - port: 80 + accept: + - ahostname.com + to: + - global: true + - port: 12345 + to: + - global: true + proto: udp + svcb: + image: nginx + expose: + - port: 80 + accept: + - bhostname.com + to: + - global: true + - port: 12346 + to: + - global: true + proto: udp + +profiles: + compute: + profilea: + resources: + cpu: + units: "100m" + gpu: + units: "1" + attributes: + vendor: + nvidia: + memory: + size: "128Mi" + storage: + - size: "1Gi" + profileb: + resources: + cpu: + units: "100m" + memory: + size: "128Mi" + storage: + - size: "1Gi" + placement: + westcoast: + attributes: + region: us-west + blalbla: foo + signedBy: + anyOf: + - 1 + - 2 + allOf: + - 3 + - 4 + pricing: + profilea: + denom: uakt + amount: 50 + profileb: + denom: uakt + amount: 50 + +deployment: + svca: + westcoast: + profile: profilea + count: 1 + svcb: + westcoast: + profile: profileb + count: 1 diff --git a/sdl/_testdata/service-mix2.yaml b/sdl/_testdata/service-mix2.yaml new file mode 100644 index 0000000000..3eb556d980 --- /dev/null +++ b/sdl/_testdata/service-mix2.yaml @@ -0,0 +1,69 @@ +--- +version: "2.0" +services: + svca: + image: nginx + expose: + - port: 80 + accept: + - ahostname.com + to: + - global: true + - port: 12345 + to: + - global: true + proto: udp + svcb: + image: nginx + expose: + - port: 80 + accept: + - bhostname.com + to: + - global: true + - port: 12346 + to: + - global: true + proto: udp + +profiles: + compute: + profilea: + resources: + cpu: + units: "100m" + gpu: + units: "1" + attributes: + vendor: + nvidia: + memory: + size: "128Mi" + storage: + - size: "1Gi" + placement: + westcoast: + attributes: + region: us-west + blalbla: foo + signedBy: + anyOf: + - 1 + - 2 + allOf: + - 3 + - 4 + pricing: + profilea: + denom: uakt + amount: 50 + +deployment: + svca: + westcoast: + profile: profilea + count: 1 + svcb: + westcoast: + profile: profilea + count: 1 diff --git a/sdl/_testdata/simple-with-ip.yaml b/sdl/_testdata/simple-with-ip.yaml new file mode 100644 index 0000000000..4308f095ee --- /dev/null +++ b/sdl/_testdata/simple-with-ip.yaml @@ -0,0 +1,50 @@ +--- +version: "2.0" +services: + web: + image: nginx + expose: + - port: 80 + accept: + - ahostname.com + to: + - global: true + - port: 12345 + to: + - global: true + ip: "meow" + proto: udp +profiles: + compute: + web: + resources: + cpu: + units: "100m" + memory: + size: "128Mi" + storage: + size: "1Gi" + placement: + westcoast: + attributes: + region: us-west + signedBy: + anyOf: + - 1 + - 2 + allOf: + - 3 + - 4 + pricing: + web: + denom: uakt + amount: 50 +deployment: + web: + westcoast: + profile: web + count: 2 + +endpoints: + meow: + kind: "ip" diff --git a/sdl/expose.go b/sdl/expose.go index 6ea0b95853..17426bd514 100644 --- a/sdl/expose.go +++ b/sdl/expose.go @@ -3,6 +3,7 @@ package sdl import ( "net/url" + manifest "github.com/akash-network/akash-api/go/manifest/v2beta2" "gopkg.in/yaml.v3" ) @@ -23,5 +24,92 @@ func (p *v2Accept) UnmarshalYAML(node *yaml.Node) error { } p.Items = accept + return nil } + +func (sdl v2Exposes) toManifestExpose(endpointNames map[string]uint32) (manifest.ServiceExposes, error) { + exposeCount := 0 + for _, expose := range sdl { + if len(expose.To) > 0 { + exposeCount += len(expose.To) + } else { + exposeCount++ + } + } + + res := make([]manifest.ServiceExpose, 0, exposeCount) + + for _, expose := range sdl { + exp, err := expose.toManifestExposes(endpointNames) + if err != nil { + return nil, err + } + + res = append(res, exp...) + } + + return res, nil +} + +func (sdl v2Expose) toManifestExposes(endpointNames map[string]uint32) (manifest.ServiceExposes, error) { + exposeCount := len(sdl.To) + if exposeCount == 0 { + exposeCount = 1 + } + + res := make(manifest.ServiceExposes, 0, exposeCount) + + proto, err := manifest.ParseServiceProtocol(sdl.Proto) + if err != nil { + return nil, err + } + + httpOptions, err := sdl.HTTPOptions.asManifest() + if err != nil { + return nil, err + } + + if len(sdl.To) > 0 { + for _, to := range sdl.To { + if !to.Global { + continue + } + + // This value is created just so it can be passed to the utility function + expose := manifest.ServiceExpose{ + Service: to.Service, + Port: sdl.Port, + ExternalPort: sdl.As, + Proto: proto, + Global: to.Global, + Hosts: sdl.Accept.Items, + HTTPOptions: httpOptions, + IP: to.IP, + } + + // Check to see if an IP endpoint is also specified + if expose.Global && len(expose.IP) != 0 { + seqNo := endpointNames[expose.IP] + expose.EndpointSequenceNumber = seqNo + } + + res = append(res, expose) + } + } else { + expose := manifest.ServiceExpose{ + Service: "", + Port: sdl.Port, + ExternalPort: sdl.As, + Proto: proto, + Global: false, + Hosts: sdl.Accept.Items, + HTTPOptions: httpOptions, + IP: "", + } + + res = append(res, expose) + } + + return res, nil +} diff --git a/sdl/gpu.go b/sdl/gpu.go index d5b5c3866d..1bb6b12556 100644 --- a/sdl/gpu.go +++ b/sdl/gpu.go @@ -2,6 +2,7 @@ package sdl import ( "fmt" + "sort" "gopkg.in/yaml.v3" @@ -97,7 +98,8 @@ func (sdl *v2GPUAttributes) UnmarshalYAML(node *yaml.Node) error { Value: "true", }) } - res.Sort() + + sort.Sort(res) if err := res.Validate(); err != nil { return fmt.Errorf("sdl: invalid GPU attributes: %w", err) diff --git a/sdl/resources.go b/sdl/resources.go index 477a9aa229..2efa9f5fd2 100644 --- a/sdl/resources.go +++ b/sdl/resources.go @@ -6,17 +6,19 @@ import ( type v2ComputeResources struct { CPU *v2ResourceCPU `yaml:"cpu"` - GPU *v2ResourceGPU `yaml:"gpu,omitempty"` + GPU *v2ResourceGPU `yaml:"gpu"` Memory *v2ResourceMemory `yaml:"memory"` Storage v2ResourceStorageArray `yaml:"storage"` } -func (sdl *v2ComputeResources) toDGroupResourceUnits() types.ResourceUnits { +func (sdl *v2ComputeResources) toResources() types.Resources { if sdl == nil { - return types.ResourceUnits{} + return types.Resources{} } - var units types.ResourceUnits + units := types.Resources{ + Endpoints: types.Endpoints{}, + } if sdl.CPU != nil { units.CPU = &types.CPU{ @@ -55,47 +57,3 @@ func (sdl *v2ComputeResources) toDGroupResourceUnits() types.ResourceUnits { return units } - -func toManifestResources(res *v2ComputeResources) types.ResourceUnits { - var units types.ResourceUnits - - if res.CPU != nil { - units.CPU = &types.CPU{ - Units: types.NewResourceValue(uint64(res.CPU.Units)), - } - } - - if res.GPU != nil { - units.GPU = &types.GPU{ - Units: types.NewResourceValue(uint64(res.GPU.Units)), - Attributes: make(types.Attributes, len(res.GPU.Attributes)), - } - copy(units.GPU.Attributes, res.GPU.Attributes) - } else { - units.GPU = &types.GPU{ - Units: types.NewResourceValue(0), - } - } - - if res.Memory != nil { - units.Memory = &types.Memory{ - Quantity: types.NewResourceValue(uint64(res.Memory.Quantity)), - } - } - - for _, storage := range res.Storage { - storageEntry := types.Storage{ - Name: storage.Name, - Quantity: types.NewResourceValue(uint64(storage.Quantity)), - } - - if storage.Attributes != nil { - storageEntry.Attributes = make(types.Attributes, len(storage.Attributes)) - copy(storageEntry.Attributes, storage.Attributes) - } - - units.Storage = append(units.Storage, storageEntry) - } - - return units -} diff --git a/sdl/sdl.go b/sdl/sdl.go index 63dc8072d1..75a3bebf91 100644 --- a/sdl/sdl.go +++ b/sdl/sdl.go @@ -1,8 +1,6 @@ package sdl import ( - "crypto/sha256" - "encoding/json" "errors" "fmt" "os" @@ -10,8 +8,6 @@ import ( "github.com/blang/semver/v4" "gopkg.in/yaml.v3" - sdk "github.com/cosmos/cosmos-sdk/types" - manifest "github.com/akash-network/akash-api/go/manifest/v2beta2" dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3" ) @@ -23,16 +19,17 @@ var ( // SDL is the interface which wraps Validate, Deployment and Manifest methods type SDL interface { - DeploymentGroups() ([]*dtypes.GroupSpec, error) + DeploymentGroups() (dtypes.GroupSpecs, error) Manifest() (manifest.Manifest, error) + Version() ([]byte, error) validate() error } var _ SDL = (*sdl)(nil) type sdl struct { - Version semver.Version `yaml:"-"` - data SDL `yaml:"-"` + Ver semver.Version `yaml:"version,-"` + data SDL `yaml:"-"` } func (s *sdl) UnmarshalYAML(node *yaml.Node) error { @@ -42,7 +39,7 @@ func (s *sdl) UnmarshalYAML(node *yaml.Node) error { for idx := range node.Content { if node.Content[idx].Value == "version" { var err error - if result.Version, err = semver.ParseTolerant(node.Content[idx+1].Value); err != nil { + if result.Ver, err = semver.ParseTolerant(node.Content[idx+1].Value); err != nil { return err } foundVersion = true @@ -55,7 +52,7 @@ func (s *sdl) UnmarshalYAML(node *yaml.Node) error { } // nolint: gocritic - if result.Version.GE(semver.MustParse("2.0.0")) && result.Version.LT(semver.MustParse("3.0.0")) { + if result.Ver.GE(semver.MustParse("2.0.0")) && result.Ver.LT(semver.MustParse("3.0.0")) { var decoded v2 if err := node.Decode(&decoded); err != nil { return err @@ -63,7 +60,7 @@ func (s *sdl) UnmarshalYAML(node *yaml.Node) error { result.data = &decoded } else { - return fmt.Errorf("%w: config: unsupported version %q", errSDLInvalid, result.Version) + return fmt.Errorf("%w: config: unsupported version %q", errSDLInvalid, result.Ver) } *s = result @@ -110,7 +107,7 @@ func Read(buf []byte) (SDL, error) { return nil, err } - if err := manifest.ValidateManifest(m); err != nil { + if err := m.Validate(); err != nil { return nil, err } @@ -118,35 +115,17 @@ func Read(buf []byte) (SDL, error) { } // Version creates the deterministic Deployment Version hash from the SDL. -func Version(s SDL) ([]byte, error) { - manifest, err := s.Manifest() - if err != nil { - return nil, err - } - return ManifestVersion(manifest) -} - -// ManifestVersion calculates the identifying deterministic hash for an SDL. -// Sha256 returns 32 byte sum of the SDL. -func ManifestVersion(manifest manifest.Manifest) ([]byte, error) { - m, err := json.Marshal(manifest) - if err != nil { - return nil, err - } - - sortedBytes, err := sdk.SortJSON(m) - if err != nil { - return nil, err +func (s *sdl) Version() ([]byte, error) { + if s.data == nil { + return nil, errUninitializedConfig } - sum := sha256.Sum256(sortedBytes) - - return sum[:], nil + return s.data.Version() } -func (s *sdl) DeploymentGroups() ([]*dtypes.GroupSpec, error) { +func (s *sdl) DeploymentGroups() (dtypes.GroupSpecs, error) { if s.data == nil { - return []*dtypes.GroupSpec{}, errUninitializedConfig + return dtypes.GroupSpecs{}, errUninitializedConfig } return s.data.DeploymentGroups() diff --git a/sdl/sdl_test.go b/sdl/sdl_test.go index de0257061d..2cc5821b96 100644 --- a/sdl/sdl_test.go +++ b/sdl/sdl_test.go @@ -13,7 +13,7 @@ func TestSDLManifestVersion(t *testing.T) { m, err := obj.Manifest() require.NoError(t, err) - version, err := ManifestVersion(m) + version, err := m.Version() require.NoError(t, err) // Should return a value require.NotEmpty(t, version) @@ -24,11 +24,11 @@ func TestSDLManifestVersion(t *testing.T) { m, err = obj.Manifest() require.NoError(t, err) - secondVersion, err := ManifestVersion(m) + secondVersion, err := m.Version() require.NoError(t, err) // Should return a value require.NotEmpty(t, secondVersion) - // Should be different than the first + // Should be different from the first require.NotEqual(t, secondVersion, version) } @@ -39,7 +39,7 @@ func TestSDLManifestVersionChangesWithVersion(t *testing.T) { m, err := obj.Manifest() require.NoError(t, err) - version, err := ManifestVersion(m) + version, err := m.Version() require.NoError(t, err) // Should return a value require.NotEmpty(t, version) @@ -50,10 +50,10 @@ func TestSDLManifestVersionChangesWithVersion(t *testing.T) { m, err = obj.Manifest() require.NoError(t, err) - secondVersion, err := ManifestVersion(m) + secondVersion, err := m.Version() require.NoError(t, err) // Should return a value require.NotEmpty(t, secondVersion) - // Should be different than the first + // Should be different from the first require.NotEqual(t, secondVersion, version) } diff --git a/sdl/util/util.go b/sdl/util/util.go index e6a67d59d4..3e80557497 100644 --- a/sdl/util/util.go +++ b/sdl/util/util.go @@ -5,21 +5,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - manifest "github.com/akash-network/akash-api/go/manifest/v2beta2" atypes "github.com/akash-network/akash-api/go/node/types/v1beta3" ) -func ShouldBeIngress(expose manifest.ServiceExpose) bool { - return expose.Proto == manifest.TCP && expose.Global && 80 == ExposeExternalPort(expose) -} - -func ExposeExternalPort(expose manifest.ServiceExpose) int32 { - if expose.ExternalPort == 0 { - return int32(expose.Port) - } - return int32(expose.ExternalPort) -} - func ComputeCommittedResources(factor float64, rv atypes.ResourceValue) atypes.ResourceValue { // If the value is less than 1, commit the original value. There is no concept of undercommit if factor <= 1.0 { diff --git a/sdl/util/util_test.go b/sdl/util/util_test.go index cd1dcaae1a..3b369399ba 100644 --- a/sdl/util/util_test.go +++ b/sdl/util/util_test.go @@ -5,42 +5,11 @@ import ( "github.com/stretchr/testify/require" - manifest "github.com/akash-network/akash-api/go/manifest/v2beta2" atypes "github.com/akash-network/akash-api/go/node/types/v1beta3" "github.com/akash-network/node/sdl/util" ) -func TestShouldBeIngress(t *testing.T) { - // Should not create ingress for something on port 81 - require.False(t, util.ShouldBeIngress(manifest.ServiceExpose{ - Global: true, - Proto: manifest.TCP, - Port: 81, - })) - - // Should create ingress for something on port 80 - require.True(t, util.ShouldBeIngress(manifest.ServiceExpose{ - Global: true, - Proto: manifest.TCP, - Port: 80, - })) - - // Should not create ingress for something on port 80 that is not Global - require.False(t, util.ShouldBeIngress(manifest.ServiceExpose{ - Global: false, - Proto: manifest.TCP, - Port: 80, - })) - - // Should not create ingress for something on port 80 that is UDP - require.False(t, util.ShouldBeIngress(manifest.ServiceExpose{ - Global: true, - Proto: manifest.UDP, - Port: 80, - })) -} - func TestComputeCommittedResources(t *testing.T) { rv := atypes.NewResourceValue(100) diff --git a/sdl/v2.go b/sdl/v2.go index 11197b6b8e..7c12f3dc07 100644 --- a/sdl/v2.go +++ b/sdl/v2.go @@ -11,8 +11,7 @@ import ( manifest "github.com/akash-network/akash-api/go/manifest/v2beta2" dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3" types "github.com/akash-network/akash-api/go/node/types/v1beta3" - - sdlutil "github.com/akash-network/node/sdl/util" + "gopkg.in/yaml.v3" ) const ( @@ -46,14 +45,23 @@ var ( endpointNameValidationRegex = regexp.MustCompile(`^[[:lower:]]+[[:lower:]-_\d]+$`) ) +var _ SDL = (*v2)(nil) + type v2 struct { - Include []string `yaml:",omitempty"` - Services map[string]v2Service `yaml:"services,omitempty"` - Profiles v2profiles `yaml:"profiles,omitempty"` - Deployments map[string]v2Deployment `yaml:"deployment"` - Endpoints map[string]v2Endpoint `yaml:"endpoints"` + Include []string `yaml:",omitempty"` + Services map[string]v2Service `yaml:"services,omitempty"` + Profiles v2profiles `yaml:"profiles,omitempty"` + Deployments v2Deployments `yaml:"deployment"` + Endpoints map[string]v2Endpoint `yaml:"endpoints"` + + result struct { + dgroups dtypes.GroupSpecs + mgroups manifest.Groups + } } +type v2Deployments map[string]v2Deployment + type v2Endpoint struct { Kind string `yaml:"kind"` } @@ -145,6 +153,8 @@ type v2Expose struct { HTTPOptions v2HTTPOptions `yaml:"http_options"` } +type v2Exposes []v2Expose + type v2Dependency struct { Service string `yaml:"service"` } @@ -158,7 +168,7 @@ type v2Service struct { Command []string `yaml:",omitempty"` Args []string `yaml:",omitempty"` Env []string `yaml:",omitempty"` - Expose []v2Expose `yaml:",omitempty"` + Expose v2Exposes `yaml:",omitempty"` Dependencies []v2Dependency `yaml:",omitempty"` Params *v2ServiceParams `yaml:",omitempty"` } @@ -175,7 +185,6 @@ type v2ServiceDeployment struct { type v2Deployment map[string]v2ServiceDeployment type v2ProfileCompute struct { - // todo are compute resources mandatory ? Resources *v2ComputeResources `yaml:"resources,omitempty"` } @@ -190,220 +199,142 @@ type v2profiles struct { Placement map[string]v2ProfilePlacement `yaml:"placement"` } -func (sdl *v2) computeEndpointSequenceNumbers() map[string]uint32 { - var endpointNames []string +func (sdl *v2) DeploymentGroups() (dtypes.GroupSpecs, error) { + return sdl.result.dgroups, nil +} - for _, serviceName := range v2DeploymentSvcNames(sdl.Deployments) { +func (sdl *v2) Manifest() (manifest.Manifest, error) { + return manifest.Manifest(sdl.result.mgroups), nil +} - for _, expose := range sdl.Services[serviceName].Expose { - for _, to := range expose.To { - if to.Global && len(to.IP) == 0 { - continue - } +// Version creates the deterministic Deployment Version hash from the SDL. +func (sdl *v2) Version() ([]byte, error) { + return manifest.Manifest(sdl.result.mgroups).Version() +} - endpointNames = append(endpointNames, to.IP) - } +func (sdl *v2) UnmarshalYAML(node *yaml.Node) error { + result := v2{} + +loop: + for i := 0; i < len(node.Content); i += 2 { + var val interface{} + switch node.Content[i].Value { + case "include": + val = &result.Include + case "services": + val = &result.Services + case "profiles": + val = &result.Profiles + case "deployment": + val = &result.Deployments + case "endpoints": + val = &result.Endpoints + case "version": + // version is already verified + continue loop + default: + return fmt.Errorf("sdl: unexpected field %s", node.Content[i].Value) + } + + if err := node.Content[i+1].Decode(val); err != nil { + return err } } - ipEndpointNames := make(map[string]uint32) - if len(endpointNames) == 0 { - return ipEndpointNames + if err := result.buildGroups(); err != nil { + return err } - // Make the assignment stable - sort.Strings(endpointNames) + *sdl = result - // Start at zero, so the first assigned one is 1 - endpointSeqNumber := uint32(0) - for _, name := range endpointNames { - endpointSeqNumber++ - seqNo := endpointSeqNumber - ipEndpointNames[name] = seqNo - } + return nil +} - return ipEndpointNames +type groupsBuilder struct { + dgroup *dtypes.GroupSpec + mgroup *manifest.Group + boundComputes map[string]map[string]int } -func (sdl *v2) DeploymentGroups() ([]*dtypes.GroupSpec, error) { - groups := make(map[string]*dtypes.GroupSpec) +// buildGroups +func (sdl *v2) buildGroups() error { + endpointsNames := sdl.computeEndpointSequenceNumbers() - ipEndpointNames := sdl.computeEndpointSequenceNumbers() - for _, svcName := range v2DeploymentSvcNames(sdl.Deployments) { + groups := make(map[string]*groupsBuilder) + + for _, svcName := range sdl.Deployments.svcNames() { depl := sdl.Deployments[svcName] - for _, placementName := range v2DeploymentPlacementNames(depl) { + for _, placementName := range depl.placementNames() { + // objects below have been ensured to exist svcdepl := depl[placementName] - - // at this moment compute, infra and price have been checked for existence compute := sdl.Profiles.Compute[svcdepl.Profile] + svc := sdl.Services[svcName] infra := sdl.Profiles.Placement[placementName] price := infra.Pricing[svcdepl.Profile] group := groups[placementName] if group == nil { - group = &dtypes.GroupSpec{ - Name: placementName, + group = &groupsBuilder{ + dgroup: &dtypes.GroupSpec{ + Name: placementName, + }, + mgroup: &manifest.Group{ + Name: placementName, + }, + boundComputes: make(map[string]map[string]int), } - group.Requirements.Attributes = infra.Attributes - group.Requirements.SignedBy = infra.SignedBy + group.dgroup.Requirements.Attributes = types.Attributes(infra.Attributes) + group.dgroup.Requirements.SignedBy = infra.SignedBy // keep ordering stable - sort.Slice(group.Requirements.Attributes, func(i, j int) bool { - return group.Requirements.Attributes[i].Key < group.Requirements.Attributes[j].Key - }) + sort.Sort(group.dgroup.Requirements.Attributes) groups[placementName] = group } - resources := dtypes.Resource{ - Resources: compute.Resources.toDGroupResourceUnits(), - Price: price.Value, - Count: svcdepl.Count, + if _, exists := group.boundComputes[placementName]; !exists { + group.boundComputes[placementName] = make(map[string]int) } - endpoints := make([]types.Endpoint, 0) - for _, expose := range sdl.Services[svcName].Expose { - for _, to := range expose.To { - if !to.Global { - continue - } - - proto, err := manifest.ParseServiceProtocol(expose.Proto) - if err != nil { - return nil, err - } - // This value is created just so it can be passed to the utility function - v := manifest.ServiceExpose{ - Port: expose.Port, - ExternalPort: expose.As, - Proto: proto, - Service: to.Service, - Global: to.Global, - Hosts: expose.Accept.Items, - IP: to.IP, - } - - // Check to see if an IP endpoint is also specified - if v.Global && len(v.IP) != 0 { - seqNo := ipEndpointNames[v.IP] - v.EndpointSequenceNumber = seqNo - endpoints = append(endpoints, - types.Endpoint{Kind: types.Endpoint_LEASED_IP, - SequenceNumber: seqNo}) - } - - kind := types.Endpoint_RANDOM_PORT - if sdlutil.ShouldBeIngress(v) { - kind = types.Endpoint_SHARED_HTTP - } - - endpoints = append(endpoints, types.Endpoint{Kind: kind}) - } + expose, err := sdl.Services[svcName].Expose.toManifestExpose(endpointsNames) + if err != nil { + return err } - resources.Resources.Endpoints = endpoints - group.Resources = append(group.Resources, resources) - } - } - - // keep ordering stable - names := make([]string, 0, len(groups)) - for name := range groups { - names = append(names, name) - } - sort.Strings(names) - - result := make([]*dtypes.GroupSpec, 0, len(names)) - for _, name := range names { - result = append(result, groups[name]) - } - - return result, nil -} - -func (sdl *v2) Manifest() (manifest.Manifest, error) { - groups := make(map[string]*manifest.Group) - - ipEndpointNames := sdl.computeEndpointSequenceNumbers() - - for _, svcName := range v2DeploymentSvcNames(sdl.Deployments) { - depl := sdl.Deployments[svcName] - - for _, placementName := range v2DeploymentPlacementNames(depl) { - svcdepl := depl[placementName] + resources := compute.Resources.toResources() + resources.Endpoints = expose.GetEndpoints() - group := groups[placementName] + if location, bound := group.boundComputes[placementName][svcdepl.Profile]; !bound { + res := compute.Resources.toResources() + res.Endpoints = expose.GetEndpoints() - if group == nil { - group = &manifest.Group{ - Name: placementName, + var resID int + if ln := len(group.dgroup.Resources); ln > 0 { + resID = ln + 1 + } else { + resID = 1 } - groups[group.Name] = group - } - // at this moment compute and svc have been checked for existence - compute := sdl.Profiles.Compute[svcdepl.Profile] - svc := sdl.Services[svcName] - - manifestResources := toManifestResources(compute.Resources) + res.ID = uint32(resID) + resources.ID = res.ID - var manifestExpose []manifest.ServiceExpose - - for _, expose := range svc.Expose { - proto, err := manifest.ParseServiceProtocol(expose.Proto) - if err != nil { - return manifest.Manifest{}, err - } + group.dgroup.Resources = append(group.dgroup.Resources, dtypes.ResourceUnit{ + Resources: res, + Price: price.Value, + Count: svcdepl.Count, + }) - httpOptions, err := expose.HTTPOptions.asManifest() - if err != nil { - return manifest.Manifest{}, err - } + group.boundComputes[placementName][svcdepl.Profile] = len(group.dgroup.Resources) - 1 + } else { + resources.ID = group.dgroup.Resources[location].ID - if len(expose.To) != 0 { - for _, to := range expose.To { - - var seqNo uint32 - if to.Global && len(to.IP) != 0 { - _, exists := sdl.Endpoints[to.IP] - if !exists { - return nil, fmt.Errorf("%w: unknown endpoint %q", errSDLInvalid, to.IP) - } - - seqNo = ipEndpointNames[to.IP] - manifestResources.Endpoints = append(manifestResources.Endpoints, types.Endpoint{ - Kind: types.Endpoint_LEASED_IP, - SequenceNumber: seqNo, - }) - } + group.dgroup.Resources[location].Count += svcdepl.Count + group.dgroup.Resources[location].Endpoints = append(group.dgroup.Resources[location].Endpoints, expose.GetEndpoints()...) - manifestExpose = append(manifestExpose, manifest.ServiceExpose{ - Service: to.Service, - Port: expose.Port, - ExternalPort: expose.As, - Proto: proto, - Global: to.Global, - Hosts: expose.Accept.Items, - HTTPOptions: httpOptions, - IP: to.IP, - EndpointSequenceNumber: seqNo, - }) - } - } else { // Nothing explicitly set, fill in without any information from "expose.To" - manifestExpose = append(manifestExpose, manifest.ServiceExpose{ - Service: "", - Port: expose.Port, - ExternalPort: expose.As, - Proto: proto, - Global: false, - Hosts: expose.Accept.Items, - HTTPOptions: httpOptions, - IP: "", - }) - } + sort.Sort(group.dgroup.Resources[location].Endpoints) } msvc := manifest.Service{ @@ -411,10 +342,10 @@ func (sdl *v2) Manifest() (manifest.Manifest, error) { Image: svc.Image, Args: svc.Args, Env: svc.Env, - Resources: manifestResources, + Resources: resources, Count: svcdepl.Count, Command: svc.Command, - Expose: manifestExpose, + Expose: expose, } if svc.Params != nil { @@ -434,46 +365,30 @@ func (sdl *v2) Manifest() (manifest.Manifest, error) { msvc.Params = params } - // stable ordering for the Expose portion - sort.Slice(msvc.Expose, func(i, j int) bool { - a, b := msvc.Expose[i], msvc.Expose[j] - - if a.Service != b.Service { - return a.Service < b.Service - } - - if a.Port != b.Port { - return a.Port < b.Port - } - - if a.Proto != b.Proto { - return a.Proto < b.Proto - } - - if a.Global != b.Global { - return a.Global - } - - return false - }) - - group.Services = append(group.Services, msvc) + group.mgroup.Services = append(group.mgroup.Services, msvc) } } - // stable ordering + // keep ordering stable names := make([]string, 0, len(groups)) for name := range groups { names = append(names, name) } sort.Strings(names) - result := make([]manifest.Group, 0, len(names)) + sdl.result.dgroups = make(dtypes.GroupSpecs, 0, len(names)) + sdl.result.mgroups = make(manifest.Groups, 0, len(names)) + for _, name := range names { - result = append(result, *groups[name]) + mgroup := *groups[name].mgroup + // stable ordering services by name + sort.Sort(mgroup.Services) + + sdl.result.dgroups = append(sdl.result.dgroups, groups[name].dgroup) + sdl.result.mgroups = append(sdl.result.mgroups, mgroup) } - return result, nil + return nil } func (sdl *v2) validate() error { @@ -494,7 +409,7 @@ func (sdl *v2) validate() error { endpointsUsed := make(map[string]struct{}) portsUsed := make(map[string]string) - for _, svcName := range v2DeploymentSvcNames(sdl.Deployments) { + for _, svcName := range sdl.Deployments.svcNames() { depl := sdl.Deployments[svcName] for _, placementName := range v2DeploymentPlacementNames(depl) { @@ -617,22 +532,67 @@ func (sdl *v2) validate() error { return nil } -// stable ordering -func v2DeploymentSvcNames(m map[string]v2Deployment) []string { - names := make([]string, 0, len(m)) - for name := range m { +func (sdl *v2) computeEndpointSequenceNumbers() map[string]uint32 { + var endpointNames []string + res := make(map[string]uint32) + + for _, serviceName := range sdl.Deployments.svcNames() { + for _, expose := range sdl.Services[serviceName].Expose { + for _, to := range expose.To { + if to.Global && len(to.IP) == 0 { + continue + } + + endpointNames = append(endpointNames, to.IP) + } + } + } + + if len(endpointNames) == 0 { + return res + } + + // Make the assignment stable + sort.Strings(endpointNames) + + // Start at zero, so the first assigned one is 1 + endpointSeqNumber := uint32(0) + for _, name := range endpointNames { + endpointSeqNumber++ + seqNo := endpointSeqNumber + res[name] = seqNo + } + + return res +} + +func (sdl v2Deployments) svcNames() []string { + names := make([]string, 0, len(sdl)) + for name := range sdl { names = append(names, name) } sort.Strings(names) + + return names +} + +// placementNames stable ordered placement names +func (sdl v2Deployment) placementNames() []string { + names := make([]string, 0, len(sdl)) + for name := range sdl { + names = append(names, name) + } + sort.Strings(names) + return names } -// stable ordering func v2DeploymentPlacementNames(m v2Deployment) []string { names := make([]string, 0, len(m)) for name := range m { names = append(names, name) } sort.Strings(names) + return names } diff --git a/sdl/v2_ip_test.go b/sdl/v2_ip_test.go index d4e5008183..4e54118b9b 100644 --- a/sdl/v2_ip_test.go +++ b/sdl/v2_ip_test.go @@ -5,15 +5,20 @@ import ( "fmt" "testing" + manifest "github.com/akash-network/akash-api/go/manifest/v2beta2" "github.com/stretchr/testify/require" types "github.com/akash-network/akash-api/go/node/types/v1beta3" ) -func findFirstIPEndpoint(t *testing.T, endpoints []types.Endpoint) types.Endpoint { +func findIPEndpoint(t *testing.T, endpoints []types.Endpoint, id int) types.Endpoint { + idx := 0 for _, endpoint := range endpoints { if endpoint.Kind == types.Endpoint_LEASED_IP { - return endpoint + idx++ + if id == idx { + return endpoint + } } } @@ -21,6 +26,38 @@ func findFirstIPEndpoint(t *testing.T, endpoints []types.Endpoint) types.Endpoin return types.Endpoint{} } +func TestV2ParseSimpleWithIP(t *testing.T) { + sdl, err := ReadFile("./_testdata/simple-with-ip.yaml") + require.NoError(t, err) + require.NotNil(t, sdl) + + groups, err := sdl.DeploymentGroups() + require.NoError(t, err) + require.Len(t, groups, 1) + group := groups[0] + resources := group.GetResourceUnits() + require.Len(t, resources, 1) + resource := resources[0] + + ipEndpoint := findIPEndpoint(t, resource.Resources.Endpoints, 1) + + require.Equal(t, ipEndpoint.Kind, types.Endpoint_LEASED_IP) + + mani, err := sdl.Manifest() + require.NoError(t, err) + var exposeIP manifest.ServiceExpose + for _, expose := range mani[0].Services[0].Expose { + if len(expose.IP) != 0 { + exposeIP = expose + break + } + } + require.NotEmpty(t, exposeIP.IP) + require.Equal(t, exposeIP.Proto, manifest.UDP) + require.Equal(t, exposeIP.Port, uint32(12345)) + require.True(t, exposeIP.Global) +} + func TestV2Parse_IP(t *testing.T) { sdl1, err := ReadFile("../x/deployment/testdata/deployment-v2-ip-endpoint.yaml") require.NoError(t, err) @@ -30,7 +67,7 @@ func TestV2Parse_IP(t *testing.T) { require.Len(t, groups, 1) group := groups[0] - resources := group.GetResources() + resources := group.GetResourceUnits() require.Len(t, resources, 1) resource := resources[0] endpoints := resource.Resources.Endpoints @@ -76,15 +113,14 @@ func TestV2Parse_SharedIP(t *testing.T) { group := groups[0] - resources := group.GetResources() - require.Len(t, resources, 2) + resources := group.GetResourceUnits() + require.Len(t, resources, 1) resource := resources[0] - ipEndpoint := findFirstIPEndpoint(t, resource.Resources.Endpoints) + ipEndpoint := findIPEndpoint(t, resource.Resources.Endpoints, 1) require.Greater(t, ipEndpoint.SequenceNumber, uint32(0)) - resource = resources[1] - ipEndpoint = findFirstIPEndpoint(t, resource.Resources.Endpoints) + ipEndpoint = findIPEndpoint(t, resource.Resources.Endpoints, 2) require.Greater(t, ipEndpoint.SequenceNumber, uint32(0)) mani, err := sdl1.Manifest() @@ -98,11 +134,11 @@ func TestV2Parse_SharedIP(t *testing.T) { require.Len(t, services, 2) serviceA := services[0] - serviceIPEndpoint := findFirstIPEndpoint(t, serviceA.Resources.Endpoints) + serviceIPEndpoint := findIPEndpoint(t, serviceA.Resources.Endpoints, 1) require.Equal(t, serviceIPEndpoint.SequenceNumber, ipEndpoint.SequenceNumber) serviceB := services[1] - serviceIPEndpoint = findFirstIPEndpoint(t, serviceB.Resources.Endpoints) + serviceIPEndpoint = findIPEndpoint(t, serviceB.Resources.Endpoints, 1) require.Equal(t, serviceIPEndpoint.SequenceNumber, ipEndpoint.SequenceNumber) } @@ -117,8 +153,8 @@ func TestV2Parse_MultipleIP(t *testing.T) { group := groups[0] - resources := group.GetResources() - require.Len(t, resources, 2) + resources := group.GetResourceUnits() + require.Len(t, resources, 1) mani, err := sdl1.Manifest() require.NoError(t, err) @@ -134,20 +170,20 @@ func TestV2Parse_MultipleGroupsIP(t *testing.T) { require.NoError(t, err) require.Len(t, groups, 2) - resources := groups[0].GetResources() + resources := groups[0].GetResourceUnits() require.Len(t, resources, 1) resource := resources[0] require.Len(t, resource.Resources.Endpoints, 2) - ipEndpointFirstGroup := findFirstIPEndpoint(t, resource.Resources.Endpoints) + ipEndpointFirstGroup := findIPEndpoint(t, resource.Resources.Endpoints, 1) require.Greater(t, ipEndpointFirstGroup.SequenceNumber, uint32(0)) - resources = groups[1].GetResources() + resources = groups[1].GetResourceUnits() require.Len(t, resources, 1) resource = resources[0] require.Len(t, resource.Resources.Endpoints, 2) - ipEndpointSecondGroup := findFirstIPEndpoint(t, resource.Resources.Endpoints) + ipEndpointSecondGroup := findIPEndpoint(t, resource.Resources.Endpoints, 1) require.Greater(t, ipEndpointSecondGroup.SequenceNumber, uint32(0)) require.NotEqual(t, ipEndpointFirstGroup.SequenceNumber, ipEndpointSecondGroup.SequenceNumber) @@ -157,16 +193,16 @@ func TestV2Parse_MultipleGroupsIP(t *testing.T) { require.Len(t, maniGroups, 2) maniGroup := maniGroups[0] - resources = maniGroup.GetResources() - require.Len(t, resources, 1) - resource = resources[0] - require.Equal(t, findFirstIPEndpoint(t, resource.Resources.Endpoints).SequenceNumber, ipEndpointFirstGroup.SequenceNumber) + mresources := maniGroup.GetResourceUnits() + require.Len(t, mresources, 1) + mresource := mresources[0] + require.Equal(t, findIPEndpoint(t, mresource.Endpoints, 1).SequenceNumber, ipEndpointFirstGroup.SequenceNumber) maniGroup = maniGroups[1] - resources = maniGroup.GetResources() - require.Len(t, resources, 1) - resource = resources[0] - require.Equal(t, findFirstIPEndpoint(t, resource.Resources.Endpoints).SequenceNumber, ipEndpointSecondGroup.SequenceNumber) + mresources = maniGroup.GetResourceUnits() + require.Len(t, mresources, 1) + mresource = mresources[0] + require.Equal(t, findIPEndpoint(t, mresource.Endpoints, 1).SequenceNumber, ipEndpointSecondGroup.SequenceNumber) } @@ -184,7 +220,7 @@ services: - global: true ip: %q accept: - - test.localhost + - test.localhost profiles: compute: diff --git a/sdl/v2_test.go b/sdl/v2_test.go index ff69c7e1f6..ec0c63e968 100644 --- a/sdl/v2_test.go +++ b/sdl/v2_test.go @@ -3,14 +3,16 @@ package sdl import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" manifest "github.com/akash-network/akash-api/go/manifest/v2beta2" - + dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3" "github.com/akash-network/akash-api/go/node/types/unit" atypes "github.com/akash-network/akash-api/go/node/types/v1beta3" + // "github.com/akash-network/node/testutil" ) func TestV2Expose(t *testing.T) { @@ -29,6 +31,12 @@ func TestV2Expose(t *testing.T) { require.NoError(t, err) } +func AkashDecCoin(t testing.TB, amount int64) sdk.DecCoin { + t.Helper() + amt := sdk.NewInt(amount) + return sdk.NewDecCoin("uakt", amt) +} + const ( randCPU uint64 = 100 randGPU uint64 = 1 @@ -36,6 +44,16 @@ const ( randStorage uint64 = 1 * unit.Gi ) +var ( + defaultHTTPOptions = manifest.ServiceExposeHTTPOptions{ + MaxBodySize: defaultMaxBodySize, + ReadTimeout: defaultReadTimeout, + SendTimeout: defaultSendTimeout, + NextTries: defaultNextTries, + NextCases: []string{"error", "timeout"}, + } +) + func TestV2ParseSimpleGPU(t *testing.T) { sdl, err := ReadFile("./_testdata/simple-gpu.yaml") require.NoError(t, err) @@ -45,7 +63,7 @@ func TestV2ParseSimpleGPU(t *testing.T) { assert.Len(t, groups, 1) group := groups[0] - assert.Len(t, group.GetResources(), 1) + assert.Len(t, group.GetResourceUnits(), 1) assert.Len(t, group.Requirements.Attributes, 2) assert.Equal(t, atypes.Attribute{ @@ -53,11 +71,12 @@ func TestV2ParseSimpleGPU(t *testing.T) { Value: "us-west", }, group.Requirements.Attributes[1]) - assert.Len(t, group.GetResources(), 1) + assert.Len(t, group.GetResourceUnits(), 1) - assert.Equal(t, atypes.Resources{ + assert.Equal(t, dtypes.ResourceUnit{ Count: 2, - Resources: atypes.ResourceUnits{ + Resources: atypes.Resources{ + ID: 1, CPU: &atypes.CPU{ Units: atypes.NewResourceValue(randCPU), }, @@ -88,7 +107,8 @@ func TestV2ParseSimpleGPU(t *testing.T) { }, }, }, - }, group.GetResources()[0]) + Price: AkashDecCoin(t, 50), + }, group.GetResourceUnits()[0]) mani, err := sdl.Manifest() require.NoError(t, err) @@ -103,7 +123,8 @@ func TestV2ParseSimpleGPU(t *testing.T) { { Name: "web", Image: "nginx", - Resources: atypes.ResourceUnits{ + Resources: atypes.Resources{ + ID: 1, CPU: &atypes.CPU{ Units: atypes.NewResourceValue(100), }, @@ -125,6 +146,14 @@ func TestV2ParseSimpleGPU(t *testing.T) { Quantity: atypes.NewResourceValue(1 * unit.Gi), }, }, + Endpoints: []atypes.Endpoint{ + { + Kind: atypes.Endpoint_SHARED_HTTP, + }, + { + Kind: atypes.Endpoint_RANDOM_PORT, + }, + }, }, Count: 2, Expose: []manifest.ServiceExpose{ @@ -161,21 +190,17 @@ func TestV2Parse_Deployments(t *testing.T) { _, err = sdl1.Manifest() require.NoError(t, err) - sha1, err := Version(sdl1) + sha1, err := sdl1.Version() require.NoError(t, err) assert.Len(t, sha1, 32) - sha2, err := Version(sdl1) - require.NoError(t, err) - assert.Len(t, sha2, 32) - - require.Equal(t, sha1, sha2) - sdl2, err := ReadFile("../x/deployment/testdata/deployment-v2.yaml") require.NoError(t, err) - sha3, err := Version(sdl2) + sha2, err := sdl2.Version() + require.NoError(t, err) - require.NotEqual(t, sha1, sha3) + assert.Len(t, sha2, 32) + require.NotEqual(t, sha1, sha2) } func Test_V2_Cross_Validates(t *testing.T) { @@ -191,7 +216,7 @@ func Test_V2_Cross_Validates(t *testing.T) { // following is ture // 1. Cross validation logic is wrong // 2. The DeploymentGroups() & Manifest() code do not agree with one another - err = manifest.ValidateManifestWithGroupSpecs(&m, dgroups) + err = m.CheckAgainstGSpecs(dgroups) require.NoError(t, err) // Repeat the same test with another file @@ -204,7 +229,7 @@ func Test_V2_Cross_Validates(t *testing.T) { // This is a single document producing both the manifest & deployment groups // These should always agree with each other - err = manifest.ValidateManifestWithGroupSpecs(&m, dgroups) + err = m.CheckAgainstGSpecs(dgroups) require.NoError(t, err) // Repeat the same test with another file @@ -217,12 +242,12 @@ func Test_V2_Cross_Validates(t *testing.T) { // This is a single document producing both the manifest & deployment groups // These should always agree with each other - err = manifest.ValidateManifestWithGroupSpecs(&m, dgroups) + err = m.CheckAgainstGSpecs(dgroups) require.NoError(t, err) } -func Test_v1_Parse_simple(t *testing.T) { +func Test_V2_Parse_simple(t *testing.T) { sdl, err := ReadFile("./_testdata/simple.yaml") require.NoError(t, err) @@ -231,18 +256,19 @@ func Test_v1_Parse_simple(t *testing.T) { assert.Len(t, groups, 1) group := groups[0] - assert.Len(t, group.GetResources(), 1) + assert.Len(t, group.GetResourceUnits(), 1) assert.Equal(t, atypes.Attribute{ Key: "region", Value: "us-west", }, group.Requirements.Attributes[0]) - assert.Len(t, group.GetResources(), 1) + assert.Len(t, group.GetResourceUnits(), 1) - assert.Equal(t, atypes.Resources{ + assert.Equal(t, dtypes.ResourceUnit{ Count: 2, - Resources: atypes.ResourceUnits{ + Resources: atypes.Resources{ + ID: 1, CPU: &atypes.CPU{ Units: atypes.NewResourceValue(randCPU), }, @@ -267,7 +293,8 @@ func Test_v1_Parse_simple(t *testing.T) { }, }, }, - }, group.GetResources()[0]) + Price: AkashDecCoin(t, 50), + }, group.GetResourceUnits()[0]) mani, err := sdl.Manifest() require.NoError(t, err) @@ -282,7 +309,8 @@ func Test_v1_Parse_simple(t *testing.T) { { Name: "web", Image: "nginx", - Resources: atypes.ResourceUnits{ + Resources: atypes.Resources{ + ID: 1, CPU: &atypes.CPU{ Units: atypes.NewResourceValue(100), }, @@ -298,6 +326,14 @@ func Test_v1_Parse_simple(t *testing.T) { Quantity: atypes.NewResourceValue(1 * unit.Gi), }, }, + Endpoints: []atypes.Endpoint{ + { + Kind: atypes.Endpoint_SHARED_HTTP, + }, + { + Kind: atypes.Endpoint_RANDOM_PORT, + }, + }, }, Count: 2, Expose: []manifest.ServiceExpose{ @@ -325,43 +361,6 @@ func Test_v1_Parse_simple(t *testing.T) { }, mani.GetGroups()[0]) } -/** -func Test_v1_Parse_simpleWithIP(t *testing.T) { - sdl, err := ReadFile("./_testdata/simple_with_ip.yaml") - require.NoError(t, err) - require.NotNil(t, sdl) - - groups, err := sdl.DeploymentGroups() - require.NoError(t, err) - require.Len(t, groups, 1) - group := groups[0] - resources := group.GetResources() - require.Len(t, resources, 1) - resource := resources[0] - var ipEndpoint types.Endpoint - for _, endpoint := range resource.Resources.Endpoints { - if endpoint.Kind == types.Endpoint_LEASED_IP { - ipEndpoint = endpoint - break - } - } - require.Equal(t, ipEndpoint.Kind, types.Endpoint_LEASED_IP) - - mani, err := sdl.Manifest() - require.NoError(t, err) - var exposeIP manifest.ServiceExpose - for _, expose := range mani[0].Services[0].Expose { - if len(expose.IP) != 0 { - exposeIP = expose - break - } - } - require.NotEmpty(t, exposeIP.IP) - require.Equal(t, exposeIP.Proto, manifest.UDP) - require.Equal(t, exposeIP.Port, uint16(12345)) - require.True(t, exposeIP.Global) -}**/ - func Test_v1_Parse_ProfileNameNotServiceName(t *testing.T) { sdl, err := ReadFile("./_testdata/profile-svc-name-mismatch.yaml") require.NoError(t, err) @@ -402,3 +401,357 @@ func Test_v2_Parse_DeploymentNameServiceNameMismatch(t *testing.T) { require.Len(t, mani.GetGroups()[0].Services[0].Expose[0].Hosts, 1) require.Equal(t, mani.GetGroups()[0].Services[0].Expose[0].Hosts[0], "ahostname.com") } + +func TestV2ParseServiceMix(t *testing.T) { + sdl, err := ReadFile("./_testdata/service-mix.yaml") + require.NoError(t, err) + + groups, err := sdl.DeploymentGroups() + require.NoError(t, err) + assert.Len(t, groups, 1) + + group := groups[0] + assert.Len(t, group.GetResourceUnits(), 2) + assert.Len(t, group.Requirements.Attributes, 2) + + assert.Equal(t, atypes.Attribute{ + Key: "region", + Value: "us-west", + }, group.Requirements.Attributes[1]) + + assert.Equal(t, dtypes.ResourceUnits{ + { + Count: 1, + Resources: atypes.Resources{ + ID: 1, + CPU: &atypes.CPU{ + Units: atypes.NewResourceValue(randCPU), + }, + GPU: &atypes.GPU{ + Units: atypes.NewResourceValue(randGPU), + Attributes: atypes.Attributes{ + { + Key: "vendor/nvidia/model/*", + Value: "true", + }, + }, + }, + Memory: &atypes.Memory{ + Quantity: atypes.NewResourceValue(randMemory), + }, + Storage: atypes.Volumes{ + { + Name: "default", + Quantity: atypes.NewResourceValue(randStorage), + }, + }, + Endpoints: []atypes.Endpoint{ + { + Kind: atypes.Endpoint_SHARED_HTTP, + }, + { + Kind: atypes.Endpoint_RANDOM_PORT, + }, + }, + }, + Price: AkashDecCoin(t, 50), + }, + { + Count: 1, + Resources: atypes.Resources{ + ID: 2, + CPU: &atypes.CPU{ + Units: atypes.NewResourceValue(randCPU), + }, + GPU: &atypes.GPU{ + Units: atypes.NewResourceValue(0), + }, + Memory: &atypes.Memory{ + Quantity: atypes.NewResourceValue(randMemory), + }, + Storage: atypes.Volumes{ + { + Name: "default", + Quantity: atypes.NewResourceValue(randStorage), + }, + }, + Endpoints: []atypes.Endpoint{ + { + Kind: atypes.Endpoint_SHARED_HTTP, + }, + { + Kind: atypes.Endpoint_RANDOM_PORT, + }, + }, + }, + Price: AkashDecCoin(t, 50), + }, + }, group.GetResourceUnits()) + + mani, err := sdl.Manifest() + require.NoError(t, err) + + assert.Len(t, mani.GetGroups(), 1) + + assert.Equal(t, manifest.Group{ + Name: "westcoast", + Services: []manifest.Service{ + { + Name: "svca", + Image: "nginx", + Resources: atypes.Resources{ + ID: 1, + CPU: &atypes.CPU{ + Units: atypes.NewResourceValue(100), + }, + GPU: &atypes.GPU{ + Units: atypes.NewResourceValue(1), + Attributes: atypes.Attributes{ + { + Key: "vendor/nvidia/model/*", + Value: "true", + }, + }, + }, + Memory: &atypes.Memory{ + Quantity: atypes.NewResourceValue(128 * unit.Mi), + }, + Storage: atypes.Volumes{ + { + Name: "default", + Quantity: atypes.NewResourceValue(1 * unit.Gi), + }, + }, + Endpoints: []atypes.Endpoint{ + { + Kind: atypes.Endpoint_SHARED_HTTP, + }, + { + Kind: atypes.Endpoint_RANDOM_PORT, + }, + }, + }, + Count: 1, + Expose: []manifest.ServiceExpose{ + { + Port: 80, Global: true, Proto: manifest.TCP, Hosts: []string{"ahostname.com"}, + HTTPOptions: defaultHTTPOptions, + }, + { + Port: 12345, Global: true, Proto: manifest.UDP, + HTTPOptions: defaultHTTPOptions, + }, + }, + }, + { + Name: "svcb", + Image: "nginx", + Resources: atypes.Resources{ + ID: 2, + CPU: &atypes.CPU{ + Units: atypes.NewResourceValue(100), + }, + GPU: &atypes.GPU{ + Units: atypes.NewResourceValue(0), + }, + Memory: &atypes.Memory{ + Quantity: atypes.NewResourceValue(128 * unit.Mi), + }, + Storage: atypes.Volumes{ + { + Name: "default", + Quantity: atypes.NewResourceValue(1 * unit.Gi), + }, + }, + Endpoints: []atypes.Endpoint{ + { + Kind: atypes.Endpoint_SHARED_HTTP, + }, + { + Kind: atypes.Endpoint_RANDOM_PORT, + }, + }, + }, + Count: 1, + Expose: []manifest.ServiceExpose{ + { + Port: 80, Global: true, Proto: manifest.TCP, Hosts: []string{"bhostname.com"}, + HTTPOptions: defaultHTTPOptions, + }, + { + Port: 12346, Global: true, Proto: manifest.UDP, + HTTPOptions: defaultHTTPOptions, + }, + }, + }, + }, + }, mani.GetGroups()[0]) +} + +func TestV2ParseServiceMix2(t *testing.T) { + sdl, err := ReadFile("./_testdata/service-mix2.yaml") + require.NoError(t, err) + + groups, err := sdl.DeploymentGroups() + require.NoError(t, err) + assert.Len(t, groups, 1) + + group := groups[0] + assert.Len(t, group.GetResourceUnits(), 1) + assert.Len(t, group.Requirements.Attributes, 2) + + assert.Equal(t, atypes.Attribute{ + Key: "region", + Value: "us-west", + }, group.Requirements.Attributes[1]) + + assert.Equal(t, dtypes.ResourceUnits{ + { + Count: 2, + Resources: atypes.Resources{ + ID: 1, + CPU: &atypes.CPU{ + Units: atypes.NewResourceValue(randCPU), + }, + GPU: &atypes.GPU{ + Units: atypes.NewResourceValue(randGPU), + Attributes: atypes.Attributes{ + { + Key: "vendor/nvidia/model/*", + Value: "true", + }, + }, + }, + Memory: &atypes.Memory{ + Quantity: atypes.NewResourceValue(randMemory), + }, + Storage: atypes.Volumes{ + { + Name: "default", + Quantity: atypes.NewResourceValue(randStorage), + }, + }, + Endpoints: []atypes.Endpoint{ + { + Kind: atypes.Endpoint_SHARED_HTTP, + }, + { + Kind: atypes.Endpoint_RANDOM_PORT, + }, + { + Kind: atypes.Endpoint_SHARED_HTTP, + }, + { + Kind: atypes.Endpoint_RANDOM_PORT, + }, + }, + }, + Price: AkashDecCoin(t, 50), + }, + }, group.GetResourceUnits()) + + mani, err := sdl.Manifest() + require.NoError(t, err) + + assert.Len(t, mani.GetGroups(), 1) + + assert.Equal(t, manifest.Group{ + Name: "westcoast", + Services: []manifest.Service{ + { + Name: "svca", + Image: "nginx", + Resources: atypes.Resources{ + ID: 1, + CPU: &atypes.CPU{ + Units: atypes.NewResourceValue(100), + }, + GPU: &atypes.GPU{ + Units: atypes.NewResourceValue(1), + Attributes: atypes.Attributes{ + { + Key: "vendor/nvidia/model/*", + Value: "true", + }, + }, + }, + Memory: &atypes.Memory{ + Quantity: atypes.NewResourceValue(128 * unit.Mi), + }, + Storage: atypes.Volumes{ + { + Name: "default", + Quantity: atypes.NewResourceValue(1 * unit.Gi), + }, + }, + Endpoints: []atypes.Endpoint{ + { + Kind: atypes.Endpoint_SHARED_HTTP, + }, + { + Kind: atypes.Endpoint_RANDOM_PORT, + }, + }, + }, + Count: 1, + Expose: []manifest.ServiceExpose{ + { + Port: 80, Global: true, Proto: manifest.TCP, Hosts: []string{"ahostname.com"}, + HTTPOptions: defaultHTTPOptions, + }, + { + Port: 12345, Global: true, Proto: manifest.UDP, + HTTPOptions: defaultHTTPOptions, + }, + }, + }, + { + Name: "svcb", + Image: "nginx", + Resources: atypes.Resources{ + ID: 1, + CPU: &atypes.CPU{ + Units: atypes.NewResourceValue(100), + }, + GPU: &atypes.GPU{ + Units: atypes.NewResourceValue(1), + Attributes: atypes.Attributes{ + { + Key: "vendor/nvidia/model/*", + Value: "true", + }, + }, + }, + Memory: &atypes.Memory{ + Quantity: atypes.NewResourceValue(128 * unit.Mi), + }, + Storage: atypes.Volumes{ + { + Name: "default", + Quantity: atypes.NewResourceValue(1 * unit.Gi), + }, + }, + Endpoints: []atypes.Endpoint{ + { + Kind: atypes.Endpoint_SHARED_HTTP, + }, + { + Kind: atypes.Endpoint_RANDOM_PORT, + }, + }, + }, + Count: 1, + Expose: []manifest.ServiceExpose{ + { + Port: 80, Global: true, Proto: manifest.TCP, Hosts: []string{"bhostname.com"}, + HTTPOptions: defaultHTTPOptions, + }, + { + Port: 12346, Global: true, Proto: manifest.UDP, + HTTPOptions: defaultHTTPOptions, + }, + }, + }, + }, + }, mani.GetGroups()[0]) +} diff --git a/testutil/base.go b/testutil/base.go index 556322d8c3..95a51b9c8d 100644 --- a/testutil/base.go +++ b/testutil/base.go @@ -85,15 +85,16 @@ func RandStorageQuantity() uint64 { // Resources produces an attribute list for populating a Group's // 'Resources' fields. -func Resources(t testing.TB) []dtypes.Resource { +func Resources(t testing.TB) []dtypes.ResourceUnit { t.Helper() count := rand.Intn(10) + 1 - vals := make([]dtypes.Resource, 0, count) + vals := make(dtypes.ResourceUnits, 0, count) for i := 0; i < count; i++ { coin := sdk.NewDecCoin(CoinDenom, sdk.NewInt(rand.Int63n(9999)+1)) - res := dtypes.Resource{ - Resources: types.ResourceUnits{ + res := dtypes.ResourceUnit{ + Resources: types.Resources{ + ID: uint32(i) + 1, CPU: &types.CPU{ Units: types.NewResourceValue(uint64(dtypes.GetValidationConfig().MinUnitCPU)), }, diff --git a/testutil/sdk.go b/testutil/sdk.go index dad6a47882..b530b7fdc1 100644 --- a/testutil/sdk.go +++ b/testutil/sdk.go @@ -16,7 +16,7 @@ func DecCoin(t testing.TB) sdk.DecCoin { return sdk.NewDecCoin("testcoin", sdk.NewInt(int64(RandRangeInt(1, 1000)))) // nolint: gosec } -// AkashCoin provides simple interface to the Akash sdk.Coin type. +// AkashCoinRandom provides simple interface to the Akash sdk.Coin type. func AkashCoinRandom(t testing.TB) sdk.Coin { t.Helper() amt := sdk.NewInt(int64(RandRangeInt(1, 1000))) diff --git a/testutil/types.go b/testutil/types.go index bb7999c2c3..87bbe5004c 100644 --- a/testutil/types.go +++ b/testutil/types.go @@ -44,8 +44,9 @@ func RandRangeUint64(min, max uint64) uint64 { return val } -func ResourceUnits(_ testing.TB) types.ResourceUnits { - return types.ResourceUnits{ +func ResourceUnits(_ testing.TB) types.Resources { + return types.Resources{ + ID: 1, CPU: &types.CPU{ Units: types.NewResourceValue(uint64(RandCPUUnits())), }, diff --git a/upgrades/software/v0.24.0/deployment.go b/upgrades/software/v0.24.0/deployment.go index 88587c989c..e1b2b800d1 100644 --- a/upgrades/software/v0.24.0/deployment.go +++ b/upgrades/software/v0.24.0/deployment.go @@ -43,5 +43,6 @@ func migrateDeploymentGroup(fromBz []byte, cdc codec.BinaryCodec) codec.ProtoMar cdc.MustUnmarshal(fromBz, &from) to := dmigrate.GroupFromV1Beta2(from) + return &to } diff --git a/x/deployment/client/cli/tx.go b/x/deployment/client/cli/tx.go index 7f9dea68bd..761ad84073 100644 --- a/x/deployment/client/cli/tx.go +++ b/x/deployment/client/cli/tx.go @@ -93,7 +93,7 @@ func cmdCreate(key string) *cobra.Command { } } - version, err := sdl.Version(sdlManifest) + version, err := sdlManifest.Version() if err != nil { return err } @@ -227,7 +227,7 @@ func cmdUpdate(key string) *cobra.Command { return err } - version, err := sdl.Version(sdlManifest) + version, err := sdlManifest.Version() if err != nil { return err } @@ -507,7 +507,7 @@ Example: func warnIfGroupVolumesExceeds(cctx client.Context, dgroups []*types.GroupSpec) { for _, group := range dgroups { - for _, resources := range group.GetResources() { + for _, resources := range group.GetResourceUnits() { if len(resources.Resources.Storage) > constants.DefaultMaxGroupVolumes { _ = cctx.PrintString(fmt.Sprintf("amount of volumes for service exceeds recommended value (%v > %v)\n"+ "there may no providers on network to bid", len(resources.Resources.Storage), constants.DefaultMaxGroupVolumes)) diff --git a/x/deployment/handler/handler_test.go b/x/deployment/handler/handler_test.go index 9cdf10ba9a..2dba86eecd 100644 --- a/x/deployment/handler/handler_test.go +++ b/x/deployment/handler/handler_test.go @@ -434,7 +434,7 @@ func (st *testSuite) createDeployment() (types.Deployment, []types.Group) { deployment := testutil.Deployment(st.t) group := testutil.DeploymentGroup(st.t, deployment.ID(), 0) - group.GroupSpec.Resources = []types.Resource{ + group.GroupSpec.Resources = types.ResourceUnits{ { Resources: testutil.ResourceUnits(st.t), Count: 1, diff --git a/x/deployment/keeper/grpc_query_test.go b/x/deployment/keeper/grpc_query_test.go index 50e3ba821c..08df49cd44 100644 --- a/x/deployment/keeper/grpc_query_test.go +++ b/x/deployment/keeper/grpc_query_test.go @@ -482,7 +482,7 @@ func (suite *grpcTestSuite) createDeployment() (types.Deployment, []types.Group) deployment := testutil.Deployment(suite.t) group := testutil.DeploymentGroup(suite.t, deployment.ID(), 0) - group.GroupSpec.Resources = []types.Resource{ + group.GroupSpec.Resources = types.ResourceUnits{ { Resources: testutil.ResourceUnits(suite.t), Count: 1, diff --git a/x/deployment/simulation/operations.go b/x/deployment/simulation/operations.go index 6205083861..a3b09853f0 100644 --- a/x/deployment/simulation/operations.go +++ b/x/deployment/simulation/operations.go @@ -112,7 +112,7 @@ func SimulateMsgCreateDeployment(ak govtypes.AccountKeeper, bk bankkeeper.Keeper if groupErr != nil { return simtypes.NoOpMsg(types.ModuleName, types.MsgTypeCreateDeployment, "unable to read groups"), nil, groupErr } - sdlSum, err := sdlv1.Version(sdl) + sdlSum, err := sdl.Version() if err != nil { return simtypes.NoOpMsg(types.ModuleName, types.MsgTypeCreateDeployment, "error parsing deployment version sum"), nil, err @@ -173,7 +173,7 @@ func SimulateMsgUpdateDeployment(ak govtypes.AccountKeeper, bk bankkeeper.Keeper return simtypes.NoOpMsg(types.ModuleName, types.MsgTypeUpdateDeployment, "unable to read config file"), nil, readError } - sdlSum, err := sdlv1.Version(sdl) + sdlSum, err := sdl.Version() if err != nil { return simtypes.NoOpMsg(types.ModuleName, types.MsgTypeUpdateDeployment, "error parsing deployment version sum"), nil, err diff --git a/x/market/handler/handler_test.go b/x/market/handler/handler_test.go index fdfe053111..e868e40658 100644 --- a/x/market/handler/handler_test.go +++ b/x/market/handler/handler_test.go @@ -159,7 +159,7 @@ func TestCreateBidClosedOrder(t *testing.T) { func TestCreateBidOverprice(t *testing.T) { suite := setupTestSuite(t) - resources := []dtypes.Resource{ + resources := dtypes.ResourceUnits{ { Price: sdk.NewDecCoin(testutil.CoinDenom, sdk.NewInt(1)), }, @@ -431,7 +431,7 @@ func (st *testSuite) createBid() (types.Bid, types.Order) { return bid, order } -func (st *testSuite) createOrder(resources []dtypes.Resource) (types.Order, dtypes.GroupSpec) { +func (st *testSuite) createOrder(resources dtypes.ResourceUnits) (types.Order, dtypes.GroupSpec) { st.t.Helper() deployment := testutil.Deployment(st.t)