From 47bf18c02478baa92719001df3fd164710659e72 Mon Sep 17 00:00:00 2001 From: deadjakk Date: Thu, 9 Nov 2023 00:50:21 -0700 Subject: [PATCH] added vast.ai integration code --- src/inc/defines/config.php | 22 ++ src/inc/vastAiUtils.php | 254 ++++++++++++++++++ src/install/hashtopolis.sql | 10 +- src/templates/agents/vastai.template.html | 82 ++++++ .../agents/vastai_rent.template.html | 109 ++++++++ .../agents/vastai_search.template.html | 78 ++++++ src/templates/config.template.html | 3 +- src/templates/struct/menu.template.html | 6 + .../struct/vastaifilter.template.html | 47 ++++ src/vastaiAgents.php | 117 ++++++++ src/vastaiAgentsRent.php | 142 ++++++++++ src/vastaiAgentsSearch.php | 133 +++++++++ 12 files changed, 1000 insertions(+), 3 deletions(-) mode change 100644 => 100755 src/inc/defines/config.php create mode 100755 src/inc/vastAiUtils.php mode change 100644 => 100755 src/install/hashtopolis.sql create mode 100755 src/templates/agents/vastai.template.html create mode 100755 src/templates/agents/vastai_rent.template.html create mode 100755 src/templates/agents/vastai_search.template.html create mode 100644 src/templates/struct/vastaifilter.template.html create mode 100755 src/vastaiAgents.php create mode 100755 src/vastaiAgentsRent.php create mode 100755 src/vastaiAgentsSearch.php diff --git a/src/inc/defines/config.php b/src/inc/defines/config.php old mode 100644 new mode 100755 index b72d25672..b026dd11f --- a/src/inc/defines/config.php +++ b/src/inc/defines/config.php @@ -108,6 +108,12 @@ class DConfig { const NOTIFICATIONS_PROXY_PORT = "notificationsProxyPort"; const NOTIFICATIONS_PROXY_TYPE = "notificationsProxyType"; + // Section: VastAI + const VAST_AI_API_KEY = "vastAiApiKey"; + const VAST_IMAGE_URL = "vastImageUrl"; + const VAST_IMAGE_LOGIN = "vastImageLogin"; + const VAST_HASHTOPOLIS_BASE_URL = "vastHashtopolisBaseUrl"; + static function getConstants() { try { $oClass = new ReflectionClass(__CLASS__); @@ -272,6 +278,14 @@ public static function getConfigType($config) { return DConfigType::TICKBOX; case DConfig::HC_ERROR_IGNORE: return DConfigType::STRING_INPUT; + case DConfig::VAST_AI_API_KEY: + return DConfigType::STRING_INPUT; + case DConfig::VAST_IMAGE_URL: + return DConfigType::STRING_INPUT; + case DConfig::VAST_IMAGE_LOGIN: + return DConfigType::STRING_INPUT; + case DConfig::VAST_HASHTOPOLIS_BASE_URL: + return DConfigType::STRING_INPUT; } return DConfigType::STRING_INPUT; } @@ -406,6 +420,14 @@ public static function getConfigDescription($config) { return "Also send 'isComplete' for each task on the User API when listing all tasks (might affect performance)"; case DConfig::HC_ERROR_IGNORE: return "Ignore error messages from crackers which contain given strings (multiple values separated by comma)"; + case DConfig::VAST_AI_API_KEY: + return "Vast.ai API Key from vast.ai"; + case DConfig::VAST_IMAGE_URL: + return "Docker image url to be loaded by Vast.ai instances. Do not change this unless you know the image to be compatible"; + case DConfig::VAST_IMAGE_LOGIN: + return "Docker image login string. ex (without quotes): '-u bob -p 9d8df!fd89ufZ docker.io'. This is optional, but prevents the instances you rent from hitting the docker per-IP address rate limit. A hub.docker.com account is free to create here. The email/password option is recommended for ease of use."; + case DConfig::VAST_HASHTOPOLIS_BASE_URL: + return "The root URL of this hashtopolis server where the Vast.ai images should connect. Note: If using HTTPS, the certificate MUST be valid. The URL MUST also be accessible via the internet. ex: https://example.com:8443"; } return $config; } diff --git a/src/inc/vastAiUtils.php b/src/inc/vastAiUtils.php new file mode 100755 index 000000000..4706d2bcd --- /dev/null +++ b/src/inc/vastAiUtils.php @@ -0,0 +1,254 @@ +internalArray = $array; + } + + public function getUptime() { + if (! $this->getVal('start_date')) { + return null; + } + $start = (int) $this->getVal('start_date'); + $start = round($start, 0); + $current = time(); + $delta = $current - $start; + + $days = floor($delta / (60 * 60 * 24)); + $delta %= (60 * 60 * 24); + + $hours = floor($delta / (60 * 60)); + $delta %= (60 * 60); + + $minutes = floor($delta / 60); + $seconds = $delta % 60; + + $duration = ''; + + if ($days > 0) { + $duration .= $days . ' day' . ($days > 1 ? 's' : '') . ' '; + } + + $duration .= sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); + + return $duration; + } + + public function getFloatValOld($key, $precision) { + return round(floatval($this->internalArray[$key]), $precision); + } + + public function getStartingVoucher() { + return $this->getVal('extra_env')[1][1]; + } + + public function getFloatval($key, $precision) { + $number = $this->getVal($key); + $retNumber = (float) number_format($number,0,'.',''); + for ($n = 0; $n <= 8; $n++) { + if ($retNumber === 0.0){ + $retNumber = (double) number_format($number,$n,'.',''); + } else { + return number_format($number,$n+(max($precision-2,0)),'.',''); + } + } + return "<0.00000000"; + } + + public function getFloatVal100($key, $precision) { + return round(floatval($this->internalArray[$key])*100, $precision); + } + + public function getVal($key) { + if (array_key_exists($key, $this->internalArray)) { + return $this->internalArray[$key]; + } else { + return null; + } + } +} + +function vastAiDestroyInstance($apiKey, $id) { + $url = 'https://console.vast.ai/api/v0/instances/' . $id . '/?api_key=' . $apiKey; + $headers = array('Accept: application/json'); + $options = array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_CUSTOMREQUEST => "DELETE" + ); + + $ch = curl_init($url); + $options[CURLOPT_HTTPHEADER] = $headers; + curl_setopt_array($ch, $options); + + $response = curl_exec($ch); + curl_close($ch); + + if($response === false) { + return 'vastaiAgentSearch curl error: ' . curl_error($ch); + } + + return json_decode($response, true); +} + +function makeVastaiVoucher($id){ + $sub = substr(md5(rand()),0,10); + $voucher = $randomSubStr = 'vast' . $sub . $id; + AgentUtils::createVoucher($voucher); + return $voucher; +} + +// retrieves the instances that the account is currently renting +function getVastAiAgents($apiKey) { + $url = 'https://console.vast.ai/api/v0/instances?api_key=' . $apiKey; + $headers = array('Accept: application/json'); + $options = array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + ); + + $ch = curl_init($url); + $options[CURLOPT_HTTPHEADER] = $headers; + curl_setopt_array($ch, $options); + + $response = curl_exec($ch); + curl_close($ch); + + if($response === false) { + return'vastaiAgentSearch curl error: ' . curl_error($ch); + } + + return json_decode($response, true); +} + +// search current GPUs, this does not require an api key +// an example query input value would be +// $query = '{"verified": {"eq": true}, "external": {"eq": false}, "rentable": {"eq": true}, "compute_cap": {"gt": "600"}, "disk_space": {"gt": "10000"}, "order": [["score", "desc"]], "type": "on-demand"}'; +function searchGpus($query) { + $url = 'https://console.vast.ai/api/v0/bundles?q=' . urlencode($query); + $headers = array('Accept: application/json'); + $options = array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + ); + + $ch = curl_init($url); + $options[CURLOPT_HTTPHEADER] = $headers; + curl_setopt_array($ch, $options); + + $response = curl_exec($ch); + curl_close($ch); + + if($response === false) { + return'vastaiAgentSearch curl error: ' . curl_error($ch); + } + + return json_decode($response, true); +} + +function rentMachine($apiKey, $id, $imageUrl, $price, $disk, $voucher, $baseUrl, $imageLogin) { + // API endpoint + $url = 'https://console.vast.ai/api/v0/asks/' . $id . '/?api_key=' . $apiKey; + if ( $imageLogin === ''){ + $imageLogin = null; + } + + // Data to be sent + $data = array( + "client_id" => "me", + "image" => $imageUrl, + "env" => array( + "TZ" => "UTC", + "VOUCHER" => $voucher, + "HCATURL" => $baseUrl + ), + "disk" => (int) $disk, + "label" => "instance-" . $id, + "extra" => null, + "image_login" => $imageLogin, + "onstart" => "/root/hcat/run.sh", + "runtype" => "ssh", + "python_utf8" => false, + "lang_utf8" => false, + "use_jupyter_lab" => false, + "jupyter_dir" => null + ); + + // Initialize cURL session + $ch = curl_init(); + + // Set cURL options + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Accept: application/json', + 'Content-Type: application/json' + )); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + + // Execute the cURL request + $response = curl_exec($ch); + // REMOVE THIS LINE, for debugging + curl_close($ch); + + // Check for any errors + if ($response === false) { + return 'cURL error: ' . curl_error($ch); + } + + // Close cURL session + return json_decode($response, true); +} + +function doesVoucherExist($voucher){ + $vouchers = Factory::getRegVoucherFactory()->filter([]); + foreach ($vouchers as $voucher){ + $voucherValue = $voucher->getVoucher(); + if (str_contains($voucherValue, "vast.ai") == false){ + continue; + } + $index = strpos($voucherValue, "-"); + $voucherInstanceId = substr($voucherValue, $index); + if ($voucherInstanceId === $intsanceId){ + return $voucherValue; + } + } + return null; +} + + +// given an instanceId it goes through all vouchers +// containing the substr vast.ai and returns the +// voucher that contains the instanceId +function getVoucherForInstance($instanceId){ + $vouchers = Factory::getRegVoucherFactory()->filter([]); + foreach ($vouchers as $voucher){ + $voucherValue = $voucher->getVoucher(); + if (str_contains($voucherValue, "vast.ai") == false){ + continue; + } + $index = strpos($voucherValue, "-"); + $voucherInstanceId = substr($voucherValue, $index); + if ($voucherInstanceId === $intsanceId){ + return $voucherValue; + } + } + return null; +} + +function get_or($arr,$key,$default){ + if (!$arr[$key]){ + return $default; + } + return $arr[$key]; +} + +?> diff --git a/src/install/hashtopolis.sql b/src/install/hashtopolis.sql old mode 100644 new mode 100755 index 41badf626..65c57834d --- a/src/install/hashtopolis.sql +++ b/src/install/hashtopolis.sql @@ -170,7 +170,11 @@ INSERT INTO `Config` (`configId`, `configSectionId`, `item`, `value`) VALUES (74, 4, 'agentUtilThreshold1', '90'), (75, 4, 'agentUtilThreshold2', '75'), (76, 3, 'uApiSendTaskIsComplete', '0'), - (77, 1, 'hcErrorIgnore', 'DeviceGetFanSpeed'); + (77, 1, 'hcErrorIgnore', 'DeviceGetFanSpeed'), + (78, 8, 'vastAiApiKey', ''), + (79, 8, 'vastImageUrl', 'deadjakk/hcat-vast-dev:latest'), + (80, 8, 'vastImageLogin', ''), + (81, 8, 'vastHashtopolisBaseUrl', ''); CREATE TABLE `ConfigSection` ( `configSectionId` INT(11) NOT NULL, @@ -184,7 +188,9 @@ INSERT INTO `ConfigSection` (`configSectionId`, `sectionName`) VALUES (4, 'UI'), (5, 'Server'), (6, 'Multicast'), - (7, 'Notifications'); + (7, 'Notifications'), + (8, 'VastAI') +; CREATE TABLE `CrackerBinary` ( `crackerBinaryId` INT(11) NOT NULL, diff --git a/src/templates/agents/vastai.template.html b/src/templates/agents/vastai.template.html new file mode 100755 index 000000000..97aef2744 --- /dev/null +++ b/src/templates/agents/vastai.template.html @@ -0,0 +1,82 @@ +{%TEMPLATE->struct/head%} +{%TEMPLATE->struct/menu%} +

Current Vast.AI Instances

+{{IF [[vastApiKey]] === '' }}
Error: Missing Vast.ai API Key, add one here
{{ENDIF}} +{{IF [[infoMessage]] != '' }}
Info: [[infoMessage]] {{ENDIF}} +{{IF [[apiError]] != '' }}
Vast.ai API Error: [[apiError]] {{ENDIF}} +{%TEMPLATE->struct/messages%} +{%TEMPLATE->tasks/autorefresh%} +  +
+
+ + + + + + + + + + + + + + + + + + + + + + {{FOREACH gpu;[[gpus]]}} + + {{IF [[accessControl.hasPermission([[$DAccessControl::MANAGE_AGENT_ACCESS]])]]}} + + {{ELSE}} + + {{ENDIF}} + + + + + + + + + + + + + + + + + + + {{ENDFOREACH}} + +
DestroyIDTotal Cost ($/hr)StatusGPU(s)GPU RAM (MB)CPU NameCPU ThreadsCPU RAM (MB)Verification StatusInet Cost Upload ($/GB)Inet Cost Download ($/GB)Storage Cost ($/GB/Month)Interruptible
+
+ + + + +
+
 [[gpu.getVal('id')]][[gpu.getFloatVal('dph_total',2)]][[gpu.getVal('actual_status')]][[gpu.getVal('gpu_name')]] x [[gpu.getVal('num_gpus')]][[gpu.getVal('gpu_ram')]][[gpu.getVal('cpu_name')]][[gpu.getVal('cpu_cores')]][[gpu.getVal('cpu_ram')]][[gpu.getVal('verification')]][[gpu.getFloatVal('inet_up_cost',2)]][[gpu.getFloatVal('inet_down_cost',2)]][[gpu.getVal('storage_cost')]]{{IF [[gpu.getVal('is_bid')]]}} True {{ELSE}} False {{ENDIF}}
Uptime: [[gpu.getUptime()]]
ssh -p [[gpu.getVal('ssh_port')]] root@[[gpu.getVal('ssh_host')]]
[[gpu.getVal('status_msg')]]
+ +
+
+{%TEMPLATE->struct/foot%} diff --git a/src/templates/agents/vastai_rent.template.html b/src/templates/agents/vastai_rent.template.html new file mode 100755 index 000000000..b143abb90 --- /dev/null +++ b/src/templates/agents/vastai_rent.template.html @@ -0,0 +1,109 @@ +{%TEMPLATE->struct/head%} +{%TEMPLATE->struct/menu%} +

Vast AI - Rent Instance

+{%TEMPLATE->struct/messages%} +{{IF [[vastApiKey]] === '' }}
Error: Missing Vast.ai API Key, add one here
{{ENDIF}} +{{IF [[infoMessage]] != '' }}
Info: [[infoMessage]] {{ENDIF}} +{{IF [[apiError]] != '' }}
Vast.ai API Error: [[apiError]] {{ENDIF}} +
+
+ + +
+
+ {{IF [[rentType]] != 'bulk'}} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Total Cost ($/hr)[[gpu.getVal('dph_total')]]
Reliability[[gpu.getFloatVal100('reliability2',2)]] %
GPU(s) Name[[gpu.getVal('gpu_name')]] x [[gpu.getVal('num_gpus')]]
GPU RAM (MB)[[gpu.getVal('gpu_ram')]]
CPU Name[[gpu.getVal('cpu_name')]]
CPU Threads[[gpu.getVal('cpu_cores_effective')]]
CPU RAM (MB)[[gpu.getVal('cpu_ram')]]
Internet Upload Cost ($/GB)[[gpu.getFloatVal('inet_up_cost',2)]]
Internet Download Cost ($/GB)[[gpu.getFloatVal('inet_down_cost',2)]]
Storage Cost ($/GB/Month)[[gpu.getFloatVal('storage_cost',2)]]
Verification Status[[gpu.getVal('verification')]]
Rentable{{IF [[gpu.getVal('rentable')]]}} True {{ELSE}} False {{ENDIF}}
Interruptible{{IF [[gpu.getVal('is_bid')]]}} True {{ELSE}} False {{ENDIF}}
+ + +
+ + +
+
+
+ + + + + + + + + + + + + + + {{ENDIF}} + {{IF [[posted]] === true }} + {{IF [[rentSuccess]] === true }} + + {{ELSE}} + + {{ENDIF}} + {{ELSE}} + + {{ENDIF}} + + + +
ID[[gpu.getVal('id')]]
Price ($/Hr)
Disk Storage Needed (GB)
RENTAL SUCCESSFUL, Your image should appear here in a few seconds. It may take between 3 to 15 minutes to join hashtopolis, depending on the instance.RENTAL FAILED, error: [[rentError]]
+
+
+
+{%TEMPLATE->struct/foot%} diff --git a/src/templates/agents/vastai_search.template.html b/src/templates/agents/vastai_search.template.html new file mode 100755 index 000000000..2198deb14 --- /dev/null +++ b/src/templates/agents/vastai_search.template.html @@ -0,0 +1,78 @@ +{%TEMPLATE->struct/head%} +{%TEMPLATE->struct/menu%} +

Search Vast.AI

+{{IF [[vastApiKey]] === '' }}
Error: Missing Vast.ai API Key, add one here
{{ENDIF}} +{{IF [[infoMessage]] != '' }}
Info: [[infoMessage]] {{ENDIF}} +{{IF [[apiError]] != '' }}
Vast.ai API Error: [[apiError]] {{ENDIF}} +{%TEMPLATE->struct/vastaifilter%} +{%TEMPLATE->struct/messages%} +
+
+ + + +
+ + + +

+
+
+ + + + + + + + + + + + + + + + + + + {{FOREACH gpu;[[gpus]]}} + + {{IF [[accessControl.hasPermission([[$DAccessControl::MANAGE_AGENT_ACCESS]])]]}} + + {{ELSE}} + + {{ENDIF}} + + + + + + + + + + + + + {{ENDFOREACH}} + + +
RentIDTotal Cost ($/hr)ReliabilityGPU(s) NameGPU #GPU RAM (MB)CPU NameCPU ThreadsCPU RAM (MB)Verification StatusInterruptible
+ +  [[gpu.getVal('id')]][[gpu.getVal('dph_total')]][[gpu.getFloatVal100('reliability2',0)]] %[[gpu.getVal('gpu_name')]][[gpu.getVal('num_gpus')]][[gpu.getVal('gpu_ram')]][[gpu.getVal('cpu_name')]][[gpu.getVal('cpu_cores')]][[gpu.getVal('cpu_ram')]][[gpu.getVal('verification')]]{{IF [[gpu.getVal('is_bid')]]}} True {{ELSE}} False {{ENDIF}}
+ +
+
+{%TEMPLATE->struct/foot%} diff --git a/src/templates/config.template.html b/src/templates/config.template.html index eeaaa1320..0d6de5e14 100755 --- a/src/templates/config.template.html +++ b/src/templates/config.template.html @@ -12,6 +12,7 @@

Server configuration

+ @@ -38,7 +39,7 @@

Server configuration

[[DConfig::getConfigDescription([[conf.getVal('item')]])]] {{IF [[DConfig::getConfigType([[conf.getVal('item')]])]] == "string"}} - + {{ENDIF}} {{IF [[DConfig::getConfigType([[conf.getVal('item')]])]] == "number"}} diff --git a/src/templates/struct/menu.template.html b/src/templates/struct/menu.template.html index ddbe694b6..d9c2b34a7 100755 --- a/src/templates/struct/menu.template.html +++ b/src/templates/struct/menu.template.html @@ -19,6 +19,12 @@ {{IF [[accessControl.hasPermission([[$DAccessControl::VIEW_AGENT_ACCESS]])]]}} Agent Status {{ENDIF}} + {{IF [[accessControl.hasPermission([[$DAccessControl::CREATE_AGENT_ACCESS]])]]}} + Show Rented Vast.AI Agents + {{ENDIF}} + {{IF [[accessControl.hasPermission([[$DAccessControl::CREATE_AGENT_ACCESS]])]]}} + Search Vast.AI Agents + {{ENDIF}} {{ENDIF}} diff --git a/src/templates/struct/vastaifilter.template.html b/src/templates/struct/vastaifilter.template.html new file mode 100644 index 000000000..e4fb51f61 --- /dev/null +++ b/src/templates/struct/vastaifilter.template.html @@ -0,0 +1,47 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Max Cost ($/hr) GPU Name Min GPU RAM (MB) Min CPU RAM (MB)
Disk Size (GB)CPU Threads Reliability (.8 is 80%)
Verified:
Uninterruptible (cannot be outbid):
Trusted Datacenters Only (secure cloud):
+
+
diff --git a/src/vastaiAgents.php b/src/vastaiAgents.php new file mode 100755 index 000000000..d180aa857 --- /dev/null +++ b/src/vastaiAgents.php @@ -0,0 +1,117 @@ +isLoggedin()) { + header("Location: index.php?err=4" . time() . "&fw=" . urlencode($_SERVER['PHP_SELF'] . "?" . $_SERVER['QUERY_STRING'])); + die(); +} + +AccessControl::getInstance()->checkPermission(DViewControl::AGENTS_VIEW_PERM); + +Template::loadInstance("agents/vastai"); +Menu::get()->setActive("vast_ai_agents"); + +//catch actions here... +if (isset($_POST['action']) && CSRF::check($_POST['csrf'])) { + $agentHandler = new AgentHandler(@$_POST['agentId']); + $agentHandler->handle($_POST['action']); + if (UI::getNumMessages() == 0) { + Util::refresh(); + } +} + +$vastaiApiKey = SConfig::getInstance()->getVal(Dconfig::VAST_AI_API_KEY); +$apiError = ''; +$infoMessage = ''; + +UI::add('vastApiKey',$vastaiApiKey); +UI::add('pageTitle', "Vast.AI Agents"); + +// DESTROY LOGIC +if (isset($_POST['destroy']) && AccessControl::getInstance()->hasPermission(DAccessControl::MANAGE_AGENT_ACCESS)) { + if ($_POST['destroy'] === '' ){ + echo "No machine id provided for the destroy action"; + die(); + } + $machineId = $_POST['destroy']; + + $response = vastAiDestroyInstance($vastaiApiKey, $machineId); + if (isset($response['error'])) { + $apiError .= "vast.ai get instances error: " . $response; + } else if (isset($response['success'])) { + $infoMessage = 'Successfully destroyed instance: ' . $machineId; + } else { + $apiError .= "vast.ai get instances error: " . $response; + } + + // REMOVE VOUCHER FOR DESTROYED INSTANCE IF IT HASNT BEEN USED YET + if (isset($_POST['startingVoucher']) && $_POST['startingVoucher'] != '') { + $qF = new QueryFilter(RegVoucher::VOUCHER, $_POST['startingVoucher'], "="); + $check = Factory::getRegVoucherFactory()->filter([Factory::FILTER => $qF]); + if ( $check != null ) { + AgentUtils::deleteVoucher($_POST['startingVoucher']); + } + } +} + +// INSTANCE LIST LOGIC to list the current vast.ai instances associated with the api key +$response = getVastAiAgents($vastaiApiKey); +if (isset($response['error'])) { + $apiError .= "vast.ai get instances error: " . $response; +} else if (isset($response['instances']) == true) { + $gpus = array(); + foreach ($response['instances'] as $gpu) { + $gpuClassInstance = new VastAiGPUArrayClass($gpu); + array_push($gpus, $gpuClassInstance); + } +} else { + $apiError .= 'missing instances, response: ' . var_export($response, true); +} + +// TEST IF AUTO-RELOAD IS ENABLED +$autorefresh = 0; +if (isset($_COOKIE['autorefresh']) && $_COOKIE['autorefresh'] == '1') { + $autorefresh = 10; +} +if (isset($_POST['toggleautorefresh'])) { + if ($autorefresh != 0) { + $autorefresh = 0; + setcookie("autorefresh", "0", time() - 600); + } + else { + $autorefresh = 10; + setcookie("autorefresh", "1", time() + 3600 * 24); + } + Util::refresh(); +} +if ($autorefresh > 0) { //renew cookie + setcookie("autorefresh", "1", time() + 3600 * 24); +} + +UI::add('autorefresh', 0); +if (isset($_GET['id']) || !isset($_GET['new'])) { + UI::add('autorefresh', $autorefresh); + UI::add('autorefreshUrl', ""); +} + +UI::add('infoMessage', $infoMessage); +UI::add('apiError', $apiError); +UI::add('gpus', $gpus); +echo Template::getInstance()->render(UI::getObjects()); diff --git a/src/vastaiAgentsRent.php b/src/vastaiAgentsRent.php new file mode 100755 index 000000000..e76473871 --- /dev/null +++ b/src/vastaiAgentsRent.php @@ -0,0 +1,142 @@ +isLoggedin()) { + header("Location: index.php?err=4" . time() . "&fw=" . urlencode($_SERVER['PHP_SELF'] . "?" . $_SERVER['QUERY_STRING'])); + die(); +} + +AccessControl::getInstance()->checkPermission(DViewControl::AGENTS_VIEW_PERM); + +//catch actions here... +if (isset($_POST['action']) && CSRF::check($_POST['csrf'])) { + $agentHandler = new AgentHandler(@$_POST['agentId']); + $agentHandler->handle($_POST['action']); + if (UI::getNumMessages() == 0) { + Util::refresh(); + } +} + +Template::loadInstance("agents/vastai_rent"); + +$apiError = ''; +$infoMessage = ''; +$rentError = ''; +$vastaiApiKey = SConfig::getInstance()->getVal(Dconfig::VAST_AI_API_KEY); +$rentSuccess = false; +UI::add('vastApiKey', $vastaiApiKey); + +if (isset($_POST['rent']) && AccessControl::getInstance()->hasPermission(DAccessControl::CREATE_AGENT_ACCESS)) { + UI::add('rentType',$_POST['rent']); + foreach(array_keys($_POST) as $postKey){ + if (strpos($postKey, 'vastid-') !== 0) { + continue; + } + + $id = substr($postKey, strlen('vastid-')); + $query = array("id" => array("eq" => $id )); + + // PULL the details for the provided id + $response = searchGpus(json_encode($query)); + if (isset($response['error'])) { + $apiError .= "vast.ai get offers error: " . $response . "
"; + } else if (isset($response['offers']) == true) { + if (sizeof($response['offers']) == 1){ + $gpu = new VastAiGPUArrayClass($response['offers'][0]); + UI::add('gpu', $gpu); + } else if (sizeof($response['offers']) > 1){ + $apiError .= 'results were ambiguous for provided machine id
'; + } else { + $apiError .= 'no results found for provided machine id
'; + } + } else { + $apiError = 'missing offer key, response: ' . var_export($response , true) . '
'; + } + + // RENT LOGIC + $imageUrl = SConfig::getInstance()->getVal(Dconfig::VAST_IMAGE_URL); + $baseUrl = SConfig::getInstance()->getVal(Dconfig::VAST_HASHTOPOLIS_BASE_URL); + $vastaiImageLogin = SConfig::getInstance()->getVal(Dconfig::VAST_IMAGE_LOGIN); + + if ($baseUrl === '') { + echo 'No base url string was set. set it here
'; + die(); + } + if ($imageUrl === '') { + echo 'No image url string was set. set it here
'; + die(); + } + if (isset($_POST['disk']) === false || $_POST['disk'] === '') { + echo 'A disk value must be set.
'; + die(); + } + if (isset($_POST['price']) === false || $_POST['price'] === ''){ + $price = null; + } + + $disk = $_POST['disk']; + $price = $_POST['price']; + // this additional substring gets referenced to properly remove + // voucher and agent artifacts by the destroy function. + // it is also intuitive when checking agents/vouchers. + $voucher = makeVastaiVoucher($id); + + $response = rentMachine($vastaiApiKey, $id, $imageUrl, $price, $disk, $voucher, $baseUrl, $vastaiImageLogin); + if (isset($response['error'])) { + $rentError .= "vast.ai error: " . $response['msg']; + } else if (isset($response['success'])) { + $infoMessage .= '
Successfully rented: ' . $id; + $rentSuccess = true; + } else { + $rentError .= "vast.ai error: " . $response . '
'; + } + + } + + UI::add('posted', true); + UI::add('rentError', $rentError); + UI::add('infoMessage', $infoMessage); + UI::add('rentSuccess', $rentSuccess); +} else if (isset($_GET['rent']) && AccessControl::getInstance()->hasPermission(DAccessControl::CREATE_AGENT_ACCESS)) { + // VIEWING DETAILS + $id = $_GET['rent']; + $query = array("id" => array("eq" => $id )); + $response = searchGpus(json_encode($query)); + if (is_array($response) === false){ + $apiError .= $response . "\n"; + } + if (isset($response['offers']) == true) { + if (sizeof($response['offers']) == 1){ + $gpu = new VastAiGPUArrayClass($response['offers'][0]); + UI::add('gpu', $gpu); + } else if (sizeof($response['offers']) > 1){ + $apiError .= 'results were ambiguous for provided machine id' . "\n"; + } else { + $apiError .= 'no results found for provided machine id' . "\n"; + } + } else { + $apiError .= 'missing offer key, response: ' . var_export($response, true) . "\n"; + } +} else { + echo "Error: No rent param provided"; + die(); +} + +UI::add('apiError', $apiError); +echo Template::getInstance()->render(UI::getObjects()); diff --git a/src/vastaiAgentsSearch.php b/src/vastaiAgentsSearch.php new file mode 100755 index 000000000..3d3a65200 --- /dev/null +++ b/src/vastaiAgentsSearch.php @@ -0,0 +1,133 @@ +isLoggedin()) { + header("Location: index.php?err=4" . time() . "&fw=" . urlencode($_SERVER['PHP_SELF'] . "?" . $_SERVER['QUERY_STRING'])); + die(); +} + +AccessControl::getInstance()->checkPermission(DViewControl::AGENTS_VIEW_PERM); + +Template::loadInstance("agents/vastai_search"); +Menu::get()->setActive("vast_ai_agents_search"); + +//catch actions here... +if (isset($_POST['action']) && CSRF::check($_POST['csrf'])) { + $agentHandler = new AgentHandler(@$_POST['agentId']); + $agentHandler->handle($_POST['action']); + if (UI::getNumMessages() == 0) { + Util::refresh(); + } +} + +$trueKeys = array('verified', 'datacenter'); +$ltNumKeys = array('dph_total', 'dph'); +$gtKeys = array('cpu_ram', 'gpu_ram', 'reliability2', 'disk_size', 'cpu_cores'); +$eqKeys = array('gpu_name', 'cpu_name'); +$vastaiApiKey = SConfig::getInstance()->getVal(Dconfig::VAST_AI_API_KEY); +UI::add('vastApiKey',$vastaiApiKey); + +// maintain current search values / set default values +UI::add('search_gpu_name',get_or($_GET, 'gpu_name', '')); +UI::add('search_dph_total',$_GET['dph_total'] ?? '.4'); +UI::add('search_gpu_ram',$_GET['gpu_ram'] ?? ''); +UI::add('search_cpu_ram',$_GET['cpu_ram'] ?? ''); +UI::add('search_disk_size',$_GET['disk_size'] ?? 8); +UI::add('search_cpu_cores',$_GET['cpu_cores'] ?? ''); +UI::add('search_reliability2',$_GET['reliability2'] ?? '.8'); + +if (isset($_GET['datacenter']) === false || $_GET['datacenter'] === 'on'){ + // default setting + UI::add('datacenterChecked',true); +} else { + UI::add('datacenterChecked',false); +} + +if (isset($_GET['verified']) === false || $_GET['verified'] === 'on'){ + // default setting + UI::add('verifiedChecked',true); +} else { + UI::add('verifiedChecked',false); +} + +if (isset($_GET['uninterruptible']) === false || $_GET['uninterruptible'] === 'on'){ + // default setting + UI::add('uninterruptibleChecked',true); +} else { + UI::add('uninterruptibleChecked',false); +} + +UI::add('pageTitle', "Vast.AI Search"); +$apiError = ''; +if (isset($_GET['search']) && AccessControl::getInstance()->hasPermission(DAccessControl::MANAGE_AGENT_ACCESS)) { + // prepare the query, I do not like this method of doing this, but it is working + $query = array(); + $query = array_merge($query, array( "rentable" => array("eq" => true))); + $query = array_merge($query, array( "rented" => array("eq" => false))); + + if(isset($_GET['uninterruptible']) == true && $_GET['uninterruptible'] == 'on'){ + $query = array_merge($query, array("type" => "ask")); + } else { + $query = array_merge($query, array("type" => "bid")); + } + + foreach ($trueKeys as $key) { + if (isset($_GET[$key]) === true && $_GET[$key] !== ''){ + if ($_GET[$key] == 'on'){ + $query = array_merge($query, array( $key => array("eq" => true))); + } + } + } + + foreach ($eqKeys as $key) { + if (isset($_GET[$key]) === true && $_GET[$key] !== ''){ + $query = array_merge($query, array( $key => array("eq" => $_GET[$key]))); + } + } + + foreach ($ltNumKeys as $key) { + if (isset($_GET[$key]) === true && $_GET[$key] !== ''){ + $query = array_merge($query, array( $key => array("lte" => $_GET[$key]))); + } + } + + foreach ($gtKeys as $key) { + if (isset($_GET[$key]) === true && $_GET[$key] !== ''){ + $query = array_merge($query, array( $key => array("gte" => $_GET[$key]))); + } + } + + // make the query + $response = searchGpus(json_encode($query)); + if (isset($response['error'])) { + $apiError .= "vast.ai get offers error: " . $response; + } else if (isset($response['offers']) == true) { + $gpus = array(); + foreach ($response ['offers'] as $gpu) { + $gpuClassInstance = new VastAiGPUArrayClass($gpu); + array_push($gpus, $gpuClassInstance); + } + } else { + $apiError = 'missing offer key, response: ' . var_export($response , true); + } +} + +UI::add('apiError', $apiError); +UI::add('gpus', $gpus); +echo Template::getInstance()->render(UI::getObjects());