diff --git a/sdl/_testdata/simple4.yaml b/sdl/_testdata/simple4.yaml new file mode 100644 index 0000000000..1f55548556 --- /dev/null +++ b/sdl/_testdata/simple4.yaml @@ -0,0 +1,90 @@ +--- +version: '2.0' +services: + wordpress: + image: wordpress + depends_on: + - db + expose: + - port: 80 + http_options: + max_body_size: 104857600 + # accept: + # - "example.com" + to: + - global: true + env: + - WORDPRESS_DB_HOST=db + - WORDPRESS_DB_USER=wordpress + - WORDPRESS_DB_PASSWORD=testpass4you + - WORDPRESS_DB_NAME=wordpress + params: + storage: + wordpress-data: + mount: /var/www/html + readOnly: false + db: + # We use a mariadb image which supports both amd64 & arm64 architecture + image: mariadb:10.6.4 + # If you really want to use MySQL, uncomment the following line + #image: mysql:8.0.27 + expose: + - port: 3306 + to: + - service: wordpress + env: + - MYSQL_RANDOM_ROOT_PASSWORD=1 + - MYSQL_DATABASE=wordpress + - MYSQL_USER=wordpress + - MYSQL_PASSWORD=testpass4you + params: + storage: + wordpress-db: + mount: /var/lib/mysql + readOnly: false +profiles: + compute: + wordpress: + resources: + cpu: + units: 1 + memory: + size: 1Gi + storage: + - size: 1Gi + - name: wordpress-data + size: 1Gi + attributes: + persistent: true + class: beta3 + db: + resources: + cpu: + units: 1 + memory: + size: 1Gi + storage: + - size: 1Gi + - name: wordpress-db + size: 1Gi + attributes: + persistent: true + class: beta3 + placement: + akash: + pricing: + wordpress: + denom: uakt + amount: 10000 + db: + denom: uakt + amount: 10000 +deployment: + wordpress: + akash: + profile: wordpress + count: 1 + db: + akash: + profile: db + count: 1 diff --git a/sdl/_testdata/v2.1-deployment-svc-mismatch.yaml b/sdl/_testdata/v2.1-deployment-svc-mismatch.yaml new file mode 100644 index 0000000000..59e98e0038 --- /dev/null +++ b/sdl/_testdata/v2.1-deployment-svc-mismatch.yaml @@ -0,0 +1,45 @@ +--- +version: "2.1" +services: + web: + image: nginx + expose: + - port: 80 + accept: + - ahostname.com + to: + - global: true + - port: 12345 + to: + - global: true + 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: + webapp: + westcoast: + profile: web + count: 2 diff --git a/sdl/_testdata/v2.1-private_service.yaml b/sdl/_testdata/v2.1-private_service.yaml new file mode 100644 index 0000000000..e4945869fc --- /dev/null +++ b/sdl/_testdata/v2.1-private_service.yaml @@ -0,0 +1,64 @@ +--- +version: "2.1" +services: + bind: + image: bind9 + expose: + - port: 53 + proto: udp + to: + - global: true + + pg: + image: postgresql + expose: + - port: 5463 + to: + - service: bind + +profiles: + compute: + bind: + resources: + cpu: + units: "50m" + memory: + size: "64Mi" + storage: + size: "16Mi" + pg: + resources: + cpu: + units: "500m" + memory: + size: "512Mi" + storage: + size: "1000Mi" + + placement: + westcoast: + attributes: + region: us-west + signedBy: + anyOf: + - 1 + - 2 + allOf: + - 3 + - 4 + pricing: + pg: + denom: uakt + amount: 1000 + bind: + denom: uakt + amount: 333 +deployment: + pg: + westcoast: + profile: pg + count: 1 + bind: + westcoast: + profile: bind + count: 8 diff --git a/sdl/_testdata/v2.1-profile-svc-name-mismatch.yaml b/sdl/_testdata/v2.1-profile-svc-name-mismatch.yaml new file mode 100644 index 0000000000..3128102fab --- /dev/null +++ b/sdl/_testdata/v2.1-profile-svc-name-mismatch.yaml @@ -0,0 +1,38 @@ +--- +version: "2.1" + +services: + webapp: + image: ghcr.io/akash-network/demo-app + expose: + - port: 80 + as: 80 + accept: + - thehostname.com + to: + - global: true + +profiles: + compute: + web: + resources: + cpu: + units: "100m" + memory: + size: "512Mi" + storage: + size: "512Mi" + placement: + san-jose: + attributes: + region: sjc + pricing: + web: + denom: uakt + amount: 25 + +deployment: + webapp: + san-jose: + profile: web + count: 1 diff --git a/sdl/_testdata/v2.1-service-mix.yaml b/sdl/_testdata/v2.1-service-mix.yaml new file mode 100644 index 0000000000..82991aff6f --- /dev/null +++ b/sdl/_testdata/v2.1-service-mix.yaml @@ -0,0 +1,80 @@ +--- +version: "2.1" +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/v2.1-service-mix2.yaml b/sdl/_testdata/v2.1-service-mix2.yaml new file mode 100644 index 0000000000..7109fd466a --- /dev/null +++ b/sdl/_testdata/v2.1-service-mix2.yaml @@ -0,0 +1,69 @@ +--- +version: "2.1" +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/v2.1-simple-gpu.yaml b/sdl/_testdata/v2.1-simple-gpu.yaml new file mode 100644 index 0000000000..cc50fd82bf --- /dev/null +++ b/sdl/_testdata/v2.1-simple-gpu.yaml @@ -0,0 +1,52 @@ +--- +version: "2.1" +services: + web: + image: nginx + expose: + - port: 80 + accept: + - ahostname.com + to: + - global: true + - port: 12345 + to: + - global: true + proto: udp +profiles: + compute: + web: + resources: + cpu: + units: "100m" + gpu: + units: 1 + attributes: + vendor: + nvidia: + - model: a100 + memory: + size: "128Mi" + storage: + - size: "1Gi" + placement: + westcoast: + attributes: + region: us-west + blalbla: foo + signedBy: + anyOf: + - 1 + - 2 + allOf: + - 3 + - 4 + pricing: + web: + denom: uakt + amount: 50 +deployment: + web: + westcoast: + profile: web + count: 2 diff --git a/sdl/_testdata/v2.1-simple-with-ip.yaml b/sdl/_testdata/v2.1-simple-with-ip.yaml new file mode 100644 index 0000000000..4121c0e545 --- /dev/null +++ b/sdl/_testdata/v2.1-simple-with-ip.yaml @@ -0,0 +1,50 @@ +--- +version: "2.1" +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/_testdata/v2.1-simple.yaml b/sdl/_testdata/v2.1-simple.yaml new file mode 100644 index 0000000000..dc1ac31515 --- /dev/null +++ b/sdl/_testdata/v2.1-simple.yaml @@ -0,0 +1,45 @@ +--- +version: "2.1" +services: + web: + image: nginx + expose: + - port: 80 + accept: + - ahostname.com + to: + - global: true + - port: 12345 + to: + - global: true + 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 diff --git a/sdl/_testdata/v2.1-simple2.yaml b/sdl/_testdata/v2.1-simple2.yaml new file mode 100644 index 0000000000..08dfa81c86 --- /dev/null +++ b/sdl/_testdata/v2.1-simple2.yaml @@ -0,0 +1,64 @@ +--- +version: "2.0" +services: + web: + image: nginx + expose: + - port: 80 + accept: + - ahostname.com + to: + - global: true + - port: 12345 + to: + - global: true + proto: udp + bew: + image: nginx + expose: + - port: 8080 + accept: + - bhostname.com + to: + - global: true + - port: 12346 + to: + - global: true + proto: udp + - port: 12347 + to: + - global: true + proto: udp +profiles: + compute: + bew: + 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 + bew: + denom: uakt + amount: 50 +deployment: + web: + westcoast: + profile: bew + count: 2 diff --git a/sdl/_testdata/v2.1-simple3.yaml b/sdl/_testdata/v2.1-simple3.yaml new file mode 100644 index 0000000000..b4d8713a02 --- /dev/null +++ b/sdl/_testdata/v2.1-simple3.yaml @@ -0,0 +1,45 @@ +--- +version: "2.1" + +services: + app: + image: ubuntu:22.04 + command: + - "sh" + - "-c" + args: + - 'sleep infinity' + expose: + - port: 80 + as: 80 + to: + - global: true + - port: 22 + as: 22 + to: + - global: true + +profiles: + compute: + app: + resources: + cpu: + units: 1 + memory: + size: 2Gi + storage: + size: 10Gi + placement: + akash: + attributes: + host: akash + pricing: + app: + denom: uakt + amount: 1000000 + +deployment: + app: + akash: + profile: app + count: 1 diff --git a/sdl/_testdata/v2.1-simple4.yaml b/sdl/_testdata/v2.1-simple4.yaml new file mode 100644 index 0000000000..6a899a90c7 --- /dev/null +++ b/sdl/_testdata/v2.1-simple4.yaml @@ -0,0 +1,90 @@ +--- +version: '2.1' +services: + wordpress: + image: wordpress + depends_on: + - db + expose: + - port: 80 + http_options: + max_body_size: 104857600 + # accept: + # - "example.com" + to: + - global: true + env: + - WORDPRESS_DB_HOST=db + - WORDPRESS_DB_USER=wordpress + - WORDPRESS_DB_PASSWORD=testpass4you + - WORDPRESS_DB_NAME=wordpress + params: + storage: + wordpress-data: + mount: /var/www/html + readOnly: false + db: + # We use a mariadb image which supports both amd64 & arm64 architecture + image: mariadb:10.6.4 + # If you really want to use MySQL, uncomment the following line + #image: mysql:8.0.27 + expose: + - port: 3306 + to: + - service: wordpress + env: + - MYSQL_RANDOM_ROOT_PASSWORD=1 + - MYSQL_DATABASE=wordpress + - MYSQL_USER=wordpress + - MYSQL_PASSWORD=testpass4you + params: + storage: + wordpress-db: + mount: /var/lib/mysql + readOnly: false +profiles: + compute: + wordpress: + resources: + cpu: + units: 1 + memory: + size: 1Gi + storage: + - size: 1Gi + - name: wordpress-data + size: 1Gi + attributes: + persistent: true + class: beta3 + db: + resources: + cpu: + units: 1 + memory: + size: 1Gi + storage: + - size: 1Gi + - name: wordpress-db + size: 1Gi + attributes: + persistent: true + class: beta3 + placement: + akash: + pricing: + wordpress: + denom: uakt + amount: 10000 + db: + denom: uakt + amount: 10000 +deployment: + wordpress: + akash: + profile: wordpress + count: 1 + db: + akash: + profile: db + count: 1 diff --git a/sdl/groupBuilder_v2.go b/sdl/groupBuilder_v2.go new file mode 100644 index 0000000000..4e124c0b43 --- /dev/null +++ b/sdl/groupBuilder_v2.go @@ -0,0 +1,141 @@ +package sdl + +import ( + "sort" + + 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" +) + +type groupsBuilderV2 struct { + dgroup *dtypes.GroupSpec + mgroup *manifest.Group + boundComputes map[string]map[string]int +} + +// buildGroups +func (sdl *v2) buildGroups() error { + endpointsNames := sdl.computeEndpointSequenceNumbers() + + groups := make(map[string]*groupsBuilderV2) + + for _, svcName := range sdl.Deployments.svcNames() { + depl := sdl.Deployments[svcName] + + for _, placementName := range depl.placementNames() { + // objects below have been ensured to exist + svcdepl := depl[placementName] + 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 = &groupsBuilderV2{ + dgroup: &dtypes.GroupSpec{ + Name: placementName, + }, + mgroup: &manifest.Group{ + Name: placementName, + }, + boundComputes: make(map[string]map[string]int), + } + + group.dgroup.Requirements.Attributes = types.Attributes(infra.Attributes) + group.dgroup.Requirements.SignedBy = infra.SignedBy + + // keep ordering stable + sort.Sort(group.dgroup.Requirements.Attributes) + + groups[placementName] = group + } + + if _, exists := group.boundComputes[placementName]; !exists { + group.boundComputes[placementName] = make(map[string]int) + } + + expose, err := sdl.Services[svcName].Expose.toManifestExpose(endpointsNames) + if err != nil { + return err + } + + resources := compute.Resources.toResources() + resources.Endpoints = expose.GetEndpoints() + + res := compute.Resources.toResources() + res.Endpoints = expose.GetEndpoints() + + var resID int + if ln := len(group.dgroup.Resources); ln > 0 { + resID = ln + 1 + } else { + resID = 1 + } + + res.ID = uint32(resID) + resources.ID = res.ID + + group.dgroup.Resources = append(group.dgroup.Resources, dtypes.ResourceUnit{ + Resources: res, + Price: price.Value, + Count: svcdepl.Count, + }) + + group.boundComputes[placementName][svcdepl.Profile] = len(group.dgroup.Resources) - 1 + + msvc := manifest.Service{ + Name: svcName, + Image: svc.Image, + Args: svc.Args, + Env: svc.Env, + Resources: resources, + Count: svcdepl.Count, + Command: svc.Command, + Expose: expose, + } + + if svc.Params != nil { + params := &manifest.ServiceParams{} + + if len(svc.Params.Storage) > 0 { + params.Storage = make([]manifest.StorageParams, 0, len(svc.Params.Storage)) + for volName, volParams := range svc.Params.Storage { + params.Storage = append(params.Storage, manifest.StorageParams{ + Name: volName, + Mount: volParams.Mount, + ReadOnly: volParams.ReadOnly, + }) + } + } + + msvc.Params = params + } + + group.mgroup.Services = append(group.mgroup.Services, msvc) + } + } + + // keep ordering stable + names := make([]string, 0, len(groups)) + for name := range groups { + names = append(names, name) + } + sort.Strings(names) + + sdl.result.dgroups = make(dtypes.GroupSpecs, 0, len(names)) + sdl.result.mgroups = make(manifest.Groups, 0, len(names)) + + for _, name := range names { + 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 nil +} diff --git a/sdl/groupBuilder_v2_1.go b/sdl/groupBuilder_v2_1.go new file mode 100644 index 0000000000..7e14642c03 --- /dev/null +++ b/sdl/groupBuilder_v2_1.go @@ -0,0 +1,150 @@ +package sdl + +import ( + "sort" + + 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" +) + +type groupsBuilderV2_1 struct { + dgroup *dtypes.GroupSpec + mgroup *manifest.Group + boundComputes map[string]map[string]int +} + +// buildGroups +func (sdl *v2_1) buildGroups() error { + endpointsNames := sdl.computeEndpointSequenceNumbers() + + groups := make(map[string]*groupsBuilderV2_1) + + for _, svcName := range sdl.Deployments.svcNames() { + depl := sdl.Deployments[svcName] + + for _, placementName := range depl.placementNames() { + // objects below have been ensured to exist + svcdepl := depl[placementName] + 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 = &groupsBuilderV2_1{ + dgroup: &dtypes.GroupSpec{ + Name: placementName, + }, + mgroup: &manifest.Group{ + Name: placementName, + }, + boundComputes: make(map[string]map[string]int), + } + + group.dgroup.Requirements.Attributes = types.Attributes(infra.Attributes) + group.dgroup.Requirements.SignedBy = infra.SignedBy + + // keep ordering stable + sort.Sort(group.dgroup.Requirements.Attributes) + + groups[placementName] = group + } + + if _, exists := group.boundComputes[placementName]; !exists { + group.boundComputes[placementName] = make(map[string]int) + } + + expose, err := sdl.Services[svcName].Expose.toManifestExpose(endpointsNames) + if err != nil { + return err + } + + resources := compute.Resources.toResources() + resources.Endpoints = expose.GetEndpoints() + + if location, bound := group.boundComputes[placementName][svcdepl.Profile]; !bound { + res := compute.Resources.toResources() + res.Endpoints = expose.GetEndpoints() + + var resID int + if ln := len(group.dgroup.Resources); ln > 0 { + resID = ln + 1 + } else { + resID = 1 + } + + res.ID = uint32(resID) + resources.ID = res.ID + + group.dgroup.Resources = append(group.dgroup.Resources, dtypes.ResourceUnit{ + Resources: res, + Price: price.Value, + Count: svcdepl.Count, + }) + + group.boundComputes[placementName][svcdepl.Profile] = len(group.dgroup.Resources) - 1 + } else { + resources.ID = group.dgroup.Resources[location].ID + + group.dgroup.Resources[location].Count += svcdepl.Count + group.dgroup.Resources[location].Endpoints = append(group.dgroup.Resources[location].Endpoints, expose.GetEndpoints()...) + + sort.Sort(group.dgroup.Resources[location].Endpoints) + } + + msvc := manifest.Service{ + Name: svcName, + Image: svc.Image, + Args: svc.Args, + Env: svc.Env, + Resources: resources, + Count: svcdepl.Count, + Command: svc.Command, + Expose: expose, + } + + if svc.Params != nil { + params := &manifest.ServiceParams{} + + if len(svc.Params.Storage) > 0 { + params.Storage = make([]manifest.StorageParams, 0, len(svc.Params.Storage)) + for volName, volParams := range svc.Params.Storage { + params.Storage = append(params.Storage, manifest.StorageParams{ + Name: volName, + Mount: volParams.Mount, + ReadOnly: volParams.ReadOnly, + }) + } + } + + msvc.Params = params + } + + group.mgroup.Services = append(group.mgroup.Services, msvc) + } + } + + // keep ordering stable + names := make([]string, 0, len(groups)) + for name := range groups { + names = append(names, name) + } + sort.Strings(names) + + sdl.result.dgroups = make(dtypes.GroupSpecs, 0, len(names)) + sdl.result.mgroups = make(manifest.Groups, 0, len(names)) + + for _, name := range names { + 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 nil +} diff --git a/sdl/sdl.go b/sdl/sdl.go index 75a3bebf91..bb4bf398ab 100644 --- a/sdl/sdl.go +++ b/sdl/sdl.go @@ -12,8 +12,12 @@ import ( dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3" ) +const ( + sdlVersionField = "version" +) + var ( - errUninitializedConfig = errors.New("uninitialized config") + errUninitializedConfig = errors.New("sdl: uninitialized") errSDLInvalidNoVersion = fmt.Errorf("%w: no version found", errSDLInvalid) ) @@ -37,7 +41,7 @@ func (s *sdl) UnmarshalYAML(node *yaml.Node) error { foundVersion := false for idx := range node.Content { - if node.Content[idx].Value == "version" { + if node.Content[idx].Value == sdlVersionField { var err error if result.Ver, err = semver.ParseTolerant(node.Content[idx+1].Value); err != nil { return err @@ -52,12 +56,19 @@ func (s *sdl) UnmarshalYAML(node *yaml.Node) error { } // nolint: gocritic - if result.Ver.GE(semver.MustParse("2.0.0")) && result.Ver.LT(semver.MustParse("3.0.0")) { + if result.Ver.EQ(semver.MustParse("2.0.0")) { var decoded v2 if err := node.Decode(&decoded); err != nil { return err } + result.data = &decoded + } else if result.Ver.GE(semver.MustParse("2.1.0")) { + var decoded v2_1 + if err := node.Decode(&decoded); err != nil { + return err + } + result.data = &decoded } else { return fmt.Errorf("%w: config: unsupported version %q", errSDLInvalid, result.Ver) diff --git a/sdl/v2.go b/sdl/v2.go index 33a62f9d92..40030b226a 100644 --- a/sdl/v2.go +++ b/sdl/v2.go @@ -231,7 +231,7 @@ loop: val = &result.Deployments case "endpoints": val = &result.Endpoints - case "version": + case sdlVersionField: // version is already verified continue loop default: @@ -252,147 +252,6 @@ loop: return nil } -type groupsBuilder struct { - dgroup *dtypes.GroupSpec - mgroup *manifest.Group - boundComputes map[string]map[string]int -} - -// buildGroups -func (sdl *v2) buildGroups() error { - endpointsNames := sdl.computeEndpointSequenceNumbers() - - groups := make(map[string]*groupsBuilder) - - for _, svcName := range sdl.Deployments.svcNames() { - depl := sdl.Deployments[svcName] - - for _, placementName := range depl.placementNames() { - // objects below have been ensured to exist - svcdepl := depl[placementName] - 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 = &groupsBuilder{ - dgroup: &dtypes.GroupSpec{ - Name: placementName, - }, - mgroup: &manifest.Group{ - Name: placementName, - }, - boundComputes: make(map[string]map[string]int), - } - - group.dgroup.Requirements.Attributes = types.Attributes(infra.Attributes) - group.dgroup.Requirements.SignedBy = infra.SignedBy - - // keep ordering stable - sort.Sort(group.dgroup.Requirements.Attributes) - - groups[placementName] = group - } - - if _, exists := group.boundComputes[placementName]; !exists { - group.boundComputes[placementName] = make(map[string]int) - } - - expose, err := sdl.Services[svcName].Expose.toManifestExpose(endpointsNames) - if err != nil { - return err - } - - resources := compute.Resources.toResources() - resources.Endpoints = expose.GetEndpoints() - - if location, bound := group.boundComputes[placementName][svcdepl.Profile]; !bound { - res := compute.Resources.toResources() - res.Endpoints = expose.GetEndpoints() - - var resID int - if ln := len(group.dgroup.Resources); ln > 0 { - resID = ln + 1 - } else { - resID = 1 - } - - res.ID = uint32(resID) - resources.ID = res.ID - - group.dgroup.Resources = append(group.dgroup.Resources, dtypes.ResourceUnit{ - Resources: res, - Price: price.Value, - Count: svcdepl.Count, - }) - - group.boundComputes[placementName][svcdepl.Profile] = len(group.dgroup.Resources) - 1 - } else { - resources.ID = group.dgroup.Resources[location].ID - - group.dgroup.Resources[location].Count += svcdepl.Count - group.dgroup.Resources[location].Endpoints = append(group.dgroup.Resources[location].Endpoints, expose.GetEndpoints()...) - - sort.Sort(group.dgroup.Resources[location].Endpoints) - } - - msvc := manifest.Service{ - Name: svcName, - Image: svc.Image, - Args: svc.Args, - Env: svc.Env, - Resources: resources, - Count: svcdepl.Count, - Command: svc.Command, - Expose: expose, - } - - if svc.Params != nil { - params := &manifest.ServiceParams{} - - if len(svc.Params.Storage) > 0 { - params.Storage = make([]manifest.StorageParams, 0, len(svc.Params.Storage)) - for volName, volParams := range svc.Params.Storage { - params.Storage = append(params.Storage, manifest.StorageParams{ - Name: volName, - Mount: volParams.Mount, - ReadOnly: volParams.ReadOnly, - }) - } - } - - msvc.Params = params - } - - group.mgroup.Services = append(group.mgroup.Services, msvc) - } - } - - // keep ordering stable - names := make([]string, 0, len(groups)) - for name := range groups { - names = append(names, name) - } - sort.Strings(names) - - sdl.result.dgroups = make(dtypes.GroupSpecs, 0, len(names)) - sdl.result.mgroups = make(manifest.Groups, 0, len(names)) - - for _, name := range names { - 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 nil -} - func (sdl *v2) validate() error { for endpointName, endpoint := range sdl.Endpoints { if !endpointNameValidationRegex.MatchString(endpointName) { diff --git a/sdl/v2_1.go b/sdl/v2_1.go new file mode 100644 index 0000000000..79dc9db6f5 --- /dev/null +++ b/sdl/v2_1.go @@ -0,0 +1,341 @@ +package sdl + +import ( + "fmt" + "path" + "sort" + "strconv" + + "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" + types "github.com/akash-network/akash-api/go/node/types/v1beta3" +) + +var _ SDL = (*v2_1)(nil) + +type v2_1 struct { + 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 + } +} + +func (sdl *v2_1) DeploymentGroups() (dtypes.GroupSpecs, error) { + return sdl.result.dgroups, nil +} + +func (sdl *v2_1) Manifest() (manifest.Manifest, error) { + return manifest.Manifest(sdl.result.mgroups), nil +} + +// Version creates the deterministic Deployment Version hash from the SDL. +func (sdl *v2_1) Version() ([]byte, error) { + return manifest.Manifest(sdl.result.mgroups).Version() +} + +func (sdl *v2_1) UnmarshalYAML(node *yaml.Node) error { + result := v2_1{} + +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 sdlVersionField: + // 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 + } + } + + if err := result.buildGroups(); err != nil { + return err + } + + *sdl = result + + return nil +} + +func (sdl *v2_1) validate() error { + for endpointName, endpoint := range sdl.Endpoints { + if !endpointNameValidationRegex.MatchString(endpointName) { + return fmt.Errorf( + "%w: endpoint named %q is not a valid name", + errSDLInvalid, + endpointName, + ) + } + + if len(endpoint.Kind) == 0 { + return fmt.Errorf("%w: endpoint named %q has no kind", errSDLInvalid, endpointName) + } + + // Validate endpoint kind, there is only one allowed value for now + if endpoint.Kind != endpointKindIP { + return fmt.Errorf( + "%w: endpoint named %q, unknown kind %q", + errSDLInvalid, + endpointName, + endpoint.Kind, + ) + } + } + + endpointsUsed := make(map[string]struct{}) + portsUsed := make(map[string]string) + for _, svcName := range sdl.Deployments.svcNames() { + depl := sdl.Deployments[svcName] + + for _, placementName := range v2DeploymentPlacementNames(depl) { + svcdepl := depl[placementName] + + compute, ok := sdl.Profiles.Compute[svcdepl.Profile] + if !ok { + return fmt.Errorf( + "%w: %v.%v: no compute profile named %v", + errSDLInvalid, + svcName, + placementName, + svcdepl.Profile, + ) + } + + infra, ok := sdl.Profiles.Placement[placementName] + if !ok { + return fmt.Errorf( + "%w: %v.%v: no placement profile named %v", + errSDLInvalid, + svcName, + placementName, + placementName, + ) + } + + if _, ok := infra.Pricing[svcdepl.Profile]; !ok { + return fmt.Errorf( + "%w: %v.%v: no pricing for profile %v", + errSDLInvalid, + svcName, + placementName, + svcdepl.Profile, + ) + } + + svc, ok := sdl.Services[svcName] + if !ok { + return fmt.Errorf( + "%w: %v.%v: no service profile named %v", + errSDLInvalid, + svcName, + placementName, + svcName, + ) + } + + for _, serviceExpose := range svc.Expose { + for _, to := range serviceExpose.To { + // Check to see if an IP endpoint is also specified + if len(to.IP) != 0 { + if !to.Global { + return fmt.Errorf( + "%w: error on %q if an IP is declared the directive must be declared as global", + errSDLInvalid, + svcName, + ) + } + endpoint, endpointExists := sdl.Endpoints[to.IP] + if !endpointExists { + return fmt.Errorf( + "%w: error on service %q no endpoint named %q exists", + errSDLInvalid, + svcName, + to.IP, + ) + } + + if endpoint.Kind != endpointKindIP { + return fmt.Errorf( + "%w: error on service %q endpoint %q has type %q, should be %q", + errSDLInvalid, + svcName, + to.IP, + endpoint.Kind, + endpointKindIP, + ) + } + + endpointsUsed[to.IP] = struct{}{} + + // Endpoint exists. Now check for port collisions across a single endpoint, port, & protocol + portKey := fmt.Sprintf( + "%s-%d-%s", + to.IP, + serviceExpose.As, + serviceExpose.Proto, + ) + otherServiceName, inUse := portsUsed[portKey] + if inUse { + return fmt.Errorf( + "%w: IP endpoint %q port: %d protocol: %s specified by service %q already in use by %q", + errSDLInvalid, + to.IP, + serviceExpose.Port, + serviceExpose.Proto, + svcName, + otherServiceName, + ) + } + portsUsed[portKey] = svcName + } + } + } + + // validate storage's attributes and parameters + volumes := make(map[string]v2ResourceStorage) + for _, volume := range compute.Resources.Storage { + // making deepcopy here as we gonna merge compute attributes and service parameters for validation below + attr := make(v2StorageAttributes, len(volume.Attributes)) + + copy(attr, volume.Attributes) + + volumes[volume.Name] = v2ResourceStorage{ + Name: volume.Name, + Quantity: volume.Quantity, + Attributes: attr, + } + } + + attr := make(map[string]string) + mounts := make(map[string]string) + + if svc.Params != nil { + for name, params := range svc.Params.Storage { + if _, exists := volumes[name]; !exists { + return fmt.Errorf( + "%w: service \"%s\" references to no-existing compute volume named \"%s\"", + errSDLInvalid, + svcName, + name, + ) + } + + if !path.IsAbs(params.Mount) { + return fmt.Errorf( + "%w: invalid value for \"service.%s.params.%s.mount\" parameter. expected absolute path", + errSDLInvalid, + svcName, + name, + ) + } + + attr[StorageAttributeMount] = params.Mount + attr[StorageAttributeReadOnly] = strconv.FormatBool(params.ReadOnly) + + mount := attr[StorageAttributeMount] + if vlname, exists := mounts[mount]; exists { + if mount == "" { + return errStorageMultipleRootEphemeral + } + + return fmt.Errorf( + "%w: mount %q already in use by volume %q", + errStorageDupMountPoint, + mount, + vlname, + ) + } + + mounts[mount] = name + } + } + + for name, volume := range volumes { + for _, nd := range types.Attributes(volume.Attributes) { + attr[nd.Key] = nd.Value + } + + persistent, _ := strconv.ParseBool(attr[StorageAttributePersistent]) + + if persistent && attr[StorageAttributeMount] == "" { + return fmt.Errorf( + "%w: compute.storage.%s has persistent=true which requires service.%s.params.storage.%s to have mount", + errSDLInvalid, + name, + svcName, + name, + ) + } + } + } + } + + for endpointName := range sdl.Endpoints { + _, inUse := endpointsUsed[endpointName] + if !inUse { + return fmt.Errorf( + "%w: endpoint %q declared but never used", + errSDLInvalid, + endpointName, + ) + } + } + + return nil +} + +func (sdl *v2_1) 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 +} diff --git a/sdl/v2_1_ip_test.go b/sdl/v2_1_ip_test.go new file mode 100644 index 0000000000..645719cfd9 --- /dev/null +++ b/sdl/v2_1_ip_test.go @@ -0,0 +1,279 @@ +package sdl + +import ( + "bytes" + "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 TestV2_1_ParseSimpleWithIP(t *testing.T) { + sdl, err := ReadFile("./_testdata/v2.1-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 TestV2_1_Parse_IP(t *testing.T) { + sdl1, err := ReadFile("../x/deployment/testdata/deployment-v2.1-ip-endpoint.yaml") + require.NoError(t, err) + groups, err := sdl1.DeploymentGroups() + require.NoError(t, err) + + require.Len(t, groups, 1) + group := groups[0] + + resources := group.GetResourceUnits() + require.Len(t, resources, 1) + resource := resources[0] + endpoints := resource.Resources.Endpoints + require.Len(t, endpoints, 2) + + var ipEndpoint types.Endpoint + for _, endpoint := range endpoints { + if endpoint.Kind == types.Endpoint_LEASED_IP { + ipEndpoint = endpoint + } + } + + require.Equal(t, ipEndpoint.Kind, types.Endpoint_LEASED_IP) + require.Greater(t, ipEndpoint.SequenceNumber, uint32(0)) + + mani, err := sdl1.Manifest() + require.NoError(t, err) + maniGroups := mani.GetGroups() + require.Len(t, maniGroups, 1) + maniGroup := maniGroups[0] + services := maniGroup.Services + require.Len(t, services, 1) + + service := services[0] + exposes := service.Expose + require.Len(t, exposes, 1) + + expose := exposes[0] + + require.True(t, expose.Global) + require.Equal(t, expose.IP, "meow") + require.Greater(t, expose.EndpointSequenceNumber, uint32(0)) +} + +func TestV2_1_Parse_SharedIP(t *testing.T) { + // Read a file with 1 group having 1 endpoint shared amongst containers + sdl1, err := ReadFile("../x/deployment/testdata/deployment-v2.1-shared-ip-endpoint.yaml") + require.NoError(t, err) + + groups, err := sdl1.DeploymentGroups() + require.NoError(t, err) + require.Len(t, groups, 1) + + group := groups[0] + + resources := group.GetResourceUnits() + require.Len(t, resources, 1) + + resource := resources[0] + ipEndpoint1 := findIPEndpoint(t, resource.Resources.Endpoints, 1) + require.Greater(t, ipEndpoint1.SequenceNumber, uint32(0)) + + ipEndpoint2 := findIPEndpoint(t, resource.Resources.Endpoints, 2) + require.Greater(t, ipEndpoint2.SequenceNumber, uint32(0)) + + mani, err := sdl1.Manifest() + require.NoError(t, err) + + maniGroups := mani.GetGroups() + require.Len(t, maniGroups, 1) + maniGroup := maniGroups[0] + + services := maniGroup.Services + require.Len(t, services, 2) + serviceA := services[0] + + serviceIPEndpoint := findIPEndpoint(t, serviceA.Resources.Endpoints, 1) + require.Equal(t, serviceIPEndpoint.SequenceNumber, ipEndpoint1.SequenceNumber) + + serviceB := services[1] + serviceIPEndpoint = findIPEndpoint(t, serviceB.Resources.Endpoints, 1) + require.Equal(t, serviceIPEndpoint.SequenceNumber, ipEndpoint2.SequenceNumber) +} + +func TestV2_1_Parse_MultipleIP(t *testing.T) { + // Read a file with 1 group having two endpoints + sdl1, err := ReadFile("../x/deployment/testdata/deployment-v2.1-multi-ip-endpoint.yaml") + require.NoError(t, err) + + groups, err := sdl1.DeploymentGroups() + require.NoError(t, err) + require.Len(t, groups, 1) + + group := groups[0] + + resources := group.GetResourceUnits() + require.Len(t, resources, 1) + + mani, err := sdl1.Manifest() + require.NoError(t, err) + _ = mani +} + +func TestV2_1_Parse_MultipleGroupsIP(t *testing.T) { + // Read a file with two groups, each one having an IP endpoint that is distinct + sdl1, err := ReadFile("../x/deployment/testdata/deployment-v2.1-multi-groups-ip-endpoint.yaml") + require.NoError(t, err) + + groups, err := sdl1.DeploymentGroups() + require.NoError(t, err) + require.Len(t, groups, 2) + + resources := groups[0].GetResourceUnits() + require.Len(t, resources, 1) + + resource := resources[0] + require.Len(t, resource.Resources.Endpoints, 2) + ipEndpointFirstGroup := findIPEndpoint(t, resource.Resources.Endpoints, 1) + require.Greater(t, ipEndpointFirstGroup.SequenceNumber, uint32(0)) + + resources = groups[1].GetResourceUnits() + require.Len(t, resources, 1) + + resource = resources[0] + require.Len(t, resource.Resources.Endpoints, 2) + ipEndpointSecondGroup := findIPEndpoint(t, resource.Resources.Endpoints, 1) + require.Greater(t, ipEndpointSecondGroup.SequenceNumber, uint32(0)) + require.NotEqual(t, ipEndpointFirstGroup.SequenceNumber, ipEndpointSecondGroup.SequenceNumber) + + mani, err := sdl1.Manifest() + require.NoError(t, err) + maniGroups := mani.GetGroups() + require.Len(t, maniGroups, 2) + + maniGroup := maniGroups[0] + 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] + mresources = maniGroup.GetResourceUnits() + require.Len(t, mresources, 1) + mresource = mresources[0] + require.Equal(t, findIPEndpoint(t, mresource.Endpoints, 1).SequenceNumber, ipEndpointSecondGroup.SequenceNumber) + +} + +func TestV2_1_Parse_IPEndpointNaming(t *testing.T) { + makeSDLWithEndpointName := func(name string) []byte { + const originalSDL = `--- +version: "2.1" + +services: + web: + image: ghcr.io/akash-network/demo-app + expose: + - port: 80 + to: + - global: true + ip: %q + accept: + - test.localhost + +profiles: + compute: + web: + resources: + cpu: + units: "0.01" + memory: + size: "128Mi" + storage: + size: "512Mi" + + placement: + global: + pricing: + web: + denom: uakt + amount: 10 + +deployment: + web: + global: + profile: web + count: 1 + +endpoints: + %q: + kind: ip +` + buf := &bytes.Buffer{} + _, err := fmt.Fprintf(buf, originalSDL, name, name) + require.NoError(t, err) + return buf.Bytes() + } + + _, err := Read(makeSDLWithEndpointName("meow72-memes")) + require.NoError(t, err) + + _, err = Read(makeSDLWithEndpointName("meow72-mem_es")) + require.NoError(t, err) + + _, err = Read(makeSDLWithEndpointName("!important")) + require.Error(t, err) + require.ErrorIs(t, err, errSDLInvalid) + require.Contains(t, err.Error(), "not a valid name") + + _, err = Read(makeSDLWithEndpointName("foo^bar")) + require.Error(t, err) + require.ErrorIs(t, err, errSDLInvalid) + require.Contains(t, err.Error(), "not a valid name") + + _, err = Read(makeSDLWithEndpointName("ROAR")) + require.Error(t, err) + require.ErrorIs(t, err, errSDLInvalid) + require.Contains(t, err.Error(), "not a valid name") + + _, err = Read(makeSDLWithEndpointName("996")) + require.Error(t, err) + require.ErrorIs(t, err, errSDLInvalid) + require.Contains(t, err.Error(), "not a valid name") + + _, err = Read(makeSDLWithEndpointName("_kittens")) + require.Error(t, err) + require.ErrorIs(t, err, errSDLInvalid) + require.Contains(t, err.Error(), "not a valid name") + + _, err = Read(makeSDLWithEndpointName("-kittens")) + require.Error(t, err) + require.ErrorIs(t, err, errSDLInvalid) + require.Contains(t, err.Error(), "not a valid name") +} diff --git a/sdl/v2_1_test.go b/sdl/v2_1_test.go new file mode 100644 index 0000000000..b867d710c6 --- /dev/null +++ b/sdl/v2_1_test.go @@ -0,0 +1,728 @@ +package sdl + +import ( + "testing" + + 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/stretchr/testify/assert" + "github.com/stretchr/testify/require" + // "github.com/akash-network/node/testutil" +) + +func TestV2_1_ParseSimpleGPU(t *testing.T) { + sdl, err := ReadFile("./_testdata/v2.1-simple-gpu.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.Len(t, group.GetResourceUnits(), 1) + + assert.Equal(t, dtypes.ResourceUnit{ + 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/a100", + 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), + }, group.GetResourceUnits()[0]) + + mani, err := sdl.Manifest() + require.NoError(t, err) + + assert.Len(t, mani.GetGroups(), 1) + + expectedHosts := make([]string, 1) + expectedHosts[0] = "ahostname.com" // nolint: goconst + assert.Equal(t, manifest.Group{ + Name: "westcoast", + Services: []manifest.Service{ + { + Name: "web", + 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/a100", + 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: 2, + Expose: []manifest.ServiceExpose{ + {Port: 80, Global: true, Proto: manifest.TCP, Hosts: expectedHosts, + HTTPOptions: manifest.ServiceExposeHTTPOptions{ + MaxBodySize: 1048576, + ReadTimeout: 60000, + SendTimeout: 60000, + NextTries: 3, + NextTimeout: 0, + NextCases: []string{"error", "timeout"}, + }}, + {Port: 12345, Global: true, Proto: manifest.UDP, + HTTPOptions: manifest.ServiceExposeHTTPOptions{ + MaxBodySize: 1048576, + ReadTimeout: 60000, + SendTimeout: 60000, + NextTries: 3, + NextTimeout: 0, + NextCases: []string{"error", "timeout"}, + }}, + }, + }, + }, + }, mani.GetGroups()[0]) +} + +func TestV2_1_Parse_Deployments(t *testing.T) { + sdl1, err := ReadFile("../x/deployment/testdata/deployment-v2.1.yaml") + require.NoError(t, err) + _, err = sdl1.DeploymentGroups() + require.NoError(t, err) + + _, err = sdl1.Manifest() + require.NoError(t, err) + + sha1, err := sdl1.Version() + require.NoError(t, err) + assert.Len(t, sha1, 32) + + sdl2, err := ReadFile("../x/deployment/testdata/deployment-v2.yaml") + require.NoError(t, err) + sha2, err := sdl2.Version() + + require.NoError(t, err) + assert.Len(t, sha2, 32) + require.NotEqual(t, sha1, sha2) +} + +func Test_V2_1_Cross_Validates(t *testing.T) { + sdl2, err := ReadFile("../x/deployment/testdata/deployment-v2.yaml") + require.NoError(t, err) + dgroups, err := sdl2.DeploymentGroups() + require.NoError(t, err) + m, err := sdl2.Manifest() + require.NoError(t, err) + + // This is a single document producing both the manifest & deployment groups + // These should always agree with each other. If this test fails at least one of the + // following is ture + // 1. Cross validation logic is wrong + // 2. The DeploymentGroups() & Manifest() code do not agree with one another + err = m.CheckAgainstGSpecs(dgroups) + require.NoError(t, err) + + // Repeat the same test with another file + sdl2, err = ReadFile("./_testdata/v2.1-simple.yaml") + require.NoError(t, err) + dgroups, err = sdl2.DeploymentGroups() + require.NoError(t, err) + m, err = sdl2.Manifest() + require.NoError(t, err) + + // This is a single document producing both the manifest & deployment groups + // These should always agree with each other + err = m.CheckAgainstGSpecs(dgroups) + require.NoError(t, err) + + // Repeat the same test with another file + sdl2, err = ReadFile("./_testdata/v2.1-simple3.yaml") + require.NoError(t, err) + dgroups, err = sdl2.DeploymentGroups() + require.NoError(t, err) + m, err = sdl2.Manifest() + require.NoError(t, err) + + // This is a single document producing both the manifest & deployment groups + // These should always agree with each other + err = m.CheckAgainstGSpecs(dgroups) + require.NoError(t, err) + + // Repeat the same test with another file + sdl2, err = ReadFile("./_testdata/v2.1-private_service.yaml") + require.NoError(t, err) + dgroups, err = sdl2.DeploymentGroups() + require.NoError(t, err) + m, err = sdl2.Manifest() + require.NoError(t, err) + + // This is a single document producing both the manifest & deployment groups + // These should always agree with each other + err = m.CheckAgainstGSpecs(dgroups) + require.NoError(t, err) + +} + +func Test_V2_1_Parse_simple(t *testing.T) { + sdl, err := ReadFile("./_testdata/v2.1-simple.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.Equal(t, atypes.Attribute{ + Key: "region", + Value: "us-west", + }, group.Requirements.Attributes[0]) + + assert.Len(t, group.GetResourceUnits(), 1) + + assert.Equal(t, dtypes.ResourceUnit{ + Count: 2, + Resources: atypes.Resources{ + ID: 1, + 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()[0]) + + mani, err := sdl.Manifest() + require.NoError(t, err) + + assert.Len(t, mani.GetGroups(), 1) + + expectedHosts := make([]string, 1) + expectedHosts[0] = "ahostname.com" + assert.Equal(t, manifest.Group{ + Name: "westcoast", + Services: []manifest.Service{ + { + Name: "web", + Image: "nginx", + Resources: atypes.Resources{ + ID: 1, + 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: 2, + Expose: []manifest.ServiceExpose{ + {Port: 80, Global: true, Proto: manifest.TCP, Hosts: expectedHosts, + HTTPOptions: manifest.ServiceExposeHTTPOptions{ + MaxBodySize: 1048576, + ReadTimeout: 60000, + SendTimeout: 60000, + NextTries: 3, + NextTimeout: 0, + NextCases: []string{"error", "timeout"}, + }}, + {Port: 12345, Global: true, Proto: manifest.UDP, + HTTPOptions: manifest.ServiceExposeHTTPOptions{ + MaxBodySize: 1048576, + ReadTimeout: 60000, + SendTimeout: 60000, + NextTries: 3, + NextTimeout: 0, + NextCases: []string{"error", "timeout"}, + }}, + }, + }, + }, + }, mani.GetGroups()[0]) +} + +func Test_v2_1_Parse_ProfileNameNotServiceName(t *testing.T) { + sdl, err := ReadFile("./_testdata/v2.1-profile-svc-name-mismatch.yaml") + require.NoError(t, err) + + dgroups, err := sdl.DeploymentGroups() + require.NoError(t, err) + assert.Len(t, dgroups, 1) + + mani, err := sdl.Manifest() + require.NoError(t, err) + assert.Len(t, mani.GetGroups(), 1) +} + +func Test_v2_1_Parse_DeploymentNameServiceNameMismatch(t *testing.T) { + sdl, err := ReadFile("./_testdata/v2.1-deployment-svc-mismatch.yaml") + require.Error(t, err) + require.Nil(t, sdl) + require.Contains(t, err.Error(), "no service profile named") + + sdl, err = ReadFile("./_testdata/v2.1-simple2.yaml") + require.NoError(t, err) + require.NotNil(t, sdl) + + dgroups, err := sdl.DeploymentGroups() + require.NoError(t, err) + assert.Len(t, dgroups, 1) + + mani, err := sdl.Manifest() + require.NoError(t, err) + assert.Len(t, mani.GetGroups(), 1) + + require.Equal(t, dgroups[0].Name, mani.GetGroups()[0].Name) + // SDL lists 2 services, but particular deployment specifies only one + require.Len(t, mani.GetGroups()[0].Services, 1) + + // make sure deployment maps to the right service + require.Len(t, mani.GetGroups()[0].Services[0].Expose, 2) + 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 TestV2_1_ParseServiceMix(t *testing.T) { + sdl, err := ReadFile("./_testdata/v2.1-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 TestV2_1_ParseServiceMix2(t *testing.T) { + sdl, err := ReadFile("./_testdata/v2.1-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/sdl/v2_ip_test.go b/sdl/v2_ip_test.go index 4e54118b9b..090f525107 100644 --- a/sdl/v2_ip_test.go +++ b/sdl/v2_ip_test.go @@ -12,6 +12,8 @@ import ( ) func findIPEndpoint(t *testing.T, endpoints []types.Endpoint, id int) types.Endpoint { + t.Helper() + idx := 0 for _, endpoint := range endpoints { if endpoint.Kind == types.Endpoint_LEASED_IP { @@ -114,14 +116,14 @@ func TestV2Parse_SharedIP(t *testing.T) { group := groups[0] resources := group.GetResourceUnits() - require.Len(t, resources, 1) + require.Len(t, resources, 2) - resource := resources[0] - ipEndpoint := findIPEndpoint(t, resource.Resources.Endpoints, 1) - require.Greater(t, ipEndpoint.SequenceNumber, uint32(0)) + // resource := resources[0] + ipEndpoint1 := findIPEndpoint(t, resources[0].Resources.Endpoints, 1) + require.Greater(t, ipEndpoint1.SequenceNumber, uint32(0)) - ipEndpoint = findIPEndpoint(t, resource.Resources.Endpoints, 2) - require.Greater(t, ipEndpoint.SequenceNumber, uint32(0)) + ipEndpoint2 := findIPEndpoint(t, resources[1].Resources.Endpoints, 1) + require.Greater(t, ipEndpoint2.SequenceNumber, uint32(0)) mani, err := sdl1.Manifest() require.NoError(t, err) @@ -135,11 +137,11 @@ func TestV2Parse_SharedIP(t *testing.T) { serviceA := services[0] serviceIPEndpoint := findIPEndpoint(t, serviceA.Resources.Endpoints, 1) - require.Equal(t, serviceIPEndpoint.SequenceNumber, ipEndpoint.SequenceNumber) + require.Equal(t, serviceIPEndpoint.SequenceNumber, ipEndpoint1.SequenceNumber) serviceB := services[1] serviceIPEndpoint = findIPEndpoint(t, serviceB.Resources.Endpoints, 1) - require.Equal(t, serviceIPEndpoint.SequenceNumber, ipEndpoint.SequenceNumber) + require.Equal(t, serviceIPEndpoint.SequenceNumber, ipEndpoint2.SequenceNumber) } func TestV2Parse_MultipleIP(t *testing.T) { @@ -154,7 +156,7 @@ func TestV2Parse_MultipleIP(t *testing.T) { group := groups[0] resources := group.GetResourceUnits() - require.Len(t, resources, 1) + require.Len(t, resources, 2) mani, err := sdl1.Manifest() require.NoError(t, err) diff --git a/sdl/v2_test.go b/sdl/v2_test.go index 0b4c21b27e..897db36293 100644 --- a/sdl/v2_test.go +++ b/sdl/v2_test.go @@ -610,7 +610,7 @@ func TestV2ParseServiceMix2(t *testing.T) { assert.Len(t, groups, 1) group := groups[0] - assert.Len(t, group.GetResourceUnits(), 1) + assert.Len(t, group.GetResourceUnits(), 2) assert.Len(t, group.Requirements.Attributes, 2) assert.Equal(t, atypes.Attribute{ @@ -620,7 +620,7 @@ func TestV2ParseServiceMix2(t *testing.T) { assert.Equal(t, dtypes.ResourceUnits{ { - Count: 2, + Count: 1, Resources: atypes.Resources{ ID: 1, CPU: &atypes.CPU{ @@ -651,6 +651,36 @@ func TestV2ParseServiceMix2(t *testing.T) { { 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(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, }, @@ -722,7 +752,7 @@ func TestV2ParseServiceMix2(t *testing.T) { Name: "svcb", Image: "nginx", Resources: atypes.Resources{ - ID: 1, + ID: 2, CPU: &atypes.CPU{ Units: atypes.NewResourceValue(100), }, diff --git a/x/deployment/testdata/deployment-v2.1-ip-endpoint.yaml b/x/deployment/testdata/deployment-v2.1-ip-endpoint.yaml new file mode 100644 index 0000000000..df653d5241 --- /dev/null +++ b/x/deployment/testdata/deployment-v2.1-ip-endpoint.yaml @@ -0,0 +1,41 @@ +--- +version: "2.1" + +services: + web: + image: ghcr.io/akash-network/demo-app + expose: + - port: 80 + to: + - global: true + ip: "meow" + accept: + - test.localhost + +profiles: + compute: + web: + resources: + cpu: + units: "0.01" + memory: + size: "128Mi" + storage: + size: "512Mi" + + placement: + global: + pricing: + web: + denom: uakt + amount: 10 + +deployment: + web: + global: + profile: web + count: 1 + +endpoints: + meow: + kind: "ip" diff --git a/x/deployment/testdata/deployment-v2.1-multi-groups-ip-endpoint.yaml b/x/deployment/testdata/deployment-v2.1-multi-groups-ip-endpoint.yaml new file mode 100644 index 0000000000..8cf888c2de --- /dev/null +++ b/x/deployment/testdata/deployment-v2.1-multi-groups-ip-endpoint.yaml @@ -0,0 +1,69 @@ +--- +version: "2.0" + +services: + web: + image: ghcr.io/akash-network/demo-app + expose: + - port: 80 + to: + - global: true + ip: "meow" + accept: + - test.localhost + anotherweb: + image: ghcr.io/akash-network/demo-app + expose: + - port: 80 + to: + - global: true + ip: "kittens" + accept: + - extratest.localhost + +profiles: + compute: + web: + resources: + cpu: + units: "0.01" + memory: + size: "128Mi" + storage: + size: "512Mi" + bob: + resources: + cpu: + units: "0.13" + memory: + size: "256Mi" + storage: + size: "99Mi" + + placement: + global: + pricing: + web: + denom: uakt + amount: 10 + bob: + pricing: + bob: + denom: uakt + amount: 99 + +deployment: + web: + global: + profile: web + count: 1 + anotherweb: + bob: + profile: bob + count: 3 + +endpoints: + meow: + kind: "ip" + kittens: + kind: "ip" diff --git a/x/deployment/testdata/deployment-v2.1-multi-ip-endpoint.yaml b/x/deployment/testdata/deployment-v2.1-multi-ip-endpoint.yaml new file mode 100644 index 0000000000..9fe15b175c --- /dev/null +++ b/x/deployment/testdata/deployment-v2.1-multi-ip-endpoint.yaml @@ -0,0 +1,56 @@ +--- +version: "2.1" + +services: + web: + image: ghcr.io/akash-network/demo-app + expose: + - port: 80 + to: + - global: true + ip: "meow" + accept: + - test.localhost + anotherweb: + image: ghcr.io/akash-network/demo-app + expose: + - port: 80 + to: + - global: true + ip: "kittens" + accept: + - extratest.localhost + +profiles: + compute: + web: + resources: + cpu: + units: "0.01" + memory: + size: "128Mi" + storage: + size: "512Mi" + + placement: + global: + pricing: + web: + denom: uakt + amount: 10 + +deployment: + web: + global: + profile: web + count: 1 + anotherweb: + global: + profile: web + count: 3 + +endpoints: + meow: + kind: "ip" + kittens: + kind: "ip" diff --git a/x/deployment/testdata/deployment-v2.1-shared-ip-endpoint.yaml b/x/deployment/testdata/deployment-v2.1-shared-ip-endpoint.yaml new file mode 100644 index 0000000000..09555f767e --- /dev/null +++ b/x/deployment/testdata/deployment-v2.1-shared-ip-endpoint.yaml @@ -0,0 +1,55 @@ +--- +version: "2.1" + +services: + web: + image: ghcr.io/akash-network/demo-app + expose: + - port: 80 + to: + - global: true + ip: "meow" + accept: + - test.localhost + anotherweb: + image: ghcr.io/akash-network/demo-app + expose: + - port: 80 + as: 81 + to: + - global: true + ip: "meow" + accept: + - extratest.localhost + +profiles: + compute: + web: + resources: + cpu: + units: "0.01" + memory: + size: "128Mi" + storage: + size: "512Mi" + + placement: + global: + pricing: + web: + denom: uakt + amount: 10 + +deployment: + web: + global: + profile: web + count: 1 + anotherweb: + global: + profile: web + count: 3 + +endpoints: + meow: + kind: "ip" diff --git a/x/deployment/testdata/deployment-v2.1.yaml b/x/deployment/testdata/deployment-v2.1.yaml new file mode 100644 index 0000000000..aaf4d294f9 --- /dev/null +++ b/x/deployment/testdata/deployment-v2.1.yaml @@ -0,0 +1,36 @@ +--- +version: "2.1" + +services: + web: + image: bubuntux/riot-web + expose: + - port: 80 + to: + - global: true + accept: + - test.localhost + +profiles: + compute: + web: + resources: + cpu: + units: "0.01" + memory: + size: "128Mi" + storage: + size: "512Mi" + + placement: + global: + pricing: + web: + denom: uakt + amount: 30 + +deployment: + web: + global: + profile: web + count: 1