Skip to content

Commit

Permalink
Merge pull request #248 from threefoldtech/development_calculator
Browse files Browse the repository at this point in the history
Development calculator
  • Loading branch information
rawdaGastan authored Jul 18, 2023
2 parents c52d87a + cfa129e commit 76266fb
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 479 deletions.
482 changes: 3 additions & 479 deletions go.work.sum

Large diffs are not rendered by default.

140 changes: 140 additions & 0 deletions grid-client/calculator/calculate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package calculator

import (
"math"

substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go"
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/subi"
)

const defaultPricingPolicyID = uint32(1)

// Calculator struct for calculating the cost of resources
type Calculator struct {
substrateConn subi.SubstrateExt
identity substrate.Identity
}

// NewCalculator creates a new Calculator
func NewCalculator(substrateConn subi.SubstrateExt, identity substrate.Identity) Calculator {
return Calculator{substrateConn: substrateConn, identity: identity}
}

// CalculateCost calculates the cost in $ per month of the given resources without a discount
func (c *Calculator) CalculateCost(cru, mru, hru, sru int64, publicIP, certified bool) (float64, error) {
tftPrice, err := c.substrateConn.GetTFTPrice()
if err != nil {
return 0, err
}

pricingPolicy, err := c.substrateConn.GetPricingPolicy(defaultPricingPolicyID)
if err != nil {
return 0, err
}

cu := calculateCU(cru, mru)
su := calculateSU(hru, sru)

var ipv4 float64
if publicIP {
ipv4 = 1
}

certifiedFactor := 1.0
if certified {
certifiedFactor = 1.25
}

costPerMonth := (cu*float64(pricingPolicy.CU.Value) + su*float64(pricingPolicy.SU.Value) + ipv4*float64(pricingPolicy.IPU.Value)) * certifiedFactor * 24 * 30
return costPerMonth / float64(tftPrice) / 1000, nil
}

// CalculateDiscount calculates the discount of a given cost
func (c *Calculator) CalculateDiscount(cost float64) (dedicatedPrice, sharedPrice float64, err error) {
tftPrice, err := c.substrateConn.GetTFTPrice()
if err != nil {
return
}

pricingPolicy, err := c.substrateConn.GetPricingPolicy(defaultPricingPolicyID)
if err != nil {
return
}

// discount for shared Nodes
sharedPrice = cost

// discount for Dedicated Nodes
discount := float64(pricingPolicy.DedicatedNodesDiscount)
dedicatedPrice = cost - cost*(discount/100)

// discount for Twin Balance in TFT
accountBalance, err := c.substrateConn.GetBalance(c.identity)
if err != nil {
return
}
balance := float64(tftPrice) / 1000 * float64(accountBalance.Free.Int64()) * 10000000

discountPackages := map[string]map[string]float64{
"none": {
"duration": 0,
"discount": 0,
},
"default": {
"duration": 1.5,
"discount": 20,
},
"bronze": {
"duration": 3,
"discount": 30,
},
"silver": {
"duration": 6,
"discount": 40,
},
"gold": {
"duration": 18,
"discount": 60,
},
}

// check which package will be used according to the balance
dedicatedPackage := "none"
sharedPackage := "none"
for pkg := range discountPackages {
if balance > dedicatedPrice*discountPackages[pkg]["duration"] {
dedicatedPackage = pkg
}
if balance > sharedPrice*discountPackages[pkg]["duration"] {
sharedPackage = pkg
}
}

dedicatedPrice = (dedicatedPrice - dedicatedPrice*(discountPackages[dedicatedPackage]["discount"]/100)) / 1e7
sharedPrice = (sharedPrice - sharedPrice*(discountPackages[sharedPackage]["discount"]/100)) / 1e7

return
}

func calculateSU(hru, sru int64) float64 {
return float64(hru/1200 + sru/200)
}

func calculateCU(cru, mru int64) float64 {
MruUsed1 := float64(mru / 4)
CruUsed1 := float64(cru / 2)
cu1 := math.Max(MruUsed1, CruUsed1)

MruUsed2 := float64(mru / 8)
CruUsed2 := float64(cru)
cu2 := math.Max(MruUsed2, CruUsed2)

MruUsed3 := float64(mru / 2)
CruUsed3 := float64(cru / 4)
cu3 := math.Max(MruUsed3, CruUsed3)

cu := math.Min(cu1, cu2)
cu = math.Min(cu, cu3)

return cu
}
93 changes: 93 additions & 0 deletions grid-client/calculator/calculate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package calculator

import (
"errors"
"math/big"
"testing"

"github.com/centrifuge/go-substrate-rpc-client/v4/types"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go"
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/mocks"
)

func TestCalculator(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

sub := mocks.NewMockSubstrateExt(ctrl)
identity, err := substrate.NewIdentityFromSr25519Phrase("//Alice")
assert.NoError(t, err)

calculator := NewCalculator(sub, identity)

sub.EXPECT().GetTFTPrice().Return(types.U32(1), nil).AnyTimes()
sub.EXPECT().GetPricingPolicy(1).Return(substrate.PricingPolicy{
ID: 1,
SU: substrate.Policy{
Value: 2,
},
CU: substrate.Policy{
Value: 2,
},
IPU: substrate.Policy{
Value: 2,
},
}, nil).AnyTimes()

cost, err := calculator.CalculateCost(8, 32, 0, 50, true, true)
assert.NoError(t, err)
assert.Equal(t, cost, 16.2)

sub.EXPECT().GetBalance(identity).Return(substrate.Balance{
Free: types.U128{
Int: big.NewInt(50000000),
},
}, nil)

dedicatedPrice, sharedPrice, err := calculator.CalculateDiscount(cost)
assert.NoError(t, err)
assert.Equal(t, dedicatedPrice, sharedPrice)
}

