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