Files
herolib/lib/mycelium/grid3/gridproxy/gridproxy_core.v
2025-12-02 10:17:45 +01:00

490 lines
19 KiB
V

module gridproxy
// client library for threefold gridproxy API.
import json
import math
import incubaid.herolib.mycelium.grid3.gridproxy.model { Bill, Contract, ContractFilter, ContractIterator, Farm, FarmFilter, FarmIterator, GridStat, Node, NodeFilter, NodeIterator, NodeStats, Node_, StatFilter, Twin, TwinFilter, TwinIterator }
import incubaid.herolib.ui.console
/*
all errors returned by the gridproxy API or the client are wrapped in a standard `Error` object with two fields.
{
msg string
code int // could be API call error code or client error code
}
`code` is an error code that can be used to identify the error.
in API call errors, `code` represents the HTTP status code. (100..599)
Client errors codes are represented by numbers in the range of 1..99
currently, the following client error codes are used:
id not found error code: 4
json parsing error code: 10
http client error code: 11
invalid response from server (e.g. empty response) error code: 24
*/
// clinet error codes
const err_not_found = 4
const err_json_parse = 10
const err_http_client = 11
const err_invalid_resp = 24
const err_grid_client = 30
// get_node_by_id fetchs specific node information by node id.
//
// * `node_id` (u64): node id.
//
// returns: `Node` or `Error`.
pub fn (mut c GridProxyClient) get_node_by_id(node_id u64) !Node {
// needed to allow to use threads
mut http_client := c.http_client
res := http_client.send(prefix: 'nodes/', id: '${node_id}') or {
return error_with_code('http client error: ${err.msg()}', err_http_client)
}
if !res.is_ok() {
return error_with_code(res.data, res.code)
}
if res.data == '' {
return error_with_code('empty response', err_invalid_resp)
}
node := json.decode(Node, res.data) or {
return error_with_code('error to get jsonstr for node data, json decode: node id: ${node_id}, data: ${res.data}',
err_json_parse)
}
return node
}
// get_node_stats_by_id fetchs specific node statistics by node id.
//
// * `node_id` (u64): node id.
//
// returns: `Node_stats` or `Error`.
pub fn (mut c GridProxyClient) get_node_stats_by_id(node_id u64) !NodeStats {
// needed to allow to use threads
mut http_client := c.http_client
res := http_client.send(prefix: 'nodes/', id: '${node_id}/statistics') or {
return error_with_code('http client error: ${err.msg()}', err_http_client)
}
if !res.is_ok() {
return error_with_code(res.data, res.code)
}
if res.data == '' {
return error_with_code('empty response', err_invalid_resp)
}
node_stats := json.decode(NodeStats, res.data) or {
return error_with_code('error to get jsonstr for node data, json decode: node id: ${node_id}, data: ${res.data}',
err_json_parse)
}
return node_stats
}
// get_gateway_by_id fetchs specific gateway information by node id.
//
// * `node_id` (u64): node id.
//
// returns: `Node` or `Error`.
pub fn (mut c GridProxyClient) get_gateway_by_id(node_id u64) !Node {
// needed to allow to use threads
mut http_client := c.http_client
res := http_client.send(prefix: 'gateways/', id: '${node_id}') or {
return error_with_code('http client error: ${err.msg()}', err_http_client)
}
if !res.is_ok() {
return error_with_code(res.data, res.code)
}
if res.data == '' {
return error_with_code('empty response', err_invalid_resp)
}
node := json.decode(Node, res.data) or {
return error_with_code('error to get jsonstr for gateway data, json decode: gateway id: ${node_id}, data: ${res.data}',
err_json_parse)
}
return node
}
// get_nodes fetchs nodes information and public configurations with pagination.
//
// * `available_for` (u64): Available for twin id. [optional].
// * `certification_type` (string): Certificate type NotCertified, Silver or Gold. [optional].
// * `city_contains` (string): Node partial city filter. [optional].
// * `city` (string): Node city filter. [optional].
// * `country_contains` (string): Node partial country filter. [optional].
// * `country` (string): Node country filter. [optional].
// * `dedicated` (bool): Set to true to get the dedicated nodes only. [optional].
// * `domain` (string): Set to true to filter nodes with domain. [optional].
// * `farm_ids` ([]u64): List of farm ids. [optional].
// * `farm_name_contains` (string): Get nodes for specific farm. [optional].
// * `farm_name` (string): Get nodes for specific farm. [optional].
// * `free_hru` (u64): Min free reservable hru in bytes. [optional].
// * `free_ips` (u64): Min number of free ips in the farm of the node. [optional].
// * `free_mru` (u64): Min free reservable mru in bytes. [optional].
// * `free_sru` (u64): Min free reservable sru in bytes. [optional].
// * `gpu_available` (bool): Filter nodes that have available GPU. [optional].
// * `gpu_device_id` (string): Filter nodes based on GPU device ID. [optional].
// * `gpu_device_name` (string): Filter nodes based on GPU device partial name. [optional].
// * `gpu_vendor_id` (string): Filter nodes based on GPU vendor ID. [optional].
// * `gpu_vendor_name` (string): Filter nodes based on GPU vendor partial name. [optional].
// * `has_gpu`: Filter nodes on whether they have GPU support or not. [optional].
// * `ipv4` (string): Set to true to filter nodes with ipv4. [optional].
// * `ipv6` (string): Set to true to filter nodes with ipv6. [optional].
// * `node_id` (u64): Node id. [optional].
// * `page` (u64): Page number. [optional].
// * `rentable` (bool): Set to true to filter the available nodes for renting. [optional].
// * `rented_by` (u64): Rented by twin id. [optional].
// * `ret_count` (bool): Set nodes' count on headers based on filter. [optional].
// * `size` (u64): Max result per page. [optional].
// * `status` (string): Node status filter, set to 'up' to get online nodes only. [optional].
// * `total_cru` (u64): Min total cru in bytes. [optional].
// * `total_hru` (u64): Min total hru in bytes. [optional].
// * `total_mru` (u64): Min total mru in bytes. [optional].
// * `total_sru` (u64): Min total sru in bytes. [optional].
// * `twin_id` (u64): Twin id. [optional].
//
// returns: `[]Node` or `Error`.
pub fn (mut c GridProxyClient) get_nodes(params NodeFilter) ![]Node {
// needed to allow to use threads
mut http_client := c.http_client
params_map := params.to_map()
res := http_client.send(prefix: 'nodes/', params: params_map) or {
return error_with_code('http client error: ${err.msg()}', err_http_client)
}
if !res.is_ok() {
return error_with_code(res.data, res.code)
}
if res.data == '' {
return error_with_code('empty response', err_invalid_resp)
}
nodes_ := json.decode([]Node_, res.data) or {
return error_with_code('error to get jsonstr for node list data, json decode: node filter: ${params_map}, data: ${res.data}',
err_json_parse)
}
nodes := nodes_.map(it.with_nested_capacity())
return nodes
}
// get_gateways fetchs gateways information and public configurations and domains with pagination.
//
// * `available_for` (u64): Available for twin id. [optional].
// * `certification_type` (string): Certificate type NotCertified, Silver or Gold. [optional].
// * `city_contains` (string): Node partial city filter. [optional].
// * `city` (string): Node city filter. [optional].
// * `country_contains` (string): Node partial country filter. [optional].
// * `country` (string): Node country filter. [optional].
// * `dedicated` (bool): Set to true to get the dedicated nodes only. [optional].
// * `domain` (bool): Set to true to filter nodes with domain. [optional].
// * `farm_ids` ([]u64): List of farm ids. [optional].
// * `farm_name_contains` (string): Get nodes for specific farm. [optional].
// * `farm_name` (string): Get nodes for specific farm. [optional].
// * `free_hru` (u64): Min free reservable hru in bytes. [optional].
// * `free_ips` (u64): Min number of free ips in the farm of the node. [optional].
// * `free_mru` (u64): Min free reservable mru in bytes. [optional].
// * `free_sru` (u64): Min free reservable sru in bytes. [optional].
// * `gpu_available` (bool): Filter nodes that have available GPU. [optional].
// * `gpu_device_id` (string): Filter nodes based on GPU device ID. [optional].
// * `gpu_device_name` (string): Filter nodes based on GPU device partial name. [optional].
// * `gpu_vendor_id` (string): Filter nodes based on GPU vendor ID. [optional].
// * `gpu_vendor_name` (string): Filter nodes based on GPU vendor partial name. [optional].
// * `has_gpu`: Filter nodes on whether they have GPU support or not. [optional].
// * `ipv4` (string): Set to true to filter nodes with ipv4. [optional].
// * `ipv6` (string): Set to true to filter nodes with ipv6. [optional].
// * `node_id` (u64): Node id. [optional].
// * `page` (u64): Page number. [optional].
// * `rentable` (bool): Set to true to filter the available nodes for renting. [optional].
// * `rented_by` (u64): Rented by twin id. [optional].
// * `ret_count` (bool): Set nodes' count on headers based on filter. [optional].
// * `size` (u64): Max result per page. [optional].
// * `status` (string): Node status filter, set to 'up' to get online nodes only. [optional].
// * `total_cru` (u64): Min total cru in bytes. [optional].
// * `total_hru` (u64): Min total hru in bytes. [optional].
// * `total_mru` (u64): Min total mru in bytes. [optional].
// * `total_sru` (u64): Min total sru in bytes. [optional].
// * `twin_id` (u64): Twin id. [optional].
//
// returns: `[]Node` or `Error`.
pub fn (mut c GridProxyClient) get_gateways(params NodeFilter) ![]Node {
// needed to allow to use threads
mut http_client := c.http_client
params_map := params.to_map()
res := http_client.send(prefix: 'gateways/', params: params_map) or {
return error_with_code('http client error: ${err.msg()}', err_http_client)
}
if !res.is_ok() {
return error_with_code(res.data, res.code)
}
if res.data == '' {
return error_with_code('empty response', err_invalid_resp)
}
nodes_ := json.decode([]Node_, res.data) or {
return error_with_code('error to get jsonstr for gateways list data, json decode: gateway filter: ${params_map}, data: ${res.data}',
err_json_parse)
}
nodes := nodes_.map(it.with_nested_capacity())
return nodes
}
// get_stats fetchs stats about the grid.
//
// * `status` (string): Node status filter, set to 'up' to get online nodes only.. [optional].
//
// returns: `GridStat` or `Error`.
pub fn (mut c GridProxyClient) get_stats(filter StatFilter) !GridStat {
// needed to allow to use threads
mut http_client := c.http_client
mut params_map := map[string]string{}
params_map['status'] = match filter.status {
.all { '' }
.online { 'up' }
}
res := http_client.send(prefix: 'stats/', params: params_map) or {
return error_with_code('http client error: ${err.msg()}', err_http_client)
}
if !res.is_ok() {
return error_with_code(res.data, res.code)
}
if res.data == '' {
return error_with_code('empty response', err_invalid_resp)
}
stats := json.decode(GridStat, res.data) or {
return error_with_code('error to get jsonstr for grid stats data, json decode: stats filter: ${params_map}, data: ${res.data}',
err_json_parse)
}
return stats
}
// get_twins fetchs twins information with pagaination.
//
// * `account_id` (string): Account address. [optional].
// * `page` (u64): Page number. [optional].
// * `public_key` (string): twin public key used for e2e encryption. [optional].
// * `relay` (string): relay domain name. [optional].
// * `ret_count` (bool): Set farms' count on headers based on filter. [optional].
// * `size` (u64): Max result per page. [optional].
// * `twin_id` (u64): Twin id. [optional].
//
// returns: `[]Twin` or `Error`.
pub fn (mut c GridProxyClient) get_twins(params TwinFilter) ![]Twin {
// needed to allow to use threads
mut http_client := c.http_client
params_map := params.to_map()
res := http_client.send(prefix: 'twins/', params: params_map) or {
return error_with_code('http client error: ${err.msg()}', err_http_client)
}
if !res.is_ok() {
return error_with_code(res.data, res.code)
}
if res.data == '' {
return error_with_code('empty response', err_invalid_resp)
}
twins := json.decode([]Twin, res.data) or {
return error_with_code('error to get jsonstr for twin list data, json decode: twin filter: ${params_map}, data: ${res.data}',
err_json_parse)
}
return twins
}
// get_contracts fetchs contracts information with pagination.
//
// * `contract_id` (u64): Contract id. [optional].
// * `contract_type` (string): [optional].
// * `deployment_data` (string): Contract deployment data in case of 'node' contracts. [optional].
// * `deployment_hash` (string): Contract deployment hash in case of 'node' contracts. [optional].
// * `name` (string): Contract name in case of 'name' contracts. [optional].
// * `node_id` (u64): Node id which contract is deployed on in case of ('rent' or 'node' contracts). [optional].
// * `number_of_public_ips` (u64): Min number of public ips in the 'node' contract. [optional].
// * `page` (u64): Page number. [optional].
// * `randomize` (bool): [optional].
// * `ret_count` (bool): Set farms' count on headers based on filter. [optional].
// * `size` (u64): Max result per page. [optional].
// * `state` (string): Contract state 'Created', or 'Deleted'. [optional].
// * `twin_id` (u64): Twin id. [optional].
// * `type` (string): Contract type 'node', 'name', or 'rent'. [optional].
//
// * returns: `[]Contract` or `Error`.
pub fn (mut c GridProxyClient) get_contracts(params ContractFilter) ![]Contract {
// needed to allow to use threads
mut http_client := c.http_client
params_map := params.to_map()
res := http_client.send(prefix: 'contracts/', params: params_map) or {
return error_with_code('http client error: ${err.msg()}', err_http_client)
}
if !res.is_ok() {
return error_with_code(res.data, res.code)
}
if res.data == '' {
return error_with_code('empty response', err_invalid_resp)
}
contracts := json.decode([]Contract, res.data) or {
return error_with_code('error to get jsonstr for contract list data, json decode: contract filter: ${params_map}, data: ${res.data}',
err_json_parse)
}
return contracts
}
pub fn (mut c GridProxyClient) get_contract_bill(contract_id u64) ![]Bill {
// needed to allow to use threads
mut http_client := c.http_client
res := http_client.send(prefix: 'contracts/', id: '${contract_id}/bills') or {
return error_with_code('http client error: ${err.msg()}', err_http_client)
}
if !res.is_ok() {
return error_with_code(res.data, res.code)
}
if res.data == '' {
return error_with_code('empty response', err_invalid_resp)
}
console.print_debug(res.data)
bills := json.decode([]Bill, res.data) or {
return error_with_code('error to get jsonstr for billing data, json decode: contract_id id: ${contract_id}, data: ${res.data}',
err_json_parse)
}
return bills
}
pub fn (mut c GridProxyClient) get_contract_hourly_bill(contract_id u64) !f64 {
bills := c.get_contract_bill(contract_id)!
if bills.len == 0 {
return f64(0)
}
mut duration := u64(0)
if bills.len >= 2 {
duration = (bills[0].timestamp - bills[1].timestamp) / 3600 // one hour
} else if bills.len == 1 {
contracts := c.get_contracts(contract_id: contract_id)!
if contracts.len > 0 {
duration = (bills[0].timestamp - contracts[0].created_at) / 3600
}
}
if duration > 0 {
return bills[0].amount_billed / duration / math.pow(10, 7)
}
return f64(0)
}
// get_farms fetchs farms information and public ips.
//
// * `certification_type` (string): Certificate type DIY or Certified. [optional].
// * `country` (string): Farm country. [optional].
// * `dedicated` (bool): Farm is dedicated. [optional].
// * `farm_id` (u64): Farm id. [optional].
// * `free_ips` (u64): Min number of free ips in the farm. [optional].
// * `name_contains` (string): Farm name contains. [optional].
// * `name` (string): Farm name. [optional].
// * `node_available_for` (u64): Twin ID of user for whom there is at least one node that is available to be deployed to in the farm. [optional].
// * `node_certified` (bool): True for farms who have at least one certified node. [optional].
// * `node_free_hru` (u64): Min free reservable hru for at least a single node that belongs to the farm, in bytes. [optional].
// * `node_free_mru` (u64): Min free reservable mru for at least a single node that belongs to the farm, in bytes. [optional].
// * `node_free_sru` (u64): Min free reservable sru for at least a single node that belongs to the farm, in bytes. [optional].
// * `node_has_gpu` (bool): True for farms who have at least one node with a GPU
// * `node_rented_by` (u64): Twin ID of user who has at least one rented node in the farm
// * `node_status` (string): Node status for at least a single node that belongs to the farm
// * `page` (u64): Page number. [optional].
// * `pricing_policy_id` (u64): Pricing policy id. [optional].
// * `randomize` (bool): [optional].
// * `ret_count` (bool): Set farms' count on headers based on filter. [optional].
// * `size` (u64): Max result per page. [optional].
// * `stellar_address` (string): Farm stellar_address. [optional].
// * `total_ips` (u64): Min number of total ips in the farm. [optional].
// * `twin_id` (u64): Twin id associated with the farm. [optional].
// * `version` (u64): Farm version. [optional].
//
// returns: `[]Farm` or `Error`.
pub fn (mut c GridProxyClient) get_farms(params FarmFilter) ![]Farm {
// needed to allow to use threads
mut http_client := c.http_client
params_map := params.to_map()
res := http_client.send(prefix: 'farms/', params: params_map) or {
return error_with_code('http client error: ${err.msg()}', err_http_client)
}
if !res.is_ok() {
return error_with_code(res.data, res.code)
}
if res.data == '' {
return error_with_code('empty response', err_invalid_resp)
}
farms := json.decode([]Farm, res.data) or {
return error_with_code('error to get jsonstr for farm list data, json decode: farm filter: ${params_map}, data: ${res.data}',
err_json_parse)
}
return farms
}
// is_pingable checks if API server is reachable and responding.
//
// returns: bool, `true` if API server is reachable and responding, `false` otherwise
pub fn (mut c GridProxyClient) is_pingable() !bool {
mut http_client := c.http_client
res := http_client.send(prefix: 'ping/') or { return false }
if !res.is_ok() {
return false
}
health_map := json.decode(map[string]string, res.data) or { return false }
if health_map['ping'] != 'pong' {
return false
}
return true
}
// Iterators have the next() method, which returns the next page of the objects.
// to be used in a loop to get all available results, or to lazely traverse pages till a specific condition is met.
// get_nodes_iterator creates an iterator through node pages with custom filter
fn (mut c GridProxyClient) get_nodes_iterator(filter NodeFilter) NodeIterator {
return NodeIterator{filter, c.get_nodes}
}
// get_gateways_iterator creates an iterator through gateway pages with custom filter
fn (mut c GridProxyClient) get_gateways_iterator(filter NodeFilter) NodeIterator {
return NodeIterator{filter, c.get_gateways}
}
// get_farms_iterator creates an iterator through farms pages with custom filter
fn (mut c GridProxyClient) get_farms_iterator(filter FarmFilter) FarmIterator {
return FarmIterator{filter, c.get_farms}
}
// get_twins_iterator creates an iterator through twin pages with custom filter
fn (mut c GridProxyClient) get_twins_iterator(filter TwinFilter) TwinIterator {
return TwinIterator{filter, c.get_twins}
}
// get_contracts_iterator creates an iterator through contracts pages with custom filter
fn (mut c GridProxyClient) get_contracts_iterator(filter ContractFilter) ContractIterator {
return ContractIterator{filter, c.get_contracts}
}