# IBM(c) 2008 EPL license http://www.eclipse.org/legal/epl-v10.html # Ver. 2.1 (4) - sf@mauricebrinkmann.de #------------------------------------------------------- =head1 xCAT plugin package to handle VirtualBox machines Supported command: rpower =cut #------------------------------------------------------- package xCAT_plugin::vbox; require Sys::Hostname; require xCAT::Table; require xCAT::Utils; require xCAT::MsgUtils; use Getopt::Long; 1; use strict; use warnings; my $cmd = 'clienttest'; #------------------------------------------------------- =head3 handled_commands Return list of commands handled by this plugin =cut #------------------------------------------------------- sub handled_commands { return {rpower => 'nodehm:power,mgt'}; } #------------------------------------------------------- =head3 preprocess_request Check and setup for hierarchy =cut #------------------------------------------------------- sub preprocess_request { my $req = shift; my $cb = shift; if ($req->{_xcatpreprocessed}->[0] == 1) { return [$req]; } #exit if preprocessed my $nodes = $req->{node}; my $service = "xcat"; # find service nodes for requested nodes # build an individual request for each service node my $sn = xCAT::Utils->get_ServiceNode($nodes, $service, "MN"); my @requests = (); # build each request for each service node foreach my $snkey (keys %$sn) { my $n=$sn->{$snkey}; print "snkey=$snkey, nodes=@$n\n"; my $reqcopy = {%$req}; $reqcopy->{node} = $sn->{$snkey}; $reqcopy->{'_xcatdest'} = $snkey; $reqcopy->{_xcatpreprocessed}->[0] = 1; push @requests, $reqcopy; } return \@requests; } #------------------------------------------------------- =head3 process_request Process the command =cut #------------------------------------------------------- sub process_request { my $request = shift; our $callback = shift; my $nodes = $request->{node}; my $command = $request->{command}->[0]; my $args = $request->{arg}; my $envs = $request->{env}; our $rsp; my $node; my $soapsupport = eval { require SOAP::Lite; }; unless ($soapsupport) { #Still no SOAP::Lite module $callback->({error=>"SOAP::Lite perl module missing, unable to fulfill Virtual Box plugin requirements",errorcode=>[42]}); return []; } require xCAT::vboxService; my @nodes=@$nodes; PROCESSNODES: foreach $node (@nodes) ###################################### { # Load node information from xCAT tables my ($error, $url, $username, $password, $vmname, $vncport) = getInfo($node); next PROCESSNODES if ($error == 1); last PROCESSNODES if ($error == 2); # Create connection to VirtualBox web service and check for errors my $vbox = getWebService($url, $username, $password, $node); next PROCESSNODES if (!$vbox); # Get the machine my $machine = getMachine($url, $vbox, $vmname, $node); if ($machine) { # Command differentiation #------------------------------------ if ($command eq "rpower" and @$args > 0) { powerCtrl($url, $vbox, $machine, $vmname, $node, $vncport, $args); } #else # unrecognized command #{ # addError("$node: $command unsupported on vm"); #} # End Command differentiation #-------------------------------- } # /existing $machine reference xCAT::vboxService->IWebsessionManager_logoff($url, $vbox); } # /for each $node in @nodes continue { # print out status before waiting for others and others and .. printMessages(); # yield??? } # last chance to print remaining messages printMessages(); return; sub addMessage ############################################################ { push @{$rsp->{data}}, shift; } # /addMessage sub addError ############################################################## { push @{$rsp->{error}}, shift; } # /addError sub printMessages ######################################################### { if (defined($rsp->{data}) or defined($rsp->{error})) { if (defined($rsp->{error})) { push @{$rsp->{errorcode}}, 1; xCAT::MsgUtils->message("E", $rsp, $callback, 0); } else { xCAT::MsgUtils->message("I", $rsp, $callback, 0); } } } # /printMessages sub powerCtrl ############################################################# { my ($url, $vbox, $machine, $vmname, $node, $vncport, $args) = (shift, shift, shift, shift, shift, shift, shift); # Get state of the machine first my $vmstate = xCAT::vboxService->IMachine_getState($url, $machine); # Check if there is anything to do if (!$vmstate) { addError("$node: Can not determine vm's state!"); return; } elsif ($vmstate =~ m/^PoweredOff|^Aborted|^Saved/) { if ($args->[0] eq "off") { addMessage("$node: off"); return; } elsif ($args->[0] =~ m/^reset|^boot/) { # if machine is off: turn on when reset was choosen $args->[0] = "on reset"; } elsif ($args->[0] =~ m/^stat/) { # addMessage("$node: off ($vmstate)"); addMessage("$node: off"); return; } } else { if ($args->[0] eq "on") { addMessage("$node: on"); return; } elsif ($args->[0] =~ m/^stat/) { # addMessage("$node: on ($vmstate)"); addMessage("$node: on"); return; } } # Determine machine's UUID - Should work since we got the state my $uuid = xCAT::vboxService->IMachine_getId($url, $machine); # Open Session my $session = getSession($url, $vbox, $node, $vmname); if ($session) { # Decide what to do if ($args->[0] =~ m/^on/) { powerOn($url, $vbox, $session, $uuid, $node, $vncport, $args); } else { powerOff($url, $vbox, $session, $machine, $uuid, $node, $args); } xCAT::vboxService->ISession_close($url, $session); } # /existing $session return; sub powerOn #********************************************************** { my ($url, $vbox, $session, $uuid, $node, $vncport, $args) = (shift, shift, shift, shift, shift, shift, shift); ########################## xCAT::vboxService->IVirtualBox_openSession( $url, $vbox, $session, $uuid); my $directmachine = xCAT::vboxService->ISession_getMachine($url, $session); if (!$directmachine) { addError("$node: Could not access mutable machine"); } else { my $vrdp = xCAT::vboxService->IMachine_getVRDPServer( $url, $directmachine); my $portno = xCAT::vboxService->IVRDPServer_getPort($url, $vrdp); if ($vncport and ($vncport != $portno)) { # If port is specified but different: Update machine settings xCAT::vboxService->IVRDPServer_setPort($url, $vrdp, $vncport); xCAT::vboxService->IMachine_saveSettings($url, $directmachine); } elsif (!$vncport) { # If not specified yet: Save port to xCAT db $vncport = $portno; my $vmTab = openTable('vm'); $vmTab->setNodeAttribs($node,{vncport=>$vncport}); $vmTab->close(); } xCAT::vboxService->IManagedObjectRef_release($url, $vrdp); xCAT::vboxService->IManagedObjectRef_release($url, $directmachine); } xCAT::vboxService->ISession_close($url, $session); ############################## my $progress = xCAT::vboxService->IVirtualBox_openRemoteSession( $url, $vbox, $session, $uuid, "vrdp", ""); if (!$progress) { addError("$node: Can not open remote session"); } else # existing $progress { xCAT::vboxService->IProgress_waitForCompletion( $url, $progress, -1); if (xCAT::vboxService->IProgress_getCompleted( $url, $progress)) { addMessage("$node: $args->[0]"); } else # not successfully completed { addError("$node: Power on failed"); } } # /existing $progress return; } # /powerOn sub powerOff #********************************************************* { my ($url, $vbox, $session, $machine, $uuid, $node, $args) = (shift, shift, shift, shift, shift, shift, shift); # Lock session with machine my $vmsessionstate = xCAT::vboxService->IMachine_getSessionState( $url, $machine); if ($vmsessionstate eq "Open") { xCAT::vboxService->IVirtualBox_openExistingSession( $url, $vbox, $session, $uuid); } elsif ($vmsessionstate eq "Closed") { xCAT::vboxService->IVirtualBox_openSession( $url, $vbox, $session, $uuid); } else # !$vmsessionstate { addError("$node: No direct session to machine"); } # The session is now locked with the machine, now its direct session or console can be optained and manipulated # Now get the console for the mutable machine my $console = xCAT::vboxService->ISession_getConsole($url, $session); if (!$console) { addError("$node: No console for mutable machine"); } else # existing $console { if ($args->[0] =~ m/^reset|^boot/) { xCAT::vboxService->IConsole_reset($url, $console); xCAT::vboxService->IManagedObjectRef_release($url, $console); addMessage("$node: on reset"); xCAT::vboxService->IManagedObjectRef_release($url, $console); return; } else { xCAT::vboxService->IConsole_powerButton($url, $console); unless (xCAT::vboxService->IConsole_getPowerButtonHandled( $url, $console) eq "true") { # In case of no reaction: force to power down machine xCAT::vboxService->IConsole_powerDown($url, $console); } addMessage("$node: off"); } xCAT::vboxService->IManagedObjectRef_release($url, $console); } # /existing $console return; } # /powerOff } # /powerCtrl sub getSession ############################################################ { my $sess; my ($url, $vbox, $node, $vmname) = (shift, shift, shift, shift); eval { $sess = xCAT::vboxService->IWebsessionManager_getSessionObject($url, $vbox); }; return $sess if ($sess and not $@); addError("$node: No session for $vmname on web service @ $url."); return undef; } # /getSession sub getMachine ############################################################ { my $machine; my ($url, $vbox, $vmname, $node) = (shift, shift, shift, shift); eval { $machine = xCAT::vboxService->IVirtualBox_findMachine($url, $vbox, $vmname); }; return $machine if ($machine and not $@); addError("$node: VM $vmname not known by the web service @ $url."); return undef; } # /getMachine sub getWebService ######################################################### { my $ws; my ($url, $user, $passwd, $node) = (shift, shift, shift, shift); eval { $ws = xCAT::vboxService->IWebsessionManager_logon($url, $user, $passwd); }; return $ws if ($ws and not $@); addError("$node: No connection to the web service @ $url."); return undef; } # /getWebService sub getInfo ############################################################### { my $node = shift; # Open tables first, return in case of a critical error = 2 my $hostsTab = openTable('hosts'); return (2, undef, undef, undef, undef) if (!$hostsTab); my $vmTab = openTable('vm'); return (2, undef, undef, undef, undef) if (!$vmTab); my $websrvTab = openTable('websrv'); return (2, undef, undef, undef, undef) if (!$websrvTab); # Load attributes, return in case of missing information error = 1 my $values; my @attributes = ('host','vncport','comments'); $values = loadValues($vmTab, $node, \@attributes); return (1, undef, undef, undef, undef) if (!defined($values)); my $hostname = $values->{host}; my $vncport = $values->{vncport}; my $comments = $values->{comments}; my $vmname = undef; if ($comments =~ m/vmname:(.+)!/) { $vmname = $1; } else { $vmname = $node; } @attributes = ('port','username', 'password'); $values = loadValues($websrvTab, $hostname, \@attributes); return (1, undef, undef, undef, undef) if (!defined($values)); my $port = $values->{port}; my $username = $values->{username}; my $password = $values->{password}; @attributes = ('ip'); $values = loadValues($hostsTab, $hostname, \@attributes); return (1, undef, undef, undef, undef) if (!defined($values)); my $ipaddr = $values->{ip}; my $url = "http://$ipaddr:$port/"; return (0, $url, $username, $password, $vmname, $vncport); sub loadValues #******************************************************* { my ($table, $node, $attributes) = (shift, shift, shift); my ($values) = $table->getAttribs({'node'=>$node}, @$attributes); $table->close; addError("$node: Missing information: @$attributes") if (!defined($values)); return $values; } # /loadValues sub openTable #******************************************************** { my $tabname = shift; my $table = xCAT::Table->new($tabname); # try to create vbox tables if they don't exist yet if (!$table and $tabname =~ m/websrv/) { $table = xCAT::Table->new( $tabname, -create=>1, -autocommit=>1 ); $table->close; addMessage("[xCAT] Info: The table $tabname has been created."); $table = xCAT::Table->new($tabname); } addError("[xCAT] Can not open table: $tabname. Command aborted.") if (!$table); return $table; } # /loadValues } # /getInfo } 1;