5d17aa0add820b1cd0e77c0f31f29a84c80b2bba
[plewww.git] / planetlab / nodes / node_actions.php
1 <?php
2
3   // $Id: node_actions.php 1184 2008-02-12 20:33:35Z thierry $
4   // This file was created when we decided to make the node page consistent with the other pages
5   // That is to say, we wanted the 'Update' and 'Delete' functions to appear in a drop-down
6   // menu like for the other objects
7   // It appeared after analysis that it was easier to have the following 
8   // functions gathered in a single php file 
9   // (*) node_update.php
10   //    (GET) id=node_id
11   //          displays current settings and prompt for changes before going to node_actions.php
12   // (*) node_actions.php 
13   //    (GET) 'del=node_id'
14   //    (GET) 'update=node_id' -> actual UpdateNode
15   //    (POST) node_id=node_id ng_add=nodegroup_id
16   //           AddNodeToNodeGroup
17   //    (POST) boot_state=boot_state node_id=node_id
18   //    (POST) ng_add=nodegroup_id
19   // (*) downloadconf.php
20   //     (GET) id=node_id [download=1]
21   //           either showed current status, or performed actual config download 
22   
23   // in this new version we support only POST behaviour with the following interface
24   // REQUIRED : node_id=node_id
25   // (*) action='prompt-update' : former node_update.php
26   // (*) action='update'
27   //     hostname=
28   //     model=                 : actually updated the node
29   // (*) action='boot-state'
30   //     boot_state=            : changes bootstate
31   // (*) action='delete'        : deletes node
32   // (*) action='download-node-floppy' : 
33   // (*) action='download-node-iso' : 
34   // (*) action='download-node-usb' : 
35   //                            : same as former downloadconf.php with download unset
36   //     if in addition POST contains a non-empty field 'download' :
37   //                            : performs actual node-dep download
38   // (*) action='download-generic-iso':
39   // (*) action='download-generic-usb':
40   //                            : performs actual generic download
41   // (*) action='add-in-nodegroup': should include this for compatibility, replaces ng_add, 
42   //                                though I could not figure where it's called
43
44
45   // TODO : manage a post field for displaying results of previous page
46   // see the special 'flash' in rails
47
48 // delivering node-dependant images requires larger memory limit
49 // trial and error, based on the current sizes
50 // generic-ISO 43980800 
51 // generic-usb 44720128 
52 // 256M OK
53 // 128M OK
54 // 96M OK
55 // 88M KO
56 // 80M KO
57 // 64M KO
58 // Bottom line is, looks like we need in the order of twice the file size
59 // so let's play it safe
60 // Thierry - for 4.2, we need a larger area, was 100 for 4.1
61 ini_set("memory_limit","150M");
62
63 // Require login
64 require_once 'plc_login.php';
65
66 // Get session and API handles
67 require_once 'plc_session.php';
68 global $plc, $api;
69
70 // Common functions
71 require_once 'plc_functions.php';
72 require_once 'plc_sorts.php';
73
74 // find person roles
75 $_person= $plc->person;
76 $_roles= $_person['role_ids'];
77
78 // NOTE: this function exits() after it completes its job, 
79 // simply returning leads to html decorations being added around the contents
80 function deliver_and_unlink ($filename) {
81   
82   // for security reasons we want to be able to unlink the resulting file once downloaded
83   // so simply redirecting through a header("Location:") is not good enough
84
85   $size= filesize($filename);
86
87   // headers
88   header ("Content-Type: application/octet-stream");
89   header ("Content-Transfer-Encoding: binary");
90   header ("Content-Disposition: attachment; filename=" . basename($filename) );
91   header ("Content-Length: " . $size );
92   // for getting IE to work properly
93   // from princeton cvs new_plc_www/planetlab/nodes/downloadconf.php 1.2->1.3
94   header ("Pragma: hack");
95   header ("Cache-Control: public, must-revalidate");
96
97   // outputs the whole file contents
98   print (file_get_contents($filename));
99   
100   // unlink the file
101   if (! unlink ($filename) ) {
102     // cannot unlink, but how can we notify this ?
103     // certainly not by printing
104   }
105   exit();
106 }
107
108 function show_download_confirm_button($api, $node_id, $action, $can_gen_config, $show_details) {
109
110   if( $can_gen_config ) {
111     if ($show_details) {
112       $preview=$api->GetBootMedium($node_id,"node-preview","");
113       print ("<hr /> <h3>Current node configuration contents</h3>");
114       print ("<pre>\n$preview</pre>\n");
115       print ("<hr />");
116     }
117     $action_labels = array ('download-node-floppy' => 'textual node config (for floppy)' ,
118                             'download-node-iso' => 'ISO image',
119                             'download-node-usb' => 'USB image' );
120     
121     $format = $action_labels [ $action ] ;
122     print( "<p><form method='post' action='node_actions.php'>\n");
123     print ("<input type='hidden' name='node_id' value='$node_id'>\n");
124     print ("<input type='hidden' name='action' value='$action'>\n");
125     print ("<input type='hidden' name='download' value='1'>\n");
126     print( "<input type='submit' value='Download $format'>\n" );
127     print( "</form>\n" );
128   } else {
129     echo "<p><font color=red>Configuration file cannot be created until missing values above are updated.</font>";
130   }
131 }
132
133 // check arguments
134
135 if (empty($_POST['node_id'])) {
136   header ('location:index.php');
137   exit();
138  } else {
139   $node_id = intval($_POST['node_id']);
140 }
141
142 $action=$_POST['action'];
143
144 switch ($action) {
145
146   // ACTION: prompt-update
147   // from former node_update.php
148  case "prompt-update":
149    
150    require_once('plc_drupal.php');
151    $node_info= $api->GetNodes( array( $node_id ), array( "hostname", "model" ) );
152    drupal_set_title("Updating " . $node_info[0]['hostname']);
153    include 'plc_header.php';
154    
155    // start form
156    echo "<form method=post action='node_actions.php'>\n";
157    echo "<input type=hidden name='node_id' value='$node_id'></input>\n";
158    echo "<input type=hidden name='action' value='update'></input>\n";
159    if( $_POST['error'] ) echo "<font color=red>". unserialize( $_POST['error'] ."</font>\n" );
160    echo "<p><table cellpadding=2><tbody>\n
161        <tr><th>Hostname: </th><td> <input type=text size=35 name='hostname' value='". $node_info[0]['hostname'] ."'></td></tr>\n
162        <tr><th>Model: </th><td> <input type=text size=35 name='model' value='". $node_info[0]['model'] ."'></td></tr>\n
163        </tbody></table>\n
164        <p><input type=submit value='Update Node'>\n
165        </form>\n";
166    
167    echo "<p><a href='index.php?id=$node_id'>Back to Node</a>\n";
168    
169    break;
170
171    // ACTION: update
172    // from former node_actions.php
173  case "update":
174
175    $hostname= $_POST['hostname'];
176    $model= $_POST['model'];
177
178    $fields= array( "hostname"=>$hostname, "model"=>$model );
179    $api->UpdateNode( intval( $node_id ), $fields );
180    $error= $api->error();
181
182    if( empty( $error ) ) {
183      header( "location: index.php?id=$node_id" );
184      exit();
185    } else {
186      echo "<font color=red>". $error . "</font>\n" ;
187      echo "<p> Press back to retry</p>";
188    }
189
190    break;
191
192    // ACTION: delete
193    // from former node_actions.php
194  case "delete":
195    $api->DeleteNode( intval( $node_id ) );
196    header( "location: index.php" );
197    exit();
198    break;
199
200    // ACTION: boot-state
201    // from former node_actions.php
202  case "boot-state":
203    switch( $_POST['boot_state'] ) {
204    case 'boot':
205    case 'dbg':
206    case 'inst':
207    case 'rins':
208    case 'rcnf':
209    case 'new':
210      $api->UpdateNode( intval( $node_id ), array( "boot_state" => $_POST['boot_state'] ) );
211      header( "location: index.php?id=$node_id" );
212      exit();
213      break;
214    default:
215      print "<div class='plc-error'> no such boot state " . $_POST['boot_state'] . "</div>";
216      break;
217    }
218    break;
219
220   // ACTION: download-generic
221  case "download-generic-iso":
222  case "download-generic-usb":
223    
224    if ($action=="download-generic-iso") {
225      $boot_action="generic-iso";
226    } else {
227      $boot_action="generic-usb";
228    }
229
230    // place the result in a random-named sub dir for clear filenames
231    $filename = $api->GetBootMedium ($node_id, $boot_action, "%d/%n-%p-%a-%v%s");
232    $error=$api->error();
233    // NOTE. for some reason, GetBootMedium sometimes does not report an error but the
234    // file is not created - this happens e.g. when directory owmer/modes are wrong 
235    // in this case we get an empty filename
236    // see /etc/httpd/logs/error_log in this case
237    if (empty($error) && empty($filename)) {
238      $error="Unexpected error from GetBootMedium - probably wrong directory modes";
239    }    
240    if (! empty($error)) {
241      print ("<div class='plc-error'> $error </div>\n");
242      print ("<p><a href='/db/nodes/index.php?id=$node_id'>Back to node </a>\n");
243      return ;
244    } else {
245      deliver_and_unlink ($filename);
246      exit();
247    }
248    break;
249
250    // ACTION: download-node
251    // from former downloadconf.php
252    
253  case "download-node-floppy":
254  case "download-node-iso":
255  case "download-node-usb":
256    
257    $return= $api->GetNodes( array( $node_id ) );
258    $node_detail= $return[0];
259
260    // non-admin people need to be affiliated with the right site
261    if( !in_array( 10, $_roles ) ) {
262      $node_site_id = $node_detail['site_id'];
263      $in_site = in_array ($node_site_id,$_person['site_ids']);
264      if( ! $in_site) {
265        $error= "Insufficient permission. You cannot create configuration files for this node.";
266      }
267    }
268
269    $hostname= $node_detail['hostname'];
270    $return= $api->GetInterfaces( array( "node_id" => $node_id ), NULL );
271    
272    $can_gen_config= 1;
273    $has_primary= 0;
274    
275    if( count($return) > 0 ) {
276      foreach( $return as $interface_detail ) {
277        if( $interface_detail['is_primary'] == true ) {
278          $has_primary= 1;
279          break;
280        }
281      }
282    }
283
284    if( !$has_primary ) {
285      $can_gen_config= 0;
286    } else {
287      if( $node_detail['hostname'] == "" ) {
288        $can_gen_config= 0;
289        $node_detail['hostname']= "<i>Missing</i>";
290      }
291      
292      $fields= array("method","ip");
293      foreach( $fields as $field ) {
294        if( $interface_detail[$field] == "" ) {
295          $can_gen_config= 0;
296          $interface_detail[$field]= "<i>Missing</i>";
297        }
298      }
299
300      if( $interface_detail['method'] == "static" ) {
301        $fields= array("gateway","netmask","network","broadcast","dns1");
302        foreach( $fields as $field ) {
303          if( $interface_detail[$field] == "" ) {
304            $can_gen_config= 0;
305            $interface_detail[$field]= "<i>Missing</i>";
306          }
307        }
308      }
309
310      if(    $interface_detail['method'] != "static" 
311          && $interface_detail['method'] != "dhcp" ) {
312        $can_gen_config= 0;
313        $interface_detail['method']= "<i>Unknown method</i>";
314      }
315    }
316
317    $download= $_POST['download'];
318    
319    if( $can_gen_config && !empty($download) ) {
320      switch ($action) {
321      case 'download-node-floppy':
322        $boot_action='node-floppy'; 
323        $location = "%d/%n-%v-rename-into-plnode%s";
324        break;
325      case 'download-node-iso':
326        $boot_action='node-iso';
327        $location = "%d/%n-%a-%v%s";
328        break;
329      case 'download-node-usb':
330        $boot_action='node-usb';
331        $location = "%d/%n-%a-%v%s";
332        break;
333      }   
334
335      $filename=$api->GetBootMedium($node_id,$boot_action,$location);
336      $error=$api->error();
337      if (empty($error) && empty($filename)) {
338        $error="Unexpected error from GetBootMedium - probably wrong directory modes";
339      }    
340      if (! empty($error)) {
341        print ("<div class='plc-error'> $error </div>\n");
342        print ("<p><a href='/db/nodes/index.php?id=$node_id'>Back to node </a>\n");
343        return ;
344      } else {
345        deliver_and_unlink ($filename);
346        exit();
347      }
348    }
349
350    drupal_set_title("Download boot material for $hostname");
351
352    $header= <<<EOF
353
354 WARNING: Creating a new configuration file for this node will generate
355 a new node key, and any existing configuration file will be unusable and
356  must be updated before the node can successfully boot, install, or
357 go into debug mode.
358
359 <p>In order to create a configuration file for this node using this page,
360 all the node network settings must be up to date. Below is summary of these
361 values. Any missing values must be entered before this can be used.
362
363 EOF;
364
365    echo $header;
366
367    show_download_confirm_button($api, $node_id, $action, $can_gen_config, false);
368    print ("<p>");
369    print ("<h3>Current node network settings</h3>\n");
370    
371 if( $has_primary ) {
372   print( "<table border=\"0\" cellspacing=\"4\">\n" );
373   
374   print( "<tr><th colspan=2><a href='index.php?id=$node_id'>Node Details</a></th></tr>" );
375   print( "<tr><th>node_id:</th>" );
376   print( "<td>$node_id</td></tr>\n" );
377   print( "<tr><th>Hostname:</th>" );
378   print( "<td>" . $node_detail['hostname'] . "</td></tr>\n" );
379
380   $nn_id = $interface_detail['interface_id'];
381   print( "<tr><th colspan=2><a href='interfaces.php?id=$nn_id'>Node Network Details</a></th></tr>" );
382
383   print( "<tr><th>Method:</th>" );
384   print( "<td>" . $interface_detail['method'] . "</td></tr>\n" );
385   print( "<tr><th>IP:</th>" );
386   print( "<td>" . $interface_detail['ip'] . "</td></tr>\n" );
387
388   if( $interface_detail['method'] == "static" ) {
389       print( "<tr><th>Gateway:</th>" );
390       print( "<td>" . $interface_detail['gateway'] . "</td></tr>\n" );
391       print( "<tr><th>Network mask:</th>" );
392       print( "<td>" . $interface_detail['netmask'] . "</td></tr>\n" );
393       print( "<tr><th>Network address:</th>" );
394       print( "<td>" . $interface_detail['network'] . "</td></tr>\n" );
395       print( "<tr><th>Broadcast address:</th>" );
396       print( "<td>" . $interface_detail['broadcast'] . "</td></tr>\n" );
397       print( "<tr><th>DNS 1:</th>" );
398       print( "<td>" . $interface_detail['dns1'] . "</td></tr>\n" );
399       print( "<tr><th>DNS 2:</th>" );
400       if( $interface_detail['dns2'] == "" ) {
401         print( "<td><i>Optional, missing</i></td></tr>\n" );
402       } else {
403         print( "<td>" . $interface_detail['dns2'] . "</td></tr>\n" );
404       }
405     }
406
407   print ("<tr><th colspan=2><a href='interfaces.php?id=$nn_id'>Additional Settings</a></th></tr>\n");
408   $nn_id = $interface_detail['interface_id'];
409   $settings=$api->GetInterfaceTags(array("interface_id" => array($nn_id)));
410   foreach ($settings as $setting) {
411     $category=$setting['category'];
412     $name=$setting['name'];
413     $value=$setting['value'];
414     print (" <tr><th> $category $name </th><td> $value </td></tr>\n");
415   }
416
417   print( "</table>\n" );
418 } else {
419   print( "<p class='plc-warning'>This node has no configured primary network.</p>\n" );
420 }
421
422  show_download_confirm_button($api, $node_id, $action, $can_gen_config, true);
423  break;
424  
425  default:
426    echo "Unkown action <$action>.";
427    header("location:index.php?id=" . $node_id);
428    exit();
429    break;
430  }
431
432 ?>