From 053fd1611fcee04505ec1c9ba45bc38c804798e6 Mon Sep 17 00:00:00 2001 From: jbjohnso Date: Fri, 6 May 2011 12:52:10 +0000 Subject: [PATCH] Backport a few features from trunk: KVM: -Remote video enhancements for password use, remote listen, and SPICE -chvm allows attaching/removing an iso as optical ESX: -Support linked clones -Presume a clone target is in the same datastore as source when not specified git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/branches/2.6@9499 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd --- xCAT-client/share/xcat/rvid/rvid.kvm | 31 ++++++- xCAT-server/lib/xcat/plugins/esx.pm | 110 ++++++++++++++++++++-- xCAT-server/lib/xcat/plugins/kvm.pm | 131 +++++++++++++++++++++++---- 3 files changed, 244 insertions(+), 28 deletions(-) mode change 120000 => 100755 xCAT-client/share/xcat/rvid/rvid.kvm diff --git a/xCAT-client/share/xcat/rvid/rvid.kvm b/xCAT-client/share/xcat/rvid/rvid.kvm deleted file mode 120000 index feb274c70..000000000 --- a/xCAT-client/share/xcat/rvid/rvid.kvm +++ /dev/null @@ -1 +0,0 @@ -rvid.xen \ No newline at end of file diff --git a/xCAT-client/share/xcat/rvid/rvid.kvm b/xCAT-client/share/xcat/rvid/rvid.kvm new file mode 100755 index 000000000..4aebabe1e --- /dev/null +++ b/xCAT-client/share/xcat/rvid/rvid.kvm @@ -0,0 +1,30 @@ +#!/bin/bash +# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html +if [ ! -z "$rvid_virturi" -a ! -z "$rvid_virtname" ]; then + if [ -x /usr/bin/virt-viewer ]; then + exec /usr/bin/virt-viewer -c $rvid_virturi $rvid_virtname +# else +# echo "Installation of virt-viewer is recommended, falling back to vncviewer" + fi +fi +myport=${rvid_vidport#*:} +if [ $rvid_vidproto = "vnc" ];then + let myport=myport-5900; + export VNC_VIA_CMD='/usr/bin/ssh -o BatchMode=yes -f -L "$L":"$H":"$R" "$G" sleep 20' + ( + flock 200 + echo $rvid_password|vncviewer :$myport -via $rvid_server AutoSelect=0 FullColor=1 passwdInput=1 >& /dev/null & + sleep 0.2 + flock -u 200 +) 200> /tmp/xcat/virtvnclock +elif [ $rvid_vidproto = "spice" ]; then + SPICEC="/usr/bin/spicec" + if [ ! -x $SPICEC -a -x /usr/libexec/spicec ]; then + SPICEC=/usr/libexec/spicec + elif [ ! -x $SPICEC ]; then + echo "Error: spice client could not be found and needed for this VM" >&2; + fi + $SPICEC -h $rvid_server -p $myport -w $rvid_password +fi + + diff --git a/xCAT-server/lib/xcat/plugins/esx.pm b/xCAT-server/lib/xcat/plugins/esx.pm index ded0c8ba0..4f5a74a82 100644 --- a/xCAT-server/lib/xcat/plugins/esx.pm +++ b/xCAT-server/lib/xcat/plugins/esx.pm @@ -560,6 +560,16 @@ sub do_cmd { 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 @@ -1986,10 +1996,18 @@ sub clonevms { } } } elsif ($target) { + if ($url =~ m!/!) { $url=$target; - $url =~ s!/([^/]*)\z!!; + $url =~ s!/([^/]*)\z!!; $mastername=$1; - $newdatastores->{$url}=[$nodes->[0]]; + } 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)) { @@ -2087,16 +2105,26 @@ sub clone_vms_from_master { 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 $clonespec = VirtualMachineCloneSpec->new( + my %clonespecargs = ( location=>$relocatespec, template=>0, powerOn=>0 ); + unless ($args{detach}) { + $clonespecargs{snapshot}=$masterview->snapshot->currentSnapshot; + } + 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, successtext => 'Successfully cloned from '.$args{mastername}, mastername=>$args{mastername}, nodetypeent=>$nodetypeent,vment=>$vment }; + $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}; @@ -2135,13 +2163,47 @@ sub clone_task_callback { 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); + #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; + 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}->{data} = { node => $node, successtext => $intent}; + } + + } elsif ($state eq 'error') { relay_vmware_err($task,"",$node); } @@ -2176,25 +2238,52 @@ sub promote_vm_to_master { ); my $clonespec = VirtualMachineCloneSpec->new( location=>$relocatespec, - template=>1, + 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, successtext => 'Successfully copied to '.$args{mastername}, mastername=>$args{mastername}, url=>$args{url} }; + $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, @@ -3834,7 +3923,12 @@ sub makecustomizedmod { 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 vimuser/; + my @otherusers = qw/nobody nfsnobody dcui daemon/; + if ($osver =~ /esxi4/) { + push @otherusers,"vimuser"; + } elsif ($osver =~ /esxi5/) { + push @otherusers,"vpxuser"; + } print $shadow "root:$password:$dayssince1970:0:99999:7:::\n"; foreach (@otherusers) { print $shadow "$_:*:$dayssince1970:0:99999:7:::\n"; diff --git a/xCAT-server/lib/xcat/plugins/kvm.pm b/xCAT-server/lib/xcat/plugins/kvm.pm index ec60eed8d..0ff57a0e8 100644 --- a/xCAT-server/lib/xcat/plugins/kvm.pm +++ b/xCAT-server/lib/xcat/plugins/kvm.pm @@ -16,6 +16,7 @@ use XML::LibXML; #now that we are in the business of modifying xml data, need so #TODO: convert all uses of XML::Simple to LibXML? Using both seems wasteful in a way.. use XML::Simple qw(XMLout); use Thread qw(yield); +use xCAT::Utils qw/genpassword/; use File::Basename qw/fileparse/; use File::Path qw/mkpath/; use IO::Socket; @@ -404,6 +405,13 @@ sub build_diskstruct { $cdhash->{readonly}; $cdhash->{target}->{dev}='hdc'; push @returns,$cdhash; + } else { #give the VM an empty optical drive, to allow chvm live attach/remove + my $cdhash; + $cdhash->{device}='cdrom'; + $cdhash->{type}='file'; + $cdhash->{readonly}; + $cdhash->{target}->{dev}='hdc'; + push @returns,$cdhash; } @@ -604,10 +612,25 @@ sub build_xmldesc { $xtree{devices}->{disk}=build_diskstruct($cdloc); $xtree{devices}->{interface}=build_nicstruct($node); #use content to force xml simple to not make model the 'name' of video - $xtree{devices}->{video}= [ { 'content'=>'','model'=> {type=>'vga',vram=>8192}}]; + if (defined ($confdata->{vm}->{$node}->[0]->{vidmodel})) { + my $model = $confdata->{vm}->{$node}->[0]->{vidmodel}; + my $vram = '8192'; + $xtree{devices}->{video}= [ { 'content'=>'','model'=> {type=>$model,vram=>8192}}]; + } else { + $xtree{devices}->{video}= [ { 'content'=>'','model'=> {type=>'vga',vram=>8192}}]; + } $xtree{devices}->{input}->{type}='tablet'; $xtree{devices}->{input}->{bus}='usb'; - $xtree{devices}->{graphics}->{type}='vnc'; + if (defined ($confdata->{vm}->{$node}->[0]->{vidproto})) { + $xtree{devices}->{graphics}->{type}=$confdata->{vm}->{$node}->[0]->{vidproto}; + } else { + $xtree{devices}->{graphics}->{type}='vnc'; + } + $xtree{devices}->{graphics}->{autoport}='yes'; + $xtree{devices}->{graphics}->{listen}='0.0.0.0'; + $xtree{devices}->{graphics}->{password}=genpassword(16); + $xtree{devices}->{sound}->{model}='ac97'; + $xtree{devices}->{console}->{type}='pty'; $xtree{devices}->{console}->{target}->{port}='1'; return XMLout(\%xtree,RootName=>"domain"); @@ -619,11 +642,12 @@ sub refresh_vm { my $newxml=$dom->get_xml_description(); $updatetable->{kvm_nodedata}->{$node}->{xml}=$newxml; $newxml = XMLin($newxml); - my $vncport=$newxml->{devices}->{graphics}->{port}; + my $vidport=$newxml->{devices}->{graphics}->{port}; + my $vidproto=$newxml->{devices}->{graphics}->{type}; my $stty=$newxml->{devices}->{console}->{tty}; - $updatetable->{vm}->{$node}={vncport=>$vncport,textconsole=>$stty}; + #$updatetable->{vm}->{$node}={vncport=>$vncport,textconsole=>$stty}; #$vmtab->setNodeAttribs($node,{vncport=>$vncport,textconsole=>$stty}); - return {vncport=>$vncport,textconsole=>$stty}; + return {vidport=>$vidport,textconsole=>$stty,vidproto=>$vidproto}; } sub getcons { @@ -649,25 +673,36 @@ sub getcons { $sconsparms->{node}->[0]->{psuedotty}=[$consdata->{textconsole}]; $sconsparms->{node}->[0]->{baudrate}=[$serialspeed]; return (0,$sconsparms); - } elsif ($type eq "vnc") { - return (0,'ssh+vnc@'.$hyper.": localhost:".$consdata->{vncport}); #$consdata->{vncport}); + } elsif ($type eq "vid") { + my $domxml = $dom->get_xml_description(); + my $parseddom = $parser->parse_string($domxml); + my ($graphicsnode) = $parseddom->findnodes("//graphics"); + + my $tpasswd=genpassword(16); + my $validto=POSIX::strftime("%Y-%m-%dT%H:%M:%S",gmtime(time()+300)); + $graphicsnode->setAttribute("passwd",$tpasswd); + $graphicsnode->setAttribute("passwdValidTo",$validto); + $dom->update_device($graphicsnode->toString()); + #$dom->update_device(""); + $consdata->{password}=$tpasswd; + $consdata->{server}=$hyper; + return $consdata; + #return (0,{$consdata->{vidproto}.'@'.$hyper.":".$consdata->{vidport}); #$consdata->{vncport}); } } sub getrvidparms { my $node=shift; - my $location = getcons($node,"vnc"); - if ($location =~ /ssh\+vnc@([^:]*):([^:]*):(\d+)/) { - my @output = ( - "method: kvm", - "server: $1", - "vncdisplay: $2:$3", - "virturi: ".$hypconn->get_uri(), - "virtname: $node", - ); - return 0,@output; - } else { - return (1,"Error: Unable to determine rvid destination for $node"); + my $location = getcons($node,"vid"); + unless ($location) { + return (1,"Error: Unable to determine rvid destination for $node"); } + my @output = ( + "method: kvm" + ); + foreach (keys %$location) { + push @output,$_.":".$location->{$_}; + } + return 0,@output; } sub pick_target { @@ -974,6 +1009,9 @@ sub makedom { } elsif (not $xml) { $xml = build_xmldesc($node,cd=>$cdloc); } + my $parseddom = $parser->parse_string($xml); + my ($graphics) = $parseddom->findnodes("//graphics"); + $graphics->setAttribute("passwd",genpassword(20)); my $errstr; eval { $dom=$hypconn->create_domain($xml); }; if ($@) { $errstr = $@; } @@ -1324,12 +1362,16 @@ sub chvm { my @purge; my @derefdisks; my $memory; + my $cdrom; + my $eject; @ARGV=@_; require Getopt::Long; GetOptions( "a=s"=>\@addsizes, "d=s"=>\@derefdisks, "mem=s"=>\$memory, + "cdrom=s"=>\$cdrom, + "eject"=>\$eject, "cpus=s" => \$cpucount, "p=s"=>\@purge, "resize=s%" => \%resize, @@ -1496,6 +1538,57 @@ sub chvm { } } + my $newcdxml; + if ($cdrom) { + my $cdpath; + if ($cdrom =~ m!://!) { + my $url = $cdrom; + $url =~ s!([^/]+)\z!!; + my $imagename=$1; + my $poolobj = get_storage_pool_by_url($url); + unless ($poolobj) { die "Cound not get storage pool for $url"; } + my $poolxml = $poolobj->get_xml_description(); #yes, I have to XML parse for even this... + my $parsedpool = $parser->parse_string($poolxml); + $cdpath = $parsedpool->findnodes("/pool/target/path/text()")->[0]->data; + $cdpath .= "/".$imagename; + } else { + if ($cdrom =~ m!^/dev/!) { + die "TODO: device pass through if anyone cares"; + } elsif ($cdrom =~ m!^/!) { #full path... I guess + $cdpath=$cdrom; + } else { + die "TODO: relative paths, use client cwd as hint?"; + } + } + unless ($cdpath) { die "unable to understand cd path specification"; } + $newcdxml = ""; + } elsif ($eject) { + $newcdxml = ""; + } + if ($newcdxml) { + if ($currstate eq 'on') { + $dom->attach_device($newcdxml); + $vmxml=$dom->get_xml_description(); + } else { + unless ($vmxml) { + $vmxml=$confdata->{kvmnodedata}->{$node}->[0]->{xml}; + } + my $domparsed = $parser->parse_string($vmxml); + my $candidatenodes=$domparsed->findnodes("//disk[\@device='cdrom']"); + if (scalar (@$candidatenodes) != 1) { + die "shouldn't be possible, should only have one cdrom"; + } + my $newcd=$parser->parse_balanced_chunk($newcdxml); + $candidatenodes->[0]->replaceNode($newcd); + my $moddedxml=$domparsed->toString; + if ($moddedxml) { + $vmxml=$moddedxml; + } + } + if ($vmxml) { + $updatetable->{kvm_nodedata}->{$node}->{xml}=$vmxml; + } + } if ($cpucount or $memory) { if ($currstate eq 'on') { xCAT::SvrUtils::sendmsg([1,"Hot add of cpus or memory not supported"],$callback,$node);