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
This commit is contained in:
@@ -1 +0,0 @@
|
||||
rvid.xen
|
||||
Executable
+30
@@ -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
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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("<graphics type='".$consdata->{vidproto}."' passwd='$tpasswd' passwdValidTo='$validto' autoport='yes'/>");
|
||||
$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 = "<disk type='file' device='cdrom'><source file='$cdpath'/><target dev='hdc'/><readonly/></disk>";
|
||||
} elsif ($eject) {
|
||||
$newcdxml = "<disk type='file' device='cdrom'><target dev='hdc'/><readonly/></disk>";
|
||||
}
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user