initial import from onelab svn codebase
[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 && method_exists($api,"GetBootMedium")) {
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 ( ! method_exists($api,"GetBootMedium")) {
225      print ("<span class='plc-warning'> API lacks support for GetBootMedium </span>");
226      return;
227    }
228      
229    if ($action=="download-generic-iso") {
230      $boot_action="generic-iso";
231    } else {
232      $boot_action="generic-usb";
233    }
234
235    // place the result in a random-named sub dir for clear filenames
236    $filename = $api->GetBootMedium ($node_id, $boot_action, "%d/%n-%p-%v%s");
237    $error=$api->error();
238    // NOTE. for some reason, GetBootMedium sometimes does not report an error but the
239    // file is not created - this happens e.g. when directory owmer/modes are wrong 
240    // in this case we get an empty filename
241    // see /etc/httpd/logs/error_log in this case
242    if (empty($error) && empty($filename)) {
243      $error="Unexpected error from GetBootMedium - probably wrong directory modes";
244    }    
245    if (! empty($error)) {
246      print ("<div class='plc-error'> $error </div>\n");
247      print ("<p><a href='/db/nodes/index.php?id=$node_id'>Back to node </a>\n");
248      return ;
249    } else {
250      deliver_and_unlink ($filename);
251      exit();
252    }
253    break;
254
255    // ACTION: download-node
256    // from former downloadconf.php
257    
258  case "download-node-floppy":
259  case "download-node-iso":
260  case "download-node-usb":
261    
262    $return= $api->GetNodes( array( $node_id ) );
263    $node_detail= $return[0];
264
265    // non-admin people need to be affiliated with the right site
266    if( !in_array( 10, $_roles ) ) {
267      $node_site_id = $node_detail['site_id'];
268      $in_site = in_array ($node_site_id,$_person['site_ids']);
269      if( ! $in_site) {
270        $error= "Insufficient permission. You cannot create configuration files for this node.";
271      }
272    }
273
274    $hostname= $node_detail['hostname'];
275    $return= $api->GetNodeNetworks( array( "node_id" => $node_id ), NULL );
276    
277    $can_gen_config= 1;
278    $has_primary= 0;
279    
280    if( count($return) > 0 ) {
281      foreach( $return as $node_network_detail ) {
282        if( $node_network_detail['is_primary'] == true ) {
283          $has_primary= 1;
284          break;
285        }
286      }
287    }
288
289    if( !$has_primary ) {
290      $can_gen_config= 0;
291    } else {
292      if( $node_detail['hostname'] == "" ) {
293        $can_gen_config= 0;
294        $node_detail['hostname']= "<i>Missing</i>";
295      }
296      
297      $fields= array("method","ip");
298      foreach( $fields as $field ) {
299        if( $node_network_detail[$field] == "" ) {
300          $can_gen_config= 0;
301          $node_network_detail[$field]= "<i>Missing</i>";
302        }
303      }
304
305      if( $node_network_detail['method'] == "static" ) {
306        $fields= array("gateway","netmask","network","broadcast","dns1");
307        foreach( $fields as $field ) {
308          if( $node_network_detail[$field] == "" ) {
309            $can_gen_config= 0;
310            $node_network_detail[$field]= "<i>Missing</i>";
311          }
312        }
313      }
314
315      if(    $node_network_detail['method'] != "static" 
316          && $node_network_detail['method'] != "dhcp" ) {
317        $can_gen_config= 0;
318        $node_network_detail['method']= "<i>Unknown method</i>";
319      }
320    }
321
322    $download= $_POST['download'];
323    
324    if( $can_gen_config && !empty($download) ) {
325      if (! method_exists ($api,"GetBootMedium")) {
326        $file_contents= $api->AdmGenerateNodeConfFile( $node_id );
327        header ("Content-Type: application/octet-stream");
328        header ("Content-Transfer-Encoding: binary");
329        header ("Content-Disposition: attachment; filename=plnode.txt" );
330        header ("Content-Length: " . strlen(file_contents));
331        header ("Pragma: hack");
332        header ("Cache-Control: public, must-revalidate");
333        print file_contents;
334        exit();
335      } else {
336        switch ($action) {
337        case 'download-node-floppy':
338          $boot_action='node-floppy'; 
339          $location = "%d/%n-%v-rename-into-plnode%s";
340          break;
341        case 'download-node-iso':
342          $boot_action='node-iso';
343          $location = "%d/%n-%v%s";
344          break;
345        case 'download-node-usb':
346          $boot_action='node-usb';
347          $location = "%d/%n-%v%s";
348          break;
349        }         
350
351        $filename=$api->GetBootMedium($node_id,$boot_action,$location);
352        $error=$api->error();
353        if (empty($error) && empty($filename)) {
354          $error="Unexpected error from GetBootMedium - probably wrong directory modes";
355        }    
356        if (! empty($error)) {
357          print ("<div class='plc-error'> $error </div>\n");
358          print ("<p><a href='/db/nodes/index.php?id=$node_id'>Back to node </a>\n");
359          return ;
360        } else {
361          deliver_and_unlink ($filename);
362          exit();
363        }
364      }
365        
366    }
367
368    drupal_set_title("Download boot material for $hostname");
369
370    $header= <<<EOF
371
372 WARNING: Creating a new configuration file for this node will generate
373 a new node key, and any existing configuration file will be unusable and
374  must be updated before the node can successfully boot, install, or
375 go into debug mode.
376
377 <p>In order to create a configuration file for this node using this page,
378 all the node network settings must be up to date. Below is summary of these
379 values. Any missing values must be entered before this can be used.
380
381 EOF;
382
383    echo $header;
384
385    show_download_confirm_button($api, $node_id, $action, $can_gen_config, false);
386    print ("<p>");
387    print ("<h3>Current node network settings</h3>\n");
388    
389 if( $has_primary ) {
390   print( "<table border=\"0\" cellspacing=\"4\">\n" );
391   
392   print( "<tr><th colspan=2><a href='index.php?id=$node_id'>Node Details</a></th></tr>" );
393   print( "<tr><th>node_id:</th>" );
394   print( "<td>$node_id</td></tr>\n" );
395   print( "<tr><th>Hostname:</th>" );
396   print( "<td>" . $node_detail['hostname'] . "</td></tr>\n" );
397
398   $nn_id = $node_network_detail['nodenetwork_id'];
399   print( "<tr><th colspan=2><a href='node_networks.php?id=$nn_id'>Node Network Details</a></th></tr>" );
400
401   print( "<tr><th>Method:</th>" );
402   print( "<td>" . $node_network_detail['method'] . "</td></tr>\n" );
403   print( "<tr><th>IP:</th>" );
404   print( "<td>" . $node_network_detail['ip'] . "</td></tr>\n" );
405
406   if( $node_network_detail['method'] == "static" ) {
407       print( "<tr><th>Gateway:</th>" );
408       print( "<td>" . $node_network_detail['gateway'] . "</td></tr>\n" );
409       print( "<tr><th>Network mask:</th>" );
410       print( "<td>" . $node_network_detail['netmask'] . "</td></tr>\n" );
411       print( "<tr><th>Network address:</th>" );
412       print( "<td>" . $node_network_detail['network'] . "</td></tr>\n" );
413       print( "<tr><th>Broadcast address:</th>" );
414       print( "<td>" . $node_network_detail['broadcast'] . "</td></tr>\n" );
415       print( "<tr><th>DNS 1:</th>" );
416       print( "<td>" . $node_network_detail['dns1'] . "</td></tr>\n" );
417       print( "<tr><th>DNS 2:</th>" );
418       if( $node_network_detail['dns2'] == "" ) {
419         print( "<td><i>Optional, missing</i></td></tr>\n" );
420       } else {
421         print( "<td>" . $node_network_detail['dns2'] . "</td></tr>\n" );
422       }
423     }
424
425   if (method_exists ($api,'GetNodeNetworkSettings')) {
426     print ("<tr><th colspan=2><a href='node_networks.php?id=$nn_id'>Additional Settings</a></th></tr>\n");
427     $nn_id = $node_network_detail['nodenetwork_id'];
428     $settings=$api->GetNodeNetworkSettings(array("nodenetwork_id" => array($nn_id)));
429     foreach ($settings as $setting) {
430       $category=$setting['category'];
431       $name=$setting['name'];
432       $value=$setting['value'];
433       print (" <tr><th> $category $name </th><td> $value </td></tr>\n");
434     }
435   }
436
437   print( "</table>\n" );
438 } else {
439   print( "<p class='plc-warning'>This node has no configured primary network.</p>\n" );
440 }
441
442  show_download_confirm_button($api, $node_id, $action, $can_gen_config, true);
443  break;
444  
445  default:
446    echo "Unkown action <$action>.";
447    header("location:index.php?id=" . $node_id);
448    exit();
449    break;
450  }
451
452 ?>