checkpoint
[plewww.git] / planetlab / nodes / node_actions.php
1 <?php
2
3   // $Id$
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 // NOTE: this function exits() after it completes its job, 
75 // simply returning leads to html decorations being added around the contents
76 function deliver_and_unlink ($filename) {
77   
78   // for security reasons we want to be able to unlink the resulting file once downloaded
79   // so simply redirecting through a header("Location:") is not good enough
80
81   $size= filesize($filename);
82
83   // headers
84   header ("Content-Type: application/octet-stream");
85   header ("Content-Transfer-Encoding: binary");
86   header ("Content-Disposition: attachment; filename=" . basename($filename) );
87   header ("Content-Length: " . $size );
88   // for getting IE to work properly
89   // from princeton cvs new_plc_www/planetlab/nodes/downloadconf.php 1.2->1.3
90   header ("Pragma: hack");
91   header ("Cache-Control: public, must-revalidate");
92
93   // outputs the whole file contents
94   print (file_get_contents($filename));
95   
96   // unlink the file
97   if (! unlink ($filename) ) {
98     // cannot unlink, but how can we notify this ?
99     // certainly not by printing
100   }
101   exit();
102 }
103
104 function show_download_confirm_button ($api, $node_id, $action, $can_gen_config, $show_details) {
105
106   if( $can_gen_config ) {
107     if ($show_details) {
108       $preview=$api->GetBootMedium($node_id,"node-preview","");
109       print ("<hr /> <h3>Current node configuration contents</h3>");
110       print ("<pre>\n$preview</pre>\n");
111       print ("<hr />");
112     }
113     $action_labels = array ('download-node-floppy' => 'textual node config (for floppy)' ,
114                             'download-node-iso' => 'ISO image',
115                             'download-node-usb' => 'USB image' );
116     
117     $format = $action_labels [ $action ] ;
118     print( "<p><form method='post' action='node_actions.php'>\n");
119     print ("<input type='hidden' name='node_id' value='$node_id'>\n");
120     print ("<input type='hidden' name='action' value='$action'>\n");
121     print ("<input type='hidden' name='download' value='1'>\n");
122     print( "<input type='submit' value='Download $format'>\n" );
123     print( "</form>\n" );
124   } else {
125     echo "<p><font color=red>Configuration file cannot be created until missing values above are updated.</font>";
126   }
127 }
128
129 // check arguments
130
131 if (empty($_POST['node_id'])) {
132   header ('location:index.php');
133   exit();
134  } else {
135   $node_id = intval($_POST['node_id']);
136 }
137
138 $action=$_POST['action'];
139
140 switch ($action) {
141
142   // ACTION: prompt-update
143   // from former node_update.php
144  case "prompt-update":
145    
146    require_once('plc_drupal.php');
147    $node_info= $api->GetNodes( array( $node_id ), array( "hostname", "model" ) );
148    drupal_set_title("Updating " . $node_info[0]['hostname']);
149    include 'plc_header.php';
150    
151    // start form
152    echo "<form method=post action='node_actions.php'>\n";
153    echo "<input type=hidden name='node_id' value='$node_id'></input>\n";
154    echo "<input type=hidden name='action' value='update'></input>\n";
155    if( $_POST['error'] ) plc_error(unserialize( $_POST['error']));
156    echo "<p><table cellpadding=2><tbody>\n
157        <tr><th>Hostname: </th><td> <input type=text size=35 name='hostname' value='". $node_info[0]['hostname'] ."'></td></tr>\n
158        <tr><th>Model: </th><td> <input type=text size=35 name='model' value='". $node_info[0]['model'] ."'></td></tr>\n
159        </tbody></table>\n
160        <p><input type=submit value='Update Node'>\n
161        </form>\n";
162    
163    echo "<p><a href='index.php?id=$node_id'>Back to Node</a>\n";
164    
165    break;
166
167    // ACTION: update
168    // from former node_actions.php
169  case "update":
170
171    $hostname= $_POST['hostname'];
172    $model= $_POST['model'];
173
174    $fields= array( "hostname"=>$hostname, "model"=>$model );
175    $api->UpdateNode( intval( $node_id ), $fields );
176    $error= $api->error();
177
178    if( empty( $error ) ) {
179      header( "location: index.php?id=$node_id" );
180      exit();
181    } else {
182      echo "<font color=red>". $error . "</font>\n" ;
183      echo "<p> Press back to retry</p>";
184    }
185
186    break;
187
188    // ACTION: delete
189    // from former node_actions.php
190  case "delete":
191    $api->DeleteNode( intval( $node_id ) );
192    header( "location: index.php" );
193    exit();
194    break;
195
196    // ACTION: boot-state
197    // from former node_actions.php
198  case "boot-state":
199    switch( $_POST['boot_state'] ) {
200    case 'boot':
201    case 'dbg':
202    case 'inst':
203    case 'rins':
204    case 'rcnf':
205    case 'new':
206      $api->UpdateNode( intval( $node_id ), array( "boot_state" => $_POST['boot_state'] ) );
207      header( "location: index.php?id=$node_id" );
208      exit();
209      break;
210    default:
211      print "<div class='plc-error'> no such boot state " . $_POST['boot_state'] . "</div>";
212      break;
213    }
214    break;
215
216   // ACTION: download-generic
217  case "download-generic-iso":
218  case "download-generic-usb":
219    
220    if ($action=="download-generic-iso") {
221      $boot_action="generic-iso";
222    } else {
223      $boot_action="generic-usb";
224    }
225
226    // place the result in a random-named sub dir for clear filenames
227    $filename = $api->GetBootMedium ($node_id, $boot_action, "%d/%n-%p-%a-%v%s");
228    $error=$api->error();
229    // NOTE. for some reason, GetBootMedium sometimes does not report an error but the
230    // file is not created - this happens e.g. when directory owmer/modes are wrong 
231    // in this case we get an empty filename
232    // see /etc/httpd/logs/error_log in this case
233    if (empty($error) && empty($filename)) {
234      $error="Unexpected error from GetBootMedium - probably wrong directory modes";
235    }    
236    if (! empty($error)) {
237      print ("<div class='plc-error'> $error </div>\n");
238      print ("<p><a href='/db/nodes/index.php?id=$node_id'>Back to node </a>\n");
239      return ;
240    } else {
241      deliver_and_unlink ($filename);
242      exit();
243    }
244    break;
245
246    // ACTION: download-node
247    // from former downloadconf.php
248    
249  case "download-node-floppy":
250  case "download-node-iso":
251  case "download-node-usb":
252    
253    $return= $api->GetNodes( array( $node_id ) );
254    $node_detail= $return[0];
255
256    // non-admin people need to be affiliated with the right site
257    if( ! plc_is_admin() ) {
258      $node_site_id = $node_detail['site_id'];
259      $in_site = plc_in_site($node_site_id);
260      if( ! $in_site) {
261        $error= "Insufficient permission. You cannot create configuration files for this node.";
262      }
263    }
264
265    $hostname= $node_detail['hostname'];
266    $return= $api->GetInterfaces( array( "node_id" => $node_id ), NULL );
267    
268    $can_gen_config= 1;
269    $has_primary= 0;
270    
271    if( count($return) > 0 ) {
272      foreach( $return as $interface_detail ) {
273        if( $interface_detail['is_primary'] == true ) {
274          $has_primary= 1;
275          break;
276        }
277      }
278    }
279
280    if( !$has_primary ) {
281      $can_gen_config= 0;
282    } else {
283      if( $node_detail['hostname'] == "" ) {
284        $can_gen_config= 0;
285        $node_detail['hostname']= "<i>Missing</i>";
286      }
287      
288      $fields= array("method","ip");
289      foreach( $fields as $field ) {
290        if( $interface_detail[$field] == "" ) {
291          $can_gen_config= 0;
292          $interface_detail[$field]= "<i>Missing</i>";
293        }
294      }
295
296      if( $interface_detail['method'] == "static" ) {
297        $fields= array("gateway","netmask","network","broadcast","dns1");
298        foreach( $fields as $field ) {
299          if( $interface_detail[$field] == "" ) {
300            $can_gen_config= 0;
301            $interface_detail[$field]= "<i>Missing</i>";
302          }
303        }
304      }
305
306      if(    $interface_detail['method'] != "static" 
307          && $interface_detail['method'] != "dhcp" ) {
308        $can_gen_config= 0;
309        $interface_detail['method']= "<i>Unknown method</i>";
310      }
311    }
312
313    $download= $_POST['download'];
314    
315    if( $can_gen_config && !empty($download) ) {
316      switch ($action) {
317      case 'download-node-floppy':
318        $boot_action='node-floppy'; 
319        $location = "%d/%n-%v-rename-into-plnode%s";
320        break;
321      case 'download-node-iso':
322        $boot_action='node-iso';
323        $location = "%d/%n-%a-%v%s";
324        break;
325      case 'download-node-usb':
326        $boot_action='node-usb';
327        $location = "%d/%n-%a-%v%s";
328        break;
329      }   
330
331      $filename=$api->GetBootMedium($node_id,$boot_action,$location);
332      $error=$api->error();
333      if (empty($error) && empty($filename)) {
334        $error="Unexpected error from GetBootMedium - probably wrong directory modes";
335      }    
336      if (! empty($error)) {
337        print ("<div class='plc-error'> $error </div>\n");
338        print ("<p><a href='/db/nodes/index.php?id=$node_id'>Back to node </a>\n");
339        return ;
340      } else {
341        deliver_and_unlink ($filename);
342        exit();
343      }
344    }
345
346    drupal_set_title("Download boot material for $hostname");
347
348    $header= <<<EOF
349
350 WARNING: Creating a new configuration file for this node will generate
351 a new node key, and any existing configuration file will be unusable and
352  must be updated before the node can successfully boot, install, or
353 go into debug mode.
354
355 <p>In order to create a configuration file for this node using this page,
356 all the interface settings must be up to date. Below is summary of these
357 values. Any missing values must be entered before this can be used.
358
359 EOF;
360
361    echo $header;
362
363    show_download_confirm_button($api, $node_id, $action, $can_gen_config, false);
364    print ("<p>");
365    print ("<h3>Current interface settings</h3>\n");
366    
367 if( $has_primary ) {
368   print( "<table border=\"0\" cellspacing=\"4\">\n" );
369   
370   print( "<tr><th colspan=2><a href='index.php?id=$node_id'>Node Details</a></th></tr>" );
371   print( "<tr><th>node_id:</th>" );
372   print( "<td>$node_id</td></tr>\n" );
373   print( "<tr><th>Hostname:</th>" );
374   print( "<td>" . $node_detail['hostname'] . "</td></tr>\n" );
375
376   $nn_id = $interface_detail['interface_id'];
377   print( "<tr><th colspan=2><a href='interfaces.php?id=$nn_id'>Interface Details</a></th></tr>" );
378
379   print( "<tr><th>Method:</th>" );
380   print( "<td>" . $interface_detail['method'] . "</td></tr>\n" );
381   print( "<tr><th>IP:</th>" );
382   print( "<td>" . $interface_detail['ip'] . "</td></tr>\n" );
383
384   if( $interface_detail['method'] == "static" ) {
385       print( "<tr><th>Gateway:</th>" );
386       print( "<td>" . $interface_detail['gateway'] . "</td></tr>\n" );
387       print( "<tr><th>Network mask:</th>" );
388       print( "<td>" . $interface_detail['netmask'] . "</td></tr>\n" );
389       print( "<tr><th>Network address:</th>" );
390       print( "<td>" . $interface_detail['network'] . "</td></tr>\n" );
391       print( "<tr><th>Broadcast address:</th>" );
392       print( "<td>" . $interface_detail['broadcast'] . "</td></tr>\n" );
393       print( "<tr><th>DNS 1:</th>" );
394       print( "<td>" . $interface_detail['dns1'] . "</td></tr>\n" );
395       print( "<tr><th>DNS 2:</th>" );
396       if( $interface_detail['dns2'] == "" ) {
397         print( "<td><i>Optional, missing</i></td></tr>\n" );
398       } else {
399         print( "<td>" . $interface_detail['dns2'] . "</td></tr>\n" );
400       }
401     }
402
403   print ("<tr><th colspan=2><a href='interfaces.php?id=$nn_id'>Additional Settings</a></th></tr>\n");
404   $nn_id = $interface_detail['interface_id'];
405   $settings=$api->GetInterfaceTags(array("interface_id" => array($nn_id)));
406   foreach ($settings as $setting) {
407     $category=$setting['category'];
408     $name=$setting['tagname'];
409     $value=$setting['value'];
410     print (" <tr><th> $category $name </th><td> $value </td></tr>\n");
411   }
412
413   print( "</table>\n" );
414 } else {
415   print( "<p class='plc-warning'>This node has no configured primary interface.</p>\n" );
416 }
417
418  show_download_confirm_button($api, $node_id, $action, $can_gen_config, true);
419  break;
420  
421  default:
422    echo "Unkown action <$action>.";
423    header("location:index.php?id=" . $node_id);
424    exit();
425    break;
426  }
427
428 ?>