Skip to content

Commit

Permalink
feat(go): migrate cert testutil from node repo (#161)
Browse files Browse the repository at this point in the history
Signed-off-by: Artur Troian <[email protected]>
  • Loading branch information
troian authored May 1, 2024
1 parent 8deb00f commit b978069
Show file tree
Hide file tree
Showing 4 changed files with 471 additions and 1 deletion.
98 changes: 97 additions & 1 deletion go/testutil/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ package testutil

import (
"fmt"
"math/rand"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/libs/rand"

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

// ensure sdkutil.init() to seal SDK config for the tests
_ "github.com/akash-network/akash-api/go/sdkutil"
)

// CoinDenom provides ability to create coins in test functions and
Expand All @@ -22,3 +30,91 @@ func Name(_ testing.TB, prefix string) string {
func Hostname(t testing.TB) string {
return Name(t, "hostname") + ".test.com"
}

func ProviderHostname(t testing.TB) string {
return "https://" + Hostname(t)
}

// Attribute generates a random sdk.Attribute
func Attribute(t testing.TB) types.Attribute {
t.Helper()
return types.NewStringAttribute(Name(t, "attr-key"), Name(t, "attr-value"))
}

// Attributes generates a set of sdk.Attribute
func Attributes(t testing.TB) []types.Attribute {
t.Helper()
count := rand.Intn(10) + 1

vals := make([]types.Attribute, 0, count)
for i := 0; i < count; i++ {
vals = append(vals, Attribute(t))
}
return vals
}

// PlacementRequirements generates placement requirements
func PlacementRequirements(t testing.TB) types.PlacementRequirements {
return types.PlacementRequirements{
Attributes: Attributes(t),
}
}

func RandCPUUnits() uint {
return RandRangeUint(
dtypes.GetValidationConfig().Unit.Min.CPU,
dtypes.GetValidationConfig().Unit.Max.CPU)
}

func RandGPUUnits() uint {
return RandRangeUint(
dtypes.GetValidationConfig().Unit.Min.GPU,
dtypes.GetValidationConfig().Unit.Max.GPU)
}

func RandMemoryQuantity() uint64 {
return RandRangeUint64(
dtypes.GetValidationConfig().Unit.Min.Memory,
dtypes.GetValidationConfig().Unit.Max.Memory)
}

func RandStorageQuantity() uint64 {
return RandRangeUint64(
dtypes.GetValidationConfig().Unit.Min.Storage,
dtypes.GetValidationConfig().Unit.Max.Storage)
}

// Resources produces an attribute list for populating a Group's
// 'Resources' fields.
func Resources(t testing.TB) []dtypes.ResourceUnit {
t.Helper()
count := rand.Intn(10) + 1

vals := make(dtypes.ResourceUnits, 0, count)
for i := 0; i < count; i++ {
coin := sdk.NewDecCoin(CoinDenom, sdk.NewInt(rand.Int63n(9999)+1))
res := dtypes.ResourceUnit{
Resources: types.Resources{
ID: uint32(i) + 1,
CPU: &types.CPU{
Units: types.NewResourceValue(uint64(dtypes.GetValidationConfig().Unit.Min.CPU)),
},
GPU: &types.GPU{
Units: types.NewResourceValue(uint64(dtypes.GetValidationConfig().Unit.Min.GPU)),
},
Memory: &types.Memory{
Quantity: types.NewResourceValue(dtypes.GetValidationConfig().Unit.Min.Memory),
},
Storage: types.Volumes{
types.Storage{
Quantity: types.NewResourceValue(dtypes.GetValidationConfig().Unit.Min.Storage),
},
},
},
Count: 1,
Price: coin,
}
vals = append(vals, res)
}
return vals
}
211 changes: 211 additions & 0 deletions go/testutil/cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package testutil

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"testing"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

types "github.com/akash-network/akash-api/go/node/cert/v1beta3"
certutils "github.com/akash-network/akash-api/go/node/cert/v1beta3/utils"
clientmocks "github.com/akash-network/akash-api/go/node/client/v1beta2/mocks"
)

type TestCertificate struct {
Cert []tls.Certificate
Serial big.Int
PEM struct {
Cert []byte
Priv []byte
Pub []byte
}
}

type certificateOption struct {
domains []string
nbf time.Time
naf time.Time
qclient *clientmocks.QueryClient
}

type CertificateOption func(*certificateOption)

func CertificateOptionDomains(domains []string) CertificateOption {
return func(opt *certificateOption) {
opt.domains = domains
}
}

func CertificateOptionNotBefore(tm time.Time) CertificateOption {
return func(opt *certificateOption) {
opt.nbf = tm
}
}

func CertificateOptionNotAfter(tm time.Time) CertificateOption {
return func(opt *certificateOption) {
opt.naf = tm
}
}

func CertificateOptionMocks(val *clientmocks.QueryClient) CertificateOption {
return func(opt *certificateOption) {
opt.qclient = val
}
}

func Certificate(t testing.TB, addr sdk.Address, opts ...CertificateOption) TestCertificate {
t.Helper()

opt := &certificateOption{}

for _, o := range opts {
o(opt)
}

priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}

if opt.nbf.IsZero() {
opt.nbf = time.Now()
}

if opt.naf.IsZero() {
opt.naf = opt.nbf.Add(time.Hour * 24 * 365)
}

extKeyUsage := []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
}

if len(opt.domains) != 0 {
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageServerAuth)
}

template := x509.Certificate{
SerialNumber: new(big.Int).SetInt64(time.Now().UTC().UnixNano()),
Subject: pkix.Name{
CommonName: addr.String(),
ExtraNames: []pkix.AttributeTypeAndValue{
{
Type: certutils.AuthVersionOID,
Value: "v0.0.1",
},
},
},
Issuer: pkix.Name{
CommonName: addr.String(),
},
NotBefore: opt.nbf,
NotAfter: opt.naf,
KeyUsage: x509.KeyUsageDataEncipherment | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: extKeyUsage,
BasicConstraintsValid: true,
}

var ips []net.IP

for i := len(opt.domains) - 1; i >= 0; i-- {
if ip := net.ParseIP(opt.domains[i]); ip != nil {
ips = append(ips, ip)
opt.domains = append(opt.domains[:i], opt.domains[i+1:]...)
}
}

if len(opt.domains) != 0 || len(ips) != 0 {
template.PermittedDNSDomainsCritical = true
template.PermittedDNSDomains = opt.domains
template.DNSNames = opt.domains
template.IPAddresses = ips
}

var certDer []byte
if certDer, err = x509.CreateCertificate(rand.Reader, &template, &template, priv.Public(), priv); err != nil {
t.Fatal(err)
}

var keyDer []byte
if keyDer, err = x509.MarshalPKCS8PrivateKey(priv); err != nil {
t.Fatal(err)
}

var pubKeyDer []byte
if pubKeyDer, err = x509.MarshalPKIXPublicKey(priv.Public()); err != nil {
t.Fatal(err)
}

res := TestCertificate{
Serial: *template.SerialNumber,
PEM: struct {
Cert []byte
Priv []byte
Pub []byte
}{
Cert: pem.EncodeToMemory(&pem.Block{
Type: types.PemBlkTypeCertificate,
Bytes: certDer,
}),
Priv: pem.EncodeToMemory(&pem.Block{
Type: types.PemBlkTypeECPrivateKey,
Bytes: keyDer,
}),
Pub: pem.EncodeToMemory(&pem.Block{
Type: types.PemBlkTypeECPublicKey,
Bytes: pubKeyDer,
}),
},
}

cert, err := tls.X509KeyPair(res.PEM.Cert, res.PEM.Priv)
if err != nil {
t.Fatal(err)
}

res.Cert = append(res.Cert, cert)

if opt.qclient != nil {
opt.qclient.On("Certificates",
mock.Anything,
&types.QueryCertificatesRequest{
Filter: types.CertificateFilter{
Owner: addr.String(),
Serial: res.Serial.String(),
State: "valid",
},
}).
Return(&types.QueryCertificatesResponse{
Certificates: types.CertificatesResponse{
types.CertificateResponse{
Certificate: types.Certificate{
State: types.CertificateValid,
Cert: res.PEM.Cert,
Pubkey: res.PEM.Pub,
},
Serial: res.Serial.String(),
},
},
}, nil)
}
return res
}

func CertificateRequireEqualResponse(t *testing.T, cert TestCertificate, resp types.CertificateResponse, state types.Certificate_State) {
t.Helper()

require.Equal(t, state, resp.Certificate.State)
require.Equal(t, cert.PEM.Cert, resp.Certificate.Cert)
require.Equal(t, cert.PEM.Pub, resp.Certificate.Pubkey)
}
61 changes: 61 additions & 0 deletions go/testutil/deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package testutil

import (
"crypto/sha256"
"math/rand"
"testing"

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

// sum256Seed provides a consistent sha256 value for initial Deployment.Version
const sum256Seed = "hihi"

// DefaultDeploymentVersion provides consistent sha256 sum for initial Deployment.Version
var DefaultDeploymentVersion = sha256.Sum256([]byte(sum256Seed))

// Deployment generates a dtype.Deployment in state `DeploymentActive`
func Deployment(t testing.TB) dtypes.Deployment {
t.Helper()
return dtypes.Deployment{
DeploymentID: DeploymentID(t),
State: dtypes.DeploymentActive,
Version: DefaultDeploymentVersion[:],
}
}

// DeploymentGroup generates a dtype.DepDeploymentGroup in state `GroupOpen`
// with a set of random required attributes
func DeploymentGroup(t testing.TB, did dtypes.DeploymentID, gseq uint32) dtypes.Group {
t.Helper()
return dtypes.Group{
GroupID: dtypes.MakeGroupID(did, gseq),
State: dtypes.GroupOpen,
GroupSpec: dtypes.GroupSpec{
Name: Name(t, "dgroup"),
Requirements: PlacementRequirements(t),
Resources: Resources(t),
},
}
}

// GroupSpec generator
func GroupSpec(t testing.TB) dtypes.GroupSpec {
t.Helper()
return dtypes.GroupSpec{
Name: Name(t, "dgroup"),
Requirements: PlacementRequirements(t),
Resources: Resources(t),
}
}

// DeploymentGroups returns a set of deployment groups generated by DeploymentGroup
func DeploymentGroups(t testing.TB, did dtypes.DeploymentID, gseq uint32) []dtypes.Group {
t.Helper()
count := rand.Intn(5) + 5 // nolint:gosec
vals := make([]dtypes.Group, 0, count)
for i := 0; i < count; i++ {
vals = append(vals, DeploymentGroup(t, did, gseq+uint32(i)))
}
return vals
}
Loading

0 comments on commit b978069

Please sign in to comment.