Skip to content

Commit

Permalink
refactor: tidy api types (#64)
Browse files Browse the repository at this point in the history
implement sort Interface for all slice types

node api (v1beta3)
types:
- ResourceUnit -> Resources
  add ID field

deployment:
- ResourceGroup -> ResourceUnit(s)

manifest (v2beta2)
- remove code duplication when validating manifest
- derive endpoints from Expose
- include endpoints check during manifest cross validation

fixes akash-network/support#104

Signed-off-by: Artur Troian <[email protected]>
  • Loading branch information
troian authored Jul 17, 2023
1 parent 9360bb5 commit e63fa1d
Show file tree
Hide file tree
Showing 47 changed files with 2,624 additions and 1,726 deletions.
191 changes: 123 additions & 68 deletions docs/swagger-ui/swagger.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import (

var (
ErrInvalidManifest = errors.New("invalid manifest")
ErrManifestCrossValidation = errors.New("manifest cross validation error")
ErrManifestCrossValidation = errors.New("manifest cross-validation error")
)
63 changes: 54 additions & 9 deletions go/manifest/v2beta2/group.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,68 @@
package v2beta2

import (
types "github.com/akash-network/akash-api/go/node/types/v1beta3"
"fmt"
"sort"

dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3"
)

var _ dtypes.ResourceGroup = (*Group)(nil)

// GetName returns the name of group
func (g Group) GetName() string {
return g.Name
}

// GetResources returns list of resources in a group
func (g Group) GetResources() []types.Resources {
resources := make([]types.Resources, 0, len(g.Services))
func (g Group) GetResourceUnits() dtypes.ResourceUnits {
groups := make(map[uint32]*dtypes.ResourceUnit)

for _, svc := range g.Services {
if _, exists := groups[svc.Resources.ID]; !exists {
groups[svc.Resources.ID] = &dtypes.ResourceUnit{
Resources: svc.Resources,
Count: 1,
}
} else {
groups[svc.Resources.ID].Count += svc.Count
}
}

units := make(dtypes.ResourceUnits, 0, len(groups))

for i := range groups {
units = append(units, *groups[i])
}

return units
}

func (g *Group) Validate(helper *validateManifestGroupsHelper) error {
if 0 == len(g.Services) {
return fmt.Errorf("%w: group %q contains no services", ErrInvalidManifest, g.GetName())
}

if !sort.IsSorted(g.Services) {
return fmt.Errorf("%w: group %q services is not sorted", ErrInvalidManifest, g.GetName())
}

for _, s := range g.Services {
resources = append(resources, types.Resources{
Resources: s.Resources,
Count: s.Count,
})
if err := s.validate(helper); err != nil {
return err
}
}

return nil
}

// checkAgainstGSpec check if manifest group is within GroupSpec resources
// NOTE: it modifies caller's gspec
func (g *Group) checkAgainstGSpec(gspec *groupSpec) error {
for _, svc := range g.Services {
if err := svc.checkAgainstGSpec(gspec); err != nil {
return fmt.Errorf("%s: group %q: %w", ErrManifestCrossValidation, g.Name, err)
}
}

return resources
return nil
}
27 changes: 14 additions & 13 deletions go/manifest/v2beta2/group.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 86 additions & 0 deletions go/manifest/v2beta2/groups.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package v2beta2

import (
"fmt"

dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3"
)

type Groups []Group

func (groups Groups) Validate() error {
helper := validateManifestGroupsHelper{
hostnames: make(map[string]int),
}

names := make(map[string]int) // used as a set

for _, group := range groups {
if err := group.Validate(&helper); err != nil {
return err
}
if _, exists := names[group.GetName()]; exists {
return fmt.Errorf("%w: duplicate group %q", ErrInvalidManifest, group.GetName())
}

names[group.GetName()] = 0 // Value stored is not used
}

if helper.globalServiceCount == 0 {
return fmt.Errorf("%w: zero global services", ErrInvalidManifest)
}

return nil
}

func (groups Groups) CheckAgainstGSpecs(gspecs dtypes.GroupSpecs) error {
if err := groups.Validate(); err != nil {
return err
}

if len(groups) != len(gspecs) {
return fmt.Errorf("invalid manifest: group count mismatch (%v != %v)", len(groups), len(gspecs))
}

dgroupByName := newGroupSpecsHelper(gspecs)

for _, mgroup := range groups {
dgroup, dgroupExists := dgroupByName[mgroup.GetName()]

if !dgroupExists {
return fmt.Errorf("invalid manifest: unknown deployment group ('%v')", mgroup.GetName())
}

if err := mgroup.checkAgainstGSpec(dgroup); err != nil {
return err
}
}

for _, gspec := range dgroupByName {
for resID, eps := range gspec.endpoints {
if eps.httpEndpoints > 0 {
return fmt.Errorf("%w: group %q: resources ID (%d): under-utilized (%d) HTTP endpoints",
ErrManifestCrossValidation, gspec.gs.Name, resID, eps.httpEndpoints)
}

if eps.portEndpoints > 0 {
return fmt.Errorf("%w: group %q: resources ID (%d): under-utilized (%d) PORT endpoints",
ErrManifestCrossValidation, gspec.gs.Name, resID, eps.portEndpoints)
}

if eps.ipEndpoints > 0 {
return fmt.Errorf("%w: group %q: resources ID (%d): under-utilized (%d) IP endpoints",
ErrManifestCrossValidation, gspec.gs.Name, resID, eps.ipEndpoints)
}
}

for _, gRes := range gspec.gs.Resources {
if gRes.Count > 0 {
return fmt.Errorf("%w: group %q: resources ID (%d): under-utilized (%d) resources",
ErrManifestCrossValidation, gspec.gs.GetName(), gRes.ID, gRes.Count)
}
}
}

return nil
}
98 changes: 98 additions & 0 deletions go/manifest/v2beta2/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package v2beta2

import (
k8svalidation "k8s.io/apimachinery/pkg/util/validation"

dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3"
types "github.com/akash-network/akash-api/go/node/types/v1beta3"
)

type validateManifestGroupsHelper struct {
hostnames map[string]int // used as a set
globalServiceCount uint
}

type validateEndpoints struct {
httpEndpoints uint
portEndpoints uint
ipEndpoints uint
}

type validateEndpointsHelper map[uint32]*validateEndpoints

type groupSpec struct {
gs dtypes.GroupSpec
endpoints validateEndpointsHelper
}

type groupSpecHelper map[string]*groupSpec

func newGroupSpecsHelper(gspecs dtypes.GroupSpecs) groupSpecHelper {
res := make(groupSpecHelper)

for _, gspec := range gspecs {
res[gspec.GetName()] = newGroupSpecHelper(*gspec)
}

return res
}

func newGroupSpecHelper(gs dtypes.GroupSpec) *groupSpec {
res := &groupSpec{
gs: gs,
endpoints: make(validateEndpointsHelper),
}

for _, gRes := range gs.Resources {
vep := &validateEndpoints{}

for _, ep := range gRes.Endpoints {
switch ep.Kind {
case types.Endpoint_SHARED_HTTP:
vep.httpEndpoints++
case types.Endpoint_RANDOM_PORT:
vep.portEndpoints++
case types.Endpoint_LEASED_IP:
vep.ipEndpoints++
}
}

res.endpoints[gRes.ID] = vep
}

return res
}

func isValidHostname(hostname string) bool {
return len(hostname) <= hostnameMaxLen && 0 == len(k8svalidation.IsDNS1123Subdomain(hostname))
}

func (ve *validateEndpoints) tryDecHTTP() bool {
if ve.httpEndpoints == 0 {
return false
}

ve.httpEndpoints--

return true
}

func (ve *validateEndpoints) tryDecPort() bool {
if ve.portEndpoints == 0 {
return false
}

ve.portEndpoints--

return true
}

func (ve *validateEndpoints) tryDecIP() bool {
if ve.ipEndpoints == 0 {
return false
}

ve.ipEndpoints--

return true
}
Loading

0 comments on commit e63fa1d

Please sign in to comment.