Skip to content

Commit

Permalink
Merge pull request #631 from threefoldtech/development_proxy_pricing_…
Browse files Browse the repository at this point in the history
…order

add node pricing
  • Loading branch information
xmonader authored Feb 19, 2024
2 parents a653e85 + 7b62a5e commit 8d88cb1
Show file tree
Hide file tree
Showing 17 changed files with 387 additions and 7 deletions.
4 changes: 3 additions & 1 deletion grid-proxy/cmds/proxy_server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ func main() {
log.Fatal().Err(err).Msg("failed to initialize database")
}

dbClient := explorer.DBClient{DB: &db}
dbClient := explorer.DBClient{
DB: &db,
}

indexer, err := gpuindexer.NewNodeGPUIndexer(
ctx,
Expand Down
24 changes: 24 additions & 0 deletions grid-proxy/docs/docs.go

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

24 changes: 24 additions & 0 deletions grid-proxy/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,12 @@
"name": "sort_order",
"in": "query"
},
{
"type": "string",
"description": "a balance in usd, used to apply staking discount on nodes price",
"name": "balance",
"in": "query"
},
{
"type": "integer",
"description": "Min free reservable mru in bytes",
Expand Down Expand Up @@ -1049,6 +1055,18 @@
"description": "get nodes owned by twin id",
"name": "owned_by",
"in": "query"
},
{
"type": "string",
"description": "get nodes with price greater than this",
"name": "price_min",
"in": "query"
},
{
"type": "string",
"description": "get nodes with price smaller than this",
"name": "price_max",
"in": "query"
}
],
"responses": {
Expand Down Expand Up @@ -1597,6 +1615,9 @@
"power": {
"$ref": "#/definitions/types.NodePower"
},
"price_usd": {
"type": "number"
},
"publicConfig": {
"$ref": "#/definitions/types.PublicConfig"
},
Expand Down Expand Up @@ -1765,6 +1786,9 @@
"power": {
"$ref": "#/definitions/types.NodePower"
},
"price_usd": {
"type": "number"
},
"publicConfig": {
"$ref": "#/definitions/types.PublicConfig"
},
Expand Down
16 changes: 16 additions & 0 deletions grid-proxy/docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ definitions:
type: integer
power:
$ref: '#/definitions/types.NodePower'
price_usd:
type: number
publicConfig:
$ref: '#/definitions/types.PublicConfig'
rentContractId:
Expand Down Expand Up @@ -237,6 +239,8 @@ definitions:
type: integer
power:
$ref: '#/definitions/types.NodePower'
price_usd:
type: number
publicConfig:
$ref: '#/definitions/types.PublicConfig'
rentContractId:
Expand Down Expand Up @@ -916,6 +920,10 @@ paths:
in: query
name: sort_order
type: string
- description: a balance in usd, used to apply staking discount on nodes price
in: query
name: balance
type: string
- description: Min free reservable mru in bytes
in: query
name: free_mru
Expand Down Expand Up @@ -1049,6 +1057,14 @@ paths:
in: query
name: owned_by
type: integer
- description: get nodes with price greater than this
in: query
name: price_min
type: string
- description: get nodes with price smaller than this
in: query
name: price_max
type: string
produces:
- application/json
responses:
Expand Down
4 changes: 3 additions & 1 deletion grid-proxy/internal/explorer/converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package explorer

import (
"encoding/json"
"math"

"github.com/pkg/errors"
"github.com/threefoldtech/tfgrid-sdk-go/grid-proxy/internal/explorer/db"
Expand Down Expand Up @@ -59,6 +60,7 @@ func nodeFromDBNode(info db.Node) types.Node {
NumGPU: info.NumGPU,
ExtraFee: info.ExtraFee,
Healthy: info.Healthy,
PriceUsd: math.Round(info.PriceUsd*1000) / 1000,
}
node.Status = nodestatus.DecideNodeStatus(node.Power, node.UpdatedAt)
node.Dedicated = info.FarmDedicated || info.NodeContractsCount == 0 || info.Renter != 0
Expand All @@ -82,7 +84,6 @@ func farmFromDBFarm(info db.Farm) (types.Farm, error) {
}

func nodeWithNestedCapacityFromDBNode(info db.Node) types.NodeWithNestedCapacity {

node := types.NodeWithNestedCapacity{
ID: info.ID,
NodeID: int(info.NodeID),
Expand Down Expand Up @@ -133,6 +134,7 @@ func nodeWithNestedCapacityFromDBNode(info db.Node) types.NodeWithNestedCapacity
NumGPU: info.NumGPU,
ExtraFee: info.ExtraFee,
Healthy: info.Healthy,
PriceUsd: math.Round(info.PriceUsd*1000) / 1000,
}
node.Status = nodestatus.DecideNodeStatus(node.Power, node.UpdatedAt)
node.Dedicated = info.FarmDedicated || info.NodeContractsCount == 0 || info.Renter != 0
Expand Down
14 changes: 11 additions & 3 deletions grid-proxy/internal/explorer/db/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func (np *NodePower) Scan(value interface{}) error {

// GetNode returns node info
func (d *PostgresDatabase) GetNode(ctx context.Context, nodeID uint32) (Node, error) {
q := d.nodeTableQuery(ctx, types.NodeFilter{}, &gorm.DB{})
q := d.nodeTableQuery(ctx, types.NodeFilter{}, &gorm.DB{}, 0)
q = q.Where("node.node_id = ?", nodeID)
var node Node
res := q.Scan(&node)
Expand Down Expand Up @@ -260,7 +260,8 @@ func printQuery(query string, args ...interface{}) {
fmt.Printf("node query: %s", query)
}

func (d *PostgresDatabase) nodeTableQuery(ctx context.Context, filter types.NodeFilter, nodeGpuSubquery *gorm.DB) *gorm.DB {
func (d *PostgresDatabase) nodeTableQuery(ctx context.Context, filter types.NodeFilter, nodeGpuSubquery *gorm.DB, balance float64) *gorm.DB {
calculatedDiscountColumn := fmt.Sprintf("calc_discount(resources_cache.price_usd, %f) AS price_usd", balance)
q := d.gormDB.WithContext(ctx).
Table("node").
Select(
Expand Down Expand Up @@ -301,6 +302,7 @@ func (d *PostgresDatabase) nodeTableQuery(ctx context.Context, filter types.Node
"resources_cache.node_contracts_count",
"resources_cache.node_gpu_count AS num_gpu",
"health_report.healthy",
calculatedDiscountColumn,
).
Joins(`
LEFT JOIN resources_cache ON node.node_id = resources_cache.node_id
Expand Down Expand Up @@ -518,7 +520,7 @@ func (d *PostgresDatabase) GetNodes(ctx context.Context, filter types.NodeFilter
nodeGpuSubquery = nodeGpuSubquery.Where("(COALESCE(node_gpu.contract, 0) = 0) = ?", *filter.GpuAvailable)
}

q := d.nodeTableQuery(ctx, filter, nodeGpuSubquery)
q := d.nodeTableQuery(ctx, filter, nodeGpuSubquery, limit.Balance)

condition := "TRUE"
if filter.Status != nil {
Expand Down Expand Up @@ -622,6 +624,12 @@ func (d *PostgresDatabase) GetNodes(ctx context.Context, filter types.NodeFilter
if filter.OwnedBy != nil {
q = q.Where(`COALESCE(farm.twin_id, 0) = ?`, *filter.OwnedBy)
}
if filter.PriceMin != nil {
q = q.Where(`calc_discount(resources_cache.price_usd, ?) >= ?`, limit.Balance, *filter.PriceMin)
}
if filter.PriceMax != nil {
q = q.Where(`calc_discount(resources_cache.price_usd, ?) <= ?`, limit.Balance, *filter.PriceMax)
}

var count int64
if limit.Randomize || limit.RetCount {
Expand Down
94 changes: 92 additions & 2 deletions grid-proxy/internal/explorer/db/setup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,75 @@ RETURN v_dec_value;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION calc_discount(
cost NUMERIC,
balance NUMERIC
) RETURNS NUMERIC AS $$

DECLARE
discount NUMERIC;

BEGIN
discount := (
CASE
WHEN balance >= cost * 18 THEN 0.6
WHEN balance >= cost * 6 THEN 0.4
WHEN balance >= cost * 3 THEN 0.3
WHEN balance >= cost * 1.5 THEN 0.2
ELSE 0
END);

RETURN cost - cost * discount;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

CREATE OR REPLACE FUNCTION calc_price(
cru NUMERIC,
sru NUMERIC,
hru NUMERIC,
mru NUMERIC,
certified BOOLEAN,
policy_id INTEGER,
extra_fee NUMERIC
) RETURNS NUMERIC AS $$

DECLARE
su NUMERIC;
cu NUMERIC;
su_value NUMERIC;
cu_value NUMERIC;
cost_per_month NUMERIC;

BEGIN
SELECT pricing_policy.cu->'value'
INTO cu_value
FROM pricing_policy
WHERE pricing_policy_id = policy_id;

SELECT pricing_policy.su->'value'
INTO su_value
FROM pricing_policy
WHERE pricing_policy_id = policy_id;

IF cu_value IS NULL OR su_value IS NULL THEN
RAISE EXCEPTION 'pricing values not found for policy_id: %', policy_id;
END IF;

cu := (LEAST(
GREATEST(mru / 4, cru / 2),
GREATEST(mru / 8, cru),
GREATEST(mru / 2, cru / 4)
));

su := (hru / 1200 + sru / 200);

cost_per_month := (cu * cu_value + su * su_value + extra_fee) *
(CASE certified WHEN true THEN 1.25 ELSE 1 END) *
(24 * 30);

RETURN cost_per_month / 10000000; -- 1e7
END;
$$ LANGUAGE plpgsql IMMUTABLE;

----
-- Clean old triggers
Expand Down Expand Up @@ -51,7 +120,10 @@ SELECT
count(node_contract.contract_id) as node_contracts_count,
COALESCE(node_gpu.node_gpu_count, 0) as node_gpu_count,
node.country as country,
country.region as region
country.region as region,
CASE WHEN node.certification = 'Certified' THEN true ELSE false END as certified,
farm.pricing_policy_id as policy_id,
COALESCE(node.extra_fee, 0) as extra_fee
FROM node
LEFT JOIN node_contract ON node.node_id = node_contract.node_id AND node_contract.state IN ('Created', 'GracePeriod')
LEFT JOIN contract_resources ON node_contract.resources_used_id = contract_resources.id
Expand All @@ -66,6 +138,7 @@ FROM node
node_twin_id
) AS node_gpu ON node.twin_id = node_gpu.node_twin_id
LEFT JOIN country ON LOWER(node.country) = LOWER(country.name)
LEFT JOIN farm ON farm.farm_id = node.farm_id
GROUP BY
node.node_id,
node_resources_total.mru,
Expand All @@ -77,6 +150,9 @@ GROUP BY
rent_contract.twin_id,
COALESCE(node_gpu.node_gpu_count, 0),
node.country,
node.certification,
node.extra_fee,
farm.pricing_policy_id,
country.region;

DROP TABLE IF EXISTS resources_cache;
Expand All @@ -99,7 +175,21 @@ CREATE TABLE IF NOT EXISTS resources_cache(
node_contracts_count INTEGER NOT NULL,
node_gpu_count INTEGER NOT NULL,
country TEXT,
region TEXT
region TEXT,
certified BOOLEAN,
policy_id INTEGER,
extra_fee NUMERIC,
price_usd NUMERIC GENERATED ALWAYS AS (
calc_price(
total_cru,
total_sru / (1024*1024*1024),
total_hru / (1024*1024*1024),
total_mru / (1024*1024*1024),
certified,
policy_id,
extra_fee
)
) STORED
);

INSERT INTO resources_cache
Expand Down
1 change: 1 addition & 0 deletions grid-proxy/internal/explorer/db/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type Node struct {
ExtraFee uint64
NodeContractsCount uint64 `gorm:"node_contracts_count"`
Healthy bool
PriceUsd float64
}

// NodePower struct is the farmerbot report for node status
Expand Down
3 changes: 3 additions & 0 deletions grid-proxy/internal/explorer/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func (a *App) getStats(r *http.Request) (interface{}, mw.Response) {
// @Param randomize query bool false "Get random patch of nodes"
// @Param sort_by query string false "Sort by specific node filed" Enums(status, node_id, farm_id, twin_id, uptime, created, updated_at, country, city, dedicated_farm, rent_contract_id, total_cru, total_mru, total_hru, total_sru, used_cru, used_mru, used_hru, used_sru, num_gpu, extra_fee)
// @Param sort_order query string false "The sorting order, default is 'asc'" Enums(desc, asc)
// @Param balance query string false "a balance in usd, used to apply staking discount on nodes price"
// @Param free_mru query int false "Min free reservable mru in bytes"
// @Param free_hru query int false "Min free reservable hru in bytes"
// @Param free_sru query int false "Min free reservable sru in bytes"
Expand Down Expand Up @@ -152,6 +153,8 @@ func (a *App) getStats(r *http.Request) (interface{}, mw.Response) {
// @Param gpu_vendor_name query string false "filter nodes based on GPU vendor partial name"
// @Param gpu_available query bool false "filter nodes that have available GPU"
// @Param owned_by query int false "get nodes owned by twin id"
// @Param price_min query string false "get nodes with price greater than this"
// @Param price_max query string false "get nodes with price smaller than this"
// @Success 200 {object} []types.Node
// @Failure 400 {object} string
// @Failure 500 {object} string
Expand Down
Loading

0 comments on commit 8d88cb1

Please sign in to comment.