package xCAT_plugin::esx; use strict; use warnings; use xCAT::Table; use xCAT::Utils; use xCAT::TableUtils; use xCAT::ServiceNodeUtils; use xCAT::TZUtils; use Time::HiRes qw (sleep); use xCAT::Template; use xCAT::MsgUtils; use xCAT::SvrUtils; use xCAT::NodeRange; use xCAT::Common; use xCAT::VMCommon; use POSIX "WNOHANG"; use Getopt::Long; use Thread qw(yield); use POSIX qw(WNOHANG nice); use File::Path qw/mkpath rmtree/; use File::Temp qw/tempdir/; use File::Copy; use Fcntl qw/:flock/; use IO::Socket; #Need name resolution use Scalar::Util qw/looks_like_number/; #use Data::Dumper; Getopt::Long::Configure("bundling"); Getopt::Long::Configure("pass_through"); my @cpiopid; our @ISA = 'xCAT::Common'; #in xCAT, the lifetime of a process ends on every request #therefore, the lifetime of assignments to these glabals as architected #is to be cleared on every request #my %esx_comm_pids; my %limbonodes; #nodes in limbo during a forced migration due to missing parent my %hyphash; #A data structure to hold hypervisor-wide variables (i.e. the current resource pool, virtual machine folder, connection object my %vcenterhash; #A data structure to reflect the state of vcenter connectivity to hypervisors my %vmhash; #store per vm info of interest my %clusterhash; my %hypready; #A structure for hypervisor readiness to be tracked before proceeding to normal operations my %running_tasks; #A struct to track this processes my $output_handler; #Pointer to the function to drive results to client my $executerequest; my $usehostnamesforvcenter; my %tablecfg; #to hold the tables my %hostrefbynode; my $currkey; my $requester; my $viavcenter; my $viavcenterbyhyp; my $vcenterautojoin = 1; my $datastoreautomount = 1; my $vcenterforceremove = 0; #used in rmhypervisor my $reconfigreset = 1; my $vmwaresdkdetect = eval { require VMware::VIRuntime; VMware::VIRuntime->import(); 1; }; my %lockhandles; sub recursion_copy { my $source = shift; my $destination = shift; my $dirhandle; opendir($dirhandle, $source); my $entry; foreach $entry (readdir($dirhandle)) { if ($entry eq '.' or $entry eq '..') { next; } my $tempsource = "$source/$entry"; my $tempdestination = "$destination/$entry"; if (-d $tempsource) { unless (-d $tempdestination) { mkdir $tempdestination or die "failure creating directory $tempdestination, $!"; } recursion_copy($tempsource, $tempdestination); } else { copy($tempsource, $tempdestination) or die "failed copy from $tempsource to $tempdestination, $!"; } } } sub lockbyname { my $name = shift; my $lckh; mkpath("/tmp/xcat/locks/"); while (-e "/tmp/xcat/locks/$name") { sleep 1; } open($lockhandles{$name}, ">>", "/tmp/xcat/locks/$name"); flock($lockhandles{$name}, LOCK_EX); } sub unlockbyname { my $name = shift; unlink("/tmp/xcat/locks/$name"); close($lockhandles{$name}); } my %guestidmap = ( "rhel.6.*" => "rhel6_", "rhel.5.*" => "rhel5_", "rhel4.*" => "rhel4_", "centos6.*" => "rhel6_", "centos5.*" => "rhel5_", "centos4.*" => "rhel4_", "sles12.*" => "sles12_", "sles11.*" => "sles11_", "sles10.*" => "sles10_", "win2k8" => "winLonghorn", "win2k8r2" => "windows7Server", "win2012" => "windows8Server", "hyperv2012" => "windows8Server", "esix5.*" => "vmkernel5", "esix4.*" => "vmkernel", "win8" => "windows8_", "win7" => "windows7_", "win2k3" => "winNetStandard", "imagex" => "winNetStandard", "boottarget" => "otherLinux" #otherGuest, otherGuest64, otherLinuxGuest, otherLinux64Guest ); sub handled_commands { return { copycd => 'esx', mknetboot => "nodetype:os=(esxi.*)", mkinstall => "nodetype:os=(esxi[56].*)", rpower => 'nodehm:power,mgt', esxiready => "esx", rsetboot => 'nodehm:power,mgt', rmigrate => 'nodehm:power,mgt', formatdisk => "nodetype:os=(esxi.*)", rescansan => "nodetype:os=(esxi.*)", mkvm => 'nodehm:mgt', rmvm => 'nodehm:mgt', clonevm => 'nodehm:mgt', createvcluster => 'esx', lsvcluster => 'esx', rmvcluster => 'esx', rinv => 'nodehm:mgt', chvm => 'nodehm:mgt', rshutdown => "nodetype:os=(esxi.*)", lsvm => [ 'hypervisor:type', 'nodetype:os=(esx.*)' ], rmhypervisor => [ 'hypervisor:type', 'nodetype:os=(esx.*)' ], chhypervisor => [ 'hypervisor:type', 'nodetype:os=(esx.*)' ], #lsvm => 'nodehm:mgt', not really supported yet }; } sub preprocess_request { my $request = shift; my $callback = shift; if ($request->{command}->[0] eq 'createvcluster' or $request->{command}->[0] eq 'lsvcluster' or $request->{command}->[0] eq 'rmvcluster') { return [$request]; } #if already preprocessed, go straight to request if ((defined($request->{_xcatpreprocessed})) && ($request->{_xcatpreprocessed}->[0] == 1)) { return [$request]; } my $username = 'root'; my $password = ''; my $vusername = "Administrator"; my $vpassword = ""; unless ($request and $request->{command} and $request->{command}->[0]) { return; } if ($request->{command}->[0] eq 'copycd') { #don't farm out copycd return [$request]; } elsif ($request->{command}->[0] eq 'mknetboot' or $request->{command}->[0] eq 'mkinstall') { return [$request]; } xCAT::Common::usage_noderange($request, $callback); if ($request->{_xcatpreprocessed} and $request->{_xcatpreprocessed}->[0] == 1) { return [$request]; } # exit if preprocesses my @requests; my $noderange; my $command = $request->{command}->[0]; if ($request->{node}) { $noderange = $request->{node}; # array ref } elsif ($command eq "esxiready") { my $node; ($node) = noderange($request->{'_xcat_clienthost'}->[0]); $noderange = [$node]; $request->{node} = $noderange; } my $extraargs = $request->{arg}; my @exargs = ($request->{arg}); my %hyp_hash = (); my %cluster_hash = (); # Get nodes from mp table and assign nodes to mp hash. my $passtab = xCAT::Table->new('passwd'); my $tmp; if ($passtab) { ($tmp) = $passtab->getAttribs({ 'key' => 'vmware' }, 'username', 'password'); if (defined($tmp)) { $username = $tmp->{username}; $password = $tmp->{password}; } ($tmp) = $passtab->getAttribs({ 'key' => 'vcenter' }, 'username', 'password'); if (defined($tmp)) { $vusername = $tmp->{username}; $vpassword = $tmp->{password}; } } my $vmtab = xCAT::Table->new("vm"); unless ($vmtab) { $callback->({ data => ["Cannot open vm table"] }); $request = {}; return; } my $vmtabhash = $vmtab->getNodesAttribs($noderange, [ 'host', 'migrationdest' ]); foreach my $node (@$noderange) { if ($command eq "rmhypervisor" or $command eq 'lsvm' or $command eq 'esxiready' or $command eq 'rshutdown' or $command eq "chhypervisor" or $command eq "formatdisk" or $command eq 'rescansan') { $hyp_hash{$node}{nodes} = [$node]; } else { my $ent = $vmtabhash->{$node}->[0]; if (defined($ent->{host})) { push @{ $hyp_hash{ $ent->{host} }{nodes} }, $node; } elsif (defined($ent->{migrationdest})) { $cluster_hash{ $ent->{migrationdest} }->{nodes}->{$node} = 1; } else { xCAT::SvrUtils::sendmsg([ 1, ": no host or cluster defined for guest" ], $callback, $node); } } } # find service nodes for the MMs # build an individual request for each service node my $service = "xcat"; my @hyps = keys(%hyp_hash); my %targethyps; if ($command eq 'rmigrate' and (scalar @{$extraargs} >= 1)) { @ARGV = @{$extraargs}; my $offline; my $junk; GetOptions( "f" => \$offline, "s=s" => \$junk #wo don't care about it, but suck up nfs:// targets so they don't get added ); my $dsthyp = $ARGV[0]; if ($dsthyp) { push @hyps, $dsthyp; $targethyps{$dsthyp} = 1; } } #TODO: per hypervisor table password lookup my @allnodes; push @allnodes, @hyps; push @allnodes, @$noderange; my $sn = xCAT::ServiceNodeUtils->get_ServiceNode(\@allnodes, $service, "MN"); #vmtabhash was from when we had vm.host do double duty for hypervisor data #$vmtabhash = $vmtab->getNodesAttribs(\@hyps,['host']); #We now use hypervisor fields to be unambiguous my $hyptab = xCAT::Table->new('hypervisor'); my $hyptabhash = {}; if ($hyptab) { $hyptabhash = $hyptab->getNodesAttribs(\@hyps, ['mgr']); } # build each request for each service node foreach my $snkey (keys %$sn) { my $reqcopy = {%$request}; $reqcopy->{'_xcatdest'} = $snkey; $reqcopy->{_xcatpreprocessed}->[0] = 1; my $hyps1 = $sn->{$snkey}; my @moreinfo = (); my @nodes = (); foreach (@$hyps1) { #This preserves the constructed data to avoid redundant table lookup my $cfgdata; if (not $targethyps{$_} and not $hyp_hash{$_}) { #a vm, skip it next; } elsif ($hyp_hash{$_}{nodes}) { push @nodes, @{ $hyp_hash{$_}{nodes} }; $cfgdata = "[$_][" . join(',', @{ $hyp_hash{$_}{nodes} }) . "][$username][$password][$vusername][$vpassword]"; #TODO: not use vm.host? } else { $cfgdata = "[$_][][$username][$password][$vusername][$vpassword]"; #TODO: not use vm.host? } if (defined $hyptabhash->{$_}->[0]->{mgr}) { $cfgdata .= "[" . $hyptabhash->{$_}->[0]->{mgr} . "]"; } else { $cfgdata .= "[]"; } push @moreinfo, $cfgdata; #"[$_][".join(',',@{$hyp_hash{$_}{nodes}})."][$username][$password]"; } foreach (keys %cluster_hash) { my $cluster; my $vcenter; if (/@/) { ($cluster, $vcenter) = split /@/, $_, 2; } else { die "TODO: implement default vcenter (for now, user, do vm.migratiodest=cluster" . '@' . "vcentername)"; } push @moreinfo, "[CLUSTER:$cluster][" . join(',', keys %{ $cluster_hash{$_}->{nodes} }) . "][$username][$password][$vusername][$vpassword][$vcenter]"; } if (scalar @nodes) { $reqcopy->{node} = \@nodes; } #print "nodes=@nodes\n"; $reqcopy->{moreinfo} = \@moreinfo; push @requests, $reqcopy; } return \@requests; } sub process_request { #$SIG{INT} = $SIG{TERM} = sub{ # foreach (keys %esx_comm_pids){ # kill 2,$_; # } # exit 0; #}; my $request = shift; $output_handler = shift; $executerequest = shift; if ($request->{_xcat_authname}->[0]) { $requester = $request->{_xcat_authname}->[0]; } %vcenterhash = (); #A data structure to reflect the state of vcenter connectivity to hypervisors my $level = shift; my $distname = undef; my $arch = undef; my $path = undef; my $command = $request->{command}->[0]; #The first segment is fulfilling the role of this plugin as #a hypervisor provisioning plugin (akin to anaconda, windows, sles plugins) if ($command eq 'copycd') { return copycd($request, $executerequest); } elsif ($command eq 'mkinstall') { return mkinstall($request, $executerequest); } elsif ($command eq 'mknetboot') { return mknetboot($request, $executerequest); } #From here on out, code for managing guests under VMware #Detect whether or not the VMware SDK is available on this specific system unless ($vmwaresdkdetect) { $vmwaresdkdetect = eval { require VMware::VIRuntime; VMware::VIRuntime->import(); 1; }; } unless ($vmwaresdkdetect) { xCAT::SvrUtils::sendmsg([ 1, "VMWare SDK required for operation, but not installed" ], $output_handler); return; } if ($command eq 'createvcluster') { create_new_cluster($request); return; } if ($command eq 'lsvcluster') { list_clusters($request); return; } if ($command eq 'rmvcluster') { remove_cluster($request); return; } my $moreinfo; my $noderange; if ($request->{node}) { $noderange = $request->{node}; # array ref } elsif ($command eq "esxiready") { my $node; ($node) = noderange($request->{'_xcat_clienthost'}->[0]); $noderange = [$node]; } xCAT::VMCommon::grab_table_data($noderange, \%tablecfg, $output_handler); my @exargs; unless ($command) { return; # Empty request } if (ref($request->{arg})) { @exargs = @{ $request->{arg} }; } else { @exargs = ($request->{arg}); } #pdu commands will be handled in the pdu plugin if ($command eq "rpower" and grep(/^pduon|pduoff|pdureset|pdustat$/, @exargs)) { return; } #my $sitetab = xCAT::Table->new('site'); #if($sitetab){ #(my $ref) = $sitetab->getAttribs({key => 'usehostnamesforvcenter'}, 'value'); my @entries = xCAT::TableUtils->get_site_attribute("usehostnamesforvcenter"); my $t_entry = $entries[0]; if (defined($t_entry)) { $usehostnamesforvcenter = $t_entry; } #($ref) = $sitetab->getAttribs({key => 'vcenterautojoin'}, 'value'); @entries = xCAT::TableUtils->get_site_attribute("vcenterautojoin"); $t_entry = $entries[0]; if (defined($t_entry)) { $vcenterautojoin = $t_entry; if ($vcenterautojoin =~ /^n/ or $vcenterautojoin =~ /^dis/) { $vcenterautojoin = 0; } } #($ref) = $sitetab->getAttribs({key => 'vmwaredatastoreautomount'}, 'value'); @entries = xCAT::TableUtils->get_site_attribute("vmwaredatastoreautomount"); $t_entry = $entries[0]; if (defined($t_entry)) { $datastoreautomount = $t_entry; if ($datastoreautomount =~ /^n/ or $datastoreautomount =~ /^dis/) { $datastoreautomount = 0; } } #($ref) = $sitetab->getAttribs({key => 'vmwarereconfigonpower'},'value'); @entries = xCAT::TableUtils->get_site_attribute("vmwarereconfigonpower"); $t_entry = $entries[0]; if (defined($t_entry)) { $reconfigreset = $t_entry; if ($reconfigreset =~ /^(n|d)/i) { #if no or disable, skip it $reconfigreset = 0; } } # } if ($request->{moreinfo}) { $moreinfo = $request->{moreinfo}; } else { $moreinfo = build_more_info($noderange, $output_handler); } foreach my $info (@$moreinfo) { $info =~ /^\[(.*?)\]\[(.*?)\]\[(.*?)\]\[(.*?)\]\[(.*?)\]\[(.*?)\]\[(.*?)\]/; my $hyp = $1; my @nodes = split(',', $2); my $username = $3; my $password = $4; my $tmpvcname = $7; my $tmpvcuname = $5; my $tmpvcpass = $6; if ($hyp =~ /^CLUSTER:/) { #a cluster, not a host. $hyp =~ s/^CLUSTER://; $clusterhash{$hyp}->{vcenter}->{name} = $tmpvcname; $clusterhash{$hyp}->{vcenter}->{username} = $tmpvcuname; $clusterhash{$hyp}->{vcenter}->{password} = $tmpvcpass; foreach (@nodes) { $clusterhash{$hyp}->{nodes}->{$_} = 1; } next; } $hyphash{$hyp}->{vcenter}->{name} = $tmpvcname; $hyphash{$hyp}->{vcenter}->{username} = $tmpvcuname; $hyphash{$hyp}->{vcenter}->{password} = $tmpvcpass; $hyphash{$hyp}->{username} = $username; # $nodeid; $hyphash{$hyp}->{password} = $password; # $nodeid; unless ($hyphash{$hyp}->{vcenter}->{password}) { $hyphash{$hyp}->{vcenter}->{password} = ""; } my $ent; for (my $i = 0 ; $i < @nodes ; $i++) { if ($command eq 'rmigrate' and grep /-f/, @exargs) { #offline migration, $hyphash{$hyp}->{offline} = 1; #if it is migrate and it has nodes, it is a source hypervisor apt to be offline #this will hint to relevant code to operate under the assumption of a #downed hypervisor source #note this will make dangerous assumptions, it will make a very minimal attempt #to operate normally, but really should only be called if the source is down and #fenced (i.e. storage, network, or turned off and stateless } my $node = $nodes[$i]; #my $nodeid = $ids[$i]; $hyphash{$hyp}->{nodes}->{$node} = 1; # $nodeid; } } my $hyptab = xCAT::Table->new('hypervisor', create => 0); if ($hyptab) { my @hyps = keys %hyphash; $tablecfg{hypervisor} = $hyptab->getNodesAttribs(\@hyps, [ 'mgr', 'netmap', 'defaultnet', 'cluster', 'preferdirect', 'datacenter' ]); } my $hoststab = xCAT::Table->new('hosts', create => 0); if ($hoststab) { my @hyps = keys %hyphash; $tablecfg{hosts} = $hoststab->getNodesAttribs(\@hyps, ['hostnames']); } #my $children = 0; #my $vmmaxp = 84; #$SIG{CHLD} = sub { my $cpid; while ($cpid = waitpid(-1, WNOHANG) > 0) { delete $esx_comm_pids{$cpid}; $children--; } }; $viavcenter = 0; if ($command eq 'rmigrate' or $command eq 'rmhypervisor') { #Only use vcenter when required, fewer prereqs $viavcenter = 1; } if ($command eq 'rmhypervisor' and grep /-f/, @exargs) { #force remove of hypervisor $vcenterforceremove = 1; } my $keytab = xCAT::Table->new('prodkey'); if ($keytab) { my @hypes = keys %hyphash; $tablecfg{prodkey} = $keytab->getNodesAttribs(\@hypes, [qw/product key/]); } my $hyp; my %needvcentervalidation; my $cluster; foreach $cluster (keys %clusterhash) { my $vcenter = $clusterhash{$cluster}->{vcenter}->{name}; unless ($vcenterhash{$vcenter}->{conn}) { eval { $vcenterhash{$vcenter}->{conn} = Vim->new(service_url => "https://$vcenter/sdk"); $vcenterhash{$vcenter}->{conn}->login(user_name => $clusterhash{$cluster}->{vcenter}->{username}, password => $clusterhash{$cluster}->{vcenter}->{password}); }; if ($@) { $vcenterhash{$vcenter}->{conn} = undef; xCAT::SvrUtils::sendmsg([ 1, "Unable to reach $vcenter vCenter server to manage cluster $cluster: $@" ], $output_handler); next; } my $clusternode; } $clusterhash{$cluster}->{conn} = $vcenterhash{$vcenter}->{conn}; foreach my $clusternode (keys %{ $clusterhash{$cluster}->{nodes} }) { $vmhash{$clusternode}->{conn} = $vcenterhash{$vcenter}->{conn}; } } foreach $hyp (sort(keys %hyphash)) { #if($pid == 0){ if ($viavcenter or (defined $tablecfg{hypervisor}->{$hyp}->[0]->{mgr} and not $tablecfg{hypervisor}->{$hyp}->[0]->{preferdirect})) { $viavcenterbyhyp->{$hyp} = 1; $hypready{$hyp} = 0; #This hypervisor requires a flag be set to signify vCenter sanenes before proceeding my $vcenter = $hyphash{$hyp}->{vcenter}->{name}; unless ($vcenterhash{$vcenter}->{conn}) { eval { $vcenterhash{$vcenter}->{conn} = Vim->new(service_url => "https://$vcenter/sdk"); $vcenterhash{$vcenter}->{conn}->login( user_name => $hyphash{$hyp}->{vcenter}->{username}, password => $hyphash{$hyp}->{vcenter}->{password} ); }; if ($@) { $vcenterhash{$vcenter}->{conn} = undef; xCAT::SvrUtils::sendmsg([ 1, "Unable to reach $vcenter vCenter server to manage $hyp: $@" ], $output_handler); next; } } my $hypnode; foreach $hypnode (keys %{ $hyphash{$hyp}->{nodes} }) { $vmhash{$hypnode}->{conn} = $vcenterhash{ $hyphash{$hyp}->{vcenter}->{name} }->{conn}; } $hyphash{$hyp}->{conn} = $vcenterhash{ $hyphash{$hyp}->{vcenter}->{name} }->{conn}; $hyphash{$hyp}->{vcenter}->{conn} = $vcenterhash{ $hyphash{$hyp}->{vcenter}->{name} }->{conn}; $needvcentervalidation{$hyp} = $vcenter; $vcenterhash{$vcenter}->{allhyps}->{$hyp} = 1; } else { eval { $hyphash{$hyp}->{conn} = Vim->new(service_url => "https://$hyp/sdk"); $hyphash{$hyp}->{conn}->login(user_name => $hyphash{$hyp}->{username}, password => $hyphash{$hyp}->{password}); }; if ($@) { $hyphash{$hyp}->{conn} = undef; xCAT::SvrUtils::sendmsg([ 1, "Unable to reach $hyp to perform operation due to $@" ], $output_handler); $hypready{$hyp} = -1; next; } my $localnode; foreach $localnode (keys %{ $hyphash{$hyp}->{nodes} }) { $vmhash{$localnode}->{conn} = $hyphash{$hyp}->{conn}; } validate_licenses($hyp); } #}else{ # $esx_comm_pids{$pid} = 1; #} } foreach $hyp (keys %needvcentervalidation) { my $vcenter = $needvcentervalidation{$hyp}; if (not defined $vcenterhash{$vcenter}->{hostviews}) { populate_vcenter_hostviews($vcenter); } if (validate_vcenter_prereqs($hyp, \&declare_ready, { hyp => $hyp, vcenter => $vcenter }) eq "failed") { $hypready{$hyp} = -1; } } while (grep { $_ == 0 } values %hypready) { wait_for_tasks(); sleep(1); #We'll check back in every second. Unfortunately, we have to poll since we are in web service land } my @badhypes; if (grep { $_ == -1 } values %hypready) { foreach (keys %hypready) { if ($hypready{$_} == -1) { unless ($hyphash{$_}->{offline}) { push @badhypes, $_; } my @relevant_nodes = sort (keys %{ $hyphash{$_}->{nodes} }); my $sadhypervisor = $_; foreach (@relevant_nodes) { if ($command eq "rmigrate" and grep /-f/, @exargs) { $limbonodes{$_} = $needvcentervalidation{$sadhypervisor}; } else { xCAT::SvrUtils::sendmsg([ 1, ": hypervisor unreachable" ], $output_handler, $_); } if ($command eq "rpower" and grep /stat/, @exargs) { $limbonodes{$_} = $needvcentervalidation{$sadhypervisor}; } #try to stat power anyway through vcenter of interest... } delete $hyphash{$_}; } } if (@badhypes) { xCAT::SvrUtils::sendmsg([ 1, ": The following hypervisors failed to become ready for the operation: " . join(',', @badhypes) ], $output_handler); } } do_cmd($command, @exargs); foreach (@badhypes) { delete $hyphash{$_}; } foreach my $vm (sort(keys %vmhash)) { $vmhash{$vm}->{conn}->logout(); } } sub validate_licenses { my $hyp = shift; my $conn = $hyphash{$hyp}->{conn}; #This can't possibly be called via a cluster stack, so hyphash is appropriate here unless ($tablecfg{prodkey}->{$hyp}) { #if no license specified, no-op return; } my $hv = get_hostview(hypname => $hyp, conn => $conn, properties => [ 'configManager', 'name' ]); my $lm = $conn->get_view(mo_ref => $hv->configManager->licenseManager); my @licenses; foreach (@{ $lm->licenses }) { push @licenses, uc($_->licenseKey); } my @newlicenses; foreach (@{ $tablecfg{prodkey}->{$hyp} }) { if (defined($_->{product}) and $_->{product} eq 'esx') { my $key = uc($_->{key}); unless (grep /$key/, @licenses) { push @newlicenses, $key; } } } foreach (@newlicenses) { $lm->UpdateLicense(licenseKey => $_); } } sub do_cmd { my $command = shift; my @exargs = @_; if ($command eq 'esxiready') { return; } if ($command eq 'rpower') { generic_vm_operation([ 'config.name', 'config.guestId', 'config.hardware.memoryMB', 'config.hardware.numCPU', 'runtime.powerState', 'runtime.host' ], \&power, @exargs); } elsif ($command eq 'rmvm') { generic_vm_operation([ 'config.name', 'runtime.powerState', 'runtime.host' ], \&rmvm, @exargs); } elsif ($command eq 'rsetboot') { generic_vm_operation([ 'config.name', 'runtime.host' ], \&setboot, @exargs); } elsif ($command eq 'rinv') { generic_vm_operation([ 'config.name', 'config', 'runtime.host', 'layoutEx' ], \&inv, @exargs); } elsif ($command eq 'formatdisk') { generic_hyp_operation(\&formatdisk, @exargs); } elsif ($command eq 'rescansan') { generic_hyp_operation(\&rescansan, @exargs); } elsif ($command eq 'rmhypervisor') { generic_hyp_operation(\&rmhypervisor, @exargs); } elsif ($command eq 'rshutdown') { generic_hyp_operation(\&rshutdown, @exargs); } elsif ($command eq 'chhypervisor') { generic_hyp_operation(\&chhypervisor, @exargs); } elsif ($command eq 'lsvm') { generic_hyp_operation(\&lsvm, @exargs); } elsif ($command eq 'clonevm') { generic_hyp_operation(\&clonevms, @exargs); } elsif ($command eq 'mkvm') { generic_hyp_operation(\&mkvms, @exargs); } elsif ($command eq 'chvm') { generic_vm_operation([ 'config.name', 'config', 'runtime.host' ], \&chvm, @exargs); #generic_hyp_operation(\&chvm,@exargs); } elsif ($command eq 'rmigrate') { #Technically, on a host view, but vcenter path is 'weirder' generic_hyp_operation(\&migrate, @exargs); } wait_for_tasks(); if ($command eq 'clonevm') { #TODO: unconditional, remove mkvms hosted copy my @dhcpnodes; foreach (keys %{ $tablecfg{dhcpneeded} }) { push @dhcpnodes, $_; delete $tablecfg{dhcpneeded}->{$_}; } unless ($::XCATSITEVALS{'dhcpsetup'} and ($::XCATSITEVALS{'dhcpsetup'} =~ /^n/i or $::XCATSITEVALS{'dhcpsetup'} =~ /^d/i or $::XCATSITEVALS{'dhcpsetup'} eq '0')) { $executerequest->({ command => ['makedhcp'], node => \@dhcpnodes }); } } } #inventory request for esx sub inv { my %args = @_; my $node = $args{node}; my $hyp = $args{hyp}; if (not defined $args{vmview}) { #attempt one refresh $args{vmview} = $vmhash{$node}->{conn}->find_entity_view(view_type => 'VirtualMachine', properties => [ 'config.name', 'runtime.powerState' ], filter => { name => $node }); if (not defined $args{vmview}) { xCAT::SvrUtils::sendmsg([ 1, "VM does not appear to exist" ], $output_handler, $node); return; } } if (not $args{vmview}->{config}) { xCAT::SvrUtils::sendmsg([ 1, "VM is in an invalid state" ], $output_handler, $node); return; } @ARGV = @{ $args{exargs} }; require Getopt::Long; my $tableUpdate; my $rc = GetOptions( 't' => \$tableUpdate, ); $SIG{__WARN__} = 'DEFAULT'; if (@ARGV > 1) { xCAT::SvrUtils::sendmsg("Invalid arguments: @ARGV", $output_handler); return; } if (!$rc) { return; } my $vmview = $args{vmview}; my $moref = $vmview->{mo_ref}->value; xCAT::SvrUtils::sendmsg("Managed Object Reference: $moref", $output_handler, $node); my $uuid = $vmview->config->uuid; $uuid =~ s/(..)(..)(..)(..)-(..)(..)-(..)(..)/$4$3$2$1-$6$5-$8$7/; xCAT::SvrUtils::sendmsg("UUID/GUID: $uuid", $output_handler, $node); my $cpuCount = $vmview->config->hardware->numCPU; xCAT::SvrUtils::sendmsg("CPUs: $cpuCount", $output_handler, $node); my $memory = $vmview->config->hardware->memoryMB; xCAT::SvrUtils::sendmsg("Memory: $memory MB", $output_handler, $node); my %updatehash = (cpus => $cpuCount, memory => $memory); my $devices = $vmview->config->hardware->device; my $label; my $size; my $fileName; my $device; if ($tableUpdate and $hyp) { validate_datastore_prereqs([$node], $hyp); #need datastoremaps to verify names... } my %vmstorageurls; foreach $device (@$devices) { $label = $device->deviceInfo->label; if ($label =~ /^Hard disk/) { $label .= " (d" . $device->controllerKey . ":" . $device->unitNumber . ")"; $size = $device->capacityInKB / 1024; $fileName = $device->backing->fileName; $output_handler->({ node => { name => $node, data => { desc => $label, contents => "$size MB @ $fileName" } } }); #if ($tableUpdate) { # $fileName =~ /\[([^\]]+)\]/; # $vmstorageurls{$hyphash{$hyp}->{datastoreurlmap}->{$1}}=1; #} } elsif ($label =~ /Network/) { xCAT::SvrUtils::sendmsg("$label: " . $device->macAddress, $output_handler, $node); } } if ($tableUpdate) { my $cfgdatastore; foreach (@{ $vmview->layoutEx->file }) { #TODO, track ALL layoutEx->file.... if ($_->type eq 'config') { $_->name =~ /\[([^\]]+)\]/; $cfgdatastore = $hyphash{$hyp}->{datastoreurlmap}->{$1}; last; } } my $cfgkey; if ($tablecfg{vm}->{$node}->[0]->{cfgstore}) { #check the config file explicitly, ignore the rest $cfgkey = 'cfgstore'; } elsif ($tablecfg{vm}->{$node}->[0]->{storage}) { #check the config file explicitly, ignore the rest $cfgkey = 'storage'; } my $configuration = $tablecfg{vm}->{$node}->[0]->{$cfgkey}; #TODO: prune urls that map to no layoutEx->file entries anymore my $configappend = $configuration; $configappend =~ s/^[^,=]*//; $tablecfg{vm}->{$node}->[0]->{$cfgkey} =~ m!nfs://([^/]+)/!; my $tablecfgserver = $1; my $cfgserver = inet_aton($tablecfgserver); if ($cfgserver) { $cfgserver = inet_ntoa($cfgserver); #get the IP address (TODO: really need to wrap getaddrinfo this handily... my $cfgurl = $tablecfg{vm}->{$node}->[0]->{$cfgkey}; $cfgurl =~ s/$tablecfgserver/$cfgserver/; if ($cfgurl ne $cfgdatastore) { $updatehash{$cfgkey} = $cfgdatastore . $configappend; } } } if ($tableUpdate) { my $vm = xCAT::Table->new('vm', -create => 1); $vm->setNodeAttribs($node, \%updatehash); } } #changes the memory, number of cpus and device size #can also add,resize and remove disks sub chvm { my %args = @_; my $node = $args{node}; my $hyp = $args{hyp}; if (not defined $args{vmview}) { #attempt one refresh $args{vmview} = $vmhash{$node}->{conn}->find_entity_view(view_type => 'VirtualMachine', properties => [ 'config.name', 'runtime.powerState' ], filter => { name => $node }); if (not defined $args{vmview}) { xCAT::SvrUtils::sendmsg([ 1, "VM does not appear to exist" ], $output_handler, $node); return; } } @ARGV = @{ $args{exargs} }; my @deregister; my @purge; my @add; my %resize; my $cpuCount; my $memory; my $vmview = $args{vmview}; require Getopt::Long; $SIG{__WARN__} = sub { xCAT::SvrUtils::sendmsg([ 1, "Could not parse options, " . shift() ], $output_handler); }; my @otherparams; my $cdrom; my $eject; my $rc = GetOptions( "d=s" => \@deregister, "p=s" => \@purge, "a=s" => \@add, "o=s" => \@otherparams, "resize=s%" => \%resize, "optical|cdrom|c=s" => \$cdrom, "eject" => \$eject, "cpus=s" => \$cpuCount, "mem=s" => \$memory ); $SIG{__WARN__} = 'DEFAULT'; if (@ARGV) { xCAT::SvrUtils::sendmsg("Invalid arguments: @ARGV", $output_handler); return; } if (!$rc) { return; } #use Data::Dumper; #xCAT::SvrUtils::sendmsg("dereg = ".Dumper(\@deregister)); #xCAT::SvrUtils::sendmsg("purge = ".Dumper(\@purge)); #xCAT::SvrUtils::sendmsg("add = ".Dumper(\@add)); #xCAT::SvrUtils::sendmsg("resize = ".Dumper(\%resize)); #xCAT::SvrUtils::sendmsg("cpus = $cpuCount"); #xCAT::SvrUtils::sendmsg("mem = ".getUnits($memory,"K",1024)); my %conargs; if ($cpuCount) { if ($cpuCount =~ /^\+(\d+)/) { $cpuCount = $vmview->config->hardware->numCPU + $1; } elsif ($cpuCount =~ /^-(\d+)/) { $cpuCount = $vmview->config->hardware->numCPU - $1; } $conargs{numCPUs} = $cpuCount; } if ($memory) { if ($memory =~ /^\+(.+)/) { $conargs{memoryMB} = $vmview->config->hardware->memoryMB + getUnits($1, "M", 1048576); } elsif ($memory =~ /^-(\d+)/) { $conargs{memoryMB} = $vmview->config->hardware->memoryMB - getUnits($1, "M", 1048576); } else { $conargs{memoryMB} = getUnits($memory, "M", 1048576); } } my $disk; my $devices = $vmview->config->hardware->device; my $label; my $device; my $cmdLabel; my $newSize; my @devChanges; if (@deregister) { for $disk (@deregister) { $device = getDiskByLabel($disk, $devices); unless ($device) { xCAT::SvrUtils::sendmsg([ 1, "Disk: $disk does not exist" ], $output_handler, $node); return; } #xCAT::SvrUtils::sendmsg(Dumper($device)); push @devChanges, VirtualDeviceConfigSpec->new( device => $device, operation => VirtualDeviceConfigSpecOperation->new('remove')); } } if (@purge) { for $disk (@purge) { $device = getDiskByLabel($disk, $devices); unless ($device) { xCAT::SvrUtils::sendmsg([ 1, "Disk: $disk does not exist" ], $output_handler, $node); return; } #xCAT::SvrUtils::sendmsg(Dumper($device)); push @devChanges, VirtualDeviceConfigSpec->new( device => $device, operation => VirtualDeviceConfigSpecOperation->new('remove'), fileOperation => VirtualDeviceConfigSpecFileOperation->new('destroy')); } } if (@add) { my $addSizes = join(',', @add); my $scsiCont; my $scsiUnit; my $ideCont; my $ideUnit; my $label; my $idefull = 0; my $scsifull = 0; foreach $device (@$devices) { $label = $device->deviceInfo->label; if ($label =~ /^SCSI controller/) { my $tmpu = getAvailUnit($device->{key}, $devices, maxnum => 15); if ($tmpu > 0) { $scsiCont = $device; $scsiUnit = $tmpu; } else { $scsifull = 1; } #ignore scsiControllers that are full, problem still remains if trying to add across two controllers in one go } if ($label =~ /^IDE/ and not $ideCont) { my $tmpu = getAvailUnit($device->{key}, $devices, maxnum => 1); if ($tmpu >= 0) { $ideCont = $device; $ideUnit = $tmpu; } elsif ($device->{key} == 201) { $idefull = 1; } } } unless ($hyphash{$hyp}->{datastoremap}) { validate_datastore_prereqs([], $hyp); } push @devChanges, create_storage_devs($node, $hyphash{$hyp}->{datastoremap}, $addSizes, $scsiCont, $scsiUnit, $ideCont, $ideUnit, $devices, idefull => $idefull, scsifull => $scsifull); } if ($cdrom or $eject) { my $opticalbackingif; my $opticalconnectable; if ($cdrom) { my $storageurl; if ($cdrom =~ m!://!) { $storageurl = $cdrom; $storageurl =~ s!/[^/]*\z!!; unless (validate_datastore_prereqs([], $hyp, { $storageurl => [$node] })) { xCAT::SvrUtils::sendmsg([ 1, "Unable to find/mount datastore holding $cdrom" ], $output_handler, $node); return; } $cdrom =~ s!.*/!!; } else { $storageurl = $tablecfg{vm}->{$node}->[0]->{storage}; $storageurl =~ s/=.*//; $storageurl =~ s/.*,//; $storageurl =~ s/\/\z//; } $opticalbackingif = VirtualCdromIsoBackingInfo->new(fileName => "[" . $hyphash{$hyp}->{datastoremap}->{$storageurl} . "] $cdrom"); $opticalconnectable = VirtualDeviceConnectInfo->new(startConnected => 1, allowGuestControl => 1, connected => 1); } elsif ($eject) { $opticalbackingif = VirtualCdromRemoteAtapiBackingInfo->new(deviceName => ""); $opticalconnectable = VirtualDeviceConnectInfo->new(startConnected => 0, allowGuestControl => 1, connected => 0); } my $oldcd; foreach my $dev (@$devices) { if ($dev->deviceInfo->label eq "CD/DVD drive 1") { $oldcd = $dev; last; } } unless ($oldcd) { if ($cdrom) { xCAT::SvrUtils::sendmsg([ 1, "Unable to find Optical drive in VM to insert ISO image" ], $output_handler, $node); } else { xCAT::SvrUtils::sendmsg([ 1, "Unable to find Optical drive in VM to perform eject" ], $output_handler, $node); } return; } my $newDevice = VirtualCdrom->new(backing => $opticalbackingif, key => $oldcd->key, controllerKey => 201, unitNumber => 0, connectable => $opticalconnectable, ); push @devChanges, VirtualDeviceConfigSpec->new( device => $newDevice, operation => VirtualDeviceConfigSpecOperation->new('edit')); } if (%resize) { while (my ($key, $value) = each(%resize)) { my @drives = split(/,/, $key); for my $device (@drives) { my $disk = $device; $device = getDiskByLabel($disk, $devices); unless ($device) { xCAT::SvrUtils::sendmsg([ 1, "Disk: $disk does not exist" ], $output_handler, $node); return; } if ($value =~ /^\+(.+)/) { $value = $device->capacityInKB + getUnits($1, "G", 1024); } else { $value = getUnits($value, "G", 1024); } my $newDevice = VirtualDisk->new(deviceInfo => $device->deviceInfo, key => $device->key, controllerKey => $device->controllerKey, unitNumber => $device->unitNumber, deviceInfo => $device->deviceInfo, backing => $device->backing, capacityInKB => $value); push @devChanges, VirtualDeviceConfigSpec->new( device => $newDevice, operation => VirtualDeviceConfigSpecOperation->new('edit')); } } } if (@devChanges) { $conargs{deviceChange} = \@devChanges; } if (@otherparams) { my $key; my $value; my @optionvals; foreach (@otherparams) { ($key, $value) = split /=/; unless ($key) { xCAT::SvrUtils::sendmsg([ 1, "Invalid format for other parameter specification" ], $output_handler, $node); return; } if ($value) { push @optionvals, OptionValue->new(key => $key, value => $value); } else { push @optionvals, OptionValue->new(key => $key); #the api doc says this is *supposed* to delete a key, don't think it works though, e.g. http://communities.vmware.com/message/1602644 } } $conargs{extraConfig} = \@optionvals; } my $reconfigspec = VirtualMachineConfigSpec->new(%conargs); #xCAT::SvrUtils::sendmsg("reconfigspec = ".Dumper($reconfigspec)); my $task = $vmview->ReconfigVM_Task(spec => $reconfigspec); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&chvm_task_callback; $running_tasks{$task}->{hyp} = $hyp; $running_tasks{$task}->{data} = { node => $node, successtext => "node successfully changed", cpus => $cpuCount, mem => $memory }; } sub getUsedUnits { my $contKey = shift; my $devices = shift; my %usedids; $usedids{7} = 1; $usedids{'7'} = 1; #TODO: figure out which of these is redundant, the string or the number variant for my $device (@$devices) { if ($device->{controllerKey} eq $contKey) { $usedids{ $device->{unitNumber} } = 1; } } return \%usedids; } sub getAvailUnit { my $contKey = shift; my $devices = shift; my %args = @_; my $maxunit = -1; if (defined $args{maxnum}) { $maxunit = $args{maxnum}; } my %usedids; $usedids{7} = 1; $usedids{'7'} = 1; #TODO: figure out which of these is redundant, the string or the number variant for my $device (@$devices) { if ($device->{controllerKey} eq $contKey) { $usedids{ $device->{unitNumber} } = 1; } } my $highestUnit = 0; while ($usedids{$highestUnit}) { if ($highestUnit == $maxunit) { return -1; } $highestUnit++; } return $highestUnit; } #given a device list from a vm and a label for a hard disk, returns the device object sub getDiskByLabel { my $cmdLabel = shift; my $devices = shift; my $device; my $label; $cmdLabel = commandLabel($cmdLabel); foreach $device (@$devices) { $label = $device->deviceInfo->label; if ($cmdLabel eq $label) { return $device; } elsif (($label =~ /^Hard disk/) and ($cmdLabel =~ /^d(.*)/)) { my $desc = $1; if ($desc =~ /(.*):(.*)/) { #specific my $controller = $1; my $unit = $2; if ($device->unitNumber == $unit and $device->controllerKey == $controller) { return $device; } } elsif ($desc =~ /\d+/ and $device->unitNumber == $desc) { #not specific return $device; } } } return undef; } #takes a label for a hard disk and prepends "Hard disk " if it's not there already sub commandLabel { my $label = shift; if (($label =~ /^Hard disk/) or ($label =~ /^d\d+/)) { return $label; } return "Hard disk " . $label; } #this function will check pending task status sub process_tasks { foreach (keys %running_tasks) { my $curcon; if (defined $running_tasks{$_}->{conn}) { $curcon = $running_tasks{$_}->{conn}; } elsif ($running_tasks{$_}->{hyp}) { $curcon = $hyphash{ $running_tasks{$_}->{hyp} }->{conn}; } elsif ($running_tasks{$_}->{vm}) { $curcon = $vmhash{ $running_tasks{$_}->{vm} }->{conn}; } elsif ($running_tasks{$_}->{cluster}) { $curcon = $clusterhash{ $running_tasks{$_}->{cluster} }->{conn}; } else { use Carp qw/confess/; confess "This stack trace indicates a cluster unfriendly path"; } my $curt = $curcon->get_view(mo_ref => $running_tasks{$_}->{task}); my $state = $curt->info->state->val; unless ($state eq 'running' or $state eq 'queued') { $running_tasks{$_}->{callback}->($curt, $running_tasks{$_}->{data}); delete $running_tasks{$_}; } if ($state eq 'running' and not $running_tasks{$_}->{questionasked}) { # and $curt->info->progress == 95) { #This is unfortunate, there should be a 'state' to indicate a question is blocking #however there isn't, so if we see something running at 95%, we just manually see if a question blocked the rest my $vm; $@ = ""; eval { $vm = $curcon->get_view(mo_ref => $curt->info->entity); }; if ($@) { $vm = 0; } if ($vm and $vm->{summary} and $vm->summary->{runtime} and $vm->summary->runtime->{question} and $vm->summary->runtime->question) { $running_tasks{$_}->{questionasked} = 1; $running_tasks{$_}->{callback}->($curt, $running_tasks{$_}->{data}, $vm->summary->runtime->question, $vm); } } } } #this function is a barrier to ensure prerequisites are met sub wait_for_tasks { while (scalar keys %running_tasks) { process_tasks; sleep(1); #We'll check back in every second. Unfortunately, we have to poll since we are in web service land } } sub connecthost_callback { my $task = shift; my $args = shift; my $hv = $args->{hostview}; my $state = $task->info->state->val; if ($state eq "success") { $hypready{ $args->{hypname} } = 1; #declare readiness enable_vmotion(hypname => $args->{hypname}, hostview => $args->{hostview}, conn => $args->{conn}); $vcenterhash{ $args->{vcenter} }->{goodhyps}->{ $args->{hypname} } = 1; if (defined $args->{depfun}) { #If a function is waiting for the host connect to go valid, call it $args->{depfun}->($args->{depargs}); } return; } my $thumbprint; eval { $thumbprint = $task->{info}->error->fault->thumbprint; }; if ($thumbprint) { #was an unknown certificate error, retry and accept the unknown certificate $args->{connspec}->{sslThumbprint} = $task->info->error->fault->thumbprint; my $task; if (defined $args->{hostview}) { #It was a reconnect request $task = $hv->ReconnectHost_Task(cnxSpec => $args->{connspec}); } elsif (defined $args->{foldview}) { #was an add host request $task = $args->{foldview}->AddStandaloneHost_Task(spec => $args->{connspec}, addConnected => 1); } elsif (defined $args->{cluster}) { #was an add host to cluster request $task = $args->{cluster}->AddHost_Task(spec => $args->{connspec}, asConnected => 1); } $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&connecthost_callback; $running_tasks{$task}->{conn} = $args->{conn}; $running_tasks{$task}->{data} = $args; #{ conn_spec=>$connspec,hostview=>$hv,hypname=>$args->{hypname},vcenter=>$args->{vcenter} }; } elsif ($state eq 'error') { my $error = $task->info->error->localizedMessage; if (defined($task->info->error->fault->faultMessage)) { #Only in 4.0, support of 3.5 must be careful? foreach (@{ $task->info->error->fault->faultMessage }) { $error .= $_->message; } } xCAT::SvrUtils::sendmsg([ 1, $error ], $output_handler); #,$node); $hypready{ $args->{hypname} } = -1; #Impossible for this hypervisor to ever be ready $vcenterhash{ $args->{vcenter} }->{badhyps}->{ $args->{hypname} } = 1; } } sub delhost_callback { #only called in rmhypervisor -f case during validate vcenter phase my $task = shift; my $args = shift; my $hv = $args->{hostview}; my $state = $task->info->state->val; if ($state eq "success") { xCAT::SvrUtils::sendmsg("removed", $output_handler, $args->{hypname}); $hypready{ $args->{hypname} } = -1; #Impossible for this hypervisor to ever be ready $vcenterhash{ $args->{vcenter} }->{badhyps}->{ $args->{hypname} } = 1; } elsif ($state eq 'error') { my $error = $task->info->error->localizedMessage; if (defined($task->info->error->fault->faultMessage)) { #Only in 4.0, support of 3.5 must be careful? foreach (@{ $task->info->error->fault->faultMessage }) { $error .= $_->message; } } xCAT::SvrUtils::sendmsg([ 1, $error ], $output_handler); #,$node); $hypready{ $args->{hypname} } = -1; #Impossible for this hypervisor to ever be ready $vcenterhash{ $args->{vcenter} }->{badhyps}->{ $args->{hypname} } = 1; } } sub get_clusterview { my %args = @_; my $clustname = $args{clustname}; my %subargs = ( view_type => 'ClusterComputeResource', ); if ($args{properties}) { $subargs{properties} = $args{properties}; } $subargs{filter} = { name => $clustname }; my $view = $args{conn}->find_entity_view(%subargs); return $view; #foreach (@{$args{conn}->find_entity_views(%subargs)}) { # if ($_->name eq "$clustname") { # return $_; # last; # } #} } sub get_hostview { my %args = @_; my $host = $args{hypname}; my %subargs = ( view_type => 'HostSystem', ); if ($args{properties}) { $subargs{properties} = $args{properties}; } my @addrs = gethostbyname($host); my $ip; my $name; my $aliases; if ($addrs[4]) { $ip = inet_ntoa($addrs[4]); ($name, $aliases) = gethostbyaddr($addrs[4], AF_INET); #TODO: IPv6 } else { ($ip, $name, $aliases) = ($host, $host, ""); } my @matchvalues = ($host, $ip, $name); foreach (split /\s+/, $aliases) { push @matchvalues, $_; } my $view; $subargs{filter} = { 'name' => qr/$host(?:\.|\z)/ }; $view = $args{conn}->find_entity_view(%subargs); if ($view) { return $view; } foreach (@matchvalues) { $subargs{filter} = { 'name' => qr/$_(?:\.|\z)/ }; $view = $args{conn}->find_entity_view(%subargs); if ($view) { return $view; } } $subargs{filter} = { 'name' => qr/localhost(?:\.|\z)/ }; $view = $args{conn}->find_entity_view(%subargs); if ($view) { return $view; } return undef; #rest of function should be obsoleted, going to run with that assumption for 2.5 at least # $subargs{filter}={'name' =~ qr/.*/}; # foreach (@{$args{conn}->find_entity_views(%subargs)}) { # my $view = $_; # if ($_->name =~ /$host(?:\.|\z)/ or $_->name =~ /localhost(?:\.|\z)/ or grep { $view->name =~ /$_(?:\.|\z)/ } @matchvalues) { # return $view; # last; # } # } } sub enable_vmotion { #TODO: vmware 3.x semantics too? this is 4.0... my %args = @_; unless ($args{hostview}) { $args{hostview} = get_hostview(conn => $args{conn}, hypname => $args{hypname}, properties => [ 'configManager', 'name' ]); } my $nicmgr = $args{conn}->get_view(mo_ref => $args{hostview}->configManager->virtualNicManager); my $qnc = $nicmgr->QueryNetConfig(nicType => "vmotion"); if ($qnc->{selectedVnic}) { return 1; } else { my $vniccount = scalar @{ $qnc->candidateVnic }; if ($vniccount == 1 or ($vniccount == 2 and $qnc->candidateVnic->[1]->spec->ip->ipAddress =~ /^169.254/)) { #There is only one possible path, use it $nicmgr->SelectVnicForNicType(nicType => "vmotion", device => $qnc->candidateVnic->[0]->device); return 1; } else { xCAT::SvrUtils::sendmsg([ 1, "TODO: use configuration to pick the nic " . $args{hypname} ], $output_handler); } return 0; } } sub mkvm_callback { my $task = shift; my $args = shift; my $node = $args->{node}; my $hyp = $args->{hyp}; if ($task->info->state->val eq 'error') { my $error = $task->info->error->localizedMessage; xCAT::SvrUtils::sendmsg([ 1, $error ], $output_handler, $node); } } sub relay_vmware_err { my $task = shift; my $extratext = shift; my @nodes = @_; my $error = $task->info->error->localizedMessage; if (defined($task->info->error->fault->faultMessage)) { #Only in 4.0, support of 3.5 must be careful? foreach (@{ $task->info->error->fault->faultMessage }) { $error .= $_->message; } } if (@nodes) { foreach (@nodes) { xCAT::SvrUtils::sendmsg([ 1, $extratext . $error ], $output_handler, $_); } } else { xCAT::SvrUtils::sendmsg([ 1, $extratext . $error ], $output_handler); } } sub relocate_callback { my $task = shift; my $parms = shift; my $state = $task->info->state->val; if ($state eq 'success') { my $vmtab = xCAT::Table->new('vm'); #TODO: update vm.storage? my $prevloc = $tablecfg{vm}->{ $parms->{node} }->[0]->{storage}; my $model; ($prevloc, $model) = split /=/, $prevloc; my $target = $parms->{target}; if ($model) { $target .= "=$model"; } $vmtab->setNodeAttribs($parms->{node}, { storage => $target }); xCAT::SvrUtils::sendmsg(":relocated to to " . $parms->{target}, $output_handler, $parms->{node}); } else { relay_vmware_err($task, "Relocating to " . $parms->{target} . " ", $parms->{node}); } } sub migrate_ok { #look like a successful migrate, callback for registering a vm my %args = @_; my $vmtab = xCAT::Table->new('vm'); $vmtab->setNodeAttribs($args{nodes}->[0], { host => $args{target} }); xCAT::SvrUtils::sendmsg("migrated to " . $args{target}, $output_handler, $args{nodes}->[0]); } sub migrate_callback { my $task = shift; my $parms = shift; my $state = $task->info->state->val; if (not $parms->{skiptodeadsource} and $state eq 'success') { my $vmtab = xCAT::Table->new('vm'); $vmtab->setNodeAttribs($parms->{node}, { host => $parms->{target} }); xCAT::SvrUtils::sendmsg("migrated to " . $parms->{target}, $output_handler, $parms->{node}); } elsif ($parms->{offline}) { #try a forceful RegisterVM instead my $target = $parms->{target}; my $hostview = $hyphash{$target}->{conn}->find_entity_view(view_type => 'VirtualMachine', properties => ['config.name'], filter => { name => $parms->{node} }); if ($hostview) { #this means vcenter still has it in inventory, but on a dead node... #unfortunately, vcenter won't give up the old one until we zap the dead hypervisor #also unfortunately, it doesn't make it easy to find said hypervisor.. $hostview = $hyphash{ $parms->{src} }->{conn}->get_view(mo_ref => $hyphash{ $parms->{src} }->{deletionref}); $task = $hostview->Destroy_Task(); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&migrate_callback; $running_tasks{$task}->{conn} = $hyphash{$target}->{vcenter}->{conn}; $running_tasks{$task}->{data} = { offline => 1, src => $parms->{src}, node => $parms->{node}, target => $target, skiptodeadsource => 1 }; } else { #it is completely gone, attempt a register_vm strategy register_vm($target, $parms->{node}, undef, \&migrate_ok, { nodes => [ $parms->{node} ], target => $target, }, "failonerror"); } } else { relay_vmware_err($task, "Migrating to " . $parms->{target} . " ", $parms->{node}); } } sub poweron_task_callback { my $task = shift; my $parms = shift; my $q = shift; #question if blocked my $vm = shift; #path to answer questions if asked my $state = $task->info->state->val; my $node = $parms->{node}; my $intent = $parms->{successtext}; if ($state eq 'success') { xCAT::SvrUtils::sendmsg($intent, $output_handler, $node); } elsif ($state eq 'error') { relay_vmware_err($task, "", $node); } elsif ($q and $q->text =~ /^msg.uuid.altered:/ and ($q->choice->choiceInfo->[0]->summary eq 'Cancel' and ($q->choice->choiceInfo->[0]->key eq '0'))) { #make sure it is what is what we have seen it to be if ($parms->{forceon} and $q->choice->choiceInfo->[1]->summary =~ /I (_)?moved it/ and $q->choice->choiceInfo->[1]->key eq '1') { #answer the question as 'moved' $vm->AnswerVM(questionId => $q->id, answerChoice => '1'); } else { $vm->AnswerVM(questionId => $q->id, answerChoice => '0'); xCAT::SvrUtils::sendmsg([ 1, "Failure powering on VM, it mismatched against the hypervisor. If positive VM is not running on another hypervisor, use -f to force VM on" ], $output_handler, $node); } } elsif ($q) { if ($q->choice->choiceInfo->[0]->summary eq 'Cancel') { xCAT::SvrUtils::sendmsg([ 1, ":Cancelling due to unexpected question executing task: " . $q->text ], $output_handler, $node); } else { xCAT::SvrUtils::sendmsg([ 1, ":Task hang due to unexpected question executing task, need to use VMware tools to clean up the mess for now: " . $q->text ], $output_handler, $node); } } } sub chvm_task_callback { my $task = shift; my $parms = shift; my $state = $task->info->state->val; my $node = $parms->{node}; my $intent = $parms->{successtext}; if ($state eq 'success') { my $updatehash; if ($parms->{cpus} and $tablecfg{vm}->{$node}->[0]->{cpus}) { #need to update $updatehash->{cpus} = $parms->{cpus}; } if ($parms->{mem} and $tablecfg{vm}->{$node}->[0]->{memory}) { #need to update $updatehash->{memory} = $parms->{mem}; } if ($updatehash) { my $vmtab = xCAT::Table->new('vm', -create => 1); $vmtab->setNodeAttribs($node, $updatehash); } xCAT::SvrUtils::sendmsg($intent, $output_handler, $node); } elsif ($state eq 'error') { relay_vmware_err($task, "", $node); } } sub generic_task_callback { my $task = shift; my $parms = shift; my $state = $task->info->state->val; my $node = $parms->{node}; my $intent = $parms->{successtext}; if ($state eq 'success') { xCAT::SvrUtils::sendmsg($intent, $output_handler, $node); } elsif ($state eq 'error') { relay_vmware_err($task, "", $node); } } sub migrate { my %args = @_; my @nodes = @{ $args{nodes} }; my $hyp = $args{hyp}; my $vcenter = $hyphash{$hyp}->{vcenter}->{name}; my $datastoredest; my $offline; @ARGV = @{ $args{exargs} }; unless (GetOptions( 's=s' => \$datastoredest, 'f' => \$offline, )) { xCAT::SvrUtils::sendmsg([ 1, "Error parsing arguments" ], $output_handler); return; } my $target = $hyp; #case for storage migration if ($datastoredest) { $datastoredest =~ s/=.*//; } #remove =scsi and similar if specified if ($datastoredest and scalar @ARGV) { xCAT::SvrUtils::sendmsg([ 1, "Unable to mix storage migration and processing of arguments " . join(' ', @ARGV) ], $output_handler); return; } elsif (@ARGV) { $target = shift @ARGV; if (@ARGV) { xCAT::SvrUtils::sendmsg([ 1, "Unrecognized arguments " . join(' ', @ARGV) ], $output_handler); return; } } elsif ($datastoredest) { #storage migration only unless (validate_datastore_prereqs([], $hyp, { $datastoredest => \@nodes })) { xCAT::SvrUtils::sendmsg([ 1, "Unable to find/mount target datastore $datastoredest" ], $output_handler); return; } foreach (@nodes) { my $hostview = $hyphash{$hyp}->{conn}->find_entity_view(view_type => 'VirtualMachine', properties => ['config.name'], filter => { name => $_ }); my $relocatspec = VirtualMachineRelocateSpec->new( datastore => $hyphash{$hyp}->{datastorerefmap}->{$datastoredest}, ); my $task = $hostview->RelocateVM_Task(spec => $relocatspec); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&relocate_callback; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{data} = { node => $_, target => $datastoredest }; process_tasks; #check for tasks needing followup actions before the task is forgotten (VMWare's memory is fairly short at times } return; } if ((not $offline and $vcenterhash{$vcenter}->{badhyps}->{$hyp}) or $vcenterhash{$vcenter}->{badhyps}->{$target}) { xCAT::SvrUtils::sendmsg([ 1, "Unable to migrate " . join(',', @nodes) . " to $target due to inability to validate vCenter connectivity" ], $output_handler); return; } if (($offline or $vcenterhash{$vcenter}->{goodhyps}->{$hyp}) and $vcenterhash{$vcenter}->{goodhyps}->{$target}) { unless (validate_datastore_prereqs(\@nodes, $target)) { xCAT::SvrUtils::sendmsg([ 1, "Unable to verify storage state on target system" ], $output_handler); return; } unless (validate_network_prereqs(\@nodes, $target)) { xCAT::SvrUtils::sendmsg([ 1, "Unable to verify target network state" ], $output_handler); return; } my $dstview = get_hostview(conn => $hyphash{$target}->{conn}, hypname => $target, properties => [ 'name', 'parent' ]); unless ($hyphash{$target}->{pool}) { $hyphash{$target}->{pool} = $hyphash{$target}->{conn}->get_view(mo_ref => $dstview->parent, properties => ['resourcePool'])->resourcePool; } foreach (@nodes) { process_tasks; #check for tasks needing followup actions before the task is forgotten (VMWare's memory is fairly short at times my $srcview = $hyphash{$target}->{conn}->find_entity_view(view_type => 'VirtualMachine', properties => ['config.name'], filter => { name => $_ }); if ($offline and not $srcview) { #we have a request to resurrect the dead.. register_vm($target, $_, undef, \&migrate_ok, { nodes => [$_], exargs => $args{exargs}, target => $target, hyp => $args{hyp}, offline => $offline }, "failonerror"); return; } elsif (not $srcview) { $srcview = $hyphash{$hyp}->{conn}->find_entity_view(view_type => 'VirtualMachine', properties => ['config.name'], filter => { name => $_ }); } unless ($srcview) { xCAT::SvrUtils::sendmsg([ 1, "Unable to locate node in vCenter" ], $output_handler, $_); next; } my $task = $srcview->MigrateVM_Task( host => $dstview, pool => $hyphash{$target}->{pool}, priority => VirtualMachineMovePriority->new('highPriority')); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&migrate_callback; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{data} = { node => $_, src => $hyp, target => $target, offline => $offline }; } } else { #xCAT::SvrUtils::sendmsg("Waiting for BOTH to be 'good'"); return; #One of them is still 'pending' } } sub reconfig_callback { my $task = shift; my $args = shift; #$args->{reconfig_args}->{vmview}->update_view_data(); delete $args->{reconfig_args}->{vmview}; #Force a reload of the view, update_view_data seems to not work as advertised.. $args->{reconfig_fun}->(%{ $args->{reconfig_args} }); } sub repower { #Called to try power again after power down for reconfig my $task = shift; my $args = shift; my $powargs = $args->{power_args}; $powargs->{pretendop} = 1; #$powargs->{vmview}->update_view_data(); delete $powargs->{vmview}; #Force a reload of the view, update_view_data seems to not work as advertised.. power(%$powargs); } sub retry_rmvm { my $task = shift; my $args = shift; my $node = $args->{node}; my $state = $task->info->state->val; if ($state eq "success") { #$Data::Dumper::Maxdepth=2; delete $args->{args}->{vmview}; rmvm(%{ $args->{args} }); } elsif ($state eq 'error') { relay_vmware_err($task, "", $node); } } sub rmvm { my %args = @_; my $node = $args{node}; my $hyp = $args{hyp}; if (not defined $args{vmview}) { #attempt one refresh $args{vmview} = $vmhash{$node}->{conn}->find_entity_view(view_type => 'VirtualMachine', properties => [ 'config.name', 'runtime.powerState' ], filter => { name => $node }); if (not defined $args{vmview}) { xCAT::SvrUtils::sendmsg([ 1, "VM does not appear to exist" ], $output_handler, $node); return; } } @ARGV = @{ $args{exargs} }; require Getopt::Long; my $forceremove; my $purge; GetOptions( 'f' => \$forceremove, 'p' => \$purge, ); my $task; unless ($args{vmview}->{'runtime.powerState'}->val eq 'poweredOff') { if ($forceremove) { $task = $args{vmview}->PowerOffVM_Task(); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&retry_rmvm, $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{vm} = $node; $running_tasks{$task}->{data} = { node => $node, args => \%args }; return; } else { xCAT::SvrUtils::sendmsg([ 1, "Cannot rmvm active guest (use -f argument to force)" ], $output_handler, $node); return; } } if ($purge) { $task = $args{vmview}->Destroy_Task(); $running_tasks{$task}->{data} = { node => $node, successtext => 'purged' }; $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; #$hyp_conns->{$hyp}; $running_tasks{$task}->{vm} = $node; } else { $task = $args{vmview}->UnregisterVM(); } } sub getreconfigspec { my %args = @_; my $node = $args{node}; my $vmview = $args{view}; my $currid = $args{view}->{'config.guestId'}; my $rightid = getguestid($node); my %conargs; my $reconfigneeded = 0; if ($currid ne $rightid) { $reconfigneeded = 1; $conargs{guestId} = $rightid; } my $newmem; if ($tablecfg{vm}->{$node}->[0]->{memory} and $newmem = getUnits($tablecfg{vm}->{$node}->[0]->{memory}, "M", 1048576)) { my $currmem = $vmview->{'config.hardware.memoryMB'}; if ($newmem ne $currmem) { $conargs{memoryMB} = $newmem; $reconfigneeded = 1; } } my $newcpus; if ($tablecfg{vm}->{$node}->[0]->{cpus} and $newcpus = $tablecfg{vm}->{$node}->[0]->{cpus}) { my $currncpu = $vmview->{'config.hardware.numCPU'}; if ($newcpus ne $currncpu) { $conargs{numCPUs} = $newcpus; $reconfigneeded = 1; } } if ($reconfigneeded) { return VirtualMachineConfigSpec->new(%conargs); } else { return 0; } } #This routine takes a single node, managing vmv instance, and task tracking hash to submit a power on request sub power { my %args = @_; my $node = $args{node}; my $hyp = $args{hyp}; my $pretendop = $args{pretendop}; #to pretend a system was on for reset or boot when we have to turn it off internally for reconfig if (not defined $args{vmview}) { #attempt one refresh $args{vmview} = $vmhash{$node}->{conn}->find_entity_view(view_type => 'VirtualMachine', properties => [ 'config.name', 'config.guestId', 'config.hardware.memoryMB', 'config.hardware.numCPU', 'runtime.powerState' ], filter => { name => $node }); #vmview not existing now is not an issue, this function #is designed to handle that and correct if reasonably possible #comes into play particularly in a stateless context } @ARGV = @{ $args{exargs} }; #for getoptions; my $forceon; require Getopt::Long; GetOptions( 'force|f' => \$forceon, ); my $subcmd = $ARGV[0]; my $intent = ""; my $task; my $currstat; if ($args{vmview}) { $currstat = $args{vmview}->{'runtime.powerState'}->val; if (grep /$subcmd/, qw/on reset boot/) { my $reconfigspec; if ($reconfigreset and ($reconfigspec = getreconfigspec(node => $node, view => $args{vmview}))) { if ($currstat eq 'poweredOff') { #xCAT::SvrUtils::sendmsg("Correcting guestId because $currid and $rightid are not the same...");#DEBUG my $task = $args{vmview}->ReconfigVM_Task(spec => $reconfigspec); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&reconfig_callback; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{vm} = $node; $running_tasks{$task}->{data} = { node => $node, reconfig_fun => \&power, reconfig_args => \%args }; return; } elsif (grep /$subcmd/, qw/reset boot/) { #going to have to do a 'cycle' and present it up normally.. #xCAT::SvrUtils::sendmsg("DEBUG: forcing a cycle"); $task = $args{vmview}->PowerOffVM_Task(); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&repower; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{vm} = $node; $running_tasks{$task}->{data} = { node => $node, power_args => \%args }; return; #we have to wait } #TODO: fixit #xCAT::SvrUtils::sendmsg("I see vm has $currid and I want it to be $rightid"); } } } else { $currstat = 'off'; } if ($currstat eq 'poweredOff') { $currstat = 'off'; } elsif ($currstat eq 'poweredOn') { $currstat = 'on'; } elsif ($currstat eq 'suspended') { $currstat = 'suspend'; } if ($subcmd =~ /^stat/) { xCAT::SvrUtils::sendmsg($currstat, $output_handler, $node); return; } if ($subcmd =~ /boot/) { $intent = "$currstat "; if ($currstat eq 'on' or $args{pretendop}) { $intent = "on "; $subcmd = 'reset'; } else { $subcmd = 'on'; } } if ($subcmd =~ /on/) { if ($currstat eq 'off' or $currstat eq 'suspend') { if (not $args{vmview}) { #We are asking to turn on a system the hypervisor #doesn't know, attempt to register it first register_vm($hyp, $node, undef, \&power, \%args); return; #We'll pick it up on the retry if it gets registered } eval { if ($hyp) { $task = $args{vmview}->PowerOnVM_Task(host => $hyphash{$hyp}->{hostview}); } else { $task = $args{vmview}->PowerOnVM_Task(); #DRS may have it's way with me } }; if ($@) { xCAT::SvrUtils::sendmsg([ 1, ":" . $@ ], $output_handler, $node); return; } $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&poweron_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; #$hyp_conns->{$hyp}; $running_tasks{$task}->{vm} = $node; $running_tasks{$task}->{data} = { node => $node, successtext => $intent . 'on', forceon => $forceon }; } else { xCAT::SvrUtils::sendmsg($currstat, $output_handler, $node); } } elsif ($subcmd =~ /softoff/) { if ($currstat eq 'on') { $args{vmview}->ShutdownGuest(); xCAT::SvrUtils::sendmsg("softoff", $output_handler, $node); } else { xCAT::SvrUtils::sendmsg($currstat, $output_handler, $node); } } elsif ($subcmd =~ /off/) { if ($currstat eq 'on') { $task = $args{vmview}->PowerOffVM_Task(); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{vm} = $node; $running_tasks{$task}->{data} = { node => $node, successtext => 'off' }; } else { xCAT::SvrUtils::sendmsg($currstat, $output_handler, $node); } } elsif ($subcmd =~ /suspend/) { if ($currstat eq 'on') { $task = $args{vmview}->SuspendVM_Task(); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{vm} = $node; $running_tasks{$task}->{data} = { node => $node, successtext => 'suspend' }; } else { xCAT::SvrUtils::sendmsg("off", $output_handler, $node); } } elsif ($subcmd =~ /reset/) { if ($currstat eq 'on') { $task = $args{vmview}->ResetVM_Task(); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{vm} = $node; $running_tasks{$task}->{data} = { node => $node, successtext => $intent . 'reset' }; } elsif ($args{pretendop}) { #It is off, but pretend it was on eval { if ($hyp) { $task = $args{vmview}->PowerOnVM_Task(host => $hyphash{$hyp}->{hostview}); } else { $task = $args{vmview}->PowerOnVM_Task(); #allow DRS } }; if ($@) { xCAT::SvrUtils::sendmsg([ 1, ":" . $@ ], $output_handler, $node); return; } $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{vm} = $node; $running_tasks{$task}->{data} = { node => $node, successtext => $intent . 'reset' }; } else { xCAT::SvrUtils::sendmsg($currstat, $output_handler, $node); } } } sub generic_vm_operation { #The general form of firing per-vm requests to ESX hypervisor my $properties = shift; #The relevant properties to the general task, MUST INCLUDE config.name my $function = shift; #The function to actually run against the right VM view my @exargs = @_; #Store the rest to pass on my $hyp; my $vmviews; my %vcviews; #views populated once per vcenter server for improved performance my $node; foreach $hyp (keys %hyphash) { if ($viavcenterbyhyp->{$hyp}) { foreach $node (keys %{ $hyphash{$hyp}->{nodes} }) { $vcenterhash{ $hyphash{$hyp}->{vcenter}->{name} }->{vms}->{$node} = 1; } } } foreach (keys %limbonodes) { $vcenterhash{ $limbonodes{$_} }->{vms}->{$_} = 1; } my $cluster; foreach $cluster (keys %clusterhash) { foreach $node (keys %{ $clusterhash{$cluster}->{nodes} }) { $vcenterhash{ $clusterhash{$cluster}->{vcenter}->{name} }->{vms}->{$node} = 1; } } my $currentvcenter; my %foundlimbo; foreach $currentvcenter (keys %vcenterhash) { #retrieve all vm views in one gulp my $vmsearchstring = join(")|(", keys %{ $vcenterhash{$currentvcenter}->{vms} }); $vmsearchstring = '^((' . $vmsearchstring . '))(\z|\.)'; my $regex = qr/$vmsearchstring/; $vcviews{$currentvcenter} = $vcenterhash{$currentvcenter}->{conn}->find_entity_views(view_type => 'VirtualMachine', properties => $properties, filter => { 'config.name' => $regex }); foreach (@{ $vcviews{$currentvcenter} }) { my $node = $_->{'config.name'}; unless (defined $tablecfg{vm}->{$node}) { $node =~ s/\..*//; #try the short name; } if (defined $tablecfg{vm}->{$node}) { #see if the host pointer requires a refresh my $hostref = $hostrefbynode{$node}; if ($hostref and $hostref eq $_->{'runtime.host'}->value) { next; } #the actual host reference matches the one that we got when populating hostviews based on what the table had to say #TODO: does this mean it is buggy if we want to mkvm/rmigrate/etc if the current vm.host is wrong and the noderange doesn't have something on the right hostview making us not get it in the #mass request? Or is it just slower because it hand gets host views? my $host = $vcenterhash{$currentvcenter}->{conn}->get_view(mo_ref => $_->{'runtime.host'}, properties => ['summary.config.name']); $host = $host->{'summary.config.name'}; my $shost = $host; $shost =~ s/\..*//; #time to figure out which of these is a node my @nodes = noderange("$host,$shost"); my $vmtab = xCAT::Table->new("vm", -create => 1); unless ($vmtab) { die "Error opening vm table"; } if ($nodes[0]) { if ($limbonodes{$node}) { $foundlimbo{$node} = $currentvcenter; } $vmtab->setNodeAttribs($node, { host => $nodes[0] }); } #else { # $vmtab->setNodeAttribs($node,{host=>$host}); #} } } } foreach my $lnode (keys %foundlimbo) { $vmviews = $vcviews{ $foundlimbo{$lnode} }; my %mgdvms; #sort into a hash for convenience foreach (@$vmviews) { $mgdvms{ $_->{'config.name'} } = $_; } $function->( node => $lnode, vm => $lnode, vmview => $mgdvms{$node}, exargs => \@exargs ); } my @entitylist; push @entitylist, keys %hyphash; push @entitylist, keys %clusterhash; foreach my $entity (@entitylist) { if ($hyphash{$entity}) { $hyp = $entity; #save some retyping... if ($viavcenterbyhyp->{$hyp}) { $vmviews = $vcviews{ $hyphash{$hyp}->{vcenter}->{name} } } else { $vmviews = []; my $node; foreach $node (sort (keys %{ $hyphash{$hyp}->{nodes} })) { push @{$vmviews}, $hyphash{$hyp}->{conn}->find_entity_view(view_type => 'VirtualMachine', properties => $properties, filter => { 'config.name' => qr/^$node/ }); } #$vmviews = $hyphash{$hyp}->{conn}->find_entity_views(view_type => 'VirtualMachine',properties=>$properties); } my %mgdvms; #sort into a hash for convenience foreach (@$vmviews) { $mgdvms{ $_->{'config.name'} } = $_; } my $node; foreach $node (sort (keys %{ $hyphash{$hyp}->{nodes} })) { $function->( node => $node, hyp => $hyp, vmview => $mgdvms{$node}, exargs => \@exargs ); process_tasks; #check for tasks needing followup actions before the task is forgotten (VMWare's memory is fairly short at times } } else { #a cluster. $vmviews = $vcviews{ $clusterhash{$entity}->{vcenter}->{name} }; my %mgdvms; #sort into a hash for convenience foreach (@$vmviews) { $mgdvms{ $_->{'config.name'} } = $_; } my $node; foreach $node (sort (keys %{ $clusterhash{$entity}->{nodes} })) { $function->( node => $node, cluster => $entity, vm => $node, vmview => $mgdvms{$node}, exargs => \@exargs ); } } } } sub generic_hyp_operation { #The general form of firing per-hypervisor requests to ESX hypervisor my $function = shift; #The function to actually run against the right VM view my @exargs = @_; #Store the rest to pass on my $hyp; if (scalar keys %limbonodes) { #we are in forced migration with dead sources, try to register them @ARGV = @exargs; my $datastoredest; my $offline; unless (GetOptions( 's=s' => \$datastoredest, 'f' => \$offline, )) { xCAT::SvrUtils::sendmsg([ 1, "Error parsing arguments" ], $output_handler); return; } if ($datastoredest) { xCAT::SvrUtils::sendmsg([ 1, "Storage migration impossible with dead hypervisor, must be migrated to live hypervisor first" ], $output_handler); return; } elsif (@ARGV) { my $target = shift @ARGV; if (@ARGV) { xCAT::SvrUtils::sendmsg([ 1, "Unrecognized arguments " . join(' ', @ARGV) ], $output_handler); return; } foreach (keys %limbonodes) { register_vm($target, $_, undef, \&migrate_ok, { nodes => [$_], target => $target, }, "failonerror"); } } else { #storage migration only xCAT::SvrUtils::sendmsg([ 1, "No target hypervisor specified" ], $output_handler); } } foreach $hyp (keys %hyphash) { process_tasks; #check for tasks needing followup actions before the task is forgotten (VMWare's memory is fairly short at times my @relevant_nodes = sort (keys %{ $hyphash{$hyp}->{nodes} }); unless (scalar @relevant_nodes) { next; } $function->( nodes => \@relevant_nodes, hyp => $hyp, exargs => \@exargs ); #my $vmviews = $hyp_conns->{$hyp}->find_entity_views(view_type => 'VirtualMachine',properties=>['runtime.powerState','config.name']); #my %mgdvms; #sort into a hash for convenience #foreach (@$vmviews) { # $mgdvms{$_->{'config.name'}} = $_; #} #my $node; #foreach $node (sort (keys %{$hyp_hash->{$hyp}->{nodes}})){ # $function->($node,$mgdvms{$node},$taskstotrack,$callback,@exargs); #REMINDER FOR RINV TO COME # foreach (@nothing) { #@{$mgdvms{$node}->config->hardware->device}) { # if (defined $_->{macAddress}) { # print "\nFound a mac: ".$_->macAddress."\n"; # } # } # } } foreach $hyp (keys %clusterhash) { #clonevm, mkvm, rmigrate could land here in clustered mode with DRS/HA process_tasks; my @relevant_nodes = sort (keys %{ $clusterhash{$hyp}->{nodes} }); unless (scalar @relevant_nodes) { next; } $function->(nodes => \@relevant_nodes, cluster => $hyp, exargs => \@exargs, conn => $clusterhash{$hyp}->{conn}); } } sub rmhypervisor_disconnected { my $task = shift; my $parms = shift; my $node = $parms->{node}; my $hyp = $node; my $state = $task->info->state->val; if ($state eq 'success') { my $task = $hyphash{$hyp}->{hostview}->Destroy_Task(); $running_tasks{$task}->{data} = { node => $node, successtext => 'removed' }; $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $hyp; } elsif ($state eq 'error') { relay_vmware_err($task, "", $node); } } sub rmhypervisor_inmaintenance { my $task = shift; my $parms = shift; my $state = $task->info->state->val; my $node = $parms->{node}; my $intent = $parms->{successtext}; if ($state eq 'success') { my $hyp = $parms->{node}; my $task = $hyphash{$hyp}->{hostview}->DisconnectHost_Task(); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&rmhypervisor_disconnected; $running_tasks{$task}->{hyp} = $hyp; $running_tasks{$task}->{data} = { node => $hyp }; } elsif ($state eq 'error') { relay_vmware_err($task, "", $node); } } sub lsvm { my %args = @_; my $hyp = $args{hyp}; $hyphash{$hyp}->{hostview} = get_hostview(hypname => $hyp, conn => $hyphash{$hyp}->{conn}); #,properties=>['config','configManager']); use Data::Dumper; my $vms = $hyphash{$hyp}->{hostview}->vm; unless ($vms) { return; } foreach (@$vms) { my $vmv = $hyphash{$hyp}->{conn}->get_view(mo_ref => $_); xCAT::SvrUtils::sendmsg($vmv->name, $output_handler, $hyp); } return; } sub chhypervisor { my %args = @_; @ARGV = @{ $args{exargs} }; #for getoptions; my $maintenance; my $online; my $stat; my $vlanaddspec; my $vlanremspec; require Getopt::Long; GetOptions( 'maintenance|m' => \$maintenance, 'online|o' => \$online, 'show|s' => \$stat, 'show|s' => \$stat, 'addvlan=s' => \$vlanaddspec, 'removevlan=s' => \$vlanremspec, ); my $hyp = $args{hyp}; $hyphash{$hyp}->{hostview} = get_hostview(hypname => $hyp, conn => $hyphash{$hyp}->{conn}); #,properties=>['config','configManager']); if ($maintenance) { if (defined $hyphash{$hyp}->{hostview}) { my $task = $hyphash{$hyp}->{hostview}->EnterMaintenanceMode_Task(timeout => 0); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{data} = { node => $hyp, successtext => "hypervisor in maintenance mode" }; } } elsif ($online) { if (defined $hyphash{$hyp}->{hostview}) { my $task = $hyphash{$hyp}->{hostview}->ExitMaintenanceMode_Task(timeout => 0); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{data} = { node => $hyp, successtext => "hypervisor online" }; } } elsif ($stat) { if (defined $hyphash{$hyp}->{hostview}) { if ($hyphash{$hyp}->{hostview}->runtime->inMaintenanceMode) { xCAT::SvrUtils::sendmsg("hypervisor in maintenance mode", $output_handler, $hyp); } else { xCAT::SvrUtils::sendmsg("hypervisor online", $output_handler, $hyp); } } } elsif ($vlanaddspec) { fixup_hostportgroup($vlanaddspec, $hyp); } elsif ($vlanremspec) { fixup_hostportgroup($vlanremspec, $hyp, action => 'remove'); } return; } sub rshutdown { #TODO: refactor with next function too my %args = @_; my $hyp = $args{hyp}; $hyphash{$hyp}->{hostview} = get_hostview(hypname => $hyp, conn => $hyphash{$hyp}->{conn}); #,properties=>['config','configManager']); if (defined $hyphash{$hyp}->{hostview}) { my $task = $hyphash{$hyp}->{hostview}->EnterMaintenanceMode_Task(timeout => 0); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&rshutdown_inmaintenance; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{data} = { node => $hyp }; } return; } sub rshutdown_inmaintenance { my $task = shift; my $parms = shift; my $state = $task->info->state->val; my $node = $parms->{node}; my $intent = $parms->{successtext}; if ($state eq 'success') { my $hyp = $parms->{node}; if (defined $hyphash{$hyp}->{hostview}) { my $task = $hyphash{$hyp}->{hostview}->ShutdownHost_Task(force => 0); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $hyp; $running_tasks{$task}->{data} = { node => $hyp, successtext => "shutdown initiated" }; } } elsif ($state eq 'error') { relay_vmware_err($task, "", $node); } return; } sub rescansan { my %args = @_; my $hyp = $args{hyp}; my $hostview = get_hostview(hypname => $hyp, conn => $hyphash{$hyp}->{conn}, properties => [ 'config', 'configManager' ]); if (defined $hostview) { my $hdss = $hostview->{vim}->get_view(mo_ref => $hostview->configManager->storageSystem); $hdss->RescanAllHba(); $hdss->RescanVmfs(); } } sub formatdisk { my %args = @_; my $hyp = $args{hyp}; $hyphash{$hyp}->{hostview} = get_hostview(hypname => $hyp, conn => $hyphash{$hyp}->{conn}, properties => [ 'config', 'configManager' ]); @ARGV = @{ $args{exargs} }; my $nid; my $name; GetOptions( 'id=s' => \$nid, 'name=s' => \$name, ); my $hostview = $hyphash{$hyp}->{hostview}; if (defined $hyphash{$hyp}->{hostview}) { my $hdss = $hostview->{vim}->get_view(mo_ref => $hostview->configManager->storageSystem); $hdss->RescanAllHba(); my $dss = $hostview->{vim}->get_view(mo_ref => $hostview->configManager->datastoreSystem); my $diskList = $dss->QueryAvailableDisksForVmfs(); foreach my $disk (@$diskList) { foreach my $id (@{ $disk->{descriptor} }) { if (lc($id->{id}) eq lc('naa.' . $nid)) { my $options = $dss->QueryVmfsDatastoreCreateOptions(devicePath => $disk->devicePath); @$options[0]->spec->vmfs->volumeName($name); my $newDatastore = $dss->CreateVmfsDatastore(spec => @$options[0]->spec); } } } } return; } sub rmhypervisor { my %args = @_; my $hyp = $args{hyp}; $hyphash{$hyp}->{hostview} = get_hostview(hypname => $hyp, conn => $hyphash{$hyp}->{conn}); #,properties=>['config','configManager']); if (defined $hyphash{$hyp}->{hostview}) { my $task = $hyphash{$hyp}->{hostview}->EnterMaintenanceMode_Task(timeout => 0); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&rmhypervisor_inmaintenance; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{data} = { node => $hyp }; } return; } sub clonevms { my %args = @_; my $nodes = $args{nodes}; my $hyp = $args{hyp}; my $cluster = $args{cluster}; @ARGV = @{ $args{exargs} }; #for getoptions; my $base; my $force; my $detach; my $specialize; my $target; require Getopt::Long; GetOptions( 'b=s' => \$base, 'f' => \$force, 'd' => \$detach, 'specialize' => \$specialize, 't=s' => \$target, ); if ($base and $target) { foreach my $node (@$nodes) { xCAT::SvrUtils::sendmsg([ 1, "Cannot specify both base (-b) and target (-t)" ], $output_handler, $node); } return; } unless ($base or $target) { foreach my $node (@$nodes) { xCAT::SvrUtils::sendmsg([ 1, "Must specify one of base (-b) or target (-t)" ], $output_handler, $node); } return; } if ($target and (scalar @{$nodes} != 1)) { foreach my $node (@$nodes) { xCAT::SvrUtils::sendmsg([ 1, "Cannot specify mulitple nodes to create a master from" ], $output_handler, $node); } return; } if ($hyp) { $hyphash{$hyp}->{hostview} = get_hostview(hypname => $hyp, conn => $hyphash{$hyp}->{conn}); } my $newdatastores; my $mastername; my $url; my $masterref; if ($base) { #if base, we need to pull in the target datastores my $mastertab = xCAT::Table->new('vmmaster'); $masterref = $mastertab->getNodeAttribs($base, [qw/storage os arch profile storagemodel nics specializeparameters/]); unless ($masterref) { foreach my $node (@$nodes) { xCAT::SvrUtils::sendmsg([ 1, "Cannot find master $base in vmmaster table" ], $output_handler, $node); } return; } $newdatastores->{ $masterref->{storage} } = []; #make sure that the master datastore is mounted... foreach (@$nodes) { my $url; if ($tablecfg{vm}->{$_}->[0]->{storage}) { $url = $tablecfg{vm}->{$_}->[0]->{storage}; $url =~ s/=.*//; } else { $url = $masterref->{storage}; } unless ($url) { die "Shouldn't be possible"; } if (ref $newdatastores->{$_}) { push @{ $newdatastores->{$url} }, $_; } else { $newdatastores->{$url} = [$_]; } } } elsif ($target) { if ($url =~ m!/!) { $url = $target; $url =~ s!/([^/]*)\z!!; $mastername = $1; } else { $url = $tablecfg{vm}->{ $nodes->[0] }->[0]->{storage}; $url =~ s/.*\|//; $url =~ s/=(.*)//; $url =~ s/,.*//; $mastername = $target } $newdatastores->{$url} = [ $nodes->[0] ]; } if ($hyp) { unless (validate_datastore_prereqs($nodes, $hyp, $newdatastores)) { return; } } else { #need to build datastore map for cluster refreshclusterdatastoremap($cluster); } sortoutdatacenters(nodes => $nodes, hyp => $hyp, cluster => $cluster); if ($target) { return promote_vm_to_master(node => $nodes->[0], target => $target, force => $force, detach => $detach, cluster => $cluster, hyp => $hyp, url => $url, mastername => $mastername); } elsif ($base) { return clone_vms_from_master(nodes => $nodes, base => $base, detach => $detach, cluster => $cluster, hyp => $hyp, mastername => $base, masterent => $masterref, specialize => $specialize); } } sub sortoutdatacenters { #figure out all the vmfolders for all the nodes passed in my %args = @_; my $nodes = $args{nodes}; my $hyp = $args{hyp}; my $cluster = $args{cluster}; my %nondefaultdcs; my $deffolder; my $conn; if ($hyp) { unless (defined $hyphash{$hyp}->{vmfolder}) { $hyphash{$hyp}->{vmfolder} = $hyphash{$hyp}->{conn}->get_view(mo_ref => $hyphash{$hyp}->{conn}->find_entity_view(view_type => 'Datacenter', properties => ['vmFolder'])->vmFolder); } $conn = $hyphash{$hyp}->{conn}; $deffolder = $hyphash{$hyp}->{vmfolder}; } else { #clustered unless (defined $clusterhash{$cluster}->{vmfolder}) { $clusterhash{$cluster}->{vmfolder} = $clusterhash{$cluster}->{conn}->get_view(mo_ref => $clusterhash{$cluster}->{conn}->find_entity_view(view_type => 'Datacenter', properties => ['vmFolder'])->vmFolder); } $deffolder = $clusterhash{$cluster}->{vmfolder}; $conn = $clusterhash{$cluster}->{conn}; } foreach (@$nodes) { if ($tablecfg{vm}->{$_}->[0]->{datacenter}) { $nondefaultdcs{ $tablecfg{vm}->{$_}->[0]->{datacenter} }->{$_} = 1; } else { $vmhash{$_}->{vmfolder} = $deffolder; } } my $datacenter; foreach $datacenter (keys %nondefaultdcs) { my $vmfolder = $conn->get_view(mo_ref => $conn->find_entity_view(view_type => 'Datacenter', properties => ['vmFolder'], filter => { name => $datacenter })->vmFolder, filter => { name => $datacenter }); foreach (keys %{ $nondefaultdcs{$datacenter} }) { $vmhash{$_}->{vmfolder} = $vmfolder; } } } sub clone_vms_from_master { my %args = @_; my $mastername = $args{mastername}; my $specialize = $args{specialize}; my $hyp = $args{hyp}; my $cluster = $args{cluster}; my $regex = qr/^$mastername\z/; my @nodes = @{ $args{nodes} }; my $node; my $conn; if ($hyp) { $conn = $hyphash{$hyp}->{conn}; } else { $conn = $clusterhash{$cluster}->{conn}; } my $masterviews = $conn->find_entity_views(view_type => 'VirtualMachine', filter => { 'config.name' => $regex }); if (scalar(@$masterviews) != 1) { foreach $node (@nodes) { xCAT::SvrUtils::sendmsg([ 1, "Unable to find master $mastername in VMWare infrastructure" ], $output_handler, $node); } return; } my $masterview = $masterviews->[0]; my $masterent = $args{masterent}; my $ostype; foreach $node (@nodes) { my $destination = $tablecfg{vm}->{$node}->[0]->{storage}; my $nodetypeent; my $vment; $ostype = $masterent->{'os'}; foreach (qw/os arch profile/) { $nodetypeent->{$_} = $masterent->{$_}; } foreach (qw/storagemodel nics/) { $vment->{$_} = $masterent->{$_}; } $vment->{master} = $args{mastername}; unless ($destination) { $destination = $masterent->{storage}; $vment->{storage} = $destination; } $destination =~ s/=.*//; my $placement_resources = get_placement_resources(hyp => $hyp, cluster => $cluster, destination => $destination); my $pool = $placement_resources->{pool}; my $dstore = $placement_resources->{datastore}; my %relocatespecargs = ( datastore => $dstore, #$hyphash{$hyp}->{datastorerefmap}->{$destination}, pool => $pool, #diskMoveType=>"createNewChildDiskBacking", #fyi, requires a snapshot, which isn't compatible with templates, moveChildMostDiskBacking would potentially be fine, but either way is ha incopmatible and limited to 8, arbitrary limitations hard to work around... ); unless ($args{detach}) { $relocatespecargs{diskMoveType} = "createNewChildDiskBacking"; } if ($hyp) { $relocatespecargs{host} = $hyphash{$hyp}->{hostview} } my $relocatespec = VirtualMachineRelocateSpec->new(%relocatespecargs); my %clonespecargs = ( location => $relocatespec, template => 0, powerOn => 0 ); unless ($args{detach}) { $clonespecargs{snapshot} = $masterview->snapshot->currentSnapshot; } if ($specialize) { my %custargs; if ($masterent->{specializeparameters}) { %custargs = (parameters => $masterent->{specializeparameters}); } $clonespecargs{customization} = make_customization_spec($node, ostype => $ostype, %custargs); } my $clonespec = VirtualMachineCloneSpec->new(%clonespecargs); my $vmfolder = $vmhash{$node}->{vmfolder}; my $task = $masterview->CloneVM_Task(folder => $vmfolder, name => $node, spec => $clonespec); $running_tasks{$task}->{data} = { node => $node, conn => $conn, successtext => 'Successfully cloned from ' . $args{mastername}, mastername => $args{mastername}, nodetypeent => $nodetypeent, vment => $vment, hyp => $args{hyp}, }; $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&clone_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; #$hyp_conns->{$hyp}; $running_tasks{$task}->{vm} = $node; #$hyp_conns->{$hyp}; } } sub make_customization_spec { my $node = shift; my %args = @_; my $password; my $wintimezone; #map of number to strings can be found at #http://osman-shener-en.blogspot.com/2008/02/unattendedtxt-time-zone-index.html my $fullname = "Unspecified User"; my $orgName = "Unspecified Organization"; if ($::XCATSITEVALS{winfullname}) { $fullname = $::XCATSITEVALS{winfullname}; } if ($::XCATSITEVALS{winorgname}) { $orgName = $::XCATSITEVALS{winorgname}; } my @runonce = (); #to be read in from postscripts table $wintimezone = xCAT::TZUtils::get_wintimezonenum(); my $ptab = xCAT::Table->new('postscripts', -create => 0); if ($ptab) { my $psent = $ptab->getNodeAttribs($node, [qw/postscripts postbootscripts/]); if ($psent and $psent->{postscripts}) { push @runonce, split /,/, $psent->{postscripts}; } if ($psent and $psent->{postbootscripts}) { push @runonce, split /,/, $psent->{postbootscripts}; } } $ptab = xCAT::Table->new('passwd', -create => 0); unless ($ptab) { die "passwd table needed"; } my ($passent) = $ptab->getAttribs({ "key" => "system", username => "Administrator" }, 'password'); unless ($passent) { die "need passwd table entry for system account Administrator"; } $password = $passent->{password}; my %lfpd; if ($args{ostype} and $args{ostype} =~ /win2k3/) { %lfpd = ( licenseFilePrintData => CustomizationLicenseFilePrintData->new( autoMode => CustomizationLicenseDataMode->new( 'perSeat' ) ) ); } my %runonce; if (scalar @runonce) { #skip section if no postscripts or postbootscripts %runonce = ( guiRunOnce => CustomizationGuiRunOnce->new( commandList => \@runonce, ) ); } my %autologonargs = (autoLogon => 0, autoLogonCount => 1,); if ($args{parameters} and $args{parameters} =~ /autoLogonCount=([^,]*)/i) { my $count = $1; if ($count) { $autologonargs{autoLogon} = 1; $autologonargs{autoLogonCount} = $count; } } my $identity = CustomizationSysprep->new( %runonce, %lfpd, guiUnattended => CustomizationGuiUnattended->new( %autologonargs, password => CustomizationPassword->new( plainText => 1, value => $password, ), timeZone => $wintimezone, ), identification => get_customizedidentification(), userData => CustomizationUserData->new( computerName => CustomizationFixedName->new(name => $node), fullName => $fullname, orgName => $orgName, productId => "", ), ); my $options = CustomizationWinOptions->new(changeSID => 1, deleteAccounts => 0); my $customizationspec = CustomizationSpec->new( globalIPSettings => CustomizationGlobalIPSettings->new(), identity => $identity, nicSettingMap => [ CustomizationAdapterMapping->new(adapter => CustomizationIPSettings->new(ip => CustomizationDhcpIpGenerator->new())) ], options => $options, ); return $customizationspec; } sub get_customizedidentification { #for now, just do a 'TBD' workgroup. VMWare not supporting joining without domain admin password is rather unfortunate return CustomizationIdentification->new( joinWorkgroup => "TBD", ); } sub get_placement_resources { my %args = @_; my $pool; my $dstore; my $hyp = $args{hyp}; my $cluster = $args{cluster}; my $destination = $args{destination}; if ($hyp) { unless (defined $hyphash{$hyp}->{pool}) { $hyphash{$hyp}->{pool} = $hyphash{$hyp}->{conn}->get_view(mo_ref => $hyphash{$hyp}->{hostview}->parent, properties => ['resourcePool'])->resourcePool; } $pool = $hyphash{$hyp}->{pool}; if ($destination) { $dstore = $hyphash{$hyp}->{datastorerefmap}->{$destination} } } else { #clustered... unless (defined $clusterhash{$cluster}->{pool}) { my $cview = get_clusterview(clustname => $cluster, conn => $clusterhash{$cluster}->{conn}); $clusterhash{$cluster}->{pool} = $cview->resourcePool; } $pool = $clusterhash{$cluster}->{pool}; if ($destination) { $dstore = $clusterhash{$cluster}->{datastorerefmap}->{$destination} } } return { pool => $pool, datastore => $dstore, } } sub clone_task_callback { my $task = shift; my $parms = shift; my $state = $task->info->state->val; my $node = $parms->{node}; my $conn = $parms->{conn}; my $intent = $parms->{successtext}; if ($state eq 'success') { #xCAT::SvrUtils::sendmsg($intent, $output_handler,$node); my $nodetype = xCAT::Table->new('nodetype', -create => 1); my $vm = xCAT::Table->new('vm', -create => 1); $vm->setAttribs({ node => $node }, $parms->{vment}); $nodetype->setAttribs({ node => $node }, $parms->{nodetypeent}); foreach (keys %{ $parms->{vment} }) { $tablecfg{vm}->{$node}->[0]->{$_} = $parms->{vment}->{$_}; } my @networks = split /,/, $tablecfg{vm}->{$node}->[0]->{nics}; my @macs = xCAT::VMCommon::getMacAddresses(\%tablecfg, $node, scalar @networks); #now with macs, change all macs in the vm to match our generated macs my $regex = qr/^$node(\z|\.)/; #have to do an expensive pull of the vm view, since it is brand new my $nodeviews = $conn->find_entity_views(view_type => 'VirtualMachine', filter => { 'config.name' => $regex }); unless (scalar @$nodeviews == 1) { die "this should be impossible"; } my $vpdtab = xCAT::Table->new('vpd', -create => 1); $vpdtab->setAttribs({ node => $node }, { uuid => $nodeviews->[0]->config->uuid }); my $ndev; my @devstochange; foreach $ndev (@{ $nodeviews->[0]->config->hardware->device }) { unless ($ndev->{macAddress}) { next; } #not an ndev $ndev->{macAddress} = shift @macs; $ndev->{addressType} = "manual"; push @devstochange, VirtualDeviceConfigSpec->new( device => $ndev, operation => VirtualDeviceConfigSpecOperation->new('edit')); } if (@devstochange) { my $reconfigspec = VirtualMachineConfigSpec->new(deviceChange => \@devstochange); my $task = $nodeviews->[0]->ReconfigVM_Task(spec => $reconfigspec); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $parms->{hyp}; $running_tasks{$task}->{conn} = $parms->{conn}; $running_tasks{$task}->{data} = { node => $node, successtext => $intent }; } } elsif ($state eq 'error') { relay_vmware_err($task, "", $node); } } sub promote_vm_to_master { my %args = @_; my $node = $args{node}; my $hyp = $args{hyp}; my $cluster = $args{cluster}; my $regex = qr/^$node(\z|\.)/; my $conn; if ($hyp) { $conn = $hyphash{$hyp}->{conn}; } else { $conn = $clusterhash{$cluster}->{conn}; } my $nodeviews = $conn->find_entity_views(view_type => 'VirtualMachine', filter => { 'config.name' => $regex }); if (scalar(@$nodeviews) != 1) { xCAT::SvrUtils::sendmsg([ 1, "Cannot find $node in VMWare infrastructure" ], $output_handler, $node); return; } my $nodeview = shift @$nodeviews; my $dstore; if ($hyp) { $dstore = $hyphash{$hyp}->{datastorerefmap}->{ $args{url} }, } else { $dstore = $clusterhash{$cluster}->{datastorerefmap}->{ $args{url} }, } my $relocatespec = VirtualMachineRelocateSpec->new( datastore => $dstore, ); my $clonespec = VirtualMachineCloneSpec->new( location => $relocatespec, template => 0, #can't go straight to template, need to clone, then snap, then templatify powerOn => 0 ); my $vmfolder = $vmhash{$node}->{vmfolder}; my $task = $nodeview->CloneVM_Task(folder => $vmfolder, name => $args{mastername}, spec => $clonespec); $running_tasks{$task}->{data} = { node => $node, hyp => $args{hyp}, conn => $conn, successtext => 'Successfully copied to ' . $args{mastername}, mastername => $args{mastername}, url => $args{url} }; $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&promote_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; #$hyp_conns->{$hyp}; $running_tasks{$task}->{vm} = $node; } sub promote_task_callback { my $task = shift; my $parms = shift; my $state = $task->info->state->val; my $node = $parms->{node}; my $intent = $parms->{successtext}; if ($state eq 'success') { #now, we have to make one snapshot for linked clones my $mastername = $parms->{mastername}; my $regex = qr/^$mastername\z/; my $masterviews = $parms->{conn}->find_entity_views(view_type => 'VirtualMachine', filter => { 'config.name' => $regex }); unless (scalar @$masterviews == 1) { die "Impossible"; } my $masterview = $masterviews->[0]; my $task = $masterview->CreateSnapshot_Task(name => "xcatsnap", memory => "false", quiesce => "false"); $parms->{masterview} = $masterview; $running_tasks{$task}->{data} = $parms; $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&promotesnap_task_callback; $running_tasks{$task}->{hyp} = $parms->{hyp}; #$hyp_conns->{$hyp}; $running_tasks{$task}->{vm} = $parms->{node}; #xCAT::SvrUtils::sendmsg($intent, $output_handler,$node); } elsif ($state eq 'error') { relay_vmware_err($task, "", $node); } } sub promotesnap_task_callback { my $task = shift; my $parms = shift; my $state = $task->info->state->val; my $node = $parms->{node}; my $intent = $parms->{successtext}; if ($state eq 'success') { $parms->{masterview}->MarkAsTemplate; #time to be a template xCAT::SvrUtils::sendmsg($intent, $output_handler, $node); my $mastertabentry = { originator => $requester, vintage => scalar(localtime), storage => $parms->{url}, }; foreach (qw/os arch profile/) { if (defined($tablecfg{nodetype}->{$node}->[0]->{$_})) { $mastertabentry->{$_} = $tablecfg{nodetype}->{$node}->[0]->{$_}; } } foreach (qw/storagemodel nics/) { if (defined($tablecfg{vm}->{$node}->[0]->{$_})) { $mastertabentry->{$_} = $tablecfg{vm}->{$node}->[0]->{$_}; } } my $vmmastertab = xCAT::Table->new('vmmaster', -create => 1); my $date = scalar(localtime); $vmmastertab->setAttribs({ name => $parms->{mastername} }, $mastertabentry); } elsif ($state eq 'error') { relay_vmware_err($task, "", $node); } } sub mkvms { my %args = @_; my $nodes = $args{nodes}; my $hyp = $args{hyp}; my $cluster = $args{cluster}; @ARGV = @{ $args{exargs} }; #for getoptions; my $disksize; require Getopt::Long; my $cpuCount; my $memory; GetOptions( 'size|s=s' => \$disksize, "cpus=s" => \$cpuCount, "mem=s" => \$memory ); my $node; my $conn; if ($hyp) { $hyphash{$hyp}->{hostview} = get_hostview(hypname => $hyp, conn => $hyphash{$hyp}->{conn}); #,properties=>['config','configManager']); unless (validate_datastore_prereqs($nodes, $hyp)) { return; } $conn = $hyphash{$hyp}->{conn}; } else { refreshclusterdatastoremap($cluster); $conn = $clusterhash{$cluster}->{conn}; } sortoutdatacenters(nodes => $nodes, hyp => $hyp, cluster => $cluster); my $placement_resources = get_placement_resources(hyp => $hyp, cluster => $cluster); #$hyphash{$hyp}->{pool} = $hyphash{$hyp}->{conn}->get_view(mo_ref=>$hyphash{$hyp}->{hostview}->parent,properties=>['resourcePool'])->resourcePool; my $cfg; foreach $node (@$nodes) { process_tasks; #check for tasks needing followup actions before the task is forgotten (VMWare's memory is fairly short at times if ($conn->find_entity_view(view_type => "VirtualMachine", filter => { name => $node })) { xCAT::SvrUtils::sendmsg([ 1, "Virtual Machine already exists" ], $output_handler, $node); next; } else { register_vm($hyp, $node, $disksize, undef, undef, undef, cpus => $cpuCount, memory => $memory, cluster => $cluster); } } my @dhcpnodes; foreach (keys %{ $tablecfg{dhcpneeded} }) { push @dhcpnodes, $_; delete $tablecfg{dhcpneeded}->{$_}; } unless ($::XCATSITEVALS{'dhcpsetup'} and ($::XCATSITEVALS{'dhcpsetup'} =~ /^n/i or $::XCATSITEVALS{'dhcpsetup'} =~ /^d/i or $::XCATSITEVALS{'dhcpsetup'} eq '0')) { $executerequest->({ command => ['makedhcp'], node => \@dhcpnodes }); } } sub setboot { my %args = @_; my $node = $args{node}; my $hyp = $args{hyp}; if (not defined $args{vmview}) { #attempt one refresh $args{vmview} = $vmhash{$node}->{conn}->find_entity_view(view_type => 'VirtualMachine', properties => ['config.name'], filter => { name => $node }); if (not defined $args{vmview}) { xCAT::SvrUtils::sendmsg([ 1, "VM does not appear to exist" ], $output_handler, $node); return; } } my $bootorder = ${ $args{exargs} }[0]; #NOTE: VMware simply does not currently seem to allow programatically changing the boot #order like other virtualization solutions supported by xCAT. #This doesn't behave quite like any existing mechanism: #vm.bootorder was meant to take the place of system nvram, vmware imitates that unfortunate aspect of bare metal too well.. #rsetboot was created to describe the ipmi scenario of a transient boot device, this is persistant *except* for setup, which is not #rbootseq was meant to be entirely persistant and ordered. #rsetboot is picked, the usage scenario matches about as good as I could think of my $reconfigspec; if ($bootorder =~ /setup/) { unless ($bootorder eq 'setup') { xCAT::SvrUtils::sendmsg([ 1, "rsetboot parameter may not contain 'setup' with other items, assuming vm.bootorder is just 'setup'" ], $output_handler, $node); } $reconfigspec = VirtualMachineConfigSpec->new( bootOptions => VirtualMachineBootOptions->new(enterBIOSSetup => 1), ); } else { $bootorder = "allow:" . $bootorder; $reconfigspec = VirtualMachineConfigSpec->new( bootOptions => VirtualMachineBootOptions->new(enterBIOSSetup => 0), extraConfig => [ OptionValue->new(key => 'bios.bootDeviceClasses', value => $bootorder) ] ); } my $task = $args{vmview}->ReconfigVM_Task(spec => $reconfigspec); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&generic_task_callback; $running_tasks{$task}->{hyp} = $args{hyp}; $running_tasks{$task}->{vm} = $args{node}; $running_tasks{$task}->{data} = { node => $node, successtext => ${ $args{exargs} }[0] }; } sub register_vm { #Attempt to register existing instance of a VM my $hyp = shift; my $node = shift; my $disksize = shift; my $blockedfun = shift; #a pointer to a blocked function to call on success my $blockedargs = shift; #hash reference to call blocked function with my $failonerr = shift; my %args = @_; #ok, went overboard with positional arguments, from now on, named arguments my $task; if ($hyp) { validate_network_prereqs([ keys %{ $hyphash{$hyp}->{nodes} } ], $hyp); unless (defined $hyphash{$hyp}->{datastoremap} or validate_datastore_prereqs([ keys %{ $hyphash{$hyp}->{nodes} } ], $hyp)) { die "unexpected condition"; } } else { scan_cluster_networks($args{cluster}); } sortoutdatacenters(nodes => [$node], hyp => $hyp, cluster => $args{cluster}); my $placement_resources = get_placement_resources(hyp => $hyp, cluster => $args{cluster}); # Try to add an existing VM to the machine folder my $success = eval { if ($hyp) { $task = $vmhash{$node}->{vmfolder}->RegisterVM_Task(path => getcfgdatastore($node, $hyphash{$hyp}->{datastoremap}) . " /$node/$node.vmx", name => $node, pool => $hyphash{$hyp}->{pool}, host => $hyphash{$hyp}->{hostview}, asTemplate => 0); } else { $task = $vmhash{$node}->{vmfolder}->RegisterVM_Task(path => getcfgdatastore($node, $clusterhash{ $args{cluster} }->{datastoremap}) . " /$node/$node.vmx", name => $node, pool => $placement_resources->{pool}, asTemplate => 0); } }; # if we couldn't add it then it means it wasn't created yet. So we create it. my $cluster = $args{cluster}; if ($@ or not $success) { #if (ref($@) eq 'SoapFault') { # if (ref($@->detail) eq 'NotFound') { register_vm_callback(undef, { node => $node, disksize => $disksize, blockedfun => $blockedfun, blockedargs => $blockedargs, errregister => $failonerr, cpus => $args{cpus}, memory => $args{memory}, hyp => $hyp, cluster => $cluster, }); } if ($task) { $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \®ister_vm_callback; $running_tasks{$task}->{hyp} = $hyp; $running_tasks{$task}->{cluster} = $cluster; $running_tasks{$task}->{data} = { node => $node, disksize => $disksize, blockedfun => $blockedfun, blockedargs => $blockedargs, errregister => $failonerr, cpus => $args{cpus}, memory => $args{memory}, hyp => $hyp, cluster => $cluster, }; } } sub register_vm_callback { my $task = shift; my $args = shift; if (not $task or $task->info->state->val eq 'error') { #TODO: fail for 'rpower' flow, mkvm is too invasive in VMWare to be induced by 'rpower on' if (not defined $args->{blockedfun}) { mknewvm($args->{node}, $args->{disksize}, $args->{hyp}, $args); } elsif ($args->{errregister}) { relay_vmware_err($task, "", $args->{node}); } else { xCAT::SvrUtils::sendmsg([ 1, "mkvm must be called before use of this function" ], $output_handler, $args->{node}); } } elsif (defined $args->{blockedfun}) { #If there is a blocked function, call it here) $args->{blockedfun}->(%{ $args->{blockedargs} }); } } sub getURI { my $method = shift; my $location = shift; my $uri = ''; if ($method =~ /nfs/) { (my $server, my $path) = split /\//, $location, 2; $server =~ s/:$//; #tolerate habitual colons my $servern = inet_aton($server); unless ($servern) { xCAT::SvrUtils::sendmsg([ 1, "could not resolve '$server' to an address from vm.storage/vm.cfgstore" ], $output_handler); } $server = inet_ntoa($servern); $uri = "nfs://$server/$path"; } elsif ($method =~ /vmfs/) { (my $name, undef) = split /\//, $location, 2; $name =~ s/:$//; #remove a : if someone put it in for some reason. $uri = "vmfs://$name"; } else { xCAT::SvrUtils::sendmsg([ 1, "Unsupported VMware Storage Method: $method. Please use 'vmfs or nfs'" ], $output_handler); } return $uri; } sub getcfgdatastore { my $node = shift; my $dses = shift; my $cfgdatastore = $tablecfg{vm}->{$node}->[0]->{cfgstore}; unless ($cfgdatastore) { $cfgdatastore = $tablecfg{vm}->{$node}->[0]->{storage}; #TODO: if multiple drives are specified, make sure to split this out #DONE: I believe the regex after this conditional takes care of that case already.. } $cfgdatastore =~ s/=.*//; (my $method, my $location) = split /:\/\//, $cfgdatastore, 2; my $uri = $cfgdatastore; unless ($dses->{$uri}) { #don't call getURI if map works out fine already $uri = getURI($method, $location); } $cfgdatastore = "[" . $dses->{$uri} . "]"; #$cfgdatastore =~ s/,.*$//; #these two lines of code were kinda pointless #$cfgdatastore =~ s/\/$//; return $cfgdatastore; } sub mknewvm { my $node = shift; my $disksize = shift; my $hyp = shift; my $otherargs = shift; my $cluster = $otherargs->{cluster}; my $placement_resources = get_placement_resources(hyp => $hyp, cluster => $cluster); my $pool = $placement_resources->{pool}; my $cfg; if ($hyp) { $cfg = build_cfgspec($node, $hyphash{$hyp}->{datastoremap}, $hyphash{$hyp}->{nets}, $disksize, $hyp, $otherargs); } else { #cluster based.. $cfg = build_cfgspec($node, $clusterhash{$cluster}->{datastoremap}, $clusterhash{$cluster}->{nets}, $disksize, $hyp, $otherargs); } my $task; if ($hyp) { $task = $vmhash{$node}->{vmfolder}->CreateVM_Task(config => $cfg, pool => $hyphash{$hyp}->{pool}, host => $hyphash{$hyp}->{hostview}); } else { $task = $vmhash{$node}->{vmfolder}->CreateVM_Task(config => $cfg, pool => $pool); #drs away } $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&mkvm_callback; $running_tasks{$task}->{hyp} = $hyp; $running_tasks{$task}->{cluster} = $cluster; $running_tasks{$task}->{data} = { hyp => $hyp, cluster => $cluster, node => $node }; } sub getUnits { my $amount = shift; my $defunit = shift; my $divisor = shift; unless ($amount) { return; } unless ($divisor) { $divisor = 1; } if ($amount =~ /(\D)$/) { #If unitless, add unit $defunit = $1; chop $amount; } if ($defunit =~ /k/i) { return $amount * 1024 / $divisor; } elsif ($defunit =~ /m/i) { return $amount * 1048576 / $divisor; } elsif ($defunit =~ /g/i) { return $amount * 1073741824 / $divisor; } } sub getguestid { my $osfound = 0; my $node = shift; if ($tablecfg{vm}->{$node}->[0]->{guestostype}) { #if admin wants to skip derivation from nodetype.os value, let em return $tablecfg{vm}->{$node}->[0]->{guestostype}; } my $nodeos = $tablecfg{nodetype}->{$node}->[0]->{os}; my $nodearch = $tablecfg{nodetype}->{$node}->[0]->{arch}; foreach (keys %guestidmap) { if (defined($nodeos) and $nodeos =~ /$_/) { if ($nodearch eq 'x86_64' and $_ !~ /vmkernel/) { $nodeos = $guestidmap{$_} . "64Guest"; } else { $nodeos = $guestidmap{$_}; $nodeos =~ s/_$//; $nodeos .= "Guest"; } $osfound = 1; last; } } unless ($osfound) { if (defined($nodearch) and $nodearch eq 'x86_64') { $nodeos = "otherGuest64"; } else { $nodeos = "otherGuest"; } } return $nodeos; } sub build_cfgspec { my $node = shift; my $dses = shift; #map to match vm table to datastore names my $netmap = shift; my $disksize = shift; my $hyp = shift; my $otherargs = shift; my $memory; my $ncpus; my $updatehash; if ($otherargs->{memory}) { $memory = getUnits($otherargs->{memory}, "M", 1048576); if ($tablecfg{vm}->{$node}->[0]->{memory}) { $updatehash->{memory} = $memory; } } elsif ($tablecfg{vm}->{$node}->[0]->{memory}) { $memory = getUnits($tablecfg{vm}->{$node}->[0]->{memory}, "M", 1048576); } else { $memory = 512; } if ($otherargs->{cpus}) { $ncpus = $otherargs->{cpus}; if ($tablecfg{vm}->{$node}->[0]->{cpus}) { $updatehash->{cpus} = $ncpus; } } elsif ($tablecfg{vm}->{$node}->[0]->{cpus}) { $ncpus = $tablecfg{vm}->{$node}->[0]->{cpus}; } else { $ncpus = 1; } if ($updatehash) { my $vmtab = xCAT::Table->new('vm', -create => 1); $vmtab->setNodeAttribs($node, $updatehash); } my @devices; $currkey = 0; my $opticalbacking = VirtualCdromRemoteAtapiBackingInfo->new(deviceName => ""); my $opticalconnectable = VirtualDeviceConnectInfo->new(startConnected => 0, allowGuestControl => 1, connected => 0); my $optical = VirtualCdrom->new(controllerKey => 201, connectable => $opticalconnectable, backing => $opticalbacking, key => $currkey++, unitNumber => 0,); push @devices, VirtualDeviceConfigSpec->new(device => $optical, operation => VirtualDeviceConfigSpecOperation->new('add')); push @devices, create_storage_devs($node, $dses, $disksize); push @devices, create_nic_devs($node, $netmap, $hyp); #my $cfgdatastore = $tablecfg{vm}->{$node}->[0]->{storage}; #TODO: need a new cfglocation field in case of stateless guest? #$cfgdatastore =~ s/,.*$//; #$cfgdatastore =~ s/\/$//; #$cfgdatastore = "[".$dses->{$cfgdatastore}."]"; my $cfgdatastore = getcfgdatastore($node, $dses); my $vfiles = VirtualMachineFileInfo->new(vmPathName => $cfgdatastore); #my $nodeos = $tablecfg{nodetype}->{$node}->[0]->{os}; #my $nodearch = $tablecfg{nodetype}->{$node}->[0]->{arch}; my $nodeos = getguestid($node); #nodeos=>$nodeos,nodearch=>$nodearch); my $uuid; if ($tablecfg{vpd}->{$node}->[0]->{uuid}) { $uuid = $tablecfg{vpd}->{$node}->[0]->{uuid}; } else { if ($tablecfg{mac}->{$node}->[0]->{mac}) { #a uuidv1 is possible, generate that for absolute uniqueness guarantee my $mac = $tablecfg{mac}->{$node}->[0]->{mac}; $mac =~ s/\|.*//; $mac =~ s/!.*//; $uuid = xCAT::Utils::genUUID(mac => $mac); } else { $uuid = xCAT::Utils::genUUID(); } my $vpdtab = xCAT::Table->new('vpd'); $vpdtab->setNodeAttribs($node, { uuid => $uuid }); } $uuid =~ s/^(..)(..)(..)(..)-(..)(..)-(..)(..)/$4$3$2$1-$6$5-$8$7/; my @optionvals; if ($tablecfg{vm}->{$node}->[0]->{othersettings}) { my $key; my $value; foreach (split /;/, $tablecfg{vm}->{$node}->[0]->{othersettings}) { ($key, $value) = split /=/; if ($value) { push @optionvals, OptionValue->new(key => $key, value => $value); } else { push @optionvals, OptionValue->new(key => $key); } } } my %specargs = ( name => $node, files => $vfiles, guestId => $nodeos, memoryMB => $memory, numCPUs => $ncpus, deviceChange => \@devices, uuid => $uuid, ); if (@optionvals) { $specargs{extraConfig} = \@optionvals; } return VirtualMachineConfigSpec->new(%specargs); } sub create_nic_devs { my $node = shift; my $netmap = shift; my $hyp = shift; my @networks = split /,/, $tablecfg{vm}->{$node}->[0]->{nics}; my @devs; my $idx = 0; my @macs = xCAT::VMCommon::getMacAddresses(\%tablecfg, $node, scalar @networks); my $connprefs = VirtualDeviceConnectInfo->new( allowGuestControl => 1, connected => 0, startConnected => 1 ); my $model = $tablecfg{vm}->{$node}->[0]->{nicmodel}; unless ($model) { $model = 'e1000'; } foreach (@networks) { my $pgname = $_; if ($hyp) { $pgname = $hyphash{$hyp}->{pgnames}->{$_}; } s/.*://; my $hadspecmodel = 0; if (m/=/) { $hadspecmodel = 1; s/=(.*)$//; } my $tmpmodel = $model; if ($hadspecmodel) { $tmpmodel = $1; } my $netname = $_; my $backing = VirtualEthernetCardNetworkBackingInfo->new( network => $netmap->{$pgname}, deviceName => $pgname, ); my %newcardargs = ( key => 0, #3, #$currkey++, backing => $backing, addressType => "manual", macAddress => shift @macs, connectable => $connprefs, wakeOnLanEnabled => 1, #TODO: configurable in tables? ); my $newcard; if ($tmpmodel eq 'e1000') { $newcard = VirtualE1000->new(%newcardargs); } elsif ($tmpmodel eq 'vmxnet3') { $newcard = VirtualVmxnet3->new(%newcardargs); } elsif ($tmpmodel eq 'pcnet32') { $newcard = VirtualPCNet32->new(%newcardargs); } elsif ($tmpmodel eq 'vmxnet2') { $newcard = VirtualVmxnet2->new(%newcardargs); } elsif ($tmpmodel eq 'vmxnet') { $newcard = VirtualVmxnet->new(%newcardargs); } else { xCAT::SvrUtils::sendmsg([ 1, "$tmpmodel not a recognized nic type, falling back to e1000 (vmxnet3, e1000, pcnet32, vmxnet2, vmxnet are recognized" ], $output_handler, $node); $newcard = VirtualE1000->new(%newcardargs); } push @devs, VirtualDeviceConfigSpec->new(device => $newcard, operation => VirtualDeviceConfigSpecOperation->new('add')); $idx++; } return @devs; die "Stop running for test"; } sub create_storage_devs { my $node = shift; my $sdmap = shift; my $sizes = shift; my @sizes = split /[,:]/, $sizes; my $existingScsiCont = shift; my $scsiUnit = shift; my $existingIdeCont = shift; my $ideUnit = shift; my $devices = shift; my %args = @_; my $scsicontrollerkey = 0; my $idecontrollerkey = 200; #IDE 'controllers' exist at 200 and 201 invariably, with no flexibility? #Cannot find documentation that declares this absolute, but attempts to do otherwise #lead in failure, also of note, these are single-channel controllers, so two devs per controller my $backingif; my @devs; my $havescsidevs = 0; my $disktype = 'ide'; my $ideunitnum = 0; my $scsiunitnum = 0; my $havescsicontroller = 0; my %usedideunits; my %usedscsiunits = (7 => 1, '7' => 1); if (defined $existingScsiCont) { $havescsicontroller = 1; $scsicontrollerkey = $existingScsiCont->{key}; $scsiunitnum = $scsiUnit; %usedscsiunits = %{ getUsedUnits($scsicontrollerkey, $devices) }; } if (defined $existingIdeCont) { $idecontrollerkey = $existingIdeCont->{key}; $ideunitnum = $ideUnit; %usedideunits = %{ getUsedUnits($idecontrollerkey, $devices) }; } my $unitnum; my %disktocont; my $dev; my @storelocs = split /,/, $tablecfg{vm}->{$node}->[0]->{storage}; my $globaldisktype = $tablecfg{vm}->{$node}->[0]->{storagemodel}; unless ($globaldisktype) { $globaldisktype = 'ide'; } #number of devices is the larger of the specified sizes (TODO: masters) or storage pools to span my $numdevs = (scalar @storelocs > scalar @sizes ? scalar @storelocs : scalar @sizes); my $controllertype = 'scsi'; while ($numdevs-- > 0) { my $storeloc = shift @storelocs; unless (scalar @storelocs) { @storelocs = ($storeloc); } #allow reuse of one cfg specified pool for multiple devs my $disksize = shift @sizes; unless (scalar @sizes) { @sizes = ($disksize); } #if we emptied the array, stick the last entry back on to allow it to specify all remaining disks $disksize = getUnits($disksize, 'G', 1024); $disktype = $globaldisktype; if ($storeloc =~ /=/) { ($storeloc, $disktype) = split /=/, $storeloc; } if ($disktype eq 'ide' and $args{idefull}) { xCAT::SvrUtils::sendmsg([ 1, "VM is at capacity for IDE devices, a drive was not added" ], $output_handler, $node); return; } elsif (($disktype eq 'scsi' or $disktype eq 'sas' or $disktype eq 'pvscsi') and $args{scsifull}) { xCAT::SvrUtils::sendmsg([ 1, "SCSI Controller at capacity, a drive was not added" ], $output_handler, $node); return; } $storeloc =~ s/\/$//; (my $method, my $location) = split /:\/\//, $storeloc, 2; my $uri = $storeloc; unless ($sdmap->{$uri}) { #don't call getURI if map works out fine already $uri = getURI($method, $location); } #(my $server,my $path) = split/\//,$location,2; #$server =~ s/:$//; #tolerate habitual colons #my $servern = inet_aton($server); #unless ($servern) { # xCAT::SvrUtils::sendmsg([1,"could not resolve '$server' to an address from vm.storage"]); # return; #} #$server = inet_ntoa($servern); #my $uri = "nfs://$server/$path"; $backingif = VirtualDiskFlatVer2BackingInfo->new(diskMode => 'persistent', thinProvisioned => 1, fileName => "[" . $sdmap->{$uri} . "]"); if ($disktype eq 'ide' and $idecontrollerkey == 1 and $ideunitnum == 0) { #reserve a spot for CD $ideunitnum = 1; } elsif ($disktype eq 'ide' and $ideunitnum == 2) { #go from current to next ide 'controller' $idecontrollerkey++; $ideunitnum = 0; } unless ($disktype eq 'ide') { push @{ $disktocont{$scsicontrollerkey} }, $currkey; } my $controllerkey; if ($disktype eq 'ide') { $controllerkey = $idecontrollerkey; $unitnum = 0; while ($usedideunits{$unitnum}) { $unitnum++; } if ($unitnum == 2) { $idecontrollerkey++; $ideunitnum = 1; $unitnum = 1; $controllerkey = $idecontrollerkey; } $usedideunits{$unitnum} = 1; } else { $controllertype = $disktype; $controllerkey = $scsicontrollerkey; $unitnum = 0; while ($usedscsiunits{$unitnum}) { $unitnum++; } $usedscsiunits{$unitnum} = 1; $havescsidevs = 1; } $dev = VirtualDisk->new(backing => $backingif, controllerKey => $controllerkey, key => $currkey++, unitNumber => $unitnum, capacityInKB => $disksize); push @devs, VirtualDeviceConfigSpec->new(device => $dev, fileOperation => VirtualDeviceConfigSpecFileOperation->new('create'), operation => VirtualDeviceConfigSpecOperation->new('add')); } #It *seems* that IDE controllers are not subject to require creation, so we skip it if ($havescsidevs and not $havescsicontroller) { #need controllers to attach the disks to foreach (0 .. $scsicontrollerkey) { if ($controllertype eq 'scsi') { $dev = VirtualLsiLogicController->new(key => $_, device => \@{ $disktocont{$_} }, sharedBus => VirtualSCSISharing->new('noSharing'), busNumber => $_); } elsif ($controllertype eq 'sas') { $dev = VirtualLsiLogicSASController->new(key => $_, device => \@{ $disktocont{$_} }, sharedBus => VirtualSCSISharing->new('noSharing'), busNumber => $_); } elsif ($controllertype eq 'pvscsi') { $dev = ParaVirtualSCSIController->new(key => $_, device => \@{ $disktocont{$_} }, sharedBus => VirtualSCSISharing->new('noSharing'), busNumber => $_); } push @devs, VirtualDeviceConfigSpec->new(device => $dev, operation => VirtualDeviceConfigSpecOperation->new('add')); } } return @devs; # my $ctlr = VirtualIDEController->new( } sub declare_ready { my %args = %{ shift() }; $hypready{ $args{hyp} } = 1; } sub populate_vcenter_hostviews { my $vcenter = shift; my @hypervisors; my %nametohypmap; my $iterations = 1; if ($usehostnamesforvcenter and $usehostnamesforvcenter !~ /no/i) { $iterations = 2; #two passes possible my $hyp; foreach $hyp (keys %{ $vcenterhash{$vcenter}->{allhyps} }) { if ($tablecfg{hosts}->{$hyp}->[0]->{hostnames}) { $nametohypmap{ $tablecfg{hosts}->{$hyp}->[0]->{hostnames} } = $hyp; } } @hypervisors = keys %nametohypmap; } else { @hypervisors = keys %{ $vcenterhash{$vcenter}->{allhyps} }; } while ($iterations and scalar(@hypervisors)) { my $hosts = join(")|(", @hypervisors); $hosts = '^((' . $hosts . '))(\z|\.)'; my $search = qr/$hosts/; my @hypviews = @{ $vcenterhash{$vcenter}->{conn}->find_entity_views(view_type => 'HostSystem', properties => [ 'summary.config.name', 'summary.runtime.connectionState', 'runtime.inMaintenanceMode', 'parent', 'configManager', 'summary.host' ], filter => { 'summary.config.name' => $search }) }; foreach (@hypviews) { my $hypname = $_->{'summary.config.name'}; my $hypv = $_; my $hyp; if ($vcenterhash{$vcenter}->{allhyps}->{$hypname}) { #simplest case, config.name is exactly the same as node name $vcenterhash{$vcenter}->{hostviews}->{$hypname} = $_; $hyp = $hypname; } elsif ($nametohypmap{$hypname}) { #second case, there is a name mapping this to a real name $vcenterhash{$vcenter}->{hostviews}->{ $nametohypmap{$hypname} } = $_; $hyp = $nametohypmap{$hypname}; } else { #name as-is doesn't work, start stripping domain and hope for the best $hypname =~ s/\..*//; if ($vcenterhash{$vcenter}->{allhyps}->{$hypname}) { #shortname is a node $vcenterhash{$vcenter}->{hostviews}->{$hypname} = $_; $hyp = $hypname; } elsif ($nametohypmap{$hypname}) { #alias for node $vcenterhash{$vcenter}->{hostviews}->{ $nametohypmap{$hypname} } = $_; $hyp = $nametohypmap{$hypname}; } } foreach my $nodename (keys %{ $hyphash{$hyp}->{nodes} }) { $hostrefbynode{$nodename} = $hypv->{'summary.host'}->value; } } $iterations--; @hypervisors = (); if ($usehostnamesforvcenter and $usehostnamesforvcenter !~ /no/i) { #check for hypervisors by native node name if missed above foreach my $hyp (keys %{ $vcenterhash{$vcenter}->{allhyps} }) { unless ($vcenterhash{$vcenter}->{hostviews}->{$hyp}) { push @hypervisors, $hyp; } } } } } sub create_new_cluster { my $req = shift; @ARGV = @{ $req->{arg} }; my $vcenter; my $password; my $user; my $datacenter; GetOptions( 'vcenter=s' => \$vcenter, 'password=s' => \$password, 'datacenter=s' => \$datacenter, 'username=s' => \$user, ); my $clustername = shift @ARGV; my $conn = Vim->new(service_url => "https://$vcenter/sdk"); $conn->login(user_name => $user, password => $password); if ($datacenter) { $datacenter = $conn->find_entity_view(view_type => 'Datacenter', properties => ['hostFolder'], filter => { name => $datacenter }); unless ($datacenter) { xCAT::SvrUtils::sendmsg([ 1, ": Unable to find requested datacenter" ], $output_handler); return; } } else { $datacenter = $conn->find_entity_view(view_type => 'Datacenter', properties => ['hostFolder']); } my $hfolder = $conn->get_view(mo_ref => $datacenter->hostFolder); my $cfgspec = ClusterConfigSpecEx->new(); $hfolder->CreateClusterEx(name => $clustername, spec => $cfgspec); } sub remove_cluster { my $req = shift; @ARGV = @{ $req->{arg} }; my $vcenter; my $user; my $password; my $clustername; GetOptions( 'vcenter=s' => \$vcenter, 'password=s' => \$password, 'username=s' => \$user, ); $clustername = shift @ARGV; my $conn = Vim->new(service_url => "https://$vcenter/sdk"); $conn->login(user_name => $user, password => $password); # $clustview = $hyphash{$hyp}->{vcenter}->{conn}->find_entity_view(view_type => 'Datacenter', properties=>['hostFolder'],filter=>{name=>$tablecfg{hypervisor}->{$hyp}->[0]->{datacenter}}); #my $conn = Vim->new(service_url=>"https://$vcenter/sdk"); $conn->login(user_name => $user, password => $password); my $clustview = $conn->find_entity_view(view_type => 'ClusterComputeResource', filter => { name => $clustername }); my $task = $clustview->Destroy_Task(); my $done = 0; while (not $done) { my $curt = $conn->get_view(mo_ref => $task); my $state = $curt->info->state->val; unless ($state eq 'running' or $state eq 'queued') { $done = 1; } } } sub list_clusters { my $req = shift; @ARGV = @{ $req->{arg} }; my $vcenter; my $password; my $user; my $datacenter; GetOptions( 'vcenter=s' => \$vcenter, 'password=s' => \$password, 'datacenter=s' => \$datacenter, 'username=s' => \$user, ); my $clustername = shift @ARGV; my $conn = Vim->new(service_url => "https://$vcenter/sdk"); $conn->login(user_name => $user, password => $password); use Data::Dumper; my $clustviews = $conn->find_entity_views(view_type => 'ClusterComputeResource'); foreach (@$clustviews) { xCAT::SvrUtils::sendmsg($_->{name}, $output_handler); } return; } sub validate_vcenter_prereqs { #Communicate with vCenter and ensure this host is added correctly to a vCenter instance when an operation requires it my $hyp = shift; my $depfun = shift; my $depargs = shift; my $vcenter = $hyphash{$hyp}->{vcenter}->{name}; unless ($hyphash{$hyp}->{vcenter}->{conn}) { eval { $hyphash{$hyp}->{vcenter}->{conn} = Vim->new(service_url => "https://$vcenter/sdk"); $hyphash{$hyp}->{vcenter}->{conn}->login(user_name => $hyphash{$hyp}->{vcenter}->{username}, password => $hyphash{$hyp}->{vcenter}->{password}); }; if ($@) { $hyphash{$hyp}->{vcenter}->{conn} = undef; } } unless ($hyphash{$hyp}->{vcenter}->{conn}) { xCAT::SvrUtils::sendmsg([ 1, ": Unable to reach vCenter server managing $hyp" ], $output_handler); return undef; } my $foundhyp; my $name = $hyp; if ($usehostnamesforvcenter and $usehostnamesforvcenter !~ /no/i) { if ($tablecfg{hosts}->{$hyp}->[0]->{hostnames}) { $name = $tablecfg{hosts}->{$hyp}->[0]->{hostnames}; } } my $connspec = HostConnectSpec->new( hostName => $name, password => $hyphash{$hyp}->{password}, userName => $hyphash{$hyp}->{username}, force => 1, ); my $hview; $hview = $vcenterhash{$vcenter}->{hostviews}->{$hyp}; if ($hview) { if ($hview->{'summary.config.name'} =~ /^$hyp(?:\.|\z)/ or $hview->{'summary.config.name'} =~ /^$name(?:\.|\z)/) { #Looks good, call the dependent function after declaring the state of vcenter to hypervisor as good if ($hview->{'summary.runtime.connectionState'}->val eq 'connected') { if ($vcenterautojoin) { #admin has requested manual vcenter management, don't mess with vmotion settings enable_vmotion(hypname => $hyp, hostview => $hview, conn => $hyphash{$hyp}->{vcenter}->{conn}); } $vcenterhash{$vcenter}->{goodhyps}->{$hyp} = 1; $depfun->($depargs); if ($hview->parent->type eq 'ClusterComputeResource') { #if it is in a cluster, we can directly remove it $hyphash{$hyp}->{deletionref} = $hview->{mo_ref}; } elsif ($hview->parent->type eq 'ComputeResource') { #For some reason, we must delete the container instead $hyphash{$hyp}->{deletionref} = $hview->{parent}; #save off a reference to delete hostview off just in case } return 1; } elsif ($vcenterautojoin or $vcenterforceremove) { #if allowed autojoin and the current view seems corrupt, throw it away and rejoin my $ref_to_delete; if ($hview->parent->type eq 'ClusterComputeResource') { #We are allowed to specifically kill a host in a cluster $ref_to_delete = $hview->{mo_ref}; } elsif ($hview->parent->type eq 'ComputeResource') { #For some reason, we must delete the container instead $ref_to_delete = $hview->{parent}; } my $task = $hyphash{$hyp}->{vcenter}->{conn}->get_view(mo_ref => $ref_to_delete)->Destroy_Task(); $running_tasks{$task}->{task} = $task; if ($vcenterautojoin) { $running_tasks{$task}->{callback} = \&addhosttovcenter; } elsif ($vcenterforceremove) { $running_tasks{$task}->{callback} = \&delhost_callback; } $running_tasks{$task}->{conn} = $hyphash{$hyp}->{vcenter}->{conn}; $running_tasks{$task}->{data} = { depfun => $depfun, depargs => $depargs, conn => $hyphash{$hyp}->{vcenter}->{conn}, connspec => $connspec, hostview => $hview, hypname => $hyp, vcenter => $vcenter }; return undef; #The rest would be shorter/ideal, but seems to be confused a lot by stateless #Maybe in a future VMWare technology level the following would work better #than it does today # my $task = $hview_->ReconnectHost_Task(cnxSpec=>$connspec); # my $task = $hview->DisconnectHost_Task(); # $running_tasks{$task}->{task} = $task; # $running_tasks{$task}->{callback} = \&disconnecthost_callback; # $running_tasks{$task}->{conn} = $hyphash{$hyp}->{vcenter}->{conn}; # $running_tasks{$task}->{data} = { depfun => $depfun, depargs => $depargs, conn=> $hyphash{$hyp}->{vcenter}->{conn}, connspec=>$connspec,hostview=>$hview,hypname=>$hyp,vcenter=>$vcenter }; #ADDHOST } else { if ($hyphash{$hyp}->{offline}) { xCAT::SvrUtils::sendmsg(": Failed to communicate with $hyp, vCenter reports it as in inventory but not connected and xCAT is set to not autojoin", $output_handler); } else { xCAT::SvrUtils::sendmsg([ 1, ": Failed to communicate with $hyp, vCenter reports it as in inventory but not connected and xCAT is set to not autojoin" ], $output_handler); } $hyphash{$hyp}->{conn} = undef; return "failed"; } } } unless ($vcenterautojoin) { if ($hyphash{$hyp}->{offline}) { xCAT::SvrUtils::sendmsg(": Failed to communicate with $hyp, vCenter does not have it in inventory and xCAT is set to not autojoin", $output_handler); } else { xCAT::SvrUtils::sendmsg([ 1, ": Failed to communicate with $hyp, vCenter does not have it in inventory and xCAT is set to not autojoin" ], $output_handler); } $hyphash{$hyp}->{conn} = undef; return "failed"; } #If still in function, haven't found any likely host entries, make a new one unless ($hyphash{$hyp}->{offline}) { eval { $hyphash{$hyp}->{conn} = Vim->new(service_url => "https://$hyp/sdk"); #Direct connect to install/check licenses $hyphash{$hyp}->{conn}->login(user_name => $hyphash{$hyp}->{username}, password => $hyphash{$hyp}->{password}); }; if ($@) { xCAT::SvrUtils::sendmsg([ 1, ": Failed to communicate with $hyp due to $@" ], $output_handler); $hyphash{$hyp}->{conn} = undef; return "failed"; } validate_licenses($hyp); } addhosttovcenter(undef, { depfun => $depfun, depargs => $depargs, conn => $hyphash{$hyp}->{vcenter}->{conn}, connspec => $connspec, hypname => $hyp, vcenter => $vcenter, }); } sub addhosttovcenter { my $task = shift; my $args = shift; my $hyp = $args->{hypname}; my $depfun = $args->{depfun}; my $depargs = $args->{depargs}; my $connspec = $args->{connspec}; my $vcenter = $args->{vcenter}; if ($task) { my $state = $task->info->state->val; if ($state eq 'error') { die; } } if ($hyphash{ $args->{hypname} }->{offline}) { #let it stay offline $hypready{ $args->{hypname} } = 1; #declare readiness #enable_vmotion(hypname=>$args->{hypname},hostview=>$args->{hostview},conn=>$args->{conn}); $vcenterhash{ $args->{vcenter} }->{goodhyps}->{ $args->{hypname} } = 1; if (defined $args->{depfun}) { #If a function is waiting for the host connect to go valid, call it $args->{depfun}->($args->{depargs}); } return; } if ($tablecfg{hypervisor}->{$hyp}->[0]->{cluster}) { my $cluster = get_clusterview(clustname => $tablecfg{hypervisor}->{$hyp}->[0]->{cluster}, conn => $hyphash{$hyp}->{vcenter}->{conn}); unless ($cluster) { xCAT::SvrUtils::sendmsg([ 1, $tablecfg{hypervisor}->{$hyp}->[0]->{cluster} . " is not a known cluster to the vCenter server." ], $output_handler); $hypready{$hyp} = -1; #Declare impossiblility to be ready return; } $task = $cluster->AddHost_Task(spec => $connspec, asConnected => 1); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&connecthost_callback; $running_tasks{$task}->{conn} = $hyphash{$hyp}->{vcenter}->{conn}; $running_tasks{$task}->{data} = { depfun => $depfun, depargs => $depargs, conn => $hyphash{$hyp}->{vcenter}->{conn}, connspec => $connspec, cluster => $cluster, hypname => $hyp, vcenter => $vcenter }; } else { my $datacenter = validate_datacenter_prereqs($hyp); unless ($datacenter) { return; } my $hfolder = $datacenter->hostFolder; #$hyphash{$hyp}->{vcenter}->{conn}->find_entity_view(view_type=>'Datacenter',properties=>['hostFolder'])->hostFolder; $hfolder = $hyphash{$hyp}->{vcenter}->{conn}->get_view(mo_ref => $hfolder); $task = $hfolder->AddStandaloneHost_Task(spec => $connspec, addConnected => 1); $running_tasks{$task}->{task} = $task; $running_tasks{$task}->{callback} = \&connecthost_callback; $running_tasks{$task}->{conn} = $hyphash{$hyp}->{vcenter}->{conn}; $running_tasks{$task}->{data} = { depfun => $depfun, depargs => $depargs, conn => $hyphash{$hyp}->{vcenter}->{conn}, connspec => $connspec, foldview => $hfolder, hypname => $hyp, vcenter => $vcenter }; } #print Dumper @{$hyphash{$hyp}->{vcenter}->{conn}->find_entity_views(view_type=>'HostSystem',properties=>['runtime.connectionState'])}; } sub validate_datacenter_prereqs { my ($hyp) = @_; my $datacenter; if ($tablecfg{hypervisor}->{$hyp}->[0]->{datacenter}) { $datacenter = $hyphash{$hyp}->{vcenter}->{conn}->find_entity_view(view_type => 'Datacenter', properties => ['hostFolder'], filter => { name => $tablecfg{hypervisor}->{$hyp}->[0]->{datacenter} }); unless ($datacenter) { xCAT::SvrUtils::sendmsg([ 1, ": Unable to find requested datacenter (hypervisor.datacenter for $hyp is " . $tablecfg{hypervisor}->{$hyp}->[0]->{datacenter} . ")" ], $output_handler); return; } } else { $datacenter = $hyphash{$hyp}->{vcenter}->{conn}->find_entity_view(view_type => 'Datacenter', properties => ['hostFolder']); } if (!defined $datacenter) { my $vconn = $hyphash{$hyp}->{vcenter}->{conn}; my $root_folder = $vconn->get_view(mo_ref => $vconn->get_service_content()->rootFolder); $root_folder->CreateDatacenter(name => 'xcat-datacenter'); $datacenter = $hyphash{$hyp}->{vcenter}->{conn}->find_entity_view(view_type => 'Datacenter', properties => ['hostFolder']); } return $datacenter; } sub get_default_switch_for_hypervisor { #This will make sure the default, implicit switch is in order in accordance #with the configuration. If nothing specified, it just spits out vSwitch0 #if something specified, make sure it exists #if it doesn't exist, and the syntax explains how to build it, build it #return undef if something is specified, doesn't exist, and lacks instruction my $hyp = shift; my $defswitch = 'vSwitch0'; my $switchmembers; if ($tablecfg{hypervisor}->{$hyp}->[0]->{defaultnet}) { $defswitch = $tablecfg{hypervisor}->{$hyp}->[0]->{defaultnet}; ($defswitch, $switchmembers) = split /=/, $defswitch, 2; my $vswitch; my $hostview = $hyphash{$hyp}->{hostview}; foreach $vswitch (@{ $hostview->config->network->vswitch }) { if ($vswitch->name eq $defswitch) { return $defswitch; } } #If still here, means we need to build the switch unless ($switchmembers) { return undef; } #No hope, no idea how to make it return create_vswitch($hyp, $defswitch, split(/&/, $switchmembers)); } else { return 'vSwitch0'; } } sub get_switchname_for_portdesc { #Thisk function will examine all current switches to find or create a switch to match the described requirement my $hyp = shift; my $portdesc = shift; my $description; #actual name to use for the virtual switch if ($tablecfg{hypervisor}->{$hyp}->[0]->{netmap}) { foreach (split /,/, $tablecfg{hypervisor}->{$hyp}->[0]->{netmap}) { if (/^$portdesc=/) { ($description, $portdesc) = split /=/, $_, 2; last; } } } else { $description = 'vsw' . $portdesc; } unless ($description) { xCAT::SvrUtils::sendmsg([ 1, ": Invalid format for hypervisor.netmap detected for $hyp" ], $output_handler); return undef; } my %requiredports; my %portkeys; foreach (split /&/, $portdesc) { $requiredports{$_} = 1; } my $hostview = $hyphash{$hyp}->{hostview}; unless ($hostview) { $hyphash{$hyp}->{hostview} = get_hostview(hypname => $hyp, conn => $hyphash{$hyp}->{conn}); #,properties=>['config','configManager']); #clustered can't run here, hyphash conn reference good $hostview = $hyphash{$hyp}->{hostview}; } foreach (@{ $hostview->config->network->pnic }) { if ($requiredports{ $_->device }) { #We establish lookups both ways $portkeys{ $_->key } = $_->device; delete $requiredports{ $_->device }; } } if (keys %requiredports) { xCAT::SvrUtils::sendmsg([ 1, ":Unable to locate the following nics on $hyp: " . join(',', keys %requiredports) ], $output_handler); return undef; } my $foundmatchswitch; my $cfgmismatch = 0; my $vswitch; foreach $vswitch (@{ $hostview->config->network->vswitch }) { $cfgmismatch = 0; #new switch, no sign of mismatch foreach (@{ $vswitch->pnic }) { if ($portkeys{$_}) { $foundmatchswitch = $vswitch->name; delete $requiredports{ $portkeys{$_} }; delete $portkeys{$_}; } else { $cfgmismatch = 1; #If this turns out to have anything, it is bad } } if ($foundmatchswitch) { last; } } if ($foundmatchswitch) { if ($cfgmismatch) { xCAT::SvrUtils::sendmsg([ 1, ": Aggregation mismatch detected, request nic is aggregated with a nic not requested" ], $output_handler); return undef; } unless (keys %portkeys) { return $foundmatchswitch; } die "TODO: add physical nics to aggregation if requested"; } else { return create_vswitch($hyp, $description, values %portkeys); } die "impossible occurance"; return undef; } sub create_vswitch { my $hyp = shift; my $description = shift; my @ports = @_; my $vswitch = HostVirtualSwitchBondBridge->new( nicDevice => \@ports ); my $vswspec = HostVirtualSwitchSpec->new( bridge => $vswitch, mtu => 1500, numPorts => 64 ); my $hostview = $hyphash{$hyp}->{hostview}; my $netman = $hyphash{$hyp}->{conn}->get_view(mo_ref => $hostview->configManager->networkSystem); #can't run in clustered mode, fine path.. $netman->AddVirtualSwitch( vswitchName => $description, spec => $vswspec ); return $description; } sub scan_cluster_networks { my $cluster = shift; use Data::Dumper; my $conn = $clusterhash{$cluster}->{conn}; my $cview = get_clusterview(clustname => $cluster, conn => $conn); if (defined $cview->{network}) { foreach (@{ $cview->network }) { my $nvw = $conn->get_view(mo_ref => $_); if (defined $nvw->name) { $clusterhash{$cluster}->{nets}->{ $nvw->name } = $_; } } } } sub fixup_hostportgroup { my $vlanspec = shift; my $hyp = shift; my %args = @_; my $action = 'add'; if ($args{action}) { $action = $args{action} } my $hostview = $hyphash{$hyp}->{hostview}; my $switchsupport = 0; eval { require xCAT::SwitchHandler; $switchsupport = 1; }; my $hypconn = $hyphash{$hyp}->{conn}; #this function can't work in clustered mode anyway, so this is appropriote. my $vldata = $vlanspec; my $switchname = get_default_switch_for_hypervisor($hyp); my $pgname; $vldata =~ s/=.*//; #TODO specify nic model with =model if ($vldata =~ /:/) { #The config specifies a particular path in some way $vldata =~ s/(.*)://; $switchname = get_switchname_for_portdesc($hyp, $1); $pgname = $switchname . "-" . $vldata; } else { #Use the default vswitch per table config to connect this through, use the same name we did before to maintain compatibility $pgname = $vldata; } my $netsys; $hyphash{$hyp}->{pgnames}->{$vlanspec} = $pgname; my $policy = HostNetworkPolicy->new(); unless ($hyphash{$hyp}->{nets}->{$pgname}) { my $vlanid; if (looks_like_number($vldata)) { $vlanid = $vldata; } elsif ($vldata =~ /trunk/) { $vlanid = 4095; } elsif ($vldata =~ /vl(an)?(\d+)$/) { $vlanid = $2; } else { $vlanid = 0; } if ($vlanid > 0 and $vlanid < 4095 and $switchsupport) { my $switchtab = xCAT::Table->new("switch", -create => 0); if ($switchtab) { my $swent = $switchtab->getNodeAttribs($hyp, [qw/switch port/]); if ($swent and $swent->{'switch'}) { my $swh = new xCAT::SwitchHandler->new($swent->{'switch'}); my @vlids = $swh->get_vlan_ids(); if ($action eq 'add') { unless (grep { $_ eq $vlanid } @vlids) { $swh->create_vlan($vlanid); } $swh->add_ports_to_vlan($vlanid, $swent->{'port'}); } elsif ($action eq 'remove') { $swh->remove_ports_from_vlan($vlanid, $swent->{'port'}); } } } } my $hostgroupdef = HostPortGroupSpec->new( name => $pgname, vlanId => $vlanid, policy => $policy, vswitchName => $switchname ); unless ($netsys) { $netsys = $hyphash{$hyp}->{conn}->get_view(mo_ref => $hostview->configManager->networkSystem); } if ($action eq 'remove') { $netsys->RemovePortGroup(pgName => $pgname); return; } elsif ($action eq 'add') { $netsys->AddPortGroup(portgrp => $hostgroupdef); } #$hyphash{$hyp}->{nets}->{$netname}=1; while ((not defined $hyphash{$hyp}->{nets}->{$pgname}) and sleep 1) { #we will only sleep if we know something will be waiting for $hostview->update_view_data(); #pull in changes induced by previous activity if (defined $hostview->{network}) { #We load the new object references foreach (@{ $hostview->network }) { my $nvw = $hypconn->get_view(mo_ref => $_); if (defined $nvw->name) { $hyphash{$hyp}->{nets}->{ $nvw->name } = $_; } } } } } } sub validate_network_prereqs { my $nodes = shift; my $hyp = shift; my $hypconn = $hyphash{$hyp}->{conn}; #this function can't work in clustered mode anyway, so this is appropriote. my $hostview = $hyphash{$hyp}->{hostview}; if ($hostview) { $hostview->update_view_data(); #pull in changes induced by previous activity } else { $hyphash{$hyp}->{hostview} = get_hostview(hypname => $hyp, conn => $hyphash{$hyp}->{conn}); #,properties=>['config','configManager','network']); $hostview = $hyphash{$hyp}->{hostview}; } my $node; my $method; my $location; if (defined $hostview->{network}) { foreach (@{ $hostview->network }) { my $nvw = $hypconn->get_view(mo_ref => $_); if (defined $nvw->name) { $hyphash{$hyp}->{nets}->{ $nvw->name } = $_; } } } foreach $node (@$nodes) { my @networks = split /,/, $tablecfg{vm}->{$node}->[0]->{nics}; foreach (@networks) { fixup_hostportgroup($_, $hyp); } } return 1; } sub refreshclusterdatastoremap { my $cluster = shift; my $conn = $clusterhash{$cluster}->{conn}; my $cview = get_clusterview(clustname => $cluster, conn => $conn); if (defined $cview->{datastore}) { foreach (@{ $cview->datastore }) { my $dsv = $conn->get_view(mo_ref => $_); if (defined $dsv->info->{nas}) { if ($dsv->info->nas->type eq 'NFS') { my $mnthost = $dsv->info->nas->remoteHost; # my $mnthost = inet_aton($dsv->info->nas->remoteHost); # if ($mnthost) { # $mnthost = inet_ntoa($mnthost); # } else { # $mnthost = $dsv->info->nas->remoteHost; # xCAT::SvrUtils::sendmsg([1,"Unable to resolve VMware specified host '".$dsv->info->nas->remoteHost."' to an address, problems may occur"], $output_handler); # } $clusterhash{$cluster}->{datastoremap}->{ "nfs://" . $mnthost . $dsv->info->nas->remotePath } = $dsv->info->name; $clusterhash{$cluster}->{datastoreurlmap}->{ $dsv->info->name } = "nfs://" . $mnthost . $dsv->info->nas->remotePath; #save off a suitable URL if needed $clusterhash{$cluster}->{datastorerefmap}->{ "nfs://" . $mnthost . $dsv->info->nas->remotePath } = $_; } #TODO: care about SMB } elsif (defined $dsv->info->{vmfs}) { my $name = $dsv->info->vmfs->name; $clusterhash{$cluster}->{datastoremap}->{ "vmfs://" . $name } = $dsv->info->name; $clusterhash{$cluster}->{datastoreurlmap}->{ $dsv->info->name } = "vmfs://" . $name; $clusterhash{$cluster}->{datastorerefmap}->{ "vmfs://" . $name } = $_; } } } #that's... about it... not doing any of the fancy mounting and stuff, if you do it cluster style, you are on your own. It's simply too terrifying to try to fixup #a whole cluster instead of chasing one host, a whole lot slower. One would hope vmware would've done this, but they don't } sub validate_datastore_prereqs { my $hyp = $_[1]; lockbyname($hyp . ".datastores"); $@ = ""; my $rc; eval { $rc = validate_datastore_prereqs_inlock(@_); }; unlockbyname($hyp . ".datastores"); if ($@) { die $@; } return $rc; } sub validate_datastore_prereqs_inlock { my $nodes = shift; my $hyp = shift; my $newdatastores = shift; # a hash reference of URLs to afflicted nodes outside of table space my $hypconn = $hyphash{$hyp}->{conn}; my $hostview = $hyphash{$hyp}->{hostview}; unless ($hostview) { $hyphash{$hyp}->{hostview} = get_hostview(hypname => $hyp, conn => $hypconn); #,properties=>['config','configManager']); $hostview = $hyphash{$hyp}->{hostview}; } my $node; my $method; my $location; # get all of the datastores that are currently available on this node. # and put them into a hash if (defined $hostview->{datastore}) { # only iterate if it exists foreach (@{ $hostview->datastore }) { my $dsv = $hypconn->get_view(mo_ref => $_); if (defined $dsv->info->{nas}) { if ($dsv->info->nas->type eq 'NFS') { my $mnthost = inet_aton($dsv->info->nas->remoteHost); if ($mnthost) { $mnthost = inet_ntoa($mnthost); } else { $mnthost = $dsv->info->nas->remoteHost; xCAT::SvrUtils::sendmsg([ 1, "Unable to resolve VMware specified host '" . $dsv->info->nas->remoteHost . "' to an address, problems may occur" ], $output_handler); } $hyphash{$hyp}->{datastoremap}->{ "nfs://" . $mnthost . $dsv->info->nas->remotePath } = $dsv->info->name; $hyphash{$hyp}->{datastoreurlmap}->{ $dsv->info->name } = "nfs://" . $mnthost . $dsv->info->nas->remotePath; $hyphash{$hyp}->{datastorerefmap}->{ "nfs://" . $mnthost . $dsv->info->nas->remotePath } = $_; } #TODO: care about SMB } elsif (defined $dsv->info->{vmfs}) { my $name = $dsv->info->vmfs->name; $hyphash{$hyp}->{datastoremap}->{ "vmfs://" . $name } = $dsv->info->name; $hyphash{$hyp}->{datastoreurlmap}->{ $dsv->info->name } = "vmfs://" . $name; $hyphash{$hyp}->{datastorerefmap}->{ "vmfs://" . $name } = $_; } } } my $refresh_names = 0; # now go through the nodes and make sure that we have matching datastores. # E.g.: if its NFS, then mount it (if not mounted) # E.g.: if its VMFS, then create it if not created already. Note: VMFS will persist on # machine reboots, unless its destroyed by being overwritten. foreach $node (@$nodes) { my @storage = split /,/, $tablecfg{vm}->{$node}->[0]->{storage}; if ($tablecfg{vm}->{$node}->[0]->{cfgstore}) { push @storage, $tablecfg{vm}->{$node}->[0]->{cfgstore}; } foreach (@storage) { #TODO: merge this with foreach loop below. Here we could build onto $newdatastores instead, for faster operation at scale s/=.*//; #remove device type information from configuration s/\/$//; #Strip trailing slash if specified, to align to VMware semantics if (/:\/\//) { ($method, $location) = split /:\/\//, $_, 2; if ($method =~ /nfs/) { # go through and see if NFS is mounted, if not, then mount it. (my $server, my $path) = split /\//, $location, 2; $server =~ s/:$//; #remove a : if someone put it in out of nfs mount habit my $servern = inet_aton($server); unless ($servern) { xCAT::SvrUtils::sendmsg([ 1, ": Unable to resolve '$server' to an address, check vm.cfgstore/vm.storage" ], $output_handler); return 0; } $server = inet_ntoa($servern); my $uri = "nfs://$server/$path"; unless ($hyphash{$hyp}->{datastoremap}->{$uri}) { #If not already there, must mount it unless ($datastoreautomount) { xCAT::SvrUtils::sendmsg([ 1, ": $uri is not currently accessible at the given location and automount is disabled in site table" ], $output_handler, $node); return 0; } $refresh_names = 1; ($hyphash{$hyp}->{datastoremap}->{$uri}, $hyphash{$hyp}->{datastorerefmap}->{$uri}) = mount_nfs_datastore($hostview, $location); $hyphash{$hyp}->{datastoreurlmap}->{ $hyphash{$hyp}->{datastoremap}->{$uri} } = $uri; } } elsif ($method =~ /vmfs/) { (my $name, undef) = split /\//, $location, 2; $name =~ s/:$//; #remove a : if someone put it in for some reason. my $uri = "vmfs://$name"; # check and see if this vmfs is on the node. unless ($hyphash{$hyp}->{datastoremap}->{$uri}) { #If not already there, try creating it. unless ($datastoreautomount) { xCAT::SvrUtils::sendmsg([ 1, ": $uri is not currently accessible at the given location and automount is disabled in site table" ], $output_handler, $node); return 0; } $refresh_names = 1; ($hyphash{$hyp}->{datastoremap}->{$uri}, $hyphash{$hyp}->{datastorerefmap}->{$uri}) = create_vmfs_datastore($hostview, $name, $hyp); unless ($hyphash{hyp}->{datastoremap}->{$uri}) { return 0; } $hyphash{$hyp}->{datastoreurlmap}->{ $hyphash{$hyp}->{datastoremap}->{$uri} } = $uri; } } else { xCAT::SvrUtils::sendmsg([ 1, ": $method is unsupported at this time (nfs would be)" ], $output_handler, $node); return 0; } } else { xCAT::SvrUtils::sendmsg([ 1, ": $_ not supported storage specification for ESX plugin,\n\t'nfs:///'\n\t\tor\n\t'vmfs://'\n only currently supported vm.storage supported for ESX at the moment" ], $output_handler, $node); return 0; } #TODO: raw device mapping, VMFS via iSCSI, VMFS via FC? } } # newdatastores are for migrations or changing vms. # TODO: make this work for VMFS. Right now only NFS. if (ref $newdatastores) { foreach (keys %$newdatastores) { my $origurl = $_; s/\/$//; #Strip trailing slash if specified, to align to VMware semantics if (/:\/\//) { ($method, $location) = split /:\/\//, $_, 2; if ($method =~ /nfs/) { (my $server, my $path) = split /\//, $location, 2; $server =~ s/:$//; #remove a : if someone put it in out of nfs mount habit my $servern = inet_aton($server); unless ($servern) { xCAT::SvrUtils::sendmsg([ 1, ": Unable to resolve '$server' to an address, check vm.cfgstore/vm.storage" ], $output_handler); return 0; } $server = inet_ntoa($servern); my $uri = "nfs://$server/$path"; unless ($method =~ /nfs/) { foreach (@{ $newdatastores->{$_} }) { xCAT::SvrUtils::sendmsg([ 1, ": $method is unsupported at this time (nfs would be)" ], $output_handler, $_); } return 0; } unless ($hyphash{$hyp}->{datastoremap}->{$uri}) { #If not already there, must mount it unless ($datastoreautomount) { xCAT::SvrUtils::sendmsg([ 1, ":) $uri is not currently accessible at the given location and automount is disabled in site table" ], $output_handler, $node); return 0; } $refresh_names = 1; ($hyphash{$hyp}->{datastoremap}->{$uri}, $hyphash{$hyp}->{datastorerefmap}->{$uri}) = mount_nfs_datastore($hostview, $location); } $hyphash{$hyp}->{datastoreurlmap}->{ $hyphash{$hyp}->{datastoremap}->{$uri} } = $uri; $hyphash{$hyp}->{datastoremap}->{$origurl} = $hyphash{$hyp}->{datastoremap}->{$uri}; #we track both the uri xCAT expected and the one vCenter actually ended up with $hyphash{$hyp}->{datastorerefmap}->{$origurl} = $hyphash{$hyp}->{datastorerefmap}->{$uri}; } elsif ($method =~ /vmfs/) { (my $name, undef) = split /\//, $location, 2; $name =~ s/:$//; #remove a : if someone put it in for some reason. my $uri = "vmfs://$name"; unless ($hyphash{$hyp}->{datastoremap}->{$uri}) { #If not already there, it should be! unless ($datastoreautomount) { xCAT::SvrUtils::sendmsg([ 1, ": $uri is not currently accessible at the given location and automount is disabled in site table" ], $output_handler, $node); return 0; } $refresh_names = 1; ($hyphash{$hyp}->{datastoremap}->{$uri}, $hyphash{$hyp}->{datastorerefmap}->{$uri}) = create_vmfs_datastore($hostview, $name, $hyp); unless ($hyphash{hyp}->{datastoremap}->{$uri}) { return 0; } } $hyphash{$hyp}->{datastoreurlmap}->{ $hyphash{$hyp}->{datastoremap}->{$uri} } = $uri; $hyphash{$hyp}->{datastoremap}->{$origurl} = $hyphash{$hyp}->{datastoremap}->{$uri}; $hyphash{$hyp}->{datastorerefmap}->{$origurl} = $hyphash{$hyp}->{datastorerefmap}->{$uri}; } else { print "$method: not NFS and not VMFS here!\n"; } } else { my $datastore = $_; foreach my $ds (@{ $newdatastores->{$_} }) { xCAT::SvrUtils::sendmsg([ 1, ": $datastore not supported storage specification for ESX plugin, 'nfs:///' only currently supported vm.storage supported for ESX at the moment" ], $output_handler, $ds); } return 0; } #TODO: raw device mapping, VMFS via iSCSI, VMFS via FC, VMFS on same local drive? } } if ($refresh_names) { #if we are in a vcenter context, vmware can rename a datastore behind our backs immediately after adding $hostview->update_view_data(); if (defined $hostview->{datastore}) { # only iterate if it exists foreach (@{ $hostview->datastore }) { my $dsv = $hypconn->get_view(mo_ref => $_); if (defined $dsv->info->{nas}) { if ($dsv->info->nas->type eq 'NFS') { my $mnthost = inet_aton($dsv->info->nas->remoteHost); if ($mnthost) { $mnthost = inet_ntoa($mnthost); } else { $mnthost = $dsv->info->nas->remoteHost; xCAT::SvrUtils::sendmsg([ 1, "Unable to resolve VMware specified host '" . $dsv->info->nas->remoteHost . "' to an address, problems may occur" ], $output_handler); } $hyphash{$hyp}->{datastoremap}->{ "nfs://" . $mnthost . $dsv->info->nas->remotePath } = $dsv->info->name; $hyphash{$hyp}->{datastoreurlmap}->{ $dsv->info->name } = "nfs://" . $mnthost . $dsv->info->nas->remotePath; $hyphash{$hyp}->{datastorerefmap}->{ "nfs://" . $mnthost . $dsv->info->nas->remotePath } = $_; } #TODO: care about SMB } #TODO: care about VMFS } } } return 1; } sub getlabel_for_datastore { my $method = shift; my $location = shift; $location =~ s/\//_/g; $location = $method . '_' . $location; #VMware has a 42 character limit, we will start mangling to get under 42. #Will try to preserve as much informative detail as possible, hence several conditionals instead of taking the easy way out if (length($location) > 42) { $location =~ s/nfs_//; #Ditch unique names for different protocols to the same path, seems unbelievably unlikely } if (length($location) > 42) { $location =~ s/\.//g; #Next, ditch host delimiter, it is unlikely that hosts will have unique names if their dots are removed } if (length($location) > 42) { $location =~ s/_//g; #Next, ditch path delimiter, it is unlikely that two paths will happen to look the same without delimiters } if (length($location) > 42) { #finally, replace the middle with ellipsis substr($location, 20, -20, '..'); } return $location; } sub mount_nfs_datastore { my $hostview = shift; my $location = shift; my $server; my $path; unless ($datastoreautomount) { die "automount of VMware datastores is disabled in site configuration, not continuing"; } ($server, $path) = split /\//, $location, 2; $location = getlabel_for_datastore('nfs', $location); my $nds = HostNasVolumeSpec->new(accessMode => 'readWrite', remoteHost => $server, localPath => $location, remotePath => "/" . $path); my $dsmv = $hostview->{vim}->get_view(mo_ref => $hostview->configManager->datastoreSystem); my $dsref; eval { $dsref = $dsmv->CreateNasDatastore(spec => $nds); }; if ($@) { die "$@" unless $@ =~ m/Fault detail: DuplicateNameFault/; die "esx plugin: a datastore was discovered with the same name referring to a different nominatum- cannot continue\n$@" unless &match_nfs_datastore($server, "/$path", $hostview->{vim}); } return ($location, $dsref); } # create a VMFS data store on a node so that VMs can live locally instead of NFS sub create_vmfs_datastore { my $hostview = shift; # VM object my $name = shift; # name of storage we wish to create. my $hyp = shift; unless ($datastoreautomount) { die "automount of VMware datastores is disabled in site configuration, not continuing"; } # call some VMware API here to create my $hdss = $hostview->{vim}->get_view(mo_ref => $hostview->configManager->datastoreSystem); my $diskList = $hdss->QueryAvailableDisksForVmfs(); my $count = scalar(@$diskList); # get the number of disks available for formatting. unless ($count > 0) { #die "No disks are available to create VMFS volume for $name"; $output_handler->({ error => ["No disks are available on $hyp to create VMFS volume for $name"], errorcode => 1 }); return 0; } foreach my $disk (@$diskList) { my $options = $hdss->QueryVmfsDatastoreCreateOptions(devicePath => $disk->devicePath); @$options[0]->spec->vmfs->volumeName($name); my $newDatastore = $hdss->CreateVmfsDatastore(spec => @$options[0]->spec); #return $newDatastore; # create it on the first disk we see. return ($name, $newDatastore); } return 0; } sub build_more_info { die("TODO: fix this function if called"); print "Does this acually get called????**********************************\n"; my $noderange = shift; my $callback = shift; my $vmtab = xCAT::Table->new("vm"); my @moreinfo = (); unless ($vmtab) { $callback->({ data => ["Cannot open mp table"] }); return @moreinfo; } my %mpa_hash = (); foreach my $node (@$noderange) { my $ent = $vmtab->getNodeAttribs($node, [ 'mpa', 'id' ]); if (defined($ent->{mpa})) { push @{ $mpa_hash{ $ent->{mpa} }{nodes} }, $node; } else { $callback->({ data => ["no mpa defined for node $node"] }); return @moreinfo; } if (defined($ent->{id})) { push @{ $mpa_hash{ $ent->{mpa} }{ids} }, $ent->{id}; } else { push @{ $mpa_hash{ $ent->{mpa} }{ids} }, ""; } } foreach (keys %mpa_hash) { push @moreinfo, "\[$_\]\[" . join(',', @{ $mpa_hash{$_}{nodes} }) . "\]\[" . join(',', @{ $mpa_hash{$_}{ids} }) . "\]"; } return \@moreinfo; } sub copycd { my $request = shift; my $doreq = shift; my $distname = ""; my $path; my $arch; my $darch; my $installroot; $installroot = "/install"; #my $sitetab = xCAT::Table->new('site'); #if($sitetab){ #(my $ref) = $sitetab->getAttribs({key => 'installdir'}, 'value'); my @entries = xCAT::TableUtils->get_site_attribute("installdir"); my $t_entry = $entries[0]; if (defined($t_entry)) { $installroot = $t_entry; } #} @ARGV = @{ $request->{arg} }; my $includeupdate = 0; GetOptions( 'n=s' => \$distname, 'a=s' => \$arch, 'm=s' => \$path, 's' => \$includeupdate ); # run a few tests to see if the copycds should use this plugin unless ($path) { # can't use us cause we need a path and you didn't provide one! return; } if ($distname and $distname !~ /^esx/) { # we're for esx, so if you didn't specify that its not us! return; } my $found = 0; if (-r $path . "/README" and -r $path . "/build_number" and -d $path . "/VMware" and -r $path . "/packages.xml") { #We have a probable new style ESX media open(LINE, $path . "/packages.xml"); my $product; my $version; while () { if (/roductLineId>([^<]*)<\/Prod/) { $product = $1; } if (/ersion>([^<]*)<\/version/) { $version = $1; $version =~ s/\.0$//; } if (/arch>([^>]*)<\/arch/) { unless ($darch and $darch =~ /x86_64/) { #prefer to be characterized as x86_64 $darch = $1; $arch = $1; } } } close(LINE); if ($product and $version) { unless ($distname) { $distname = $product . $version; } $found = 1; } } elsif (-r $path . "/README" and -r $path . "/open_source_licenses.txt" and -d $path . "/VMware") { #Candidate to be ESX 3.5 open(LINE, $path . "/README"); while () { if (/VMware ESX Server 3.5\s*$/) { $darch = 'x86'; $arch = 'x86'; unless ($distname) { $distname = 'esx3.5'; } $found = 1; last; } } close(LINE); } elsif (-r $path . "/README.txt" and -r $path . "/vmkernel.gz") { # its an esxi dvd! # if we got here its probably ESX they want to copy my $line; my $darch; open(LINE, $path . "/README.txt") or die "couldn't open!"; while ($line = ) { chomp($line); if ($line =~ /VMware ESXi(?: version)? 4\.(\d+)/) { $darch = "x86_64"; unless ($distname) { $distname = "esxi4"; if ($1) { $distname .= '.' . $1; } } $found = 1; if ($arch and $arch ne $darch) { xCAT::SvrUtils::sendmsg([ 1, "Requested distribution architecture $arch, but media is $darch" ], $output_handler); return; } $arch = $darch; last; # we found our distro! end this loop madness. } } close(LINE); unless ($found) { xCAT::SvrUtils::sendmsg([ 1, "I don't recognize this VMware ESX DVD" ], $output_handler); return; # doesn't seem to be a valid DVD or CD } } elsif (-r $path . "/vmkernel.gz" and -r $path . "/isolinux.cfg") { open(LINE, $path . "/isolinux.cfg"); while () { if (/ThinESX Installer/) { $darch = 'x86'; $arch = 'x86'; unless ($distname) { $distname = 'esxi3.5'; } $found = 1; last; } } close(LINE); } elsif (-r $path . "/upgrade/metadata.xml") { open(LINE, $path . "/upgrade/metadata.xml"); my $detectdistname; while () { if (/esxVersion>([^<]*)([^<]*)) { if (/VMware ESXi 5\.0/) { $darch = "x86_64"; $arch = "x86_64"; unless ($distname) { $distname = 'esxi5'; } $found = 1; last; } if (/VMware ESXi 5\.1/) { $darch = "x86_64"; $arch = "x86_64"; unless ($distname) { $distname = 'esxi5.1'; } $found = 1; last; } if (/VMware ESXi 5\.5/) { $darch = "x86_64"; $arch = "x86_64"; unless ($distname) { $distname = 'esxi5.5'; } $found = 1; last; } } } unless ($found) { return; } #not our media if ($::XCATSITEVALS{osimagerequired}) { my ($nohaveimages, $errstr) = xCAT::SvrUtils->update_tables_with_templates($distname, $arch, "", "", checkonly => 1); if ($nohaveimages) { $output_handler->({ error => "No Templates found to support $distname($arch)", errorcode => 2 }); } } xCAT::SvrUtils::sendmsg("Copying media to $installroot/$distname/$arch/", $output_handler); my $omask = umask 0022; mkpath("$installroot/$distname/$arch"); umask $omask; my $rc; my $reaped = 0; $SIG{INT} = $SIG{TERM} = sub { foreach (@cpiopid) { kill 2, $_; } if ($path) { chdir("/"); system("umount $path"); } }; my $KID; chdir $path; my $numFiles = `find . -print | wc -l`; my $child = open($KID, "|-"); unless (defined $child) { xCAT::SvrUtils::sendmsg([ 1, "Media copy operation fork failure" ], $output_handler); return; } if ($child) { push @cpiopid, $child; my @finddata = `find .`; for (@finddata) { print $KID $_; } close($KID); $rc = $?; } else { nice 10; my $c = "nice -n 20 cpio -vdump $installroot/$distname/$arch"; my $k2 = open(PIPE, "$c 2>&1 |") || xCAT::SvrUtils::sendmsg([ 1, "Media copy operation fork failure" ], $output_handler); push @cpiopid, $k2; my $copied = 0; my ($percent, $fout); while () { next if /^cpio:/; $percent = $copied / $numFiles; $fout = sprintf "%0.2f%%", $percent * 100; $output_handler->({ sinfo => "$fout" }); ++$copied; } exit; } # let everyone read it #chdir "/tmp"; chmod 0755, "$installroot/$distname/$arch"; if ($distname =~ /esxi[56]/) { #going to tweak boot.cfg for install and default stateless case if (!-r "$installroot/$distname/$arch/boot.cfg.stateless") { copy("$installroot/$distname/$arch/boot.cfg", "$installroot/$distname/$arch/boot.cfg.stateless"); my $bootcfg; open($bootcfg, "<", "$installroot/$distname/$arch/boot.cfg"); my @bootcfg = <$bootcfg>; close($bootcfg); foreach (@bootcfg) { #no point in optimizing trivial, infrequent code, readable this way s!kernel=/!kernel=!; # remove leading / s!modules=/!modules=!; #remove leading / s!--- /!--- !g; #remove all the 'absolute' slashes } open($bootcfg, ">", "$installroot/$distname/$arch/boot.cfg.install"); foreach (@bootcfg) { if (/^modules=/ and $_ !~ /xcatmod.tgz/ and not $::XCATSITEVALS{xcatesximoddisable}) { chomp(); s! *\z! --- xcatmod.tgz\n!; } print $bootcfg $_; } close($bootcfg); foreach (@bootcfg) { #no point in optimizing trivial, infrequent code, readable this way s/runweasel//; #don't run the installer in stateless mode s!--- imgdb.tgz!!; #don't need the imgdb for stateless s!--- imgpayld.tgz!!; #don't need the boot payload since we aren't installing s!--- tools.t00!!; #tools could be useful, but for now skip the memory requirement s!--- weaselin.i00!!; #and also don't need the weasel install images if... not installing if (/^modules=/ and $_ !~ /xcatmod.tgz/ and not $::XCATSITEVALS{xcatesximoddisable}) { chomp(); s! *\z! --- xcatmod.tgz\n!; } s!Loading ESXi installer!xCAT is loading ESXi stateless!; } open($bootcfg, ">", "$installroot/$distname/$arch/boot.cfg.stateless"); foreach (@bootcfg) { print $bootcfg $_; } close($bootcfg); if (grep /LSIProvi.v00/, @bootcfg and !-r "$installroot/$distname/$arch/LSIProvi.v00" and -r "$installroot/$distname/$arch/lsiprovi.v00") { #there is media with LSIProv.v00 expected, but the install media was mal-constructed, fix it move("$installroot/$distname/$arch/lsiprovi.v00", "$installroot/$distname/$arch/LSIProvi.v00"); } } } if ($rc != 0) { xCAT::SvrUtils::sendmsg([ 1, "Media copy operation failed, status $rc" ], $output_handler); } else { xCAT::SvrUtils::sendmsg("Media copy operation successful", $output_handler); my @ret = xCAT::SvrUtils->update_tables_with_templates($distname, $arch); if ($ret[0] != 0) { xCAT::SvrUtils::sendmsg("Error when updating the osimage tables: " . $ret[1], $output_handler); } } } sub makecustomizedmod { my $osver = shift; my $dest = shift; if ($::XCATSITEVALS{xcatesximoddisable}) { return 1; } my $modname; if ($osver =~ /esxi4/) { #want more descriptive name,but don't break esxi4 setups. $modname = "mod.tgz"; # if it already exists, do not overwrite it because it may be someone # else's custom image if (-f "$dest/$modname") { return 1; } } else { $modname = "xcatmod.tgz"; } my $passtab = xCAT::Table->new('passwd'); my $tmp; my $password; if ($passtab) { ($tmp) = $passtab->getAttribs({ 'key' => 'vmware' }, 'username', 'password'); if (defined($tmp)) { $password = $tmp->{password}; } } unless ($password) { xCAT::SvrUtils::sendmsg([ 1, ": Unable to find a password entry for esxi in passwd table" ], $output_handler); return 0; } mkpath("/tmp/xcat"); my $tempdir = tempdir("/tmp/xcat/esxmodcustXXXXXXXX"); my $shadow; mkpath($tempdir . "/etc/"); my $oldmask = umask(0077); open($shadow, ">", $tempdir . "/etc/shadow"); $password = crypt($password, '$1$' . xCAT::Utils::genpassword(8)); my $dayssince1970 = int(time() / 86400); #Be truthful about /etc/shadow my @otherusers = qw/nobody nfsnobody dcui daemon/; if ($osver =~ /esxi4/) { push @otherusers, "vimuser"; } elsif ($osver =~ /esxi[56]/) { push @otherusers, "vpxuser"; } print $shadow "root:$password:$dayssince1970:0:99999:7:::\n"; foreach (@otherusers) { print $shadow "$_:*:$dayssince1970:0:99999:7:::\n"; } close($shadow); umask($oldmask); if ($osver =~ /esxi4/ and -e "$::XCATROOT/share/xcat/netboot/esxi/38.xcat-enableipv6") { mkpath($tempdir . "/etc/vmware/init/init.d"); copy("$::XCATROOT/share/xcat/netboot/esxi/38.xcat-enableipv6", $tempdir . "/etc/vmware/init/init.d/38.xcat-enableipv6"); } elsif ($osver =~ /esxi[56]/ and -e "$::XCATROOT/share/xcat/netboot/esxi/xcat-ipv6.json") { mkpath($tempdir . "/usr/libexec/jumpstart/plugins/"); copy("$::XCATROOT/share/xcat/netboot/esxi/xcat-ipv6.json", $tempdir . "/usr/libexec/jumpstart/plugins/xcat-ipv6.json"); } if ($osver =~ /esxi4/ and -e "$::XCATROOT/share/xcat/netboot/esxi/47.xcat-networking") { copy("$::XCATROOT/share/xcat/netboot/esxi/47.xcat-networking", $tempdir . "/etc/vmware/init/init.d/47.xcat-networking"); } elsif ($osver =~ /esxi[56]/ and -e "$::XCATROOT/share/xcat/netboot/esxi/39.ipv6fixup") { mkpath($tempdir . "/etc/init.d"); copy("$::XCATROOT/share/xcat/netboot/esxi/39.ipv6fixup", $tempdir . "/etc/init.d/39.ipv6fixup"); chmod(0755, "$tempdir/etc/init.d/39.ipv6fixup"); } if ($osver =~ /esxi[56]/ and -e "$::XCATROOT/share/xcat/netboot/esxi/48.esxifixup") { mkpath($tempdir . "/etc/init.d"); copy("$::XCATROOT/share/xcat/netboot/esxi/48.esxifixup", $tempdir . "/etc/init.d/48.esxifixup"); chmod(0755, "$tempdir/etc/init.d/48.esxifixup"); } if ($osver =~ /esxi5/ and -e "$::XCATROOT/share/xcat/netboot/esxi/99.esxiready") { mkpath($tempdir . "/etc/init.d"); copy("$::XCATROOT/share/xcat/netboot/esxi/99.esxiready", $tempdir . "/etc/init.d/99.esxiready"); chmod(0755, "$tempdir/etc/init.d/99.esxiready"); } if (-e "$::XCATROOT/share/xcat/netboot/esxi/xcatsplash") { mkpath($tempdir . "/etc/vmware/"); copy("$::XCATROOT/share/xcat/netboot/esxi/xcatsplash", $tempdir . "/etc/vmware/welcome"); } my $dossh = 0; if (-r "/root/.ssh/id_rsa.pub") { $dossh = 1; my $umask = umask(0077); #don't remember if dropbear is picky, but just in case if ($osver =~ /esxi4/) { #esxi4 used more typical path mkpath($tempdir . "/.ssh"); copy("/root/.ssh/id_rsa.pub", $tempdir . "/.ssh/authorized_keys"); } elsif ($osver =~ /esxi5/) { #weird path to keys mkpath($tempdir . "/etc/ssh/keys-root"); copy("/root/.ssh/id_rsa.pub", $tempdir . "/etc/ssh/keys-root/authorized_keys"); } umask($umask); } my $tfile; mkpath($tempdir . "/var/run/vmware"); open $tfile, ">", $tempdir . "/var/run/vmware/show-tech-support-login"; close($tfile); #TODO: auto-enable ssh and request boot-time customization rather than on-demand? require Cwd; my $dir = Cwd::cwd(); chdir($tempdir); if (-e "$dest/$modname") { unlink("$dest/$modname"); } if ($dossh and $osver =~ /esxi4/) { system("tar czf $dest/$modname * .ssh"); } else { system("tar czf $dest/$modname *"); } chdir($dir); rmtree($tempdir); return 1; } sub getplatform { my $os = shift; if ($os =~ /esxi/) { return "esxi"; } return $os; } sub esxi_kickstart_from_template { my %args = @_; my $installdir = "/install"; if ($::XCATSITEVALS{installdir}) { $installdir = $::XCATSITEVALS{installdir}; } my $plat = getplatform($args{os}); my $template = xCAT::SvrUtils::get_tmpl_file_name("$installdir/custom/install/$plat", $args{profile}, $args{os}, $args{arch}, $args{os}); unless ($template) { $template = xCAT::SvrUtils::get_tmpl_file_name("$::XCATROOT/share/xcat/install/$plat", $args{profile}, $args{os}, $args{arch}, $args{os}); } my $tmperr; if (-r "$template") { $tmperr = xCAT::Template->subvars($template, "$installdir/autoinst/" . $args{node}, $args{node}, undef, undef, undef, $args{tmpl_hash}); } else { $tmperr = "Unable to find template in /install/custom/install/$plat or $::XCATROOT/share/xcat/install/$plat (for $args{profile}/$args{os}/$args{arch} combination)"; } if ($tmperr) { xCAT::SvrUtils::sendmsg([ 1, $tmperr ], $output_handler, $args{node}); } } sub mkinstall { return mkcommonboot("install", @_); } sub mknetboot { return mkcommonboot("stateless", @_); } sub merge_esxi5_append { my $tmpl = shift; my $append = shift; my $outfile = shift; my $in; my $out; open($in, "<", $tmpl); open($out, ">", $outfile); my $line; while ($line = <$in>) { if ($line =~ /kernelopt=/) { chomp($line); $line .= $append . "\n"; #if ($line =~ /modules=b.b00/) { # $line =~ s/modules=b.b00/modules=b.b00 $append/; } unless ($line =~ /^prefix=/) { print $out $line; } } } sub mkcommonboot { my $bootmode = shift; my $req = shift; my $doreq = shift; my $globaltftpdir = "/tftpboot"; my @nodes = @{ $req->{node} }; my $ostab = xCAT::Table->new('nodetype'); #my $sitetab = xCAT::Table->new('site'); my $bptab = xCAT::Table->new('bootparams', -create => 1); my $installroot = "/install"; #if ($sitetab){ #(my $ref) = $sitetab->getAttribs({key => 'installdir'}, 'value'); my @entries = xCAT::TableUtils->get_site_attribute("installdir"); my $t_entry = $entries[0]; if (defined($t_entry)) { $installroot = $t_entry; } #($ref) = $sitetab->getAttribs({key => 'tftpdir'}, 'value'); @entries = xCAT::TableUtils->get_site_attribute("tftpdir"); $t_entry = $entries[0]; if (defined($t_entry)) { $globaltftpdir = $t_entry; } #} my %donetftp = (); my $bpadds = $bptab->getNodesAttribs(\@nodes, ['addkcmdline']); my $nodehmtab = xCAT::Table->new('nodehm', -create => 0); my $serialconfig; if ($nodehmtab) { $serialconfig = $nodehmtab->getNodesAttribs(\@nodes, [ 'serialport', 'serialspeed' ]); } my $restab = xCAT::Table->new('noderes', -create => 0); my $resents; if ($restab) { $resents = $restab->getNodesAttribs(\@nodes, [ 'tftpdir', 'nfsserver', 'xcatmaster', 'tftpserver']); } my %tablecolumnsneededforaddkcmdline; my %nodesubdata; foreach my $key (keys %$bpadds) { #First, we identify all needed table.columns needed to aggregate database call my $add = $bpadds->{$key}->[0]->{addkcmdline}; next if !defined $add; while ($add =~ /#NODEATTRIB:([^:#]+):([^:#]+)#/) { push @{ $tablecolumnsneededforaddkcmdline{$1} }, $2; $add =~ s/#NODEATTRIB:([^:#]+):([^:#]+)#//; } } foreach my $table (keys %tablecolumnsneededforaddkcmdline) { my $tab = xCAT::Table->new($table, -create => 0); if ($tab) { $nodesubdata{$table} = $tab->getNodesAttribs(\@nodes, $tablecolumnsneededforaddkcmdline{$table}); } } my $osents = $ostab->getNodesAttribs(\@nodes, [ 'os', 'arch', 'profile' ]); foreach my $node (@nodes) { my $ent = $osents->{$node}->[0]; my $arch = $ent->{'arch'}; my $profile = $ent->{'profile'}; my $osver = $ent->{'os'}; my $tftpdir; my $ksserver; my %tmpl_hash; if ($resents and $resents->{$node}->[0]->{nfsserver}) { $ksserver = $resents->{$node}->[0]->{nfsserver}; } else { $ksserver = '!myipfn!'; } if ($resents and $resents->{$node}->[0]->{tftpdir}) { $tftpdir = $resents->{$node}->[0]->{tftpdir}; } else { $tftpdir = $globaltftpdir; } if ($resents and $resents->{$node}->[0]->{xcatmaster} ) { $tmpl_hash{"xcatmaster"} = $resents->{$node}->[0]->{xcatmaster}; } if ($resents and $resents->{$node}->[0]->{tftpserver}) { $tmpl_hash{"tftpserver"} = $resents->{$node}->[0]->{tftpserver}; } #if($arch ne 'x86'){ # xCAT::SvrUtils::sendmsg([1,"VMware ESX hypervisors are x86, please change the nodetype.arch value to x86 instead of $arch for $node before proceeding: #e.g: nodech $node nodetype.arch=x86\n"]); # return; #} # first make sure copycds was done: my $custprofpath = $profile; unless ($custprofpath =~ /^\//) { #If profile begins with a /, assume it already is a path $custprofpath = $installroot . "/custom/install/$osver/$arch/$profile"; unless (-d $custprofpath) { $custprofpath = $installroot . "/custom/install/esxi/$arch/$profile"; } } unless ( -r "$custprofpath/vmkboot.gz" or -r "$custprofpath/b.z" or -r "$custprofpath/mboot.c32" or -r "$custprofpath/install.tgz" or -r "$installroot/$osver/$arch/mboot.c32" or -r "$installroot/$osver/$arch/install.tgz") { xCAT::SvrUtils::sendmsg([ 1, "Please run copycds first for $osver or create custom image in $custprofpath/" ], $output_handler); } my @reqmods = qw/vmkboot.gz vmk.gz sys.vgz cim.vgz/; #Required modules for an image to be considered complete if (-r "$custprofpath/b.z") { #if someone hand extracts from imagedd, a different name scheme is used @reqmods = qw/b.z k.z s.z c.z/; } my %mods; foreach (@reqmods) { $mods{$_} = 1; } my $shortprofname = $profile; $shortprofname =~ s/\/\z//; $shortprofname =~ s/.*\///; mkpath("$tftpdir/xcat/netboot/$osver/$arch/$shortprofname/"); my $havemod = 0; unless ($donetftp{ $osver, $arch, $profile, $tftpdir }) { my $srcdir = "$installroot/$osver/$arch"; my $dest = "$tftpdir/xcat/netboot/$osver/$arch/$shortprofname"; cpNetbootImages($osver, $srcdir, $dest, $custprofpath, \%mods, bootmode => $bootmode); if ($havemod = makecustomizedmod($osver, $dest)) { push @reqmods, "mod.tgz"; $mods{"mod.tgz"} = 1; } if ($osver =~ /esxi4/ and -r "$::XCATROOT/share/xcat/netboot/syslinux/mboot.c32") { #prefer xCAT patched mboot.c32 with BOOTIF for mboot copy("$::XCATROOT/share/xcat/netboot/syslinux/mboot.c32", $dest); } elsif (-r "$custprofpath/mboot.c32") { copy("$custprofpath/mboot.c32", $dest); } elsif (-r "$srcdir/mboot.c32") { copy("$srcdir/mboot.c32", $dest); } if (-f "$srcdir/efiboot.img") { copy("$srcdir/efiboot.img", $dest); print("$srcdir/efi"); mkpath("$dest/efi"); recursion_copy("$srcdir/efi", "$dest/efi"); } $donetftp{ $osver, $arch, $profile, $tftpdir } = 1; } my $tp = "xcat/netboot/$osver/$arch/$shortprofname"; my $kernel; my $kcmdline; my $append; my $shortappend; if ($osver =~ /esxi4/) { my $bail = 0; foreach (@reqmods) { unless (-r "$tftpdir/$tp/$_") { xCAT::SvrUtils::sendmsg([ 1, "$_ is missing from the target destination, ensure that either copycds has been run or that $custprofpath contains this file" ], $output_handler); $bail = 1; #only flag to bail, present as many messages as possible to user } } if ($bail) { #if the above loop detected one or more failures, bail out return; } # now make file entry stuff $kernel = "$tp/mboot.c32"; my $prepend; if ($reqmods[0] eq "vmkboot.gz") { $prepend = "$tp/vmkboot.gz"; delete $mods{"vmkboot.gz"}; $append = " --- $tp/vmk.gz"; delete $mods{"vmk.gz"}; $append .= " --- $tp/sys.vgz"; delete $mods{"sys.vgz"}; $append .= " --- $tp/cim.vgz"; delete $mods{"cim.vgz"}; } else { #the single letter style $prepend = "$tp/b.z"; delete $mods{"b.z"}; $append = " --- $tp/k.z"; delete $mods{"k.z"}; $append .= " --- $tp/s.z"; delete $mods{"s.z"}; $append .= " --- $tp/c.z"; delete $mods{"c.z"}; } if ($mods{"mod.tgz"}) { $append .= " --- $tp/mod.tgz"; delete $mods{"mod.tgz"}; } foreach (keys %mods) { $append .= " --- $tp/$_"; } if (defined $bpadds->{$node}->[0]->{addkcmdline}) { my $modules; ($kcmdline, $modules) = split /---/, $bpadds->{$node}->[0]->{addkcmdline}, 2; $kcmdline =~ s/#NODEATTRIB:([^:#]+):([^:#]+)#/$nodesubdata{$1}->{$node}->[0]->{$2}/eg; if ($modules) { $append .= " --- " . $modules; } $prepend .= " " . $kcmdline; } $append = $prepend . $append; } elsif ($osver =~ /esxi[56]/) { #do a more straightforward thing.. $kernel = "$tp/mboot.c32"; if (-r "$tftpdir/$tp/boot.cfg.$bootmode.tmpl") { #so much for straightforward.. $shortappend = "-c $tp/boot.cfg.$bootmode.$node"; } else { $append = "-c $tp/boot.cfg.$bootmode"; } $append .= " xcatd=$ksserver:3001"; if ($bootmode eq "install") { $append .= " ks=http://$ksserver/install/autoinst/$node"; esxi_kickstart_from_template(node => $node, os => $osver, arch => $arch, profile => $profile, tmpl_hash => \%tmpl_hash); } if ($bootmode ne "install" and $serialconfig->{$node}) { #don't do it for install, installer croaks currently my $comport = 1; if (defined $serialconfig->{$node}->[0]->{serialport}) { $comport = $serialconfig->{$node}->[0]->{serialport} + 1; $append .= " -S $comport tty2port=com$comport"; } if (defined $serialconfig->{$node}->[0]->{serialspeed}) { $append .= " -s " . $serialconfig->{$node}->[0]->{serialspeed} . " com" . $comport . "_baud=" . $serialconfig->{$node}->[0]->{serialspeed}; } } if (defined $bpadds->{$node}->[0]->{addkcmdline}) { $append .= " " . $bpadds->{$node}->[0]->{addkcmdline}; $append =~ s/#NODEATTRIB:([^:#]+):([^:#]+)#/$nodesubdata{$1}->{$node}->[0]->{$2}/eg; } } if ($shortappend) { #esxi5 user desiring to put everything in one boot config file. . . merge_esxi5_append("$tftpdir/$tp/boot.cfg.$bootmode.tmpl", $append, "$tftpdir/$tp/boot.cfg.$bootmode.$node"); $append = $shortappend; } $output_handler->({ node => [ { name => [$node], '_addkcmdlinehandled' => [1] } ] }); my $bootparams = ${$req->{bootparams}}; $bootparams->{$node}->[0]->{kernel} = $kernel; $bootparams->{$node}->[0]->{kcmdline} = $append; $bootparams->{$node}->[0]->{initrd} = ""; } # end of node loop } # this is where we extract the netboot images out of the copied ISO image sub cpNetbootImages { my $osver = shift; my $srcDir = shift; my $destDir = shift; my $overridedir = shift; my $modulestoadd = shift; my %parmargs = @_; my $bootmode = "stateless"; if ($parmargs{bootmode}) { $bootmode = $parmargs{bootmode} } my $tmpDir = "/tmp/xcat.$$"; if ($osver =~ /esxi4/) { # we don't want to go through this all the time, so if its already # there we're not going to extract: unless (-r "$destDir/vmk.gz" and -r "$destDir/vmkboot.gz" and -r "$destDir/sys.vgz" and -r "$destDir/cim.vgz" and -r "$destDir/cimstg.tgz" ) { if (-r "$srcDir/image.tgz") { #it still may work without image.tgz if profile customization has everything replaced mkdir($tmpDir); chdir($tmpDir); xCAT::SvrUtils::sendmsg("extracting netboot files from OS image. This may take about a minute or two...hopefully you have ~1GB free in your /tmp dir\n", $output_handler); my $cmd = "tar zxf $srcDir/image.tgz"; if (system($cmd)) { xCAT::SvrUtils::sendmsg([ 1, "Unable to extract $srcDir/image.tgz\n" ], $output_handler); } # this has the big image and may take a while. # this should now create: # /tmp/xcat.1234/usr/lib/vmware/installer/VMware-VMvisor-big-164009-x86_64.dd.bz2 or some other version. We need to extract partition 5 from it. system("bunzip2 $tmpDir/usr/lib/vmware/installer/*bz2"); xCAT::SvrUtils::sendmsg("finished extracting, now copying files...\n", $output_handler); # now we need to get partition 5 which has the installation goods in it. my $scmd = "fdisk -lu $tmpDir/usr/lib/vmware/installer/*dd 2>&1 | grep dd5 | awk '{print \$2}'"; my $sector = `$scmd`; chomp($sector); my $offset = $sector * 512; mkdir "/mnt/xcat"; my $mntcmd = "mount $tmpDir/usr/lib/vmware/installer/*dd /mnt/xcat -o loop,offset=$offset"; if (system($mntcmd)) { xCAT::SvrUtils::sendmsg([ 1, "unable to mount partition 5 of the ESX netboot image to /mnt/xcat" ], $output_handler); return; } if (!-d $destDir) { if (-e $destDir) { xCAT::SvrUtils::sendmsg([ 1, "Could not copy netboot contents to $destDir, it exists but is not currently a directory" ], $output_handler); return; } mkpath($destDir); } if (system("cp /mnt/xcat/* $destDir/")) { xCAT::SvrUtils::sendmsg([ 1, "Could not copy netboot contents to $destDir" ], $output_handler); chdir("/"); system("umount /mnt/xcat"); return; } chdir("/tmp"); system("umount /mnt/xcat"); system("rm -rf $tmpDir"); } elsif (-r "$srcDir/cim.vgz" and -r "$srcDir/vmkernel.gz" and -r "$srcDir/vmkboot.gz" and -r "$srcDir/sys.vgz") { use File::Basename; if (!-d $destDir) { mkpath($destDir); } #In ESXI 4.1, the above breaks, this seems to work, much simpler too foreach ("$srcDir/cim.vgz", "$srcDir/vmkernel.gz", "$srcDir/vmkboot.gz", "$srcDir/sys.vgz", "$srcDir/sys.vgz") { my $mod = scalar fileparse($_); if ($mod =~ /vmkernel.gz/) { copy($_, "$destDir/vmk.gz") or xCAT::SvrUtils::sendmsg([ 1, "Could not copy netboot contents from $_ to $destDir/$mod" ], $output_handler); } else { copy($_, "$destDir/$mod") or xCAT::SvrUtils::sendmsg([ 1, "Could not copy netboot contents from $_ to $destDir/$mod" ], $output_handler); } } } } #this is the override directory if there is one, otherwise it's actually the default dir if (-d $overridedir) { mkdir($overridedir); } #Copy over all modules use File::Basename; foreach (glob "$overridedir/*") { my $mod = scalar fileparse($_); if ($mod =~ /gz\z/ and $mod !~ /pkgdb.tgz/ and $mod !~ /vmkernel.gz/) { $modulestoadd->{$mod} = 1; copy($_, "$destDir/$mod") or xCAT::SvrUtils::sendmsg([ 1, "Could not copy netboot contents from $overridedir to $destDir" ], $output_handler); } elsif ($mod =~ /vmkernel.gz/) { $modulestoadd->{"vmk.gz"} = 1; copy($_, "$destDir/vmk.gz") or xCAT::SvrUtils::sendmsg([ 1, "Could not copy netboot contents from $overridedir to $destDir" ], $output_handler); } } } elsif ($osver =~ /esxi[56]/) { #we need boot.cfg.stateles my @filestocopy = ("boot.cfg.$bootmode"); if (-r "$srcDir/boot.cfg.$bootmode" or -r "$overridedir/boot.cfg.$bootmode") { @filestocopy = ("boot.cfg.$bootmode"); } elsif (-r "$srcDir/boot.cfg.$bootmode.tmpl" or -r "$overridedir/boot.cfg.$bootmode.tmpl") { @filestocopy = ("boot.cfg.$bootmode.tmpl"); } else { xCAT::SvrUtils::sendmsg([ 1, "$srcDir is missing boot.cfg.$bootmode file required for $bootmode boot" ], $output_handler); return; } my $statelesscfg; if (-r "$overridedir/boot.cfg.$bootmode.tmpl") { open($statelesscfg, "<", "$overridedir/boot.cfg.$bootmode.tmpl"); @filestocopy = ("boot.cfg.$bootmode.tmpl"); } elsif (-r "$overridedir/boot.cfg.$bootmode") { open($statelesscfg, "<", "$overridedir/boot.cfg.$bootmode"); } elsif (-r "$srcDir/boot.cfg.$bootmode.tmpl") { @filestocopy = ("boot.cfg.$bootmode.tmpl"); open($statelesscfg, "<", "$srcDir/boot.cfg.$bootmode.tmpl"); } elsif (-r "$srcDir/boot.cfg.$bootmode") { open($statelesscfg, "<", "$srcDir/boot.cfg.$bootmode"); } else { die "boot.cfg.$bootmode was missing from $srcDir???"; } my @statelesscfg = <$statelesscfg>; foreach (@statelesscfg) { #search for files specified by the boot cfg and pull them in if (/^kernel=(.*)/) { push @filestocopy, $1; } elsif (/^modules=(.*)/) { foreach (split / --- /, $1) { s/^\s*//; s/\s.*//; push @filestocopy, $_; } } } #now that we have a list, do the copy (mostly redundant, but PXE needs them tftp accessible) foreach (@filestocopy) { chomp; s/ *\z//; my $mod = scalar fileparse($_); if (-r "$overridedir/$mod") { copyIfNewer("$overridedir/$mod", "$destDir/$mod") or xCAT::SvrUtils::sendmsg([ 1, "Could not copy netboot contents from $overridedir/$mod to $destDir/$mod, $!" ], $output_handler); } elsif (-r "$srcDir/$mod") { copyIfNewer($srcDir . "/" . $mod, "$destDir/$mod") or xCAT::SvrUtils::sendmsg([ 1, "Could not copy netboot contents from $srcDir/$mod to $destDir/$mod, $!" ], $output_handler); } elsif ($mod ne "xcatmod.tgz") { xCAT::SvrUtils::sendmsg([ 1, "Could not copy netboot contents from $srcDir/$mod to $destDir/$mod, $srcDir/$mod not found" ], $output_handler); } } } else { xCAT::SvrUtils::sendmsg([ 1, "VMware $osver is not supported for netboot" ], $output_handler); } } sub copyIfNewer { my $source = shift; my $dest = shift; if (!-e $dest or -C $source < -C $dest) { return copy($source, $dest); } return 1; } # compares nfs target described by parameters to every share mounted by target hypervisor # returns 1 if matching datastore is present and 0 otherwise sub match_nfs_datastore { my ($host, $path, $hypconn) = @_; die "esx plugin bug: no host provided for match_datastore" unless defined $host; die "esx plugin bug: no path provided for match_datastore" unless defined $path; my @ip; eval { if ($host =~ m/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\//) { use Socket; @ip = ($host); $host = gethostbyaddr(inet_aton($host, AF_INET), AF_INET); } else { use Socket; (undef, undef, undef, undef, @ip) = gethostbyname($host); my @ip_ntoa = (); foreach (@ip) { push(@ip_ntoa, inet_ntoa($_)); } @ip = @ip_ntoa; } }; if ($@) { die "error while resolving datastore host: $@\n"; } my %viewcrit = ( view_type => 'HostSystem', properties => ['config.fileSystemVolume'], ); my $dsviews = $hypconn->find_entity_views(%viewcrit); foreach (@$dsviews) { foreach my $mount (@{ $_->get_property('config.fileSystemVolume.mountInfo') }) { next unless $mount->{'volume'}{'type'} eq 'NFS'; my $hostMatch = 0; HOSTMATCH: foreach (@ip, $host) { next HOSTMATCH unless $mount->{'volume'}{'remoteHost'} eq $_; $hostMatch = 1; last HOSTMATCH; } next unless $hostMatch; next unless $mount->{'volume'}{'remotePath'} eq $path; return 1; } } return 0; } 1; # vi: set ts=4 sw=4 filetype=perl: