xcat-core/xCAT-server/lib/xcat/plugins/vbox.pm
2014-04-11 05:45:37 -04:00

688 lines
22 KiB
Perl

# IBM(c) 2008 EPL license http://www.eclipse.org/legal/epl-v10.html
# Ver. 2.1 (4) - sf@mauricebrinkmann.de
# Ver. 3.0 - Herbert Mehlhose, IBM - add support of VirtualBox V4
#
#-------------------------------------------------------
=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::TableUtils;
require xCAT::ServiceNodeUtils;
require xCAT::MsgUtils;
use Getopt::Long;
1;
use strict;
use warnings;
#-------------------------------------------------------
=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::ServiceNodeUtils->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
my $api; # we assume V4 as default API
my $vbox = getWebService($url, $username, $password, $node);
next PROCESSNODES if (!$vbox);
# get version
my $vboxvers = xCAT::vboxService->IVirtualBox_getVersion($url, $vbox);
#$callback->({info=>"$node: Vbox version: '$vboxvers', requested vncport via xCAT 'vm' table: '$vncport'"});
if ($vboxvers =~ "^3.0" and $vncport =~ ",")
{
addError("$node: VirtualBox version 3.0 does only support a single RDP port, please correct entry vncport='$vncport' in table 'vm' for this node.");
next PROCESSNODES;
}
if($vboxvers =~ "^3.") {
$api="3"; # There is no getAPIVersion in Version 3, set to 3
} elsif($vboxvers =~ "^4.0") {
$api="4_0"; # There is no getAPIVersion in Version 4.0 set to 4_0
} else {
$api = xCAT::vboxService->IVirtualBox_getAPIVersion($url, $vbox);
}
# Get the machine
my $machine = getMachine($api, $url, $vbox, $vmname, $node);
if ($machine) {
# Command differentiation #------------------------------------
if ($command eq "rpower" and @$args > 0) {
powerCtrl($vboxvers, $api, $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 ($vboxvers, $api, $url, $vbox, $machine, $vmname, $node, $vncport, $args) =
(shift, shift, shift, shift, shift, shift, shift, shift, shift);
# Get state of the machine first
my $cmd = "IMachine_getState";
if($api eq "3") {
$cmd=$cmd . "_V3";
}
my $vmstate = xCAT::vboxService->$cmd($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
$cmd = "IMachine_getId";
if($api eq "3") {$cmd=$cmd . "_V3";}
my $uuid = xCAT::vboxService->$cmd($url, $machine);
# Open Session
my $session = getSession($api, $url, $vbox, $node, $vmname);
if ($session) {
# Decide what to do
if ($args->[0] =~ m/^on/) {
if($api eq "3") {
powerOn_V3($vboxvers, $url, $vbox, $machine, $session, $uuid, $node, $vncport, $args);
} else {
powerOn($url, $vbox, $machine, $session, $uuid, $node, $vncport, $args);
}
} else {
if($api eq "3") {
powerOff_V3($url, $vbox, $session, $machine, $uuid, $node, $args);
} else {
powerOff($url, $vbox, $session, $machine, $uuid, $node, $args);
}
}
xCAT::vboxService->ISession_unlockMachine($url,$session);
} # /existing $session
return;
sub powerOn #**********************************************************
{
my ($url, $vbox, $machine, $session, $uuid, $node, $vncport, $args) =
(shift, shift, shift, shift, shift, shift, shift, shift);
my $locktype="Write";
xCAT::vboxService->IMachine_lockMachine($url,$machine,$session,$locktype);
my $directmachine = xCAT::vboxService->ISession_getMachine($url, $session);
if (!$directmachine) {
addError("$node: Could not access mutable machine");
} else {
my $vrde = xCAT::vboxService->IMachine_getVRDEServer($url, $directmachine);
# 'Oracle VM VirtualBox Extension Pack' needs to be installed on vbox server
# to be able to use RDP
my $extpack;
my @VRDEextpack = xCAT::vboxService->IVRDEServer_getVRDEExtPack($url, $vrde);
foreach(@VRDEextpack) {
if($_ eq "Oracle VM VirtualBox Extension Pack") {
$extpack=1;
}
}
if($extpack) {
my $hasVRDE = xCAT::vboxService->IVRDEServer_getEnabled($url, $vrde);
my $prop="TCP/Ports";
my $portno = xCAT::vboxService->IVRDEServer_getVRDEProperty($url, $vrde, $prop);
if ($vncport) { # vncport determines, if rdp will be enabled
my $change;
# If port is specified but different: Update machine settings
if($hasVRDE eq "false") {
$hasVRDE="true";
xCAT::vboxService->IVRDEServer_setEnabled($url, $vrde, $hasVRDE);
$change=1;
}
if($vncport ne $portno) {
xCAT::vboxService->IVRDEServer_setVRDEProperty($url, $vrde, $prop, $vncport);
$change=1;
}
if($change) {
xCAT::vboxService->IMachine_saveSettings($url, $directmachine);
}
} elsif (!$vncport and $hasVRDE eq "true") {
# If not specified: disable RDP for the guest
$hasVRDE="false";
xCAT::vboxService->IVRDEServer_setEnabled($url, $vrde, $hasVRDE);
xCAT::vboxService->IMachine_saveSettings($url, $directmachine);
}
#xCAT::vboxService->IMachine_saveSettings($url, $directmachine);
xCAT::vboxService->IManagedObjectRef_release($url, $directmachine);
} else {
if($vncport) {
addMessage("Warning: for VirtualBox Version 4 and above, RDP needs the VRDE Extension Pack to be installed on the VirtualBox host system. Either install this package to use RDP with the guest machine or remove the 'vncport' from xCAT vm table for '$node' to avoid this warning message to appear. Starting without RDP for now.");
}
}
}
xCAT::vboxService->ISession_unlockMachine($url,$session);
my $progress = xCAT::vboxService->IMachine_launchVMProcess($url, $machine, $session, "headless", ""); #gui
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 powerOn_V3 #**********************************************************
{
my ($vboxvers, $url, $vbox, $machine, $session, $uuid, $node, $vncport, $args) =
(shift, shift, shift, shift, shift, shift, shift, shift, shift);
xCAT::vboxService->IVirtualBox_openSession_V3($url, $vbox, $session, $uuid);
my $directmachine = xCAT::vboxService->ISession_getMachine_V3($url, $session);
my $hasVRDP;
if (!$directmachine) {
addError("$node: Could not access mutable machine");
} else {
my $vrdp = xCAT::vboxService->IMachine_getVRDPServer_V3($url, $directmachine);
$hasVRDP = xCAT::vboxService->IVRDPServer_getEnabled_V3($url, $vrdp);
my $portno;
my $vncportmismatch; # current rdp port differs from vm table vncport definition
if($vboxvers =~ "^3.0") { # returns single port as unsigned long
$portno = xCAT::vboxService->IVRDPServer_getPort_V30($url, $vrdp);
if ($vncport and ($vncport != $portno)) {
$vncportmismatch=1;
}
} else { # returns one or more ports as string
$portno = xCAT::vboxService->IVRDPServer_getPorts_V3($url, $vrdp);
if ($vncport and ($vncport ne $portno)) {
$vncportmismatch=1;
}
}
my $change;
if($vncport and ($hasVRDP eq "false")) {
$hasVRDP="true";
xCAT::vboxService->IVRDPServer_setEnabled_V3($url, $vrdp, $hasVRDP);
$change=1;
}
if ($vncportmismatch) {
if($vboxvers =~ "^3.0") { # returns single port as unsigned long
xCAT::vboxService->IVRDPServer_setPort_V30($url, $vrdp, $vncport);
} else {
xCAT::vboxService->IVRDPServer_setPorts_V3($url, $vrdp, $vncport);
}
$change=1;
}
if (!$vncport and $hasVRDP eq "true") {
$hasVRDP="false";
xCAT::vboxService->IVRDPServer_setEnabled_V3($url, $vrdp, $hasVRDP);
$change=1;
}
if ($change) {
xCAT::vboxService->IMachine_saveSettings_V3($url, $directmachine);
}
xCAT::vboxService->IManagedObjectRef_release_V3($url, $vrdp);
xCAT::vboxService->IManagedObjectRef_release_V3($url, $directmachine);
}
xCAT::vboxService->ISession_close_V3($url, $session);
my $v3session;
if ($hasVRDP eq "true") {
$v3session="vrdp"; #vrdp
} else {
# found "headles" to be undocumented in SDK ref, but this allows to open a headless session w/o VRDP
# and thus we can disable rdp without being forced to launch the gui. This allows to follow
# the logic, that an empty vncport in the vm table will disable rdp, and having a value in
# table vm will define the port and enable rdp.
$v3session="headless"; # gui
}
#my $progress = xCAT::vboxService->IVirtualBox_openRemoteSession_V3($url, $vbox, $session, $uuid, "vrdp", "");
my $progress = xCAT::vboxService->IVirtualBox_openRemoteSession_V3($url, $vbox, $session, $uuid, $v3session, "");
if (!$progress) {
addError("$node: Can not open remote session");
} else { # existing $progress
xCAT::vboxService->IProgress_waitForCompletion_V3($url, $progress, -1);
if (xCAT::vboxService->IProgress_getCompleted_V3($url, $progress)) {
addMessage("$node: $args->[0]");
} else { # not successfully completed
addError("$node: Power on failed");
}
} # /existing $progress
return;
} # /powerOn_V3
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);
my $vmsessiontype = xCAT::vboxService->IMachine_getSessionType($url, $machine);
if ($vmsessionstate eq "Locked") {
my $locktype="Shared";
xCAT::vboxService->IMachine_lockMachine($url,$machine,$session,$locktype);
} elsif ($vmsessionstate eq "Unlocked") {
my $locktype="Write";
xCAT::vboxService->IMachine_lockMachine($url,$machine,$session,$locktype);
} else { # !$vmsessionstate
addError("$node: No direct session to machine");
}
# just test: check sessiontype - it turns out, that this needs a lock from traces
#my $teste = xCAT::vboxService->ISession_getType($url, $session);
#addError("$node: sessiontype =$teste");
# 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
sub powerOff_V3 #*********************************************************
{
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_V3($url, $machine);
if ($vmsessionstate eq "Open") {
xCAT::vboxService->IVirtualBox_openExistingSession_V3($url, $vbox, $session, $uuid);
} elsif ($vmsessionstate eq "Closed") {
xCAT::vboxService->IVirtualBox_openSession_V3($url, $vbox, $session, $uuid);
} else {
addError("$node: No direct session to machine");
}
# Now get the console for the mutable machine
my $console = xCAT::vboxService->ISession_getConsole_V3($url, $session);
if (!$console) {
addError("$node: No console for mutable machine");
} else { # existing $console
if ($args->[0] =~ m/^reset|^boot/) {
xCAT::vboxService->IConsole_reset_V3($url, $console);
xCAT::vboxService->IManagedObjectRef_release_V3($url, $console);
addMessage("$node: on reset");
#xCAT::vboxService->IManagedObjectRef_release_V3($url, $console);
return;
} else {
xCAT::vboxService->IConsole_powerButton_V3($url, $console);
unless (xCAT::vboxService->IConsole_getPowerButtonHandled_V3($url, $console) eq "true") {
# In case of no reaction: force to power down machine
xCAT::vboxService->IConsole_powerDown_V3($url, $console);
} addMessage("$node: off");
}
xCAT::vboxService->IManagedObjectRef_release_V3($url, $console);
} # /existing $console
return;
} # /powerOff_V3
} # /powerCtrl
sub getSession ############################################################
{
my $sess;
my ($api, $url, $vbox, $node, $vmname) = (shift, shift, shift, shift, shift);
my $cmd = "IWebsessionManager_getSessionObject";
if($api eq "3") {$cmd=$cmd . "_V3";}
eval {
$sess = xCAT::vboxService->$cmd($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 ($api, $url, $vbox, $vmname, $node) = (shift, shift, shift, shift, shift);
my $cmd = "IVirtualBox_findMachine";
if($api eq "3") {$cmd=$cmd . "_V3";}
eval {
$machine = xCAT::vboxService->$cmd($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);
my $cmd = "IWebsessionManager_logon";
eval {
$ws = xCAT::vboxService->$cmd($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;
} # /openTable
} # /getInfo
}
# see Lite.pm for other serializer code, e.g. as_string
sub SOAP::Serializer::as_LockType
{
my ($self, $value, $name, $type, $attr) = @_;
die "String value expected instead of @{[ref $value]} reference\n"
if ref $value;
return [
$name,
{'xsi:type' => 'vbox:LockType', %$attr},
SOAP::Utils::encode_data($value)
];
}
1;