From e896549ab882a789217bcb21407c42eb439f2340 Mon Sep 17 00:00:00 2001 From: Anupama Sarjoshi Date: Tue, 12 Mar 2024 15:53:34 +0000 Subject: [PATCH] Add Load-balancing cookie support These changes support AWS load-balancer stickiness, by saving and sending back the cookie set by the load balancer using curl. --- classes/jobesandbox.php | 54 ++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/classes/jobesandbox.php b/classes/jobesandbox.php index 725e87fb..3b0833b0 100644 --- a/classes/jobesandbox.php +++ b/classes/jobesandbox.php @@ -50,6 +50,7 @@ class qtype_coderunner_jobesandbox extends qtype_coderunner_sandbox { * - with haproxy try "balance hdr(X-CodeRunner-Job-Id)" (not tested) * - with a Netscaler use rule-based persistence with expression * HTTP.REQ.HEADER(“X-CodeRunner-Job-Id”) + * - with cookies support */ private $currentjobid = null; @@ -136,6 +137,8 @@ public function get_languages() { */ public function execute($sourcecode, $language, $input, $files = null, $params = null) { + global $CFG; + $language = strtolower($language); if (is_null($input)) { $input = ''; @@ -201,21 +204,36 @@ public function execute($sourcecode, $language, $input, $files = null, $params = $postbody = ['run_spec' => $runspec]; $this->currentjobid = sprintf('%08x', mt_rand()); + // Create a single curl object here, with support for cookies, and use it for all requests. + // This supports Jobe back-ends that use cookies for sticky load-balancing. + // Make a place to store the cookies. + make_temp_directory('qtype_coderunner'); + $cookiefile = $CFG->tempdir . '/qtype_coderunner/session_cookies_' . $this->currentjobid . '.txt'; + + $curl = new curl(); + $curl->setopt([ + 'CURLOPT_COOKIEJAR' => $cookiefile, + 'CURLOPT_COOKIEFILE' => $cookiefile, + ]); + // Try submitting the job. If we get a 404, try again after // putting all the files on the server. Anything else is an error. - $httpcode = $this->submit($postbody); + $httpcode = $this->submit($postbody, $curl); if ($httpcode == 404) { // If it's a file not found error ... foreach ($files as $filename => $contents) { - if (($httpcode = $this->put_file($contents)) != 204) { + if (($httpcode = $this->put_file($contents, $curl)) != 204) { break; } } if ($httpcode == 204) { // Try again if put_files all worked. - $httpcode = $this->submit($postbody); + $httpcode = $this->submit($postbody, $curl); } } + // Delete the cookie file. + unlink($cookiefile); + $runresult = []; $runresult['sandboxinfo'] = [ 'jobeserver' => $this->jobeserver, @@ -327,11 +345,10 @@ private function get_error_code($httpcode) { } // Put the given file to the server, using its MD5 checksum as the id. + // If you pass a curl object, this will be used to make the request. // Returns the HTTP response code, or -1 if the HTTP request fails // altogether. - // Moodle curl class doesn't support an appropriate form of PUT so - // we use raw PHP curl here. - private function put_file($contents) { + private function put_file($contents, $curl) { $id = md5($contents); $contentsb64 = base64_encode($contents); $resource = "files/$id"; @@ -339,16 +356,9 @@ private function put_file($contents) { [$url, $headers] = $this->get_jobe_connection_info($resource); $body = ['file_contents' => $contentsb64]; - $curl = curl_init(); - curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT"); - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($body)); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - $result = curl_exec($curl); - $info = curl_getinfo($curl); - curl_close($curl); - return $result === false ? -1 : $info['http_code']; + $result = $curl->put($url, json_encode($body)); + $returncode = $curl->info['http_code']; + return $result === false ? -1 : $returncode; } /** @@ -402,8 +412,9 @@ private function get_jobe_connection_info($resource) { // response was 400 Bad Parameter. // We don't at this stage deal with Jobe servers that may defer requests // i.e. that return 202 Accepted rather than 200 OK. - private function submit($job) { - [$returncode, $response] = $this->http_request('runs', self::HTTP_POST, $job); + // If you pass a curl object, this will be used to make the request. + private function submit($job, $curl) { + [$returncode, $response] = $this->http_request('runs', self::HTTP_POST, $job, $curl); $this->response = $response; return $returncode; } @@ -418,10 +429,13 @@ private function submit($job) { // Note that the Moodle curl class documentation lies when it says the // return value from get and post is a bool. It's either the value false // if the request failed or the actual string response, otherwise. - private function http_request($resource, $method, $body = null) { + // If you pass a curl object, this will be used to make the request. + private function http_request($resource, $method, $body = null, $curl = null) { [$url, $headers] = $this->get_jobe_connection_info($resource); - $curl = new curl(); + if ($curl == null) { + $curl = new curl(); + } $curl->setHeader($headers); if ($method === self::HTTP_GET) {