enable null extension
[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
12 require_once 'plc_config.php';
13
14 class PLCAPI
15 {
16   var $auth;
17   var $server;
18   var $port;
19   var $path;
20   var $errors;
21   var $trace;
22   var $calls;
23   var $multicall;
24
25   function PLCAPI($auth = NULL,
26                   $server = PLC_API_HOST,
27                   $port = PLC_API_PORT,
28                   $path = PLC_API_PATH,
29                   $cainfo = NULL)
30   {
31     $this->auth = $auth;
32     $this->server = $server;
33     $this->port = $port;
34     $this->path = $path;
35     $this->cainfo = $cainfo;
36     $this->errors = array();
37     $this->trace = array();
38     $this->calls = array();
39     $this->multicall = false;
40   }
41
42   function rec_join ($arg) {
43     if ( is_array($arg) ) {
44         $ret = "";
45         foreach ( $arg as $i ) {
46             $l = $this->rec_join($i);
47             # ignore html code.
48             if ( $l[0] != "<" ) { $ret .= $l . ", "; }
49         }
50         return $ret;
51     } else {
52         settype($arg, "string");
53         return $arg;
54     }
55   }
56
57   function backtrace_php () {
58     $backtrace = debug_backtrace();
59     $msg = "";
60     $len = count($backtrace);
61     $cnt = 1;
62     foreach( array_reverse($backtrace) as $line ) {
63         $msg .= "File '". $line['file'] . "' line " . $line['line'] . "\n";
64         $msg .= "    " . $line['function'] . "( "  . $this->rec_join($line['args']) . ")\n";
65         $cnt += 1;
66         if ( $cnt == $len ) { break; }
67     }
68     return $msg;
69   }
70
71   function error_log($error_msg, $backtrace_level = 1)
72   {
73     $backtrace = debug_backtrace();
74     $file = $backtrace[$backtrace_level]['file'];
75     $line = $backtrace[$backtrace_level]['line'];
76
77     $error_line='PLCAPI error:  ' . $error_msg ;
78     if ($file) $error_line .= ' in file ' . $file;
79     if ($line) $error_line .= ' on line ' . $line;
80     $this->errors[] = $error_line;
81     # TODO: setup a config variable for more detailed stack traces, for API errors.
82     if ( TRUE ){
83       error_log($error_line);
84     } else {
85        error_log($this->backtrace_php());
86     }
87   }
88
89   function error()
90   {
91     if (empty($this->trace)) {
92       return NULL;
93     } else {
94       $last_trace = end($this->trace);
95       return implode("\\n", $last_trace['errors']);
96     }
97   }
98
99   function trace()
100   {
101     return $this->trace;
102   }
103
104   function microtime_float()
105   {
106     list($usec, $sec) = explode(" ", microtime());
107     return ((float) $usec + (float) $sec);
108   }
109
110   function call($method, $args = NULL)
111   {
112     if ($this->multicall) {
113       $this->calls[] = array ('methodName' => $method,
114                                 'params' => $args);
115       return NULL;
116     } else {
117       return $this->internal_call ($method, $args, 3);
118     }
119   }
120
121   function internal_call($method, $args = NULL, $backtrace_level = 2)
122   {
123     $curl = curl_init();
124
125     // Verify peer certificate if talking over SSL
126     if ($this->port == 443) {
127       curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 2);
128       if (!empty($this->cainfo)) {
129         curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo);
130       } elseif (defined('PLC_API_CA_SSL_CRT')) {
131         curl_setopt($curl, CURLOPT_CAINFO, PLC_API_CA_SSL_CRT);
132       }
133       $url = 'https://';
134     } else {
135       $url = 'http://';
136     }
137
138     // Set the URL for the request
139     $url .= $this->server . ':' . $this->port . '/' . $this->path;
140     curl_setopt($curl, CURLOPT_URL, $url);
141
142     // this tentatively allows to tune mainstream xmlrpc php lib
143     // so as to achieve the same behaviour as with our patched lib
144     $xmlrpc_null_extension = TRUE;
145     // Marshal the XML-RPC request as a POST variable. <nil/> is an
146     // extension to the XML-RPC spec that is supported in our custom
147     // version of xmlrpc.so via the 'allow_null' output_encoding key.
148     $request = xmlrpc_encode_request($method, $args, array('allow_null' => TRUE));
149     curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
150
151     // Construct the HTTP header
152     $header[] = 'Content-type: text/xml';
153     $header[] = 'Content-length: ' . strlen($request);
154     curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
155
156     // Set some miscellaneous options
157     curl_setopt($curl, CURLOPT_TIMEOUT, 180);
158
159     // Get the output of the request
160     curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
161     $t0 = $this->microtime_float();
162     $output = curl_exec($curl);
163     $t1 = $this->microtime_float();
164
165     if (curl_errno($curl)) {
166       $this->error_log('curl: ' . curl_error($curl), true);
167       $ret = NULL;
168     } else {
169       $ret = xmlrpc_decode($output);
170       if (is_array($ret) && xmlrpc_is_fault($ret)) {
171         $this->error_log('Fault Code ' . $ret['faultCode'] . ': ' .
172                          $ret['faultString'], $backtrace_level, true);
173         $ret = NULL;
174       }
175     }
176
177     curl_close($curl);
178
179     $this->trace[] = array('method' => $method,
180                            'args' => $args,
181                            'runtime' => $t1 - $t0,
182                            'return' => $ret,
183                            'errors' => $this->errors);
184     $this->errors = array();
185
186     return $ret;
187   }
188
189   function begin()
190   {
191     if (!empty($this->calls)) {
192       $this->error_log ('Warning: multicall already in progress');
193     }
194
195     $this->multicall = true;
196   }
197
198   function commit()
199   {
200     if (!empty ($this->calls)) {
201       $ret = array();
202       $results = $this->internal_call ('system.multicall', array ($this->calls));
203       foreach ($results as $result) {
204         if (is_array($result)) {
205           if (xmlrpc_is_fault($result)) {
206             $this->error_log('Fault Code ' . $result['faultCode'] . ': ' .
207                              $result['faultString'], 1, true);
208             $ret[] = NULL;
209             // Thierry - march 30 2007 
210             // using $adm->error() is broken with begin/commit style 
211             // this is because error() uses last item in trace and checks for ['errors']
212             // when using begin/commit we do run internal_call BUT internal_call checks for 
213             // multicall's result globally, not individual results, so ['errors'] comes empty
214             // I considered hacking internal_call 
215             // to *NOT* maintain this->trace at all when invoked with multicall
216             // but it is too complex to get all values right
217             // so let's go for the hacky way, and just record individual errors at the right place
218             $this->trace[count($this->trace)-1]['errors'][] = end($this->errors);
219           } else {
220             $ret[] = $result[0];
221           }
222         } else {
223           $ret[] = $result;
224         }
225       }
226     } else {
227       $ret = NULL;
228     }
229
230     $this->calls = array();
231     $this->multicall = false;
232
233     return $ret;
234   }
235
236   //
237   // PLCAPI Methods
238   //
239
240   function __call($name, $args)
241   {
242      array_unshift($args, $this->auth);
243      return $this->call($name, $args);
244   }
245 }
246
247 global $adm;
248
249 $adm = new PLCAPI(array('AuthMethod' => "capability",
250                         'Username' => PLC_API_MAINTENANCE_USER,
251                         'AuthString' => PLC_API_MAINTENANCE_PASSWORD));
252
253 ?>