61fb298726b74f15b7d60ff3c43f1f2050c038c1
[plcapi.git] / php / plc_api.php
1 <?php
2 //
3 // PlanetLab Central Slice API (PLCAPI) PHP interface
4 //
5 // DO NOT EDIT. This file was automatically generated at
6 // @DATE@.
7 //
8 // Mark Huang <mlhuang@cs.princeton.edu>
9 // Copyright (C) 2005-2006 The Trustees of Princeton University
10 //
11 // $Id$
12 // $URL$
13 //
14 //
15
16 require_once 'plc_config.php';
17
18 class PLCAPI
19 {
20   var $auth;
21   var $server;
22   var $port;
23   var $path;
24   var $errors;
25   var $trace;
26   var $calls;
27   var $multicall;
28
29   function PLCAPI($auth = NULL,
30                   $server = PLC_API_HOST,
31                   $port = PLC_API_PORT,
32                   $path = PLC_API_PATH,
33                   $cainfo = NULL)
34   {
35     $this->auth = $auth;
36     $this->server = $server;
37     $this->port = $port;
38     $this->path = $path;
39     $this->cainfo = $cainfo;
40     $this->errors = array();
41     $this->trace = array();
42     $this->calls = array();
43     $this->multicall = false;
44   }
45
46   function error_log($error_msg, $backtrace_level = 1)
47   {
48     $backtrace = debug_backtrace();
49     $file = $backtrace[$backtrace_level]['file'];
50     $line = $backtrace[$backtrace_level]['line'];
51
52     $this->errors[] = 'PLCAPI error:  ' . $error_msg . ' in ' . $file . ' on line ' . $line;
53     error_log(end($this->errors));
54   }
55
56   function error()
57   {
58     if (empty($this->trace)) {
59       return NULL;
60     } else {
61       $last_trace = end($this->trace);
62       return implode("\\n", $last_trace['errors']);
63     }
64   }
65
66   function trace()
67   {
68     return $this->trace;
69   }
70
71   function microtime_float()
72   {
73     list($usec, $sec) = explode(" ", microtime());
74     return ((float) $usec + (float) $sec);
75   }
76
77   function generateKey($method, $args)
78   {
79     $excluded_API_functions = array("AddSession", "GetSession", "DeleteSession", "AuthCheck", "VerifyPerson");
80
81     if (array_search($method, $excluded_API_functions))
82         return NULL;
83
84     // Key is the md5sum of method + (arguments list - session variables)
85     $arguments = $args;
86     unset($arguments[0]);
87     return md5($method . serialize(arguments));
88   }
89
90   function lookup($method, $args = NULL)
91   {
92     $key = $this->generateKey($method, $args);
93     if ($key == NULL)
94         return NULL;
95
96     $memcache = new Memcache;
97     $memcache->connect($this->server, 11211) or die ("Could not connect");
98
99     $result = $memcache->get($key);
100
101     if ($result == FALSE) {
102         $this->error_log ("MEMCACHE: " . $method . " with " . count($args) . " args lookup failed for " . $key);
103     }
104     else
105     {
106         $this->error_log ("MEMCACHE: " . $method . " with " . count($args) . " args lookup succeded for " . $key);
107         if (gettype($result) == "array")
108             return $result;
109         else if ($result == "NULL")
110             return NULL;
111         else
112             return unserialize($result);
113     }
114     return NULL;
115   }
116
117   function call($method, $args = NULL)
118   {
119     if ($this->multicall) {
120       $this->calls[] = array ('methodName' => $method,
121                                 'params' => $args);
122       return NULL;
123     } else {
124       $result = $this->lookup($method, $args);
125       if ($result == NULL)
126       {
127           $memcache = new Memcache;
128           $memcache->connect($this->server, 11211) or die ("Could not connect");
129
130           $result = $this->internal_call ($method, $args, 3);
131
132           $key = $this->generateKey($method, $args);
133           if ($key != NULL)
134           {
135               $memcache->set($key, $result == NULL ? "NULL" : $result, false, 6000);
136           }
137       }
138       return $result;
139     }
140   }
141
142   function internal_call($method, $args = NULL, $backtrace_level = 2)
143   {
144     $curl = curl_init();
145
146     // Verify peer certificate if talking over SSL
147     if ($this->port == 443) {
148       curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 2);
149       if (!empty($this->cainfo)) {
150         curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo);
151       } elseif (defined('PLC_API_CA_SSL_CRT')) {
152         curl_setopt($curl, CURLOPT_CAINFO, PLC_API_CA_SSL_CRT);
153       }
154       $url = 'https://';
155     } else {
156       $url = 'http://';
157     }
158
159     // Set the URL for the request
160     $url .= $this->server . ':' . $this->port . '/' . $this->path;
161     curl_setopt($curl, CURLOPT_URL, $url);
162
163     // Marshal the XML-RPC request as a POST variable. <nil/> is an
164     // extension to the XML-RPC spec that is supported in our custom
165     // version of xmlrpc.so via the 'allow_null' output_encoding key.
166     $request = xmlrpc_encode_request($method, $args, array('allow_null' => TRUE));
167     curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
168
169     // Construct the HTTP header
170     $header[] = 'Content-type: text/xml';
171     $header[] = 'Content-length: ' . strlen($request);
172     curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
173
174     // Set some miscellaneous options
175     curl_setopt($curl, CURLOPT_TIMEOUT, 180);
176
177     // Get the output of the request
178     curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
179     $t0 = $this->microtime_float();
180     $output = curl_exec($curl);
181     $t1 = $this->microtime_float();
182
183     if (curl_errno($curl)) {
184       $this->error_log('curl: ' . curl_error($curl), true);
185       $ret = NULL;
186     } else {
187       $ret = xmlrpc_decode($output);
188       if (is_array($ret) && xmlrpc_is_fault($ret)) {
189         $this->error_log('Fault Code ' . $ret['faultCode'] . ': ' .
190                          $ret['faultString'], $backtrace_level, true);
191         $ret = NULL;
192       }
193     }
194
195     curl_close($curl);
196
197     $this->trace[] = array('method' => $method,
198                            'args' => $args,
199                            'runtime' => $t1 - $t0,
200                            'return' => $ret,
201                            'errors' => $this->errors);
202     $this->errors = array();
203
204     return $ret;
205   }
206
207   function begin()
208   {
209     if (!empty($this->calls)) {
210       $this->error_log ('Warning: multicall already in progress');
211     }
212
213     $this->multicall = true;
214   }
215
216   function commit()
217   {
218     if (!empty ($this->calls)) {
219       $ret = array();
220       $results = $this->internal_call ('system.multicall', array ($this->calls));
221       foreach ($results as $result) {
222         if (is_array($result)) {
223           if (xmlrpc_is_fault($result)) {
224             $this->error_log('Fault Code ' . $result['faultCode'] . ': ' .
225                              $result['faultString'], 1, true);
226             $ret[] = NULL;
227             // Thierry - march 30 2007 
228             // using $adm->error() is broken with begin/commit style 
229             // this is because error() uses last item in trace and checks for ['errors']
230             // when using begin/commit we do run internal_call BUT internal_call checks for 
231             // multicall's result globally, not individual results, so ['errors'] comes empty
232             // I considered hacking internal_call 
233             // to *NOT* maintain this->trace at all when invoked with multicall
234             // but it is too complex to get all values right
235             // so let's go for the hacky way, and just record individual errors at the right place
236             $this->trace[count($this->trace)-1]['errors'][] = end($this->errors);
237           } else {
238             $ret[] = $result[0];
239           }
240         } else {
241           $ret[] = $result;
242         }
243       }
244     } else {
245       $ret = NULL;
246     }
247
248     $this->calls = array();
249     $this->multicall = false;
250
251     return $ret;
252   }
253
254   //
255   // PLCAPI Methods
256   //
257
258   function __call($name, $args)
259   {
260      array_unshift($args, $this->auth);
261      return $this->call($name, $args);
262   }
263 }
264
265 global $adm;
266
267 $adm = new PLCAPI(array('AuthMethod' => "capability",
268                         'Username' => PLC_API_MAINTENANCE_USER,
269                         'AuthString' => PLC_API_MAINTENANCE_PASSWORD));
270
271 ?>