func TestSubstrateErrors(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

sub := mocks.NewMockSubstrateExt(ctrl)
identity, err := substrate.NewIdentityFromSr25519Phrase("//Alice")
assert.NoError(t, err)

calculator := NewCalculator(sub, identity)

t.Run("test tft price error", func(t *testing.T) {
sub.EXPECT().GetTFTPrice().Return(types.U32(1), errors.New("error")).AnyTimes()

_, err := calculator.CalculateCost(0, 0, 0, 0, false, false)
assert.Error(t, err)

_, _, err = calculator.CalculateDiscount(200)
assert.Error(t, err)
})

t.Run("test tft pricing policy error", func(t *testing.T) {
sub.EXPECT().GetTFTPrice().Return(types.U32(1), nil).AnyTimes()
sub.EXPECT().GetPricingPolicy(1).Return(substrate.PricingPolicy{}, errors.New("error")).AnyTimes()

_, err := calculator.CalculateCost(0, 0, 0, 0, false, false)
assert.Error(t, err)

_, _, err = calculator.CalculateDiscount(200)
assert.Error(t, err)
})

t.Run("test tft balance error", func(t *testing.T) {
sub.EXPECT().GetTFTPrice().Return(types.U32(1), nil).AnyTimes()
sub.EXPECT().GetPricingPolicy(1).Return(substrate.PricingPolicy{}, nil).AnyTimes()
sub.EXPECT().GetBalance(identity).Return(substrate.Balance{}, errors.New("error")).AnyTimes()

_, _, err = calculator.CalculateDiscount(0)
assert.Error(t, err)
})
}
6 changes: 6 additions & 0 deletions grid-client/deployer/tf_plugin_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go"
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/calculator"
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/graphql"
client "github.com/threefoldtech/tfgrid-sdk-go/grid-client/node"
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/state"
Expand Down Expand Up @@ -88,6 +89,9 @@ type TFPluginClient struct {
graphQl graphql.GraphQl
ContractsGetter graphql.ContractsGetter

// calculator
Calculator calculator.Calculator

cancelRelayContext context.CancelFunc
}

Expand Down Expand Up @@ -233,6 +237,8 @@ func NewTFPluginClient(

tfPluginClient.ContractsGetter = graphql.NewContractsGetter(tfPluginClient.TwinID, tfPluginClient.graphQl, tfPluginClient.SubstrateConn, tfPluginClient.NcPool)

tfPluginClient.Calculator = calculator.NewCalculator(tfPluginClient.SubstrateConn, tfPluginClient.Identity)

return tfPluginClient, nil
}

Expand Down
31 changes: 31 additions & 0 deletions grid-client/mocks/substrate_manager_mock.go

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

15 changes: 15 additions & 0 deletions grid-client/subi/substrate_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"sync"

"github.com/centrifuge/go-substrate-rpc-client/v4/types"
"github.com/pkg/errors"
substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go"
)
Expand Down Expand Up @@ -54,6 +55,8 @@ type SubstrateExt interface {
CreateNameContract(identity substrate.Identity, name string) (uint64, error)
GetAccount(identity substrate.Identity) (substrate.AccountInfo, error)
GetBalance(identity substrate.Identity) (balance substrate.Balance, err error)
GetTFTPrice() (balance types.U32, err error)
GetPricingPolicy(policyID uint32) (pricingPolicy substrate.PricingPolicy, err error)
GetTwinPK(twinID uint32) ([]byte, error)
GetContractIDByNameRegistration(name string) (uint64, error)
BatchCreateContract(identity substrate.Identity, contractsData []substrate.BatchCreateContractData) ([]uint64, *int, error)
Expand Down Expand Up @@ -84,6 +87,18 @@ func (s *SubstrateImpl) GetBalance(identity substrate.Identity) (balance substra
return balance, normalizeNotFoundErrors(err)
}

// GetTFTPrice returns the TFT's price
func (s *SubstrateImpl) GetTFTPrice() (balance types.U32, err error) {
price, err := s.Substrate.GetTFTPrice()
return price, normalizeNotFoundErrors(err)
}

// GetPricingPolicy returns a pricing policy
func (s *SubstrateImpl) GetPricingPolicy(policyID uint32) (pricingPolicy substrate.PricingPolicy, err error) {
pricingPolicy, err = s.Substrate.GetPricingPolicy(policyID)
return pricingPolicy, normalizeNotFoundErrors(err)
}

// GetNodeTwin returns the twin ID for a node ID
func (s *SubstrateImpl) GetNodeTwin(nodeID uint32) (uint32, error) {
node, err := s.Substrate.GetNode(nodeID)
Expand Down

0 comments on commit 76266fb

Please sign in to comment.