Merge pull request #33 from freeflowuniverse/development_runpod
Development runpod
This commit is contained in:
93
examples/develop/runpod/runpod_example.vsh
Executable file
93
examples/develop/runpod/runpod_example.vsh
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env -S v -n -w -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||||
|
|
||||||
|
// import freeflowuniverse.herolib.core.base
|
||||||
|
import freeflowuniverse.herolib.clients.runpod
|
||||||
|
import json
|
||||||
|
import x.json2
|
||||||
|
|
||||||
|
// Create client with direct API key
|
||||||
|
// This uses RUNPOD_API_KEY from environment
|
||||||
|
mut rp := runpod.get()!
|
||||||
|
|
||||||
|
// Create a new on demand pod
|
||||||
|
on_demand_pod_response := rp.create_on_demand_pod(
|
||||||
|
name: 'RunPod Tensorflow'
|
||||||
|
image_name: 'runpod/tensorflow'
|
||||||
|
cloud_type: 'ALL'
|
||||||
|
gpu_count: 1
|
||||||
|
volume_in_gb: 5
|
||||||
|
container_disk_in_gb: 5
|
||||||
|
min_memory_in_gb: 4
|
||||||
|
min_vcpu_count: 1
|
||||||
|
gpu_type_id: 'NVIDIA RTX A4000'
|
||||||
|
ports: '8888/http'
|
||||||
|
volume_mount_path: '/workspace'
|
||||||
|
env: [
|
||||||
|
runpod.EnvironmentVariableInput{
|
||||||
|
key: 'JUPYTER_PASSWORD'
|
||||||
|
value: 'rn51hunbpgtltcpac3ol'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)!
|
||||||
|
|
||||||
|
println('Created pod with ID: ${on_demand_pod_response.id}')
|
||||||
|
|
||||||
|
// create a spot pod
|
||||||
|
spot_pod_response := rp.create_spot_pod(
|
||||||
|
port: 1826
|
||||||
|
bid_per_gpu: 0.2
|
||||||
|
cloud_type: 'SECURE'
|
||||||
|
gpu_count: 1
|
||||||
|
volume_in_gb: 5
|
||||||
|
container_disk_in_gb: 5
|
||||||
|
min_vcpu_count: 1
|
||||||
|
min_memory_in_gb: 4
|
||||||
|
gpu_type_id: 'NVIDIA RTX A4000'
|
||||||
|
name: 'RunPod Pytorch'
|
||||||
|
image_name: 'runpod/pytorch'
|
||||||
|
docker_args: ''
|
||||||
|
ports: '8888/http'
|
||||||
|
volume_mount_path: '/workspace'
|
||||||
|
env: [
|
||||||
|
runpod.EnvironmentVariableInput{
|
||||||
|
key: 'JUPYTER_PASSWORD'
|
||||||
|
value: 'rn51hunbpgtltcpac3ol'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)!
|
||||||
|
println('Created spot pod with ID: ${spot_pod_response.id}')
|
||||||
|
|
||||||
|
// stop on-demand pod
|
||||||
|
stop_on_demand_pod := rp.stop_pod(
|
||||||
|
pod_id: '${on_demand_pod_response.id}'
|
||||||
|
)!
|
||||||
|
println('Stopped on-demand pod with ID: ${stop_on_demand_pod.id}')
|
||||||
|
|
||||||
|
// stop spot pod
|
||||||
|
stop_spot_pod := rp.stop_pod(
|
||||||
|
pod_id: '${spot_pod_response.id}'
|
||||||
|
)!
|
||||||
|
println('Stopped spot pod with ID: ${stop_spot_pod.id}')
|
||||||
|
|
||||||
|
// start on-demand pod
|
||||||
|
start_on_demand_pod := rp.start_on_demand_pod(pod_id: '${on_demand_pod_response.id}', gpu_count: 1)!
|
||||||
|
println('Started on demand pod with ID: ${on_demand_pod_response.id}')
|
||||||
|
|
||||||
|
// start spot pod
|
||||||
|
start_spot_pod := rp.start_spot_pod(
|
||||||
|
pod_id: '${spot_pod_response.id}'
|
||||||
|
gpu_count: 1
|
||||||
|
bid_per_gpu: 0.2
|
||||||
|
)!
|
||||||
|
println('Started spot pod with ID: ${spot_pod_response.id}')
|
||||||
|
|
||||||
|
get_pod := rp.get_pod(
|
||||||
|
pod_id: '${spot_pod_response.id}'
|
||||||
|
)!
|
||||||
|
println('Get pod result: ${get_pod}')
|
||||||
|
|
||||||
|
rp.terminate_pod(pod_id: '${spot_pod_response.id}')!
|
||||||
|
println('pod with id ${spot_pod_response.id} is terminated')
|
||||||
|
|
||||||
|
rp.terminate_pod(pod_id: '${on_demand_pod_response.id}')!
|
||||||
|
println('pod with id ${on_demand_pod_response.id} is terminated')
|
||||||
8
lib/clients/runpod/.heroscript
Normal file
8
lib/clients/runpod/.heroscript
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
!!hero_code.generate_client
|
||||||
|
name:'runpod'
|
||||||
|
classname:'RunPod'
|
||||||
|
singleton:0
|
||||||
|
default:1
|
||||||
|
hasconfig:1
|
||||||
|
reset:0
|
||||||
169
lib/clients/runpod/client.v
Normal file
169
lib/clients/runpod/client.v
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
module runpod
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pub struct EnvironmentVariableInput {
|
||||||
|
pub mut:
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents the nested machine structure in the response
|
||||||
|
pub struct Machine {
|
||||||
|
pub:
|
||||||
|
pod_host_id string @[json: 'podHostId']
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response structure for the mutation
|
||||||
|
pub struct PodResult {
|
||||||
|
pub:
|
||||||
|
id string @[json: 'id']
|
||||||
|
image_name string @[json: 'imageName']
|
||||||
|
env []string @[json: 'env']
|
||||||
|
machine_id int @[json: 'machineId']
|
||||||
|
machine Machine @[json: 'machine']
|
||||||
|
desired_status string @[json: 'desiredStatus']
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input structure for the mutation
|
||||||
|
@[params]
|
||||||
|
pub struct PodFindAndDeployOnDemandRequest {
|
||||||
|
pub mut:
|
||||||
|
cloud_type string @[json: 'cloudType']
|
||||||
|
gpu_count int @[json: 'gpuCount']
|
||||||
|
volume_in_gb int @[json: 'volumeInGb']
|
||||||
|
container_disk_in_gb int @[json: 'containerDiskInGb']
|
||||||
|
min_vcpu_count int @[json: 'minVcpuCount']
|
||||||
|
min_memory_in_gb int @[json: 'minMemoryInGb']
|
||||||
|
gpu_type_id string @[json: 'gpuTypeId']
|
||||||
|
name string @[json: 'name']
|
||||||
|
image_name string @[json: 'imageName']
|
||||||
|
docker_args string @[json: 'dockerArgs']
|
||||||
|
ports string @[json: 'ports']
|
||||||
|
volume_mount_path string @[json: 'volumeMountPath']
|
||||||
|
env []EnvironmentVariableInput @[json: 'env']
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (p PodFindAndDeployOnDemandRequest) json_str() string {
|
||||||
|
return json.encode(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create On-Demand Pod
|
||||||
|
pub fn (mut rp RunPod) create_on_demand_pod(input PodFindAndDeployOnDemandRequest) !PodResult {
|
||||||
|
return rp.create_on_demand_pod_request(input)!
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct PodRentInterruptableInput {
|
||||||
|
pub mut:
|
||||||
|
port int @[json: 'port']
|
||||||
|
network_volume_id string @[json: 'networkVolumeId'; omitempty]
|
||||||
|
start_jupyter bool @[json: 'startJupyter']
|
||||||
|
start_ssh bool @[json: 'startSsh']
|
||||||
|
bid_per_gpu f32 @[json: 'bidPerGpu']
|
||||||
|
cloud_type string @[json: 'cloudType']
|
||||||
|
container_disk_in_gb int @[json: 'containerDiskInGb']
|
||||||
|
country_code string @[json: 'countryCode'; omitempty]
|
||||||
|
docker_args string @[json: 'dockerArgs'; omitempty]
|
||||||
|
env []EnvironmentVariableInput @[json: 'env']
|
||||||
|
gpu_count int @[json: 'gpuCount']
|
||||||
|
gpu_type_id string @[json: 'gpuTypeId'; omitempty]
|
||||||
|
image_name string @[json: 'imageName'; omitempty]
|
||||||
|
min_disk int @[json: 'minDisk']
|
||||||
|
min_download int @[json: 'minDownload']
|
||||||
|
min_memory_in_gb int @[json: 'minMemoryInGb']
|
||||||
|
min_upload int @[json: 'minUpload']
|
||||||
|
min_vcpu_count int @[json: 'minVcpuCount']
|
||||||
|
name string @[json: 'name'; omitempty]
|
||||||
|
ports string @[json: 'ports'; omitempty]
|
||||||
|
stop_after string @[json: 'stopAfter'; omitempty]
|
||||||
|
support_public_ip bool @[json: 'supportPublicIp']
|
||||||
|
template_id string @[json: 'templateId'; omitempty]
|
||||||
|
terminate_after string @[json: 'terminateAfter'; omitempty]
|
||||||
|
volume_in_gb int @[json: 'volumeInGb']
|
||||||
|
volume_key string @[json: 'volumeKey'; omitempty]
|
||||||
|
volume_mount_path string @[json: 'volumeMountPath'; omitempty]
|
||||||
|
data_center_id string @[json: 'dataCenterId'; omitempty]
|
||||||
|
cuda_version string @[json: 'cudeVersion'; omitempty]
|
||||||
|
allowed_cuda_versions []string @[json: 'allowedCudaVersions']
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (p PodRentInterruptableInput) json_str() string {
|
||||||
|
return json.encode(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Spot Pod
|
||||||
|
pub fn (mut rp RunPod) create_spot_pod(input PodRentInterruptableInput) !PodResult {
|
||||||
|
return rp.create_spot_pod_request(input)!
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct PodResumeInput {
|
||||||
|
pub mut:
|
||||||
|
pod_id string @[json: 'podId'; required]
|
||||||
|
gpu_count int @[json: 'gpuCount']
|
||||||
|
sync_machine bool @[json: 'syncMachine']
|
||||||
|
compute_type string @[json: 'computeType'; omitempty]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (p PodResumeInput) json_str() string {
|
||||||
|
return json.encode(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start On-Demand Pod
|
||||||
|
pub fn (mut rp RunPod) start_on_demand_pod(input PodResumeInput) !PodResult {
|
||||||
|
return rp.start_on_demand_pod_request(input)!
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct PodBidResumeInput {
|
||||||
|
pub mut:
|
||||||
|
pod_id string @[json: 'podId'; required]
|
||||||
|
gpu_count int @[json: 'gpuCount']
|
||||||
|
bid_per_gpu f32 @[json: 'bidPerGpu']
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (p PodBidResumeInput) json_str() string {
|
||||||
|
return json.encode(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start Spot Pod
|
||||||
|
pub fn (mut rp RunPod) start_spot_pod(input PodBidResumeInput) !PodResult {
|
||||||
|
return rp.start_spot_pod_request(input)!
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct PodStopInput {
|
||||||
|
pub:
|
||||||
|
pod_id string @[json: 'podId']
|
||||||
|
increment_version bool @[json: 'incrementVersion']
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (p PodStopInput) json_str() string {
|
||||||
|
return json.encode(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop Pod
|
||||||
|
pub fn (mut rp RunPod) stop_pod(input PodStopInput) !PodResult {
|
||||||
|
return rp.stop_pod_request(input)!
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct PodTerminateInput {
|
||||||
|
pub:
|
||||||
|
pod_id string @[json: 'podId']
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut rp RunPod) terminate_pod(input PodTerminateInput) ! {
|
||||||
|
rp.terminate_pod_request(input)!
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct PodFilter {
|
||||||
|
pub:
|
||||||
|
pod_id string @[json: 'podId'; required]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut rp RunPod) get_pod(input PodFilter) !PodResult {
|
||||||
|
return rp.get_pod_request(input)!
|
||||||
|
}
|
||||||
42
lib/clients/runpod/readme.md
Normal file
42
lib/clients/runpod/readme.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# runpod
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To get started
|
||||||
|
|
||||||
|
```vlang
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import freeflowuniverse.crystallib.clients. runpod
|
||||||
|
|
||||||
|
mut client:= runpod.get()!
|
||||||
|
|
||||||
|
client...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## example heroscript
|
||||||
|
|
||||||
|
|
||||||
|
```hero
|
||||||
|
!!runpod.configure
|
||||||
|
secret: '...'
|
||||||
|
host: 'localhost'
|
||||||
|
port: 8888
|
||||||
|
```
|
||||||
|
|
||||||
|
**RunPod API Example**
|
||||||
|
|
||||||
|
This script demonstrates creating, stopping, starting, and terminating RunPod pods using the RunPod API. It creates both on-demand and spot pods.
|
||||||
|
|
||||||
|
**Requirements**
|
||||||
|
|
||||||
|
* Environment variable `RUNPOD_API_KEY` set with your RunPod API key
|
||||||
|
|
||||||
|
**How to Run**
|
||||||
|
|
||||||
|
- Find out our example in: examples/develop/runpod/runpod_example.vsh
|
||||||
102
lib/clients/runpod/runpod_factory_.v
Normal file
102
lib/clients/runpod/runpod_factory_.v
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
module runpod
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.core.base
|
||||||
|
import freeflowuniverse.herolib.core.playbook
|
||||||
|
import freeflowuniverse.herolib.ui.console
|
||||||
|
|
||||||
|
__global (
|
||||||
|
runpod_global map[string]&RunPod
|
||||||
|
runpod_default string
|
||||||
|
)
|
||||||
|
|
||||||
|
/////////FACTORY
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct ArgsGet {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn args_get(args_ ArgsGet) ArgsGet {
|
||||||
|
mut args := args_
|
||||||
|
if args.name == '' {
|
||||||
|
args.name = runpod_default
|
||||||
|
}
|
||||||
|
if args.name == '' {
|
||||||
|
args.name = 'default'
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(args_ ArgsGet) !&RunPod {
|
||||||
|
mut args := args_get(args_)
|
||||||
|
if args.name !in runpod_global {
|
||||||
|
if args.name == 'default' {
|
||||||
|
if !config_exists(args) {
|
||||||
|
if default {
|
||||||
|
config_save(args)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config_load(args)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return runpod_global[args.name] or {
|
||||||
|
println(runpod_global)
|
||||||
|
panic('could not get config for runpod with name:${args.name}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_exists(args_ ArgsGet) bool {
|
||||||
|
mut args := args_get(args_)
|
||||||
|
mut context := base.context() or { panic('bug') }
|
||||||
|
return context.hero_config_exists('runpod', args.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_load(args_ ArgsGet) ! {
|
||||||
|
mut args := args_get(args_)
|
||||||
|
mut context := base.context()!
|
||||||
|
mut heroscript := context.hero_config_get('runpod', args.name)!
|
||||||
|
play(heroscript: heroscript)!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_save(args_ ArgsGet) ! {
|
||||||
|
mut args := args_get(args_)
|
||||||
|
mut context := base.context()!
|
||||||
|
context.hero_config_set('runpod', args.name, heroscript_default()!)!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(o RunPod) ! {
|
||||||
|
mut o2 := obj_init(o)!
|
||||||
|
runpod_global[o.name] = &o2
|
||||||
|
runpod_default = o.name
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct PlayArgs {
|
||||||
|
pub mut:
|
||||||
|
heroscript string // if filled in then plbook will be made out of it
|
||||||
|
plbook ?playbook.PlayBook
|
||||||
|
reset bool
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play(args_ PlayArgs) ! {
|
||||||
|
mut args := args_
|
||||||
|
|
||||||
|
if args.heroscript == '' {
|
||||||
|
args.heroscript = heroscript_default()!
|
||||||
|
}
|
||||||
|
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
|
||||||
|
|
||||||
|
mut install_actions := plbook.find(filter: 'runpod.configure')!
|
||||||
|
if install_actions.len > 0 {
|
||||||
|
for install_action in install_actions {
|
||||||
|
mut p := install_action.params
|
||||||
|
cfg_play(p)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch instance to be used for runpod
|
||||||
|
pub fn switch(name string) {
|
||||||
|
runpod_default = name
|
||||||
|
}
|
||||||
354
lib/clients/runpod/runpod_http.v
Normal file
354
lib/clients/runpod/runpod_http.v
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
module runpod
|
||||||
|
|
||||||
|
import x.json2
|
||||||
|
import net.http { Method }
|
||||||
|
import freeflowuniverse.herolib.core.httpconnection
|
||||||
|
|
||||||
|
// GraphQL response wrapper
|
||||||
|
struct GqlResponse[T] {
|
||||||
|
pub mut:
|
||||||
|
data map[string]T
|
||||||
|
errors []map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// #### Internally method doing a network call to create a new on-demand pod.
|
||||||
|
// - Build the required query based pn the input sent by the user and send the request.
|
||||||
|
// - Decode the response received from the API into two objects `Data` and `Error`.
|
||||||
|
// - The data field should contains the pod details same as `PodResult` struct.
|
||||||
|
// - The error field should contain the error message.
|
||||||
|
fn (mut rp RunPod) create_on_demand_pod_request(input PodFindAndDeployOnDemandRequest) !PodResult {
|
||||||
|
mut fields := []Field{}
|
||||||
|
mut machine_fields := []Field{}
|
||||||
|
mut output_fields := []Field{}
|
||||||
|
mut builder := QueryBuilder{}
|
||||||
|
|
||||||
|
machine_fields << new_field(name: 'podHostId')
|
||||||
|
output_fields << new_field(name: 'id')
|
||||||
|
output_fields << new_field(name: 'imageName')
|
||||||
|
output_fields << new_field(name: 'env')
|
||||||
|
output_fields << new_field(name: 'machineId')
|
||||||
|
output_fields << new_field(name: 'desiredStatus')
|
||||||
|
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||||
|
fields << new_field(
|
||||||
|
name: 'podFindAndDeployOnDemand'
|
||||||
|
arguments: {
|
||||||
|
'input': '\$arguments'
|
||||||
|
}
|
||||||
|
sub_fields: output_fields
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.add_operation(
|
||||||
|
operation: .mutation
|
||||||
|
fields: fields
|
||||||
|
variables: {
|
||||||
|
'\$arguments': 'PodFindAndDeployOnDemandInput'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
mut variables := {
|
||||||
|
'arguments': json2.Any(type_to_map(input)!)
|
||||||
|
}
|
||||||
|
query := builder.build_query(variables: variables)
|
||||||
|
|
||||||
|
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||||
|
return response.data['podFindAndDeployOnDemand'] or {
|
||||||
|
return error('Could not find "podFindAndDeployOnDemand" in response data: ${response.data}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #### Internally method doing a network call to create a new spot pod.
|
||||||
|
// - Build the required query based pn the input sent by the user and send the request.
|
||||||
|
// - Decode the response received from the API into two objects `Data` and `Error`.
|
||||||
|
// - The data field should contains the pod details same as `PodResult` struct.
|
||||||
|
// - The error field should contain the error message.
|
||||||
|
fn (mut rp RunPod) create_spot_pod_request(input PodRentInterruptableInput) !PodResult {
|
||||||
|
mut fields := []Field{}
|
||||||
|
mut machine_fields := []Field{}
|
||||||
|
mut output_fields := []Field{}
|
||||||
|
mut builder := QueryBuilder{}
|
||||||
|
|
||||||
|
machine_fields << new_field(name: 'podHostId')
|
||||||
|
output_fields << new_field(name: 'id')
|
||||||
|
output_fields << new_field(name: 'imageName')
|
||||||
|
output_fields << new_field(name: 'env')
|
||||||
|
output_fields << new_field(name: 'machineId')
|
||||||
|
output_fields << new_field(name: 'desiredStatus')
|
||||||
|
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||||
|
fields << new_field(
|
||||||
|
name: 'podRentInterruptable'
|
||||||
|
arguments: {
|
||||||
|
'input': '\$arguments'
|
||||||
|
}
|
||||||
|
sub_fields: output_fields
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.add_operation(
|
||||||
|
operation: .mutation
|
||||||
|
fields: fields
|
||||||
|
variables: {
|
||||||
|
'\$arguments': 'PodRentInterruptableInput!'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
mut variables := {
|
||||||
|
'arguments': json2.Any(type_to_map(input)!)
|
||||||
|
}
|
||||||
|
query := builder.build_query(variables: variables)
|
||||||
|
|
||||||
|
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||||
|
return response.data['podRentInterruptable'] or {
|
||||||
|
return error('Could not find "podRentInterruptable" in response data: ${response.data}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #### Internally method doing a network call to start on demand pod.
|
||||||
|
// - Build the required query based pn the input sent by the user and send the request.
|
||||||
|
// - Decode the response received from the API into two objects `Data` and `Error`.
|
||||||
|
// - The data field should contains the pod details same as `PodResult` struct.
|
||||||
|
// - The error field should contain the error message.
|
||||||
|
fn (mut rp RunPod) start_on_demand_pod_request(input PodResumeInput) !PodResult {
|
||||||
|
mut fields := []Field{}
|
||||||
|
mut machine_fields := []Field{}
|
||||||
|
mut output_fields := []Field{}
|
||||||
|
mut builder := QueryBuilder{}
|
||||||
|
|
||||||
|
machine_fields << new_field(name: 'podHostId')
|
||||||
|
output_fields << new_field(name: 'id')
|
||||||
|
output_fields << new_field(name: 'imageName')
|
||||||
|
output_fields << new_field(name: 'env')
|
||||||
|
output_fields << new_field(name: 'machineId')
|
||||||
|
output_fields << new_field(name: 'desiredStatus')
|
||||||
|
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||||
|
fields << new_field(
|
||||||
|
name: 'podResume'
|
||||||
|
arguments: {
|
||||||
|
'input': '\$arguments'
|
||||||
|
}
|
||||||
|
sub_fields: output_fields
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.add_operation(
|
||||||
|
operation: .mutation
|
||||||
|
fields: fields
|
||||||
|
variables: {
|
||||||
|
'\$arguments': 'PodResumeInput!'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
mut variables := {
|
||||||
|
'arguments': json2.Any(type_to_map(input)!)
|
||||||
|
}
|
||||||
|
query := builder.build_query(variables: variables)
|
||||||
|
|
||||||
|
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||||
|
return response.data['podResume'] or {
|
||||||
|
return error('Could not find "podResume" in response data: ${response.data}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #### Internally method doing a network call to start spot pod.
|
||||||
|
// - Build the required query based pn the input sent by the user and send the request.
|
||||||
|
// - Decode the response received from the API into two objects `Data` and `Error`.
|
||||||
|
// - The data field should contains the pod details same as `PodResult` struct.
|
||||||
|
// - The error field should contain the error message.
|
||||||
|
fn (mut rp RunPod) start_spot_pod_request(input PodBidResumeInput) !PodResult {
|
||||||
|
mut fields := []Field{}
|
||||||
|
mut machine_fields := []Field{}
|
||||||
|
mut output_fields := []Field{}
|
||||||
|
mut builder := QueryBuilder{}
|
||||||
|
|
||||||
|
machine_fields << new_field(name: 'podHostId')
|
||||||
|
output_fields << new_field(name: 'id')
|
||||||
|
output_fields << new_field(name: 'imageName')
|
||||||
|
output_fields << new_field(name: 'env')
|
||||||
|
output_fields << new_field(name: 'machineId')
|
||||||
|
output_fields << new_field(name: 'desiredStatus')
|
||||||
|
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||||
|
fields << new_field(
|
||||||
|
name: 'podBidResume'
|
||||||
|
arguments: {
|
||||||
|
'input': '\$arguments'
|
||||||
|
}
|
||||||
|
sub_fields: output_fields
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.add_operation(
|
||||||
|
operation: .mutation
|
||||||
|
fields: fields
|
||||||
|
variables: {
|
||||||
|
'\$arguments': 'PodBidResumeInput!'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
mut variables := {
|
||||||
|
'arguments': json2.Any(type_to_map(input)!)
|
||||||
|
}
|
||||||
|
query := builder.build_query(variables: variables)
|
||||||
|
|
||||||
|
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||||
|
return response.data['podBidResume'] or {
|
||||||
|
return error('Could not find "podBidResume" in response data: ${response.data}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #### Internally method doing a network call to stop a pod.
|
||||||
|
// - Build the required query based pn the input sent by the user and send the request.
|
||||||
|
// - Decode the response received from the API into two objects `Data` and `Error`.
|
||||||
|
// - The data field should contains the pod details same as `PodResult` struct.
|
||||||
|
// - The error field should contain the error message.
|
||||||
|
fn (mut rp RunPod) stop_pod_request(input PodStopInput) !PodResult {
|
||||||
|
mut fields := []Field{}
|
||||||
|
mut machine_fields := []Field{}
|
||||||
|
mut output_fields := []Field{}
|
||||||
|
mut builder := QueryBuilder{}
|
||||||
|
|
||||||
|
machine_fields << new_field(name: 'podHostId')
|
||||||
|
output_fields << new_field(name: 'id')
|
||||||
|
output_fields << new_field(name: 'imageName')
|
||||||
|
output_fields << new_field(name: 'env')
|
||||||
|
output_fields << new_field(name: 'machineId')
|
||||||
|
output_fields << new_field(name: 'desiredStatus')
|
||||||
|
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||||
|
fields << new_field(
|
||||||
|
name: 'podStop'
|
||||||
|
arguments: {
|
||||||
|
'input': '\$arguments'
|
||||||
|
}
|
||||||
|
sub_fields: output_fields
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.add_operation(
|
||||||
|
operation: .mutation
|
||||||
|
fields: fields
|
||||||
|
variables: {
|
||||||
|
'\$arguments': 'PodStopInput!'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
mut variables := {
|
||||||
|
'arguments': json2.Any(type_to_map(input)!)
|
||||||
|
}
|
||||||
|
query := builder.build_query(variables: variables)
|
||||||
|
|
||||||
|
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||||
|
return response.data['podStop'] or {
|
||||||
|
return error('Could not find "podStop" in response data: ${response.data}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut rp RunPod) terminate_pod_request(input PodTerminateInput) ! {
|
||||||
|
mut fields := []Field{}
|
||||||
|
mut builder := QueryBuilder{}
|
||||||
|
|
||||||
|
fields << new_field(
|
||||||
|
name: 'podTerminate'
|
||||||
|
arguments: {
|
||||||
|
'input': '\$arguments'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.add_operation(
|
||||||
|
operation: .mutation
|
||||||
|
fields: fields
|
||||||
|
variables: {
|
||||||
|
'\$arguments': 'PodTerminateInput!'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
mut variables := {
|
||||||
|
'arguments': json2.Any(type_to_map(input)!)
|
||||||
|
}
|
||||||
|
query := builder.build_query(variables: variables)
|
||||||
|
|
||||||
|
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||||
|
_ := response.data['podTerminate'] or {
|
||||||
|
return error('Could not find "podTerminate" in response data: ${response.data}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut rp RunPod) get_pod_request(input PodFilter) !PodResult {
|
||||||
|
mut fields := []Field{}
|
||||||
|
mut machine_fields := []Field{}
|
||||||
|
mut output_fields := []Field{}
|
||||||
|
mut builder := QueryBuilder{}
|
||||||
|
|
||||||
|
machine_fields << new_field(name: 'podHostId')
|
||||||
|
output_fields << new_field(name: 'id')
|
||||||
|
output_fields << new_field(name: 'imageName')
|
||||||
|
output_fields << new_field(name: 'env')
|
||||||
|
output_fields << new_field(name: 'machineId')
|
||||||
|
output_fields << new_field(name: 'desiredStatus')
|
||||||
|
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||||
|
fields << new_field(
|
||||||
|
name: 'pod'
|
||||||
|
arguments: {
|
||||||
|
'input': '\$arguments'
|
||||||
|
}
|
||||||
|
sub_fields: output_fields
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.add_operation(
|
||||||
|
operation: .query
|
||||||
|
fields: fields
|
||||||
|
variables: {
|
||||||
|
'\$arguments': 'PodFilter'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
mut variables := {
|
||||||
|
'arguments': json2.Any(type_to_map(input)!)
|
||||||
|
}
|
||||||
|
query := builder.build_query(variables: variables)
|
||||||
|
|
||||||
|
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||||
|
return response.data['pod'] or {
|
||||||
|
return error('Could not find "pod" in response data: ${response.data}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents the main structure for interacting with the RunPod API.
|
||||||
|
// Provides utilities to manage HTTP connections and perform GraphQL queries.
|
||||||
|
fn (mut rp RunPod) httpclient() !&httpconnection.HTTPConnection {
|
||||||
|
mut http_conn := httpconnection.new(
|
||||||
|
name: 'runpod_vclient_${rp.name}'
|
||||||
|
url: rp.base_url
|
||||||
|
cache: true
|
||||||
|
retry: 3
|
||||||
|
)!
|
||||||
|
http_conn.default_header.add(.authorization, 'Bearer ${rp.api_key}')
|
||||||
|
return http_conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends an HTTP request to the RunPod API with the specified method, path, and data.
|
||||||
|
fn (mut rp RunPod) make_request[T](method Method, path string, data string) !T {
|
||||||
|
mut request := httpconnection.Request{
|
||||||
|
prefix: path
|
||||||
|
data: data
|
||||||
|
debug: true
|
||||||
|
dataformat: .json
|
||||||
|
}
|
||||||
|
|
||||||
|
mut http_client := rp.httpclient()!
|
||||||
|
mut response := T{}
|
||||||
|
|
||||||
|
match method {
|
||||||
|
.get {
|
||||||
|
request.method = .get
|
||||||
|
response = http_client.get_json_generic[T](request)!
|
||||||
|
}
|
||||||
|
.post {
|
||||||
|
request.method = .post
|
||||||
|
response = http_client.post_json_generic[T](request)!
|
||||||
|
}
|
||||||
|
.put {
|
||||||
|
request.method = .put
|
||||||
|
response = http_client.put_json_generic[T](request)!
|
||||||
|
}
|
||||||
|
.delete {
|
||||||
|
request.method = .delete
|
||||||
|
response = http_client.delete_json_generic[T](request)!
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return error('unsupported method: ${method}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.errors.len > 0 {
|
||||||
|
return error('Error while sending the request due to: ${response.errors[0]['message']}')
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
43
lib/clients/runpod/runpod_model.v
Normal file
43
lib/clients/runpod/runpod_model.v
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
module runpod
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.data.paramsparser
|
||||||
|
import os
|
||||||
|
|
||||||
|
pub const version = '1.14.3'
|
||||||
|
const singleton = false
|
||||||
|
const default = true
|
||||||
|
|
||||||
|
// heroscript_default returns the default heroscript configuration for RunPod
|
||||||
|
pub fn heroscript_default() !string {
|
||||||
|
return "
|
||||||
|
!!runpod.configure
|
||||||
|
name:'default'
|
||||||
|
api_key:'${os.getenv('RUNPOD_API_KEY')}'
|
||||||
|
base_url:'https://api.runpod.io/'
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunPod represents a RunPod client instance
|
||||||
|
@[heap]
|
||||||
|
pub struct RunPod {
|
||||||
|
pub mut:
|
||||||
|
name string = 'default'
|
||||||
|
api_key string
|
||||||
|
base_url string = 'https://api.runpod.io/'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cfg_play(p paramsparser.Params) ! {
|
||||||
|
// THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above
|
||||||
|
mut mycfg := RunPod{
|
||||||
|
name: p.get_default('name', 'default')!
|
||||||
|
api_key: p.get_default('api_key', os.getenv('RUNPOD_API_KEY'))!
|
||||||
|
base_url: p.get_default('base_url', 'https://api.runpod.io/')!
|
||||||
|
}
|
||||||
|
set(mycfg)!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn obj_init(obj_ RunPod) !RunPod {
|
||||||
|
// never call get here, only thing we can do here is work on object itself
|
||||||
|
mut obj := obj_
|
||||||
|
return obj
|
||||||
|
}
|
||||||
118
lib/clients/runpod/utils.v
Normal file
118
lib/clients/runpod/utils.v
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
module runpod
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.core.httpconnection
|
||||||
|
import x.json2
|
||||||
|
|
||||||
|
enum OperationType {
|
||||||
|
query
|
||||||
|
mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QueryBuilder {
|
||||||
|
pub mut:
|
||||||
|
operation OperationType
|
||||||
|
fields []Field
|
||||||
|
variables map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Field {
|
||||||
|
name string
|
||||||
|
arguments map[string]string
|
||||||
|
sub_fields []Field
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct NewFieldArgs {
|
||||||
|
pub:
|
||||||
|
name string
|
||||||
|
arguments map[string]string
|
||||||
|
sub_fields []Field
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_field(args NewFieldArgs) Field {
|
||||||
|
return Field{
|
||||||
|
name: args.name
|
||||||
|
arguments: args.arguments
|
||||||
|
sub_fields: args.sub_fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_arguments(args map[string]string) string {
|
||||||
|
if args.len == 0 {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
mut sb := ''
|
||||||
|
sb += '('
|
||||||
|
|
||||||
|
for key, value in args {
|
||||||
|
if value.len == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sb += '${key}: ${value}, '
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.trim_right(', ') + ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_fields(fields []Field) string {
|
||||||
|
mut sb := ' { '
|
||||||
|
for field in fields {
|
||||||
|
sb += field.name
|
||||||
|
if field.arguments.len > 0 {
|
||||||
|
sb += build_arguments(field.arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.sub_fields.len > 0 {
|
||||||
|
sb += build_fields(field.sub_fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
sb += ' '
|
||||||
|
}
|
||||||
|
sb += ' } '
|
||||||
|
return sb
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct AddOperationArgs {
|
||||||
|
pub:
|
||||||
|
operation OperationType
|
||||||
|
fields []Field
|
||||||
|
variables map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut q QueryBuilder) add_operation(args AddOperationArgs) {
|
||||||
|
q.operation = args.operation
|
||||||
|
q.fields = args.fields
|
||||||
|
q.variables = args.variables.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct BuildQueryArgs {
|
||||||
|
pub:
|
||||||
|
variables map[string]json2.Any
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (q QueryBuilder) build_query(args BuildQueryArgs) string {
|
||||||
|
mut query := ''
|
||||||
|
query += '${q.operation}' + ' myOperation'
|
||||||
|
|
||||||
|
if q.variables.len > 0 {
|
||||||
|
query += build_arguments(q.variables)
|
||||||
|
}
|
||||||
|
|
||||||
|
query += build_fields(q.fields)
|
||||||
|
|
||||||
|
mut q_map := {
|
||||||
|
'query': json2.Any(query)
|
||||||
|
'variables': json2.Any(args.variables)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json2.encode(q_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_to_map[T](t T) !map[string]json2.Any {
|
||||||
|
encoded_input := json2.encode(t)
|
||||||
|
return json2.raw_decode(encoded_input)!.as_map()
|
||||||
|
}
|
||||||
@@ -12,6 +12,20 @@ pub fn (mut h HTTPConnection) post_json_generic[T](req Request) !T {
|
|||||||
return json.decode(T, data) or { return error("couldn't decode json for ${req} for ${data}") }
|
return json.decode(T, data) or { return error("couldn't decode json for ${req} for ${data}") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
pub fn (mut h HTTPConnection) put_json_generic[T](req Request) !T {
|
||||||
|
// data := h.put_json_str(req)!
|
||||||
|
// return json.decode(T, data) or { return error("couldn't decode json for ${req} for ${data}") }
|
||||||
|
return T{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
pub fn (mut h HTTPConnection) delete_json_generic[T](req Request) !T {
|
||||||
|
// data := h.delete_json_str(req)!
|
||||||
|
// return json.decode(T, data) or { return error("couldn't decode json for ${req} for ${data}") }
|
||||||
|
return T{}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn (mut h HTTPConnection) get_json_list_generic[T](req Request) ![]T {
|
pub fn (mut h HTTPConnection) get_json_list_generic[T](req Request) ![]T {
|
||||||
mut r := []T{}
|
mut r := []T{}
|
||||||
for item in h.get_json_list(req)! {
|
for item in h.get_json_list(req)! {
|
||||||
|
|||||||
Reference in New Issue
Block a user