Commit 6572e51d authored by Dietmar Maurer's avatar Dietmar Maurer

remove OpenVZ related code

There is still no OpenVZ for kernel 3.10 or newer, so we remove that code now.
parent 99658ee3
......@@ -12,8 +12,7 @@ PERLSOURCE = \
Pool.pm \
Tasks.pm \
Network.pm \
Services.pm \
OpenVZ.pm
Services.pm
all:
......
......@@ -20,7 +20,6 @@ use PVE::JSONSchema qw(get_standard_option);
use PVE::AccessControl;
use PVE::Storage;
use PVE::Firewall;
use PVE::OpenVZ;
use PVE::APLInfo;
use PVE::HA::Config;
use PVE::QemuServer;
......@@ -31,7 +30,6 @@ use PVE::API2::Tasks;
use PVE::API2::Storage::Scan;
use PVE::API2::Storage::Status;
use PVE::API2::Qemu;
use PVE::API2::OpenVZ;
use PVE::API2::VZDump;
use PVE::API2::APT;
use PVE::API2::Ceph;
......@@ -50,11 +48,6 @@ __PACKAGE__->register_method ({
path => 'ceph',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::OpenVZ",
path => 'openvz',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::VZDump",
path => 'vzdump',
......@@ -142,7 +135,6 @@ __PACKAGE__->register_method ({
{ name => 'scan' },
{ name => 'storage' },
{ name => 'qemu' },
{ name => 'openvz' },
{ name => 'vzdump' },
{ name => 'ubcfailcnt' },
{ name => 'network' },
......@@ -183,47 +175,6 @@ __PACKAGE__->register_method ({
return PVE::pvecfg::version_info();
}});
__PACKAGE__->register_method({
name => 'beancounters_failcnt',
path => 'ubcfailcnt',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
},
method => 'GET',
proxyto => 'node',
proxyto => 'node',
protected => 1, # openvz /proc entries are only readable by root
description => "Get user_beancounters failcnt for all active containers.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
id => { type => 'string' },
failcnt => { type => 'number' },
},
},
},
code => sub {
my ($param) = @_;
my $ubchash = PVE::OpenVZ::read_user_beancounters();
my $res = [];
foreach my $vmid (keys %$ubchash) {
next if !$vmid;
push @$res, { id => $vmid, failcnt => $ubchash->{$vmid}->{failcntsum} };
}
return $res;
}});
__PACKAGE__->register_method({
name => 'status',
path => 'status',
......@@ -1119,110 +1070,6 @@ __PACKAGE__->register_method({
return $res;
}});
__PACKAGE__->register_method({
name => 'apl_download',
path => 'aplinfo',
method => 'POST',
permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
},
description => "Download appliance templates.",
proxyto => 'node',
protected => 1,
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id'),
template => { type => 'string', maxLength => 255 },
},
},
returns => { type => "string" },
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $node = $param->{node};
my $list = PVE::APLInfo::load_data();
my $template = $param->{template};
my $pd = $list->{all}->{$template};
raise_param_exc({ template => "no such template"}) if !$pd;
my $cfg = cfs_read_file("storage.cfg");
my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
die "cannot download to storage type '$scfg->{type}'"
if !($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs');
die "unknown template type '$pd->{type}'\n" if $pd->{type} ne 'openvz';
die "storage '$param->{storage}' does not support templates\n"
if !$scfg->{content}->{vztmpl};
my $src = $pd->{location};
my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
my $dest = "$tmpldir/$template";
my $tmpdest = "$tmpldir/${template}.tmp.$$";
my $worker = sub {
my $upid = shift;
print "starting template download from: $src\n";
print "target file: $dest\n";
eval {
if (-f $dest) {
my $md5 = (split (/\s/, `md5sum '$dest'`))[0];
if ($md5 && (lc($md5) eq lc($pd->{md5sum}))) {
print "file already exists $md5 - no need to download\n";
return;
}
}
local %ENV;
my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
if ($dccfg->{http_proxy}) {
$ENV{http_proxy} = $dccfg->{http_proxy};
}
my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
if (system (@cmd) != 0) {
die "download failed - $!\n";
}
my $md5 = (split (/\s/, `md5sum '$tmpdest'`))[0];
if (!$md5 || (lc($md5) ne lc($pd->{md5sum}))) {
die "wrong checksum: $md5 != $pd->{md5sum}\n";
}
if (system ('mv', $tmpdest, $dest) != 0) {
die "unable to save file - $!\n";
}
};
my $err = $@;
unlink $tmpdest;
if ($err) {
print "\n";
die $err if $err;
}
print "download finished\n";
};
return $rpcenv->fork_worker('download', undef, $user, $worker);
}});
my $get_start_stop_list = sub {
my ($nodename, $autostart) = @_;
......@@ -1239,16 +1086,7 @@ my $get_start_stop_list = sub {
my $bootorder = LONG_MAX;
if ($d->{type} eq 'openvz') {
my $conf = PVE::OpenVZ::load_config($vmid);
return if $autostart && !($conf->{onboot} && $conf->{onboot}->{value});
if ($conf->{bootorder} && defined($conf->{bootorder}->{value})) {
$bootorder = $conf->{bootorder}->{value};
}
$startup = { order => $bootorder };
} elsif ($d->{type} eq 'qemu') {
if ($d->{type} eq 'qemu') {
my $conf = PVE::QemuServer::load_config($vmid);
return if $autostart && !$conf->{onboot};
......@@ -1331,11 +1169,7 @@ __PACKAGE__->register_method ({
my $default_delay = 0;
my $upid;
if ($d->{type} eq 'openvz') {
return if PVE::OpenVZ::check_running($vmid);
print STDERR "Starting CT $vmid\n";
$upid = PVE::API2::OpenVZ->vm_start({node => $nodename, vmid => $vmid });
} elsif ($d->{type} eq 'qemu') {
if ($d->{type} eq 'qemu') {
$default_delay = 3; # to redruce load
return if PVE::QemuServer::check_running($vmid, 1);
print STDERR "Starting VM $vmid\n";
......@@ -1360,12 +1194,8 @@ __PACKAGE__->register_method ({
}
}
} else {
if ($d->{type} eq 'openvz') {
print STDERR "Starting CT $vmid failed: $status\n";
} elsif ($d->{type} eq 'qemu') {
print STDERR "Starting VM $vmid failed: status\n";
}
}
};
warn $@ if $@;
}
......@@ -1380,13 +1210,7 @@ my $create_stop_worker = sub {
my ($nodename, $type, $vmid, $down_timeout) = @_;
my $upid;
if ($type eq 'openvz') {
return if !PVE::OpenVZ::check_running($vmid);
my $timeout = defined($down_timeout) ? int($down_timeout) : 60;
print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n";
$upid = PVE::API2::OpenVZ->vm_shutdown({node => $nodename, vmid => $vmid,
timeout => $timeout, forceStop => 1 });
} elsif ($type eq 'qemu') {
if ($type eq 'qemu') {
return if !PVE::QemuServer::check_running($vmid, 1);
my $timeout = defined($down_timeout) ? int($down_timeout) : 60*3;
print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n";
......@@ -1475,12 +1299,7 @@ my $create_migrate_worker = sub {
my ($nodename, $type, $vmid, $target) = @_;
my $upid;
if ($type eq 'openvz') {
my $online = PVE::OpenVZ::check_running($vmid) ? 1 : 0;
print STDERR "Migrating CT $vmid\n";
$upid = PVE::API2::OpenVZ->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
online => $online });
} elsif ($type eq 'qemu') {
if ($type eq 'qemu') {
my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
print STDERR "Migrating VM $vmid\n";
$upid = PVE::API2::Qemu->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
......
package PVE::API2::OpenVZ;
use strict;
use warnings;
use File::Basename;
use File::Path;
use POSIX qw (LONG_MAX);
use PVE::SafeSyslog;
use PVE::Tools qw(extract_param run_command);
use PVE::Exception qw(raise raise_param_exc);
use PVE::INotify;
use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
use PVE::AccessControl;
use PVE::Storage;
use PVE::RESTHandler;
use PVE::RPCEnvironment;
use PVE::OpenVZ;
use PVE::OpenVZMigrate;
use PVE::JSONSchema qw(get_standard_option);
use PVE::API2::Firewall::VM;
use base qw(PVE::RESTHandler);
use Data::Dumper; # fixme: remove
my $pve_base_ovz_config = <<__EOD;
ONBOOT="no"
PHYSPAGES="0:256M"
SWAPPAGES="0:256M"
KMEMSIZE="116M:128M"
DCACHESIZE="58M:64M"
LOCKEDPAGES="128M"
PRIVVMPAGES="unlimited"
SHMPAGES="unlimited"
NUMPROC="unlimited"
VMGUARPAGES="0:unlimited"
OOMGUARPAGES="0:unlimited"
NUMTCPSOCK="unlimited"
NUMFLOCK="unlimited"
NUMPTY="unlimited"
NUMSIGINFO="unlimited"
TCPSNDBUF="unlimited"
TCPRCVBUF="unlimited"
OTHERSOCKBUF="unlimited"
DGRAMRCVBUF="unlimited"
NUMOTHERSOCK="unlimited"
NUMFILE="unlimited"
NUMIPTENT="unlimited"
# Disk quota parameters (in form of softlimit:hardlimit)
DISKSPACE="unlimited:unlimited"
DISKINODES="unlimited:unlimited"
QUOTATIME="0"
QUOTAUGIDLIMIT="0"
# CPU fair scheduler parameter
CPUUNITS="1000"
CPUS="1"
__EOD
my $get_container_storage = sub {
my ($stcfg, $vmid, $veconf) = @_;
my $path = PVE::OpenVZ::get_privatedir($veconf, $vmid);
my ($vtype, $volid) = PVE::Storage::path_to_volume_id($stcfg, $path);
my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1) if $volid;
return wantarray ? ($sid, $volname, $path) : $sid;
};
my $check_ct_modify_config_perm = sub {
my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
return 1 if $authuser ne 'root@pam';
foreach my $opt (@$key_list) {
if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
} elsif ($opt eq 'disk' || $opt eq 'quotatime' || $opt eq 'quotaugidlimit') {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
} elsif ($opt eq 'memory' || $opt eq 'swap') {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
} elsif ($opt eq 'netif' || $opt eq 'ip_address' || $opt eq 'nameserver' ||
$opt eq 'searchdomain' || $opt eq 'hostname') {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
} else {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
}
}
return 1;
};
__PACKAGE__->register_method({
name => 'vmlist',
path => '',
method => 'GET',
description => "OpenVZ container index (per node).",
permissions => {
description => "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
user => 'all',
},
proxyto => 'node',
protected => 1, # openvz proc files are only readable by root
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{vmid}" } ],
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $vmstatus = PVE::OpenVZ::vmstatus();
my $res = [];
foreach my $vmid (keys %$vmstatus) {
next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
my $data = $vmstatus->{$vmid};
$data->{vmid} = $vmid;
push @$res, $data;
}
return $res;
}});
my $restore_openvz = sub {
my ($private, $archive, $vmid, $force) = @_;
my $vzconf = PVE::OpenVZ::read_global_vz_config();
my $conffile = PVE::OpenVZ::config_file($vmid);
my $cfgdir = dirname($conffile);
my $root = $vzconf->{rootdir};
$root =~ s/\$VEID/$vmid/;
print "you choose to force overwriting VPS config file, private and root directories.\n" if $force;
die "unable to create CT $vmid - container already exists\n"
if !$force && -f $conffile;
die "unable to create CT $vmid - directory '$private' already exists\n"
if !$force && -d $private;
die "unable to create CT $vmid - directory '$root' already exists\n"
if !$force && -d $root;
my $conf;
eval {
if ($force && -f $conffile) {
my $conf = PVE::OpenVZ::load_config($vmid);
my $oldprivate = PVE::OpenVZ::get_privatedir($conf, $vmid);
rmtree $oldprivate if -d $oldprivate;
my $oldroot = $conf->{ve_root} ? $conf->{ve_root}->{value} : $root;
rmtree $oldroot if -d $oldroot;
};
mkpath $private || die "unable to create private dir '$private'";
mkpath $root || die "unable to create root dir '$root'";
my $cmd = ['tar', 'xpf', $archive, '--totals', '--sparse', '-C', $private];
if ($archive eq '-') {
print "extracting archive from STDIN\n";
run_command($cmd, input => "<&STDIN");
} else {
print "extracting archive '$archive'\n";
run_command($cmd);
}
my $backup_cfg = "$private/etc/vzdump/vps.conf";
if (-f $backup_cfg) {
print "restore configuration to '$conffile'\n";
my $conf = PVE::Tools::file_get_contents($backup_cfg);
$conf =~ s/VE_ROOT=.*/VE_ROOT=\"$root\"/;
$conf =~ s/VE_PRIVATE=.*/VE_PRIVATE=\"$private\"/;
$conf =~ s/host_ifname=veth[0-9]+\./host_ifname=veth${vmid}\./g;
PVE::Tools::file_set_contents($conffile, $conf);
foreach my $s (PVE::OpenVZ::SCRIPT_EXT) {
my $tfn = "$cfgdir/${vmid}.$s";
my $sfn = "$private/etc/vzdump/vps.$s";
if (-f $sfn) {
my $sc = PVE::Tools::file_get_contents($sfn);
PVE::Tools::file_set_contents($tfn, $sc);
}
}
}
rmtree "$private/etc/vzdump";
};
my $err = $@;
if ($err) {
rmtree $private;
rmtree $root;
unlink $conffile;
foreach my $s (PVE::OpenVZ::SCRIPT_EXT) {
unlink "$cfgdir/${vmid}.$s";
}
die $err;
}
return $conf;
};
# create_vm is also used by vzrestore
__PACKAGE__->register_method({
name => 'create_vm',
path => '',
method => 'POST',
description => "Create or restore a container.",
permissions => {
user => 'all', # check inside
description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
"For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
"You also need 'Datastore.AllocateSpace' permissions on the storage.",
},
protected => 1,
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => PVE::OpenVZ::json_config_properties({
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
ostemplate => {
description => "The OS template or backup file.",
type => 'string',
maxLength => 255,
},
password => {
optional => 1,
type => 'string',
description => "Sets root password inside container.",
},
storage => get_standard_option('pve-storage-id', {
description => "Target storage.",
default => 'local',
optional => 1,
}),
force => {
optional => 1,
type => 'boolean',
description => "Allow to overwrite existing container.",
},
restore => {
optional => 1,
type => 'boolean',
description => "Mark this as restore task.",
},
pool => {
optional => 1,
type => 'string', format => 'pve-poolid',
description => "Add the VM to the specified pool.",
},
}),
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $node = extract_param($param, 'node');
my $vmid = extract_param($param, 'vmid');
my $password = extract_param($param, 'password');
my $storage = extract_param($param, 'storage') || 'local';
my $pool = extract_param($param, 'pool');
my $storage_cfg = cfs_read_file("storage.cfg");
my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node);
raise_param_exc({ storage => "storage '$storage' does not support openvz root directories"})
if !$scfg->{content}->{rootdir};
my $private = PVE::Storage::get_private_dir($storage_cfg, $storage, $vmid);
my $basecfg_fn = PVE::OpenVZ::config_file($vmid);
if (defined($pool)) {
$rpcenv->check_pool_exist($pool);
$rpcenv->check_perm_modify($authuser, "/pool/$pool");
}
$rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
# OK
} elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
# OK
} elsif ($param->{restore} && $param->{force} && (-f $basecfg_fn) &&
$rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
# OK: user has VM.Backup permissions, and want to restore an existing VM
} else {
raise_perm_exc();
}
&$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
PVE::Storage::activate_storage($storage_cfg, $storage);
my $conf = PVE::OpenVZ::parse_ovz_config("/tmp/openvz/$vmid.conf", $pve_base_ovz_config);
my $ostemplate = extract_param($param, 'ostemplate');
my $archive;
if ($ostemplate eq '-') {
die "pipe requires cli environment\n"
if $rpcenv->{type} ne 'cli';
die "pipe can only be used with restore tasks\n"
if !$param->{restore};
$archive = '-';
} else {
$rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
$archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
}
if (!defined($param->{searchdomain}) &&
!defined($param->{nameserver})) {
my $resolv = PVE::INotify::read_file('resolvconf');
$param->{searchdomain} = $resolv->{search} if $resolv->{search};
my @ns = ();
push @ns, $resolv->{dns1} if $resolv->{dns1};
push @ns, $resolv->{dns2} if $resolv->{dns2};
push @ns, $resolv->{dns3} if $resolv->{dns3};
$param->{nameserver} = join(' ', @ns) if scalar(@ns);
}
# try to append domain to hostmane
if ($param->{hostname} && $param->{hostname} !~ m/\./ &&
$param->{searchdomain}) {
$param->{hostname} .= ".$param->{searchdomain}";
}
my $check_vmid_usage = sub {
if ($param->{force}) {
die "cant overwrite mounted container\n"
if PVE::OpenVZ::check_mounted($conf, $vmid);
} else {
die "CT $vmid already exists\n" if -f $basecfg_fn;
}
};
my $code = sub {
&$check_vmid_usage(); # final check after locking
PVE::OpenVZ::update_ovz_config($vmid, $conf, $param);
my $rawconf = PVE::OpenVZ::generate_raw_config($pve_base_ovz_config, $conf);
PVE::Cluster::check_cfs_quorum();
if ($param->{restore}) {
&$restore_openvz($private, $archive, $vmid, $param->{force});
# is this really needed?
my $cmd = ['vzctl', '--skiplock', '--quiet', 'set', $vmid,
'--applyconfig_map', 'name', '--save'];
run_command($cmd);
# reload config
$conf = PVE::OpenVZ::load_config($vmid);
# and initialize quota
my $disk_quota = $conf->{disk_quota}->{value};
if (!defined($disk_quota) || ($disk_quota != 0)) {
$cmd = ['vzctl', '--skiplock', 'quotainit', $vmid];
run_command($cmd);
}
} else {
PVE::Tools::file_set_contents($basecfg_fn, $rawconf);
my $cmd = ['vzctl', '--skiplock', 'create', $vmid,
'--ostemplate', $archive, '--private', $private];
run_command($cmd);
# hack: vzctl '--userpasswd' starts the CT, but we want
# to avoid that for create
PVE::OpenVZ::set_rootpasswd($private, $password)
if defined($password);
}
PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
};
my $realcmd = sub { PVE::OpenVZ::lock_container($vmid, 1, $code); };
&$check_vmid_usage(); # first check before locking
return $rpcenv->fork_worker($param->{restore} ? 'vzrestore' : 'vzcreate',
$vmid, $authuser, $realcmd);
}});
my $vm_config_perm_list = [
'VM.Config.Disk',
'VM.Config.CPU',
'VM.Config.Memory',
'VM.Config.Network',
'VM.Config.Options',
];
__PACKAGE__->register_method({
name => 'update_vm',
path => '{vmid}/config',
method => 'PUT',
protected => 1,
proxyto => 'node',
description => "Set virtual machine options.",
permissions => {
check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
},
parameters => {
additionalProperties => 0,
properties => PVE::OpenVZ::json_config_properties(
{
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
digest => {
type => 'string',
description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
maxLength => 40,
optional => 1,
}
}),
},
returns => { type => 'null'},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $node = extract_param($param, 'node');
my $vmid = extract_param($param, 'vmid');
my $digest = extract_param($param, 'digest');
die "no options specified\n" if !scalar(keys %$param);
&$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
my $code = sub {
my $conf = PVE::OpenVZ::load_config($vmid);
die "checksum missmatch (file change by other user?)\n"
if $digest && $digest ne $conf->{digest};
my $changes = PVE::OpenVZ::update_ovz_config($vmid, $conf, $param);
return if scalar (@$changes) <= 0;
my $cmd = ['vzctl', '--skiplock', 'set', $vmid, @$changes, '--save'];
PVE::Cluster::log_msg('info', $authuser, "update CT $vmid: " . join(' ', @$changes));
run_command($cmd);
};
PVE::OpenVZ::lock_container($vmid, undef, $code);
return undef;
}});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Firewall::CT",
path => '{vmid}/firewall',
});
__PACKAGE__->register_method({
name => 'vmdiridx',
path => '{vmid}',
method => 'GET',
proxyto => 'node',
description => "Directory index",
permissions => {
user => 'all',
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
subdir => { type => 'string' },
},
},
links => [ { rel => 'child', href => "{subdir}" } ],
},
code => sub {
my ($param) = @_;
# test if VM exists
my $conf = PVE::OpenVZ::load_config($param->{vmid});
my $res = [
{ subdir => 'config' },
{ subdir => 'status' },
{ subdir => 'vncproxy' },
{ subdir => 'spiceproxy' },
{ subdir => 'migrate' },
{ subdir => 'initlog' },
{ subdir => 'rrd' },
{ subdir => 'rrddata' },
{ subdir => 'firewall' },
];
return $res;
}});
__PACKAGE__->register_method({
name => 'rrd',
path => '{vmid}/rrd',
method => 'GET',
protected => 1, # fixme: can we avoid that?
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
},
description => "Read VM RRD statistics (returns PNG)",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
timeframe => {
description => "Specify the time frame you are interested in.",
type => 'string',
enum => [ 'hour', 'day', 'week', 'month', 'year' ],
},
ds => {
description => "The list of datasources you want to display.",
type => 'string', format => 'pve-configid-list',
},
cf => {
description => "The RRD consolidation function",
type => 'string',
enum => [ 'AVERAGE', 'MAX' ],
optional => 1,
},
},
},
returns => {
type => "object",
properties => {
filename => { type => 'string' },
},
},
code => sub {
my ($param) = @_;
return PVE::Cluster::create_rrd_graph(
"pve2-vm/$param->{vmid}", $param->{timeframe},
$param->{ds}, $param->{cf});
}});
__PACKAGE__->register_method({
name => 'rrddata',
path => '{vmid}/rrddata',
method => 'GET',
protected => 1, # fixme: can we avoid that?
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
},
description => "Read VM RRD statistics",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
timeframe => {
description => "Specify the time frame you are interested in.",
type => 'string',
enum => [ 'hour', 'day', 'week', 'month', 'year' ],
},
cf => {
description => "The RRD consolidation function",
type => 'string',
enum => [ 'AVERAGE', 'MAX' ],
optional => 1,
},
},
},
returns => {
type => "array",
items => {
type => "object",
properties => {},
},
},
code => sub {
my ($param) = @_;
return PVE::Cluster::create_rrd_data(
"pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
}});
__PACKAGE__->register_method({
name => 'initlog',
path => '{vmid}/initlog',
method => 'GET',
protected => 1,
proxyto => 'node',
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
},
description => "Read init log.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
start => {
type => 'integer',
minimum => 0,
optional => 1,
},
limit => {
type => 'integer',
minimum => 0,
optional => 1,
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
n => {
description=> "Line number",
type=> 'integer',
},
t => {
description=> "Line text",
type => 'string',
}
}
}
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $vmid = $param->{vmid};
my $conf = PVE::OpenVZ::load_config($vmid);
my $privatedir = PVE::OpenVZ::get_privatedir($conf, $vmid);
my $logfn = "$privatedir/var/log/init.log";
my ($count, $lines) = PVE::Tools::dump_logfile($logfn, $param->{start}, $param->{limit});
$rpcenv->set_result_attrib('total', $count);
return $lines;
}});
__PACKAGE__->register_method({
name => 'vm_config',
path => '{vmid}/config',
method => 'GET',
proxyto => 'node',
description => "Get container configuration.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => "object",
properties => {
digest => {
type => 'string',
description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
}
},
},
code => sub {
my ($param) = @_;
my $veconf = PVE::OpenVZ::load_config($param->{vmid});
# we only return selected/converted values
my $conf = { digest => $veconf->{digest} };
if ($veconf->{ostemplate} && $veconf->{ostemplate}->{value}) {
$conf->{ostemplate} = $veconf->{ostemplate}->{value};
}
my $stcfg = cfs_read_file("storage.cfg");
my ($sid, undef, $path) = &$get_container_storage($stcfg, $param->{vmid}, $veconf);
$conf->{storage} = $sid || $path;
my $properties = PVE::OpenVZ::json_config_properties();
foreach my $k (keys %$properties) {
next if $k eq 'memory';
next if $k eq 'swap';
next if $k eq 'disk';
next if !$veconf->{$k};
next if !defined($veconf->{$k}->{value});
if ($k eq 'description') {
$conf->{$k} = PVE::Tools::decode_text($veconf->{$k}->{value});
} else {
$conf->{$k} = $veconf->{$k}->{value};
}
}
($conf->{memory}, $conf->{swap}) = PVE::OpenVZ::ovz_config_extract_mem_swap($veconf, 1024*1024);
my $diskspace = $veconf->{diskspace}->{bar} || LONG_MAX;
if ($diskspace == LONG_MAX) {
$conf->{disk} = 0;
} else {
$conf->{disk} = $diskspace/(1024*1024);
}
return $conf;
}});
__PACKAGE__->register_method({
name => 'destroy_vm',
path => '{vmid}',
method => 'DELETE',
protected => 1,
proxyto => 'node',
description => "Destroy the container (also delete all uses files).",
permissions => {
check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $vmid = $param->{vmid};
# test if VM exists
my $conf = PVE::OpenVZ::load_config($param->{vmid});
my $realcmd = sub {
my $cmd = ['vzctl', 'destroy', $vmid ];
run_command($cmd);
PVE::AccessControl::remove_vm_from_pool($vmid);
};
return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
}});
my $sslcert;
__PACKAGE__->register_method ({
name => 'vncproxy',
path => '{vmid}/vncproxy',
method => 'POST',
protected => 1,
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
},
description => "Creates a TCP VNC proxy connections.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
websocket => {
optional => 1,
type => 'boolean',
description => "use websocket instead of standard VNC.",
},
},
},
returns => {
additionalProperties => 0,
properties => {
user => { type => 'string' },
ticket => { type => 'string' },
cert => { type => 'string' },
port => { type => 'integer' },
upid => { type => 'string' },
},
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $vmid = $param->{vmid};
my $node = $param->{node};
my $authpath = "/vms/$vmid";
my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
$sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
if !$sslcert;
my $port = PVE::Tools::next_vnc_port();
my $remip;
if ($node ne PVE::INotify::nodename()) {
$remip = PVE::Cluster::remote_node_ip($node);
}
# NOTE: vncterm VNC traffic is already TLS encrypted,
# so we select the fastest chipher here (or 'none'?)
my $remcmd = $remip ?
['/usr/bin/ssh', '-t', $remip] : [];
my $shcmd = [ '/usr/bin/dtach', '-A',
"/var/run/dtach/vzctlconsole$vmid",
'-r', 'winch', '-z',
'/usr/sbin/vzctl', 'console', $vmid ];
my $realcmd = sub {
my $upid = shift;
syslog ('info', "starting openvz vnc proxy $upid\n");
my $timeout = 10;
my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
'-timeout', $timeout, '-authpath', $authpath,
'-perm', 'VM.Console'];
if ($param->{websocket}) {
$ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
push @$cmd, '-notls', '-listen', 'localhost';
}
push @$cmd, '-c', @$remcmd, @$shcmd;
run_command($cmd);
return;
};
my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
PVE::Tools::wait_for_vnc_port($port);
return {
user => $authuser,
ticket => $ticket,
port => $port,
upid => $upid,
cert => $sslcert,
};
}});
__PACKAGE__->register_method({
name => 'vncwebsocket',
path => '{vmid}/vncwebsocket',
method => 'GET',
permissions => {
description => "You also need to pass a valid ticket (vncticket).",
check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
},
description => "Opens a weksocket for VNC traffic.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
vncticket => {
description => "Ticket from previous call to vncproxy.",
type => 'string',
maxLength => 512,
},
port => {
description => "Port number returned by previous vncproxy call.",
type => 'integer',
minimum => 5900,
maximum => 5999,
},
},
},
returns => {
type => "object",
properties => {
port => { type => 'string' },
},
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $authpath = "/vms/$param->{vmid}";
PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
my $port = $param->{port};
return { port => $port };
}});
__PACKAGE__->register_method ({
name => 'spiceproxy',
path => '{vmid}/spiceproxy',
method => 'POST',
protected => 1,
proxyto => 'node',
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
},
description => "Returns a SPICE configuration to connect to the CT.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
proxy => get_standard_option('spice-proxy', { optional => 1 }),
},
},
returns => get_standard_option('remote-viewer-config'),
code => sub {
my ($param) = @_;
my $vmid = $param->{vmid};
my $node = $param->{node};
my $proxy = $param->{proxy};
my $authpath = "/vms/$vmid";
my $permissions = 'VM.Console';
my $shcmd = ['/usr/bin/dtach', '-A',
"/var/run/dtach/vzctlconsole$vmid",
'-r', 'winch', '-z',
'/usr/sbin/vzctl', 'console', $vmid];
my $title = "CT $vmid";
return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
}});
__PACKAGE__->register_method({
name => 'vmcmdidx',
path => '{vmid}/status',
method => 'GET',
proxyto => 'node',
description => "Directory index",
permissions => {
user => 'all',
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
subdir => { type => 'string' },
},
},
links => [ { rel => 'child', href => "{subdir}" } ],
},
code => sub {
my ($param) = @_;
# test if VM exists
my $conf = PVE::OpenVZ::load_config($param->{vmid});
my $res = [
{ subdir => 'current' },
{ subdir => 'ubc' },
{ subdir => 'start' },
{ subdir => 'stop' },
];
return $res;
}});
__PACKAGE__->register_method({
name => 'vm_status',
path => '{vmid}/status/current',
method => 'GET',
proxyto => 'node',
protected => 1, # openvz /proc entries are only readable by root
description => "Get virtual machine status.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => { type => 'object' },
code => sub {
my ($param) = @_;
# test if VM exists
my $conf = PVE::OpenVZ::load_config($param->{vmid});
my $vmstatus = PVE::OpenVZ::vmstatus($param->{vmid});
my $status = $vmstatus->{$param->{vmid}};
$status->{ha} = PVE::Cluster::vm_is_ha_managed($param->{vmid});
return $status;
}});
__PACKAGE__->register_method({
name => 'vm_user_beancounters',
path => '{vmid}/status/ubc',
method => 'GET',
proxyto => 'node',
protected => 1, # openvz /proc entries are only readable by root
description => "Get container user_beancounters.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
id => { type => 'string' },
held => { type => 'number' },
maxheld => { type => 'number' },
bar => { type => 'number' },
lim => { type => 'number' },
failcnt => { type => 'number' },
},
},
},
code => sub {
my ($param) = @_;
# test if VM exists
my $conf = PVE::OpenVZ::load_config($param->{vmid});
my $ubchash = PVE::OpenVZ::read_user_beancounters();
my $ubc = $ubchash->{$param->{vmid}} || {};
delete $ubc->{failcntsum};
return PVE::RESTHandler::hash_to_array($ubc, 'id');
}});
__PACKAGE__->register_method({
name => 'vm_start',
path => '{vmid}/status/start',
method => 'POST',
protected => 1,
proxyto => 'node',
description => "Start the container.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $node = extract_param($param, 'node');
my $vmid = extract_param($param, 'vmid');
die "CT $vmid already running\n" if PVE::OpenVZ::check_running($vmid);
if (PVE::Cluster::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
my $hacmd = sub {
my $upid = shift;
my $service = "pvevm:$vmid";
my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
print "Executing HA start for CT $vmid\n";
PVE::Tools::run_command($cmd);
return;
};
return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
} else {
my $realcmd = sub {
my $upid = shift;
syslog('info', "starting CT $vmid: $upid\n");
my $veconf = PVE::OpenVZ::load_config($vmid);
my $stcfg = cfs_read_file("storage.cfg");
if (my $sid = &$get_container_storage($stcfg, $vmid, $veconf)) {
PVE::Storage::activate_storage($stcfg, $sid);
}
my $vzconf = PVE::OpenVZ::read_global_vz_config();
# make sure mount point is there (see bug #276)
my $root = PVE::OpenVZ::get_rootdir($veconf, $vmid);
mkpath $root || die "unable to create root dir '$root'";
my $cmd = ['vzctl', 'start', $vmid];
run_command($cmd);
return;
};
return $rpcenv->fork_worker('vzstart', $vmid, $authuser, $realcmd);
}
}});
__PACKAGE__->register_method({
name => 'vm_stop',
path => '{vmid}/status/stop',
method => 'POST',
protected => 1,
proxyto => 'node',
description => "Stop the container.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $node = extract_param($param, 'node');
my $vmid = extract_param($param, 'vmid');
die "CT $vmid not running\n" if !PVE::OpenVZ::check_running($vmid);
if (PVE::Cluster::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
my $hacmd = sub {
my $upid = shift;
my $service = "pvevm:$vmid";
my $cmd = ['clusvcadm', '-d', $service];
print "Executing HA stop for CT $vmid\n";
PVE::Tools::run_command($cmd);
return;
};
return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
} else {
my $realcmd = sub {
my $upid = shift;
syslog('info', "stoping CT $vmid: $upid\n");
my $cmd = ['vzctl', 'stop', $vmid, '--fast'];
run_command($cmd);
return;
};
return $rpcenv->fork_worker('vzstop', $vmid, $authuser, $realcmd);
}
}});
__PACKAGE__->register_method({
name => 'vm_mount',
path => '{vmid}/status/mount',
method => 'POST',
protected => 1,
proxyto => 'node',
description => "Mounts container private area.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $node = extract_param($param, 'node');
my $vmid = extract_param($param, 'vmid');
die "CT $vmid is running\n" if PVE::OpenVZ::check_running($vmid);
my $realcmd = sub {
my $upid = shift;
syslog('info', "mount CT $vmid: $upid\n");
my $cmd = ['vzctl', 'mount', $vmid];
run_command($cmd);
return;
};
return $rpcenv->fork_worker('vzmount', $vmid, $authuser, $realcmd);
}});
__PACKAGE__->register_method({
name => 'vm_umount',
path => '{vmid}/status/umount',
method => 'POST',
protected => 1,
proxyto => 'node',
description => "Unmounts container private area.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $node = extract_param($param, 'node');
my $vmid = extract_param($param, 'vmid');
die "CT $vmid is running\n" if PVE::OpenVZ::check_running($vmid);
my $realcmd = sub {
my $upid = shift;
syslog('info', "umount CT $vmid: $upid\n");
my $cmd = ['vzctl', 'umount', $vmid];
run_command($cmd);
return;
};
return $rpcenv->fork_worker('vzumount', $vmid, $authuser, $realcmd);
}});
__PACKAGE__->register_method({
name => 'vm_shutdown',
path => '{vmid}/status/shutdown',
method => 'POST',
protected => 1,
proxyto => 'node',
description => "Shutdown the container.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
timeout => {
description => "Wait maximal timeout seconds.",
type => 'integer',
minimum => 0,
optional => 1,
default => 60,
},
forceStop => {
description => "Make sure the Container stops.",
type => 'boolean',
optional => 1,
default => 0,
}
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $node = extract_param($param, 'node');
my $vmid = extract_param($param, 'vmid');
my $timeout = extract_param($param, 'timeout');
die "CT $vmid not running\n" if !PVE::OpenVZ::check_running($vmid);
my $realcmd = sub {
my $upid = shift;
syslog('info', "shutdown CT $vmid: $upid\n");
my $cmd = ['vzctl', 'stop', $vmid];
$timeout = 60 if !defined($timeout);
eval { run_command($cmd, timeout => $timeout); };
my $err = $@;
return if !$err;
die $err if !$param->{forceStop};
warn "shutdown failed - forcing stop now\n";
push @$cmd, '--fast';
run_command($cmd);
return;
};
my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $authuser, $realcmd);
return $upid;
}});
__PACKAGE__->register_method({
name => 'vm_suspend',
path => '{vmid}/status/suspend',
method => 'POST',
protected => 1,
proxyto => 'node',
description => "Suspend the container.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $node = extract_param($param, 'node');
my $vmid = extract_param($param, 'vmid');
die "CT $vmid not running\n" if !PVE::OpenVZ::check_running($vmid);
my $realcmd = sub {
my $upid = shift;
syslog('info', "suspend CT $vmid: $upid\n");
PVE::OpenVZ::vm_suspend($vmid);
return;
};
my $upid = $rpcenv->fork_worker('vzsuspend', $vmid, $authuser, $realcmd);
return $upid;
}});
__PACKAGE__->register_method({
name => 'vm_resume',
path => '{vmid}/status/resume',
method => 'POST',
protected => 1,
proxyto => 'node',
description => "Resume the container.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $node = extract_param($param, 'node');
my $vmid = extract_param($param, 'vmid');
die "CT $vmid already running\n" if PVE::OpenVZ::check_running($vmid);
my $realcmd = sub {
my $upid = shift;
syslog('info', "resume CT $vmid: $upid\n");
PVE::OpenVZ::vm_resume($vmid);
return;
};
my $upid = $rpcenv->fork_worker('vzresume', $vmid, $authuser, $realcmd);
return $upid;
}});
__PACKAGE__->register_method({
name => 'migrate_vm',
path => '{vmid}/migrate',
method => 'POST',
protected => 1,
proxyto => 'node',
description => "Migrate the container to another node. Creates a new migration task.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
target => get_standard_option('pve-node', { description => "Target node." }),
online => {
type => 'boolean',
description => "Use online/live migration.",
optional => 1,
},
},
},
returns => {
type => 'string',
description => "the task ID.",
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $target = extract_param($param, 'target');
my $localnode = PVE::INotify::nodename();
raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
PVE::Cluster::check_cfs_quorum();
PVE::Cluster::check_node_exists($target);
my $targetip = PVE::Cluster::remote_node_ip($target);
my $vmid = extract_param($param, 'vmid');
# test if VM exists
PVE::OpenVZ::load_config($vmid);
# try to detect errors early
if (PVE::OpenVZ::check_running($vmid)) {
die "cant migrate running container without --online\n"
if !$param->{online};
}
if (PVE::Cluster::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
my $hacmd = sub {
my $upid = shift;
my $service = "pvevm:$vmid";
my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
print "Executing HA migrate for CT $vmid to node $target\n";
PVE::Tools::run_command($cmd);
return;
};
return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
} else {
my $realcmd = sub {
my $upid = shift;
PVE::OpenVZMigrate->migrate($target, $targetip, $vmid, $param);
return;
};
return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
}
}});
1;
......@@ -11,8 +11,6 @@ PERLSOURCE = \
NoVncIndex.pm \
HTTPServer.pm \
REST.pm \
OpenVZ.pm \
OpenVZMigrate.pm \
APLInfo.pm \
AutoBalloon.pm \
CephTools.pm \
......
package PVE::OpenVZ;
use strict;
use LockFile::Simple;
use File::stat qw();
use POSIX qw (LONG_MAX);
use IO::Dir;
use IO::File;
use PVE::Tools qw(run_command extract_param $IPV6RE $IPV4RE);
use PVE::ProcFSTools;
use PVE::Cluster qw(cfs_register_file cfs_read_file);
use PVE::SafeSyslog;
use PVE::INotify;
use PVE::JSONSchema;
use Digest::SHA;
use Encode;
use constant SCRIPT_EXT => qw (start stop mount umount premount postumount);
my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
my $nodename = PVE::INotify::nodename();
my $global_vzconf = read_global_vz_config();
my $res_unlimited = LONG_MAX;
sub config_list {
my $vmlist = PVE::Cluster::get_vmlist();
my $res = {};
return $res if !$vmlist || !$vmlist->{ids};
my $ids = $vmlist->{ids};
foreach my $vmid (keys %$ids) {
next if !$vmid; # skip VE0
my $d = $ids->{$vmid};
next if !$d->{node} || $d->{node} ne $nodename;
next if !$d->{type} || $d->{type} ne 'openvz';
$res->{$vmid}->{type} = 'openvz';
}
return $res;
}
sub cfs_config_path {
my ($vmid, $node) = @_;
$node = $nodename if !$node;
return "nodes/$node/openvz/$vmid.conf";
}
sub config_file {
my ($vmid, $node) = @_;
my $cfspath = cfs_config_path($vmid, $node);
return "/etc/pve/$cfspath";
}
sub load_config {
my ($vmid) = @_;
my $cfspath = cfs_config_path($vmid);
my $conf = PVE::Cluster::cfs_read_file($cfspath);
die "container $vmid does not exists\n" if !defined($conf);
return $conf;
}
sub check_mounted {
my ($conf, $vmid) = @_;
my $root = get_rootdir($conf, $vmid);
return (-d "$root/etc" || -d "$root/proc");
}
# warning: this is slow
sub check_running {
my ($vmid) = @_;
if (my $fh = new IO::File ("/proc/vz/vestat", "r")) {
while (defined (my $line = <$fh>)) {
if ($line =~ m/^\s*(\d+)\s+/) {
if ($vmid == $1) {
close($fh);
return 1;
}
}
}
close($fh);
}
return undef;
}
sub get_privatedir {
my ($conf, $vmid) = @_;
my $private = $global_vzconf->{privatedir};
if ($conf->{ve_private} && $conf->{ve_private}->{value}) {
$private = $conf->{ve_private}->{value};
}
$private =~ s/\$VEID/$vmid/;
return $private;
}
sub get_rootdir {
my ($conf, $vmid) = @_;
my $root = $global_vzconf->{rootdir};
if ($conf && $conf->{ve_root} && $conf->{ve_root}->{value}) {
$root = $conf->{ve_root}->{value};
}
$root =~ s/\$VEID/$vmid/;
return $root;
}
sub get_disk_quota {
my ($conf) = @_;
my $disk_quota = $global_vzconf->{disk_quota};
if ($conf->{disk_quota} && defined($conf->{disk_quota}->{value})) {
$disk_quota = $conf->{disk_quota}->{value};
}
return $disk_quota;
}
sub read_user_beancounters {
my $ubc = {};
if (my $fh = IO::File->new ("/proc/bc/resources", "r")) {
my $vmid;
while (defined (my $line = <$fh>)) {
if ($line =~ m|\s*((\d+):\s*)?([a-z]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$|) {
$vmid = $2 if defined($2);
next if !defined($vmid);
my ($name, $held, $maxheld, $bar, $lim, $failcnt) = (lc($3), $4, $5, $6, $7, $8);
next if $name eq 'dummy';
$ubc->{$vmid}->{failcntsum} += $failcnt;
$ubc->{$vmid}->{$name} = {
held => $held,
maxheld => $maxheld,
bar => $bar,
lim => $lim,
failcnt => $failcnt,
};
}
}
close($fh);
}
return $ubc;
}
sub read_container_network_usage {
my ($vmid) = @_;
my $recv = 0;
my $trmt = 0;
my $netparser = sub {
my $line = shift;
if ($line =~ m/^\s*(.*):\s*(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)\s+/) {
return if $1 eq 'lo';
$recv += $2;
$trmt += $3;
}
};
# fixme: can we get that info directly (with vzctl exec)?
my $cmd = ['/usr/sbin/vzctl', 'exec', $vmid, '/bin/cat', '/proc/net/dev'];
eval { run_command($cmd, outfunc => $netparser); };
my $err = $@;
syslog('err', $err) if $err;
return ($recv, $trmt);
};
sub read_container_blkio_stat {
my ($vmid) = @_;
my $read = 0;
my $write = 0;
my $filename = "/proc/vz/beancounter/$vmid/blkio.io_service_bytes";
if (my $fh = IO::File->new ($filename, "r")) {
while (defined (my $line = <$fh>)) {
if ($line =~ m/^\S+\s+Read\s+(\d+)$/) {
$read += $1;
} elsif ($line =~ m/^\S+\s+Write\s+(\d+)$/) {
$write += $1;
}
}
}
return ($read, $write);
};
my $last_proc_vestat = {};
sub vmstatus {
my ($opt_vmid) = @_;
my $list = $opt_vmid ? { $opt_vmid => { type => 'openvz' }} : config_list();
my $cpucount = $cpuinfo->{cpus} || 1;
foreach my $vmid (keys %$list) {
next if $opt_vmid && ($vmid ne $opt_vmid);
my $d = $list->{$vmid};
$d->{status} = 'stopped';
my $cfspath = cfs_config_path($vmid);
if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) {
$d->{name} = $conf->{hostname}->{value} || "CT$vmid";
$d->{name} =~ s/[\s]//g;
$d->{cpus} = $conf->{cpus}->{value} || 1;
$d->{cpus} = $cpucount if $d->{cpus} > $cpucount;
$d->{disk} = 0;
$d->{maxdisk} = int($conf->{diskspace}->{bar} * 1024);
$d->{mem} = 0;
$d->{swap} = 0;
($d->{maxmem}, $d->{maxswap}) = ovz_config_extract_mem_swap($conf);
$d->{nproc} = 0;
$d->{failcnt} = 0;
$d->{uptime} = 0;
$d->{cpu} = 0;
$d->{netout} = 0;
$d->{netin} = 0;
$d->{diskread} = 0;
$d->{diskwrite} = 0;
if (my $ip = $conf->{ip_address}->{value}) {
$ip =~ s/,;/ /g;
$d->{ip} = (split(/\s+/, $ip))[0];
} else {
$d->{ip} = '-';
}
$d->{status} = 'mounted' if check_mounted($conf, $vmid);
} else {
delete $list->{$vmid};
}
}
my $maxpages = ($res_unlimited / 4096);
my $ubchash = read_user_beancounters();
foreach my $vmid (keys %$ubchash) {
my $d = $list->{$vmid};
my $ubc = $ubchash->{$vmid};
if ($d && defined($d->{status}) && $ubc) {
$d->{failcnt} = $ubc->{failcntsum};
$d->{mem} = $ubc->{physpages}->{held} * 4096;
if ($ubc->{swappages}->{held} < $maxpages) {
$d->{swap} = $ubc->{swappages}->{held} * 4096
}
$d->{nproc} = $ubc->{numproc}->{held};
}
}
if (my $fh = IO::File->new ("/proc/vz/vzquota", "r")) {
while (defined (my $line = <$fh>)) {
if ($line =~ m|^(\d+):\s+\S+/private/\d+$|) {
my $vmid = $1;
my $d = $list->{$vmid};
if ($d && defined($d->{status})) {
$line = <$fh>;
if ($line =~ m|^\s*1k-blocks\s+(\d+)\s+(\d+)\s|) {
$d->{disk} = int ($1 * 1024);
$d->{maxdisk} = int ($2 * 1024);
}
}
}
}
close($fh);
}
# Note: OpenVZ does not use POSIX::_SC_CLK_TCK
my $hz = 1000;
# see http://wiki.openvz.org/Vestat
if (my $fh = new IO::File ("/proc/vz/vestat", "r")) {
while (defined (my $line = <$fh>)) {
if ($line =~ m/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+/) {
my $vmid = $1;
my $user = $2;
my $nice = $3;
my $system = $4;
my $ut = $5;
my $sum = $8*$cpucount; # uptime in jiffies * cpus = available jiffies
my $used = $9; # used time in jiffies
my $uptime = int ($ut / $hz);
my $d = $list->{$vmid};
next if !($d && defined($d->{status}));
$d->{status} = 'running';
$d->{uptime} = $uptime;
if (!defined ($last_proc_vestat->{$vmid}) ||
($last_proc_vestat->{$vmid}->{sum} > $sum)) {
$last_proc_vestat->{$vmid} = { used => 0, sum => 0, cpu => 0 };
}
my $diff = $sum - $last_proc_vestat->{$vmid}->{sum};
if ($diff > 1000) { # don't update too often
my $useddiff = $used - $last_proc_vestat->{$vmid}->{used};
my $cpu = (($useddiff/$diff) * $cpucount) / $d->{cpus};
$last_proc_vestat->{$vmid}->{sum} = $sum;
$last_proc_vestat->{$vmid}->{used} = $used;
$last_proc_vestat->{$vmid}->{cpu} = $d->{cpu} = $cpu;
} else {
$d->{cpu} = $last_proc_vestat->{$vmid}->{cpu};
}
}
}
close($fh);
}
foreach my $vmid (keys %$list) {
my $d = $list->{$vmid};
next if !$d || !$d->{status} || $d->{status} ne 'running';
($d->{netin}, $d->{netout}) = read_container_network_usage($vmid);
($d->{diskread}, $d->{diskwrite}) = read_container_blkio_stat($vmid);
}
return $list;
}
my $confdesc = {
onboot => {
optional => 1,
type => 'boolean',
description => "Specifies whether a VM will be started during system bootup.",
default => 0,
},
cpus => {
optional => 1,
type => 'integer',
description => "The number of CPUs for this container.",
minimum => 1,
default => 1,
},
cpuunits => {
optional => 1,
type => 'integer',
description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
minimum => 0,
maximum => 500000,
default => 1000,
},
memory => {
optional => 1,
type => 'integer',
description => "Amount of RAM for the VM in MB.",
minimum => 16,
default => 512,
},
swap => {
optional => 1,
type => 'integer',
description => "Amount of SWAP for the VM in MB.",
minimum => 0,
default => 512,
},
disk => {
optional => 1,
type => 'number',
description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
minimum => 0,
default => 2,
},
quotatime => {
optional => 1,
type => 'integer',
description => "Set quota grace period (seconds).",
minimum => 0,
default => 0,
},
quotaugidlimit => {
optional => 1,
type => 'integer',
description => "Set maximum number of user/group IDs in a container for which disk quota inside the container will be accounted. If this value is set to 0, user and group quotas inside the container will not.",
minimum => 0,
default => 0,
},
hostname => {
optional => 1,
description => "Set a host name for the container.",
type => 'string',
maxLength => 255,
},
description => {
optional => 1,
type => 'string',
description => "Container description. Only used on the configuration web interface.",
},
searchdomain => {
optional => 1,
type => 'string',
description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
},
nameserver => {
optional => 1,
type => 'string',
description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
},
ip_address => {
optional => 1,
type => 'string',
description => "Specifies the address the container will be assigned.",
},
netif => {
optional => 1,
type => 'string', format => 'pve-openvz-netif',
description => "Specifies network interfaces for the container.",
},
};
# add JSON properties for create and set function
sub json_config_properties {
my $prop = shift;
foreach my $opt (keys %$confdesc) {
$prop->{$opt} = $confdesc->{$opt};
}
return $prop;
}
# read global vz.conf
sub read_global_vz_config {
my $res = {
rootdir => '/var/lib/vz/root/$VEID', # note '$VEID' is a place holder
privatedir => '/var/lib/vz/private/$VEID', # note '$VEID' is a place holder
dumpdir => '/var/lib/vz/dump',
lockdir => '/var/lib/vz/lock',
disk_quota => 1,
};
my $filename = "/etc/vz/vz.conf";
return $res if ! -f $filename;
my $data = PVE::Tools::file_get_contents($filename);
if ($data =~ m/^\s*VE_PRIVATE=(.*)$/m) {
my $dir = $1;
$dir =~ s/^\"(.*)\"/$1/;
if ($dir !~ m/\$VEID/) {
warn "VE_PRIVATE does not contain '\$VEID' ('$dir')\n";
} else {
$res->{privatedir} = $dir;
}
}
if ($data =~ m/^\s*VE_ROOT=(.*)$/m) {
my $dir = $1;
$dir =~ s/^\"(.*)\"/$1/;
if ($dir !~ m/\$VEID/) {
warn "VE_ROOT does not contain '\$VEID' ('$dir')\n";
} else {
$res->{rootdir} = $dir;
}
}
if ($data =~ m/^\s*DUMPDIR=(.*)$/m) {
my $dir = $1;
$dir =~ s/^\"(.*)\"/$1/;
$dir =~ s|/\$VEID$||;
$res->{dumpdir} = $dir;
}
if ($data =~ m/^\s*LOCKDIR=(.*)$/m) {
my $dir = $1;
$dir =~ s/^\"(.*)\"/$1/;
$res->{lockdir} = $dir;
}
if ($data =~ m/^\s*DISK_QUOTA=(no|false|off|0)$/m) {
$res->{disk_quota} = 0;
}
return $res;
};
sub parse_netif {
my ($data, $vmid) = @_;
my $res = {};
return $res if !$data;
my $host_ifnames = {};
my $find_next_hostif_name = sub {
for (my $i = 0; $i < 100; $i++) {
my $name = "veth${vmid}.$i";
if (!$host_ifnames->{$name}) {
$host_ifnames->{$name} = 1;
return $name;
}
}
die "unable to find free host_ifname"; # should not happen
};
foreach my $iface (split (/;/, $data)) {
my $d = {};
foreach my $pv (split (/,/, $iface)) {
if ($pv =~ m/^(ifname|mac|bridge|host_ifname|host_mac|mac_filter)=(.+)$/) {
if ($1 eq 'host_ifname') {
$d->{$1} = $2;
$host_ifnames->{$2} = $1;
} elsif ($1 eq 'mac_filter') {
$d->{$1} = parse_boolean('mac_filter', $2);
} else {
$d->{$1} = $2;
}
}
}
if ($d->{ifname}) {
$d->{mac} = PVE::Tools::random_ether_addr() if !$d->{mac};
$d->{host_mac} = PVE::Tools::random_ether_addr() if !$d->{host_mac};
$d->{raw} = print_netif($d);
$res->{$d->{ifname}} = $d;
} else {
return undef;
}
}
foreach my $iface (keys %$res) {
my $d = $res->{$iface};
if ($vmid && !$d->{host_ifname}) {
$d->{host_ifname} = &$find_next_hostif_name($iface);
}
}
return $res;
}
sub print_netif {
my $net = shift;
my $res = "ifname=$net->{ifname}";
$res .= ",mac=$net->{mac}" if $net->{mac};
$res .= ",host_ifname=$net->{host_ifname}" if $net->{host_ifname};
$res .= ",host_mac=$net->{host_mac}" if $net->{host_mac};
$res .= ",bridge=$net->{bridge}" if $net->{bridge};
if (defined($net->{mac_filter}) && !$net->{mac_filter}) {
$res .= ",mac_filter=off"; # 'on' is the default
}
return $res;
}
PVE::JSONSchema::register_format('pve-openvz-netif', \&verify_netif);
sub verify_netif {
my ($value, $noerr) = @_;
return $value if parse_netif($value);
return undef if $noerr;
die "unable to parse --netif value";
}
sub parse_res_num_ignore {
my ($key, $text) = @_;
if ($text =~ m/^(\d+|unlimited)(:.*)?$/) {
return { bar => $1 eq 'unlimited' ? $res_unlimited : $1 };
}
return undef;
}
sub parse_res_num_num {
my ($key, $text) = @_;
if ($text =~ m/^(\d+|unlimited)(:(\d+|unlimited))?$/) {
my $res = { bar => $1 eq 'unlimited' ? $res_unlimited : $1 };
if (defined($3)) {
$res->{lim} = $3 eq 'unlimited' ? $res_unlimited : $3;
} else {
$res->{lim} = $res->{bar};
}
return $res;
}
return undef;
}
sub parse_res_bar_limit {
my ($text, $base) = @_;
return $res_unlimited if $text eq 'unlimited';
if ($text =~ m/^(\d+)([TGMKP])?$/i) {
my $val = $1;
my $mult = $2 ? lc($2) : '';
if ($mult eq 'k') {
$val = $val * 1024;
} elsif ($mult eq 'm') {
$val = $val * 1024 * 1024;
} elsif ($mult eq 'g') {
$val = $val * 1024 * 1024 * 1024;
} elsif ($mult eq 't') {
$val = $val * 1024 * 1024 * 1024 * 1024;
} elsif ($mult eq 'p') {
$val = $val * 4096;
} else {
return $val;
}
return int($val/$base);
}
return undef;
}
sub parse_res_bytes_bytes {
my ($key, $text) = @_;
my @a = split(/:/, $text);
$a[1] = $a[0] if !defined($a[1]);
my $bar = parse_res_bar_limit($a[0], 1);
my $lim = parse_res_bar_limit($a[1], 1);
if (defined($bar) && defined($lim)) {
return { bar => $bar, lim => $lim };
}
return undef;
}
sub parse_res_block_block {
my ($key, $text) = @_;
my @a = split(/:/, $text);
$a[1] = $a[0] if !defined($a[1]);
my $bar = parse_res_bar_limit($a[0], 1024);
my $lim = parse_res_bar_limit($a[1], 1024);
if (defined($bar) && defined($lim)) {
return { bar => $bar, lim => $lim };
}
return undef;
}
sub parse_res_pages_pages {
my ($key, $text) = @_;
my @a = split(/:/, $text);
$a[1] = $a[0] if !defined($a[1]);
my $bar = parse_res_bar_limit($a[0], 4096);
my $lim = parse_res_bar_limit($a[1], 4096);
if (defined($bar) && defined($lim)) {
return { bar => $bar, lim => $lim };
}
return undef;
}
sub parse_res_pages_unlimited {
my ($key, $text) = @_;
my @a = split(/:/, $text);
my $bar = parse_res_bar_limit($a[0], 4096);
if (defined($bar)) {
return { bar => $bar, lim => $res_unlimited };
}
return undef;
}
sub parse_res_pages_ignore {
my ($key, $text) = @_;
my @a = split(/:/, $text);
my $bar = parse_res_bar_limit($a[0], 4096);
if (defined($bar)) {
return { bar => $bar };
}
return undef;
}
sub parse_res_ignore_pages {
my ($key, $text) = @_;
my @a = split(/:/, $text);
$a[1] = $a[0] if !defined($a[1]);
my $lim = parse_res_bar_limit($a[1] , 4096);
if (defined($lim)) {
return { bar => 0, lim => $lim };
}
return undef;
}
sub parse_boolean {
my ($key, $text) = @_;
return { value => 1 } if $text =~ m/^(yes|true|on|1)$/i;
return { value => 0 } if $text =~ m/^(no|false|off|0)$/i;
return undef;
};
sub parse_integer {
my ($key, $text) = @_;
if ($text =~ m/^(\d+)$/) {
return { value => int($1) };
}
return undef;
};
my $ovz_ressources = {
numproc => \&parse_res_num_ignore,
numtcpsock => \&parse_res_num_ignore,
numothersock => \&parse_res_num_ignore,
numfile => \&parse_res_num_ignore,
numflock => \&parse_res_num_num,
numpty => \&parse_res_num_ignore,
numsiginfo => \&parse_res_num_ignore,
numiptent => \&parse_res_num_ignore,
vmguarpages => \&parse_res_pages_unlimited,
oomguarpages => \&parse_res_pages_unlimited,
lockedpages => \&parse_res_pages_ignore,
privvmpages => \&parse_res_pages_pages,
shmpages => \&parse_res_pages_ignore,
physpages => \&parse_res_pages_pages,
swappages => \&parse_res_ignore_pages,
kmemsize => \&parse_res_bytes_bytes,
tcpsndbuf => \&parse_res_bytes_bytes,
tcprcvbuf => \&parse_res_bytes_bytes,
othersockbuf => \&parse_res_bytes_bytes,
dgramrcvbuf => \&parse_res_bytes_bytes,
dcachesize => \&parse_res_bytes_bytes,
disk_quota => \&parse_boolean,
diskspace => \&parse_res_block_block,
diskinodes => \&parse_res_num_num,
quotatime => \&parse_integer,
quotaugidlimit => \&parse_integer,
cpuunits => \&parse_integer,
cpulimit => \&parse_integer,
cpus => \&parse_integer,
cpumask => 'string',
meminfo => 'string',
iptables => 'string',
ip_address => 'string',
netif => 'string',
hostname => 'string',
nameserver => 'string',
searchdomain => 'string',
name => 'string',
description => 'string',
onboot => \&parse_boolean,
initlog => \&parse_boolean,
bootorder => \&parse_integer,
ostemplate => 'string',
ve_root => 'string',
ve_private => 'string',
disabled => \&parse_boolean,
origin_sample => 'string',
noatime => \&parse_boolean,
capability => 'string',
devnodes => 'string',
devices => 'string',
pci => 'string',
features => 'string',
ioprio => \&parse_integer,
};
sub parse_ovz_config {
my ($filename, $raw) = @_;
return undef if !defined($raw);
my $data = {
digest => Digest::SHA::sha1_hex($raw),
};
$filename =~ m|/openvz/(\d+)\.conf$|
|| die "got strange filename '$filename'";
my $vmid = $1;
while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
my $line = $1;
next if $line =~ m/^\#/;
next if $line =~ m/^\s*$/;
if ($line =~ m/^\s*([A-Z][A-Z0-9_]*)\s*=\s*\"(.*)\"\s*$/i) {
my $name = lc($1);
my $text = $2;
my $parser = $ovz_ressources->{$name};
if (!$parser || !ref($parser)) {
$data->{$name}->{value} = $text;
next;
} else {
if (my $res = &$parser($name, $text)) {
$data->{$name} = $res;
next;
}
}
}
die "unable to parse config line: $line\n";
}
return $data;
}
cfs_register_file('/openvz/', \&parse_ovz_config);
sub format_res_value {
my ($key, $value) = @_;
return 'unlimited' if $value == $res_unlimited;
return 0 if $value == 0;
if ($key =~ m/pages$/) {
my $bytes = $value * 4096;
my $mb = int ($bytes / (1024 * 1024));
return "${mb}M" if $mb * 1024 * 1024 == $bytes;
} elsif ($key =~ m/space$/) {
my $bytes = $value * 1024;
my $gb = int ($bytes / (1024 * 1024 * 1024));
return "${gb}G" if $gb * 1024 * 1024 * 1024 == $bytes;
my $mb = int ($bytes / (1024 * 1024));
return "${mb}M" if $mb * 1024 * 1024 == $bytes;
} elsif ($key =~ m/size$/) {
my $bytes = $value;
my $mb = int ($bytes / (1024 * 1024));
return "${mb}M" if $mb * 1024 * 1024 == $bytes;
}
return $value;
}
sub format_res_bar_lim {
my ($key, $data) = @_;
if (defined($data->{lim}) && ($data->{lim} ne $data->{bar})) {
return format_res_value($key, $data->{bar}) . ":" . format_res_value($key, $data->{lim});
} else {
return format_res_value($key, $data->{bar});
}
}
sub create_config_line {
my ($key, $data) = @_;
my $text;
if (defined($data->{value})) {
if ($confdesc->{$key} && $confdesc->{$key}->{type} eq 'boolean') {
my $txt = $data->{value} ? 'yes' : 'no';
$text .= uc($key) . "=\"$txt\"\n";
} else {
$text .= uc($key) . "=\"$data->{value}\"\n";
}
} elsif (defined($data->{bar})) {
my $tmp = format_res_bar_lim($key, $data);
$text .= uc($key) . "=\"$tmp\"\n";
}
}
sub ovz_config_extract_mem_swap {
my ($veconf, $unit) = @_;
$unit = 1 if !$unit;
my ($mem, $swap) = (int((512*1024*1024 + $unit - 1)/$unit), 0);
my $maxpages = ($res_unlimited / 4096);
if ($veconf->{swappages}) {
if ($veconf->{physpages} && $veconf->{physpages}->{lim} &&
($veconf->{physpages}->{lim} < $maxpages)) {
$mem = int(($veconf->{physpages}->{lim} * 4096 + $unit - 1) / $unit);
}
if ($veconf->{swappages}->{lim} && ($veconf->{swappages}->{lim} < $maxpages)) {
$swap = int (($veconf->{swappages}->{lim} * 4096 + $unit - 1) / $unit);
}
} else {
if ($veconf->{vmguarpages} && $veconf->{vmguarpages}->{bar} &&
($veconf->{vmguarpages}->{bar} < $maxpages)) {
$mem = int(($veconf->{vmguarpages}->{bar} * 4096 + $unit - 1) / $unit);
}
}
return ($mem, $swap);
}
sub update_ovz_config {
my ($vmid, $veconf, $param) = @_;
my $changes = [];
# test if barrier or limit changed
my $push_bl_changes = sub {
my ($name, $bar, $lim) = @_;
my $old = format_res_bar_lim($name, $veconf->{$name})
if $veconf->{$name} && defined($veconf->{$name}->{bar});
my $new = format_res_bar_lim($name, { bar => $bar, lim => $lim });
if (!$old || ($old ne $new)) {
$veconf->{$name}->{bar} = $bar;
$veconf->{$name}->{lim} = $lim;
push @$changes, "--$name", $new;
}
};
my ($mem, $swap) = ovz_config_extract_mem_swap($veconf, 1024*1024);
my $disk = ($veconf->{diskspace}->{bar} || $res_unlimited) / (1024*1024);
my $cpuunits = $veconf->{cpuunits}->{value} || 1000;
my $quotatime = $veconf->{quotatime}->{value} || 0;
my $quotaugidlimit = $veconf->{quotaugidlimit}->{value} || 0;
my $cpus = $veconf->{cpus}->{value} || 1;
if ($param->{memory}) {
$mem = $param->{memory};
}
if (defined ($param->{swap})) {
$swap = $param->{swap};
}
if ($param->{disk}) {
$disk = $param->{disk};
}
if ($param->{cpuunits}) {
$cpuunits = $param->{cpuunits};
}
if (defined($param->{quotatime})) {
$quotatime = $param->{quotatime};
}
if (defined($param->{quotaugidlimit})) {
$quotaugidlimit = $param->{quotaugidlimit};
}
if ($param->{cpus}) {
$cpus = $param->{cpus};
}
# memory related parameter
&$push_bl_changes('vmguarpages', 0, $res_unlimited);
&$push_bl_changes('oomguarpages', 0, $res_unlimited);
&$push_bl_changes('privvmpages', $res_unlimited, $res_unlimited);
# lock half of $mem
my $lockedpages = int($mem*1024/8);
&$push_bl_changes('lockedpages', $lockedpages, undef);
my $kmemsize = int($mem/2);
&$push_bl_changes('kmemsize', int($kmemsize/1.1)*1024*1024, $kmemsize*1024*1024);
my $dcachesize = int($mem/4);
&$push_bl_changes('dcachesize', int($dcachesize/1.1)*1024*1024, $dcachesize*1024*1024);
my $physpages = int($mem*1024/4);
&$push_bl_changes('physpages', 0, $physpages);
my $swappages = int($swap*1024/4);
&$push_bl_changes('swappages', 0, $swappages);
# disk quota parameters
if (!$disk || ($disk * 1.1) >= ($res_unlimited / (1024 * 1024))) {
&$push_bl_changes('diskspace', $res_unlimited, $res_unlimited);
&$push_bl_changes('diskinodes', $res_unlimited, $res_unlimited);
} else {
my $diskspace = int ($disk * 1024 * 1024);
my $diskspace_lim = int ($diskspace * 1.1);
&$push_bl_changes('diskspace', $diskspace, $diskspace_lim);
my $diskinodes = int ($disk * 200000);
my $diskinodes_lim = int ($disk * 220000);
&$push_bl_changes('diskinodes', $diskinodes, $diskinodes_lim);
}
if ($veconf->{'quotatime'}->{value} != $quotatime) {
$veconf->{'quotatime'}->{value} = $quotatime;
push @$changes, '--quotatime', "$quotatime";
}
if ($veconf->{'quotaugidlimit'}->{value} != $quotaugidlimit) {
$veconf->{'quotaugidlimit'}->{value} = $quotaugidlimit;
push @$changes, '--quotaugidlimit', "$quotaugidlimit";
}
# cpu settings
if ($veconf->{'cpuunits'}->{value} != $cpuunits) {
$veconf->{'cpuunits'}->{value} = $cpuunits;
push @$changes, '--cpuunits', "$cpuunits";
}
if ($veconf->{'cpus'}->{value} != $cpus) {
$veconf->{'cpus'}->{value} = $cpus;
push @$changes, '--cpus', "$cpus";
}
my $cond_set_boolean = sub {
my ($name) = @_;
return if !defined($param->{$name});
my $newvalue = $param->{$name} ? 1 : 0;
my $oldvalue = $veconf->{$name}->{value};
if (!defined($oldvalue) || ($oldvalue ne $newvalue)) {
$veconf->{$name}->{value} = $newvalue;
push @$changes, "--$name", $newvalue ? 'yes' : 'no';
}
};
my $cond_set_value = sub {
my ($name, $newvalue) = @_;
$newvalue = defined($newvalue) ? $newvalue : $param->{$name};
return if !defined($newvalue);
my $oldvalue = $veconf->{$name}->{value};
if (!defined($oldvalue) || ($oldvalue ne $newvalue)) {
$veconf->{$name}->{value} = $newvalue;
push @$changes, "--$name", $newvalue;
}
};
&$cond_set_boolean('onboot');
&$cond_set_value('hostname');
&$cond_set_value('searchdomain');
if ($param->{'description'}) {
&$cond_set_value('description', PVE::Tools::encode_text($param->{'description'}));
}
if (defined($param->{ip_address})) {
my $iphash = {};
if (defined($veconf->{'ip_address'}) && $veconf->{'ip_address'}->{value}) {
foreach my $ip (split (/\s+/, $veconf->{ip_address}->{value})) {
$iphash->{$ip} = 1;
}
}
my $newhash = {};
foreach my $ip (PVE::Tools::split_list($param->{'ip_address'})) {
next if $ip !~ m!^(?:$IPV6RE|$IPV4RE)(?:/\d+)?$!;
$newhash->{$ip} = 1;
if (!$iphash->{$ip}) {
push @$changes, '--ipadd', $ip;
$iphash->{$ip} = 1; # only add once
}
}
foreach my $ip (keys %$iphash) {
if (!$newhash->{$ip}) {
push @$changes, '--ipdel', $ip;
}
}
$veconf->{'ip_address'}->{value} = join(' ', keys %$iphash);
}
if (defined($param->{netif})) {
my $ifaces = {};
if (defined ($veconf->{netif}) && $veconf->{netif}->{value}) {
$ifaces = parse_netif($veconf->{netif}->{value}, $vmid);
}
my $newif = parse_netif($param->{netif}, $vmid);
foreach my $ifname (sort keys %$ifaces) {
if (!$newif->{$ifname}) {
push @$changes, '--netif_del', $ifname;
}
}
my $newvalue = '';
foreach my $ifname (sort keys %$newif) {
$newvalue .= ';' if $newvalue;
$newvalue .= print_netif($newif->{$ifname});
my $ifadd = $ifname;
$ifadd .= $newif->{$ifname}->{mac} ? ",$newif->{$ifname}->{mac}" : ',';
$ifadd .= $newif->{$ifname}->{host_ifname} ? ",$newif->{$ifname}->{host_ifname}" : ',';
$ifadd .= $newif->{$ifname}->{host_mac} ? ",$newif->{$ifname}->{host_mac}" : ',';
$ifadd .= $newif->{$ifname}->{bridge} ? ",$newif->{$ifname}->{bridge}" : '';
# not possible with current vzctl
#$ifadd .= $newif->{$ifname}->{mac_filter} ? ",$newif->{$ifname}->{mac_filter}" : '';
if (!$ifaces->{$ifname} || ($ifaces->{$ifname}->{raw} ne $newif->{$ifname}->{raw})) {
push @$changes, '--netif_add', $ifadd;
}
}
$veconf->{netif}->{value} = $newvalue;
}
if (defined($param->{'nameserver'})) {
# remove duplicates
my $nshash = {};
my $newvalue = '';
foreach my $ns (PVE::Tools::split_list($param->{'nameserver'})) {
if (!$nshash->{$ns}) {
push @$changes, '--nameserver', $ns;
$nshash->{$ns} = 1;
$newvalue .= $newvalue ? " $ns" : $ns;
}
}
$veconf->{'nameserver'}->{value} = $newvalue if $newvalue;
}
# foreach my $nv (@$changes) { print "CHANGE: $nv\n"; }
return $changes;
}
sub generate_raw_config {
my ($raw, $conf) = @_;
my $text = '';
my $found = {};
while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
my $line = $1;
if ($line =~ m/^\#/ || $line =~ m/^\s*$/) {
$text .= "$line\n";
next;
}
if ($line =~ m/^\s*([A-Z][A-Z0-9_]*)\s*=\s*\"(.*)\"\s*$/i) {
my $name = lc($1);
if ($conf->{$name}) {
$found->{$name} = 1;
if (my $line = create_config_line($name, $conf->{$name})) {
$text .= $line;
}
}
}
}
foreach my $key (keys %$conf) {
next if $found->{$key};
next if $key eq 'digest';
if (my $line = create_config_line($key, $conf->{$key})) {
$text .= $line;
}
}
return $text;
}
sub create_lock_manager {
my ($max) = @_;
return LockFile::Simple->make(-format => '%f',
-autoclean => 1,
-max => defined($max) ? $max : 60,
-delay => 1,
-stale => 1,
-nfs => 0);
}
sub lock_container {
my ($vmid, $max, $code, @param) = @_;
my $filename = $global_vzconf->{lockdir} . "/${vmid}.lck";
my $lock;
my $res;
eval {
my $lockmgr = create_lock_manager($max);
$lock = $lockmgr->lock($filename) || die "can't lock container $vmid\n";
$res = &$code(@param);
};
my $err = $@;
$lock->release() if $lock;
die $err if $err;
return $res;
}
sub vm_suspend {
my ($vmid) = @_;
my $cmd = ['vzctl', 'chkpnt', $vmid];
eval { run_command($cmd); };
if (my $err = $@) {
syslog("err", "CT $vmid suspend failed - $err");
die $err;
}
}
sub vm_resume {
my ($vmid) = @_;
my $cmd = ['vzctl', 'restore', $vmid];
eval { run_command($cmd); };
if (my $err = $@) {
syslog("err", "CT $vmid resume failed - $err");
die $err;
}
}
sub replacepw {
my ($file, $epw) = @_;
my $tmpfile = "$file.$$";
eval {
open (SRC, "<$file") ||
die "unable to open file '$file' - $!";
my $st = File::stat::stat(\*SRC) ||
die "unable to stat file - $!";
open (DST, ">$tmpfile") ||
die "unable to open file '$tmpfile' - $!";
# copy owner and permissions
chmod $st->mode, \*DST;
chown $st->uid, $st->gid, \*DST;
while (defined (my $line = <SRC>)) {
$line =~ s/^root:[^:]*:/root:${epw}:/;
print DST $line;
}
};
my $err = $@;
close (SRC);
close (DST);
if ($err) {
unlink $tmpfile;
} else {
rename $tmpfile, $file;
unlink $tmpfile; # in case rename fails
}
}
sub set_rootpasswd {
my ($privatedir, $opt_rootpasswd) = @_;
my $pwfile = "$privatedir/etc/passwd";
return if ! -f $pwfile;
my $shadow = "$privatedir/etc/shadow";
if ($opt_rootpasswd !~ m/^\$/) {
my $time = substr (Digest::SHA::sha1_base64 (time), 0, 8);
$opt_rootpasswd = crypt(encode("utf8", $opt_rootpasswd), "\$1\$$time\$");
};
if (-f $shadow) {
replacepw ($shadow, $opt_rootpasswd);
replacepw ($pwfile, 'x');
} else {
replacepw ($pwfile, $opt_rootpasswd);
}
}
package PVE::OpenVZMigrate;
use strict;
use warnings;
use PVE::AbstractMigrate;
use File::Basename;
use File::Copy;
use PVE::Tools;
use PVE::INotify;
use PVE::Cluster;
use PVE::Storage;
use PVE::OpenVZ;
use base qw(PVE::AbstractMigrate);
# fixme: lock VM on target node
sub lock_vm {
my ($self, $vmid, $code, @param) = @_;
return PVE::OpenVZ::lock_container($vmid, undef, $code, @param);
}
sub prepare {
my ($self, $vmid) = @_;
my $online = $self->{opts}->{online};
$self->{storecfg} = PVE::Storage::config();
$self->{vzconf} = PVE::OpenVZ::read_global_vz_config(),
# test is VM exist
my $conf = $self->{vmconf} = PVE::OpenVZ::load_config($vmid);
my $path = PVE::OpenVZ::get_privatedir($conf, $vmid);
my ($vtype, $volid) = PVE::Storage::path_to_volume_id($self->{storecfg}, $path);
my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1) if $volid;
die "can't determine assigned storage\n" if !$storage;
# check if storage is available on both nodes
my $scfg = PVE::Storage::storage_check_node($self->{storecfg}, $storage);
PVE::Storage::storage_check_node($self->{storecfg}, $storage, $self->{node});
# we simply use the backup dir to store temporary dump files
# Note: this is on shared storage if the storage is 'shared'
$self->{dumpdir} = PVE::Storage::get_backup_dir($self->{storecfg}, $storage);
PVE::Storage::activate_volumes($self->{storecfg}, [ $volid ]);
$self->{storage} = $storage;
$self->{privatedir} = $path;
$self->{rootdir} = PVE::OpenVZ::get_rootdir($conf, $vmid);
$self->{shared} = $scfg->{shared};
my $running = 0;
if (PVE::OpenVZ::check_running($vmid)) {
die "cant migrate running container without --online\n" if !$online;
$running = 1;
}
# fixme: test if VM uses local resources
# test ssh connection
my $cmd = [ @{$self->{rem_ssh}}, '/bin/true' ];
eval { $self->cmd_quiet($cmd); };
die "Can't connect to destination address using public key\n" if $@;
if ($running) {
# test if OpenVZ is running
$cmd = [ @{$self->{rem_ssh}}, '/etc/init.d/vz status' ];
eval { $self->cmd_quiet($cmd); };
die "OpenVZ is not running on the target machine\n" if $@;
# test if CPT modules are loaded for online migration
die "vzcpt module is not loaded\n" if ! -f '/proc/cpt';
$cmd = [ @{$self->{rem_ssh}}, 'test -f /proc/rst' ];
eval { $self->cmd_quiet($cmd); };
die "vzrst module is not loaded on the target machine\n" if $@;
}
# fixme: do we want to test if IPs exists on target node?
return $running;
}
sub phase1 {
my ($self, $vmid) = @_;
$self->log('info', "starting migration of CT $self->{vmid} to node '$self->{node}' ($self->{nodeip})");
my $conf = $self->{vmconf};
if ($self->{running}) {
$self->log('info', "container is running - using online migration");
}
my $cmd = [ @{$self->{rem_ssh}}, 'mkdir', '-p', $self->{rootdir} ];
$self->cmd_quiet($cmd, errmsg => "Failed to make container root directory");
my $privatedir = $self->{privatedir};
if (!$self->{shared}) {
$cmd = [ @{$self->{rem_ssh}}, 'mkdir', '-p', $privatedir ];
$self->cmd_quiet($cmd, errmsg => "Failed to make container private directory");
$self->{undo_private} = $privatedir;
$self->log('info', "starting rsync phase 1");
my $basedir = dirname($privatedir);
$cmd = [ @{$self->{rsync_cmd}}, '--sparse', $privatedir, "root\@$self->{nodeip}:$basedir" ];
$self->cmd($cmd, errmsg => "Failed to sync container private area");
} else {
$self->log('info', "container data is on shared storage '$self->{storage}'");
}
my $conffile = PVE::OpenVZ::config_file($vmid);
my $newconffile = PVE::OpenVZ::config_file($vmid, $self->{node});
my $srccfgdir = dirname($conffile);
my $newcfgdir = dirname($newconffile);
foreach my $s (PVE::OpenVZ::SCRIPT_EXT) {
my $scriptfn = "${vmid}.$s";
my $srcfn = "$srccfgdir/$scriptfn";
next if ! -f $srcfn;
my $dstfn = "$newcfgdir/$scriptfn";
copy($srcfn, $dstfn) || die "copy '$srcfn' to '$dstfn' failed - $!\n";
}
if ($self->{running}) {
# fixme: save state and quota
$self->log('info', "start live migration - suspending container");
$cmd = [ 'vzctl', '--skiplock', 'chkpnt', $vmid, '--suspend' ];
$self->cmd_quiet($cmd, errmsg => "Failed to suspend container");
$self->{undo_suspend} = 1;
$self->log('info', "dump container state");
$self->{dumpfile} = "$self->{dumpdir}/dump.$vmid";
$cmd = [ 'vzctl', '--skiplock', 'chkpnt', $vmid, '--dump', '--dumpfile', $self->{dumpfile} ];
$self->cmd_quiet($cmd, errmsg => "Failed to dump container state");
if (!$self->{shared}) {
$self->log('info', "copy dump file to target node");
$self->{undo_copy_dump} = 1;
$cmd = [ @{$self->{scp_cmd}}, $self->{dumpfile}, "root\@$self->{nodeip}:$self->{dumpfile}"];
$self->cmd_quiet($cmd, errmsg => "Failed to copy dump file");
$self->log('info', "starting rsync (2nd pass)");
my $basedir = dirname($privatedir);
$cmd = [ @{$self->{rsync_cmd}}, $privatedir, "root\@$self->{nodeip}:$basedir" ];
$self->cmd($cmd, errmsg => "Failed to sync container private area");
}
} else {
if (PVE::OpenVZ::check_mounted($conf, $vmid)) {
$self->log('info', "unmounting container");
$cmd = [ 'vzctl', '--skiplock', 'umount', $vmid ];
$self->cmd_quiet($cmd, errmsg => "Failed to umount container");
}
}
my $disk_quota = PVE::OpenVZ::get_disk_quota($conf);
if (!defined($disk_quota) || ($disk_quota != 0)) {
$disk_quota = $self->{disk_quota} = 1;
$self->log('info', "dump 2nd level quota");
$self->{quotadumpfile} = "$self->{dumpdir}/quotadump.$vmid";
$cmd = "vzdqdump $vmid -U -G -T > " . PVE::Tools::shellquote($self->{quotadumpfile});
$self->cmd_quiet($cmd, errmsg => "Failed to dump 2nd level quota");
if (!$self->{shared}) {
$self->log('info', "copy 2nd level quota to target node");
$self->{undo_copy_quota_dump} = 1;
$cmd = [@{$self->{scp_cmd}}, $self->{quotadumpfile},
"root\@$self->{nodeip}:$self->{quotadumpfile}"];
$self->cmd_quiet($cmd, errmsg => "Failed to copy 2nd level quota dump");
}
}
# everythin copied - make sure container is stoped
# fixme_ do we need to start on the other node first?
if ($self->{running}) {
delete $self->{undo_suspend};
$cmd = [ 'vzctl', '--skiplock', 'chkpnt', $vmid, '--kill' ];
$self->cmd_quiet($cmd, errmsg => "Failed to kill container");
$cmd = [ 'vzctl', '--skiplock', 'umount', $vmid ];
sleep(1); # hack: wait - else there are open files
$self->cmd_quiet($cmd, errmsg => "Failed to umount container");
}
# move config
die "Failed to move config to node '$self->{node}' - rename failed: $!\n"
if !rename($conffile, $newconffile);
}
sub phase1_cleanup {
my ($self, $vmid, $err) = @_;
$self->log('info', "aborting phase 1 - cleanup resources");
my $conf = $self->{vmconf};
if ($self->{undo_suspend}) {
my $cmd = [ 'vzctl', '--skiplock', 'chkpnt', $vmid, '--resume' ];
$self->cmd_logerr($cmd, errmsg => "Failed to resume container");
}
if ($self->{undo_private}) {
$self->log('info', "removing copied files on target node");
my $cmd = [ @{$self->{rem_ssh}}, 'rm', '-rf', $self->{undo_private} ];
$self->cmd_logerr($cmd, errmsg => "Failed to remove copied files");
}
# fixme: that seem to be very dangerous and not needed
#my $cmd = [ @{$self->{rem_ssh}}, 'rm', '-rf', $self->{rootdir} ];
#eval { $self->cmd_quiet($cmd); };
my $newconffile = PVE::OpenVZ::config_file($vmid, $self->{node});
my $newcfgdir = dirname($newconffile);
foreach my $s (PVE::OpenVZ::SCRIPT_EXT) {
my $scriptfn = "${vmid}.$s";
my $dstfn = "$newcfgdir/$scriptfn";
if (-f $dstfn) {
$self->log('err', "unlink '$dstfn' failed - $!") if !unlink $dstfn;
}
}
}
sub init_target_vm {
my ($self, $vmid) = @_;
my $conf = $self->{vmconf};
$self->log('info', "initialize container on remote node '$self->{node}'");
my $cmd = [ @{$self->{rem_ssh}}, 'vzctl', '--quiet', 'set', $vmid,
'--applyconfig_map', 'name', '--save' ];
$self->cmd_quiet($cmd, errmsg => "Failed to apply config on target node");
if ($self->{disk_quota}) {
$self->log('info', "initializing remote quota");
$cmd = [ @{$self->{rem_ssh}}, 'vzctl', 'quotainit', $vmid];
$self->cmd_quiet($cmd, errmsg => "Failed to initialize quota");
$self->log('info', "turn on remote quota");
$cmd = [ @{$self->{rem_ssh}}, 'vzctl', 'quotaon', $vmid];
$self->cmd_quiet($cmd, errmsg => "Failed to turn on quota");
$self->log('info', "load 2nd level quota");
$cmd = [ @{$self->{rem_ssh}}, "(vzdqload $vmid -U -G -T < " .
PVE::Tools::shellquote($self->{quotadumpfile}) .
" && vzquota reload2 $vmid)"];
$self->cmd_quiet($cmd, errmsg => "Failed to load 2nd level quota");
if (!$self->{running}) {
$self->log('info', "turn off remote quota");
$cmd = [ @{$self->{rem_ssh}}, 'vzquota', 'off', $vmid];
$self->cmd_quiet($cmd, errmsg => "Failed to turn off quota");
}
}
}
sub phase2 {
my ($self, $vmid) = @_;
my $conf = $self->{vmconf};
$self->{target_initialized} = 1;
init_target_vm($self, $vmid);
$self->log('info', "starting container on remote node '$self->{node}'");
$self->log('info', "restore container state");
$self->{dumpfile} = "$self->{dumpdir}/dump.$vmid";
my $cmd = [ @{$self->{rem_ssh}}, 'vzctl', 'restore', $vmid, '--undump',
'--dumpfile', $self->{dumpfile}, '--skip_arpdetect' ];
$self->cmd_quiet($cmd, errmsg => "Failed to restore container");
$cmd = [ @{$self->{rem_ssh}}, 'vzctl', 'restore', $vmid, '--resume' ];
$self->cmd_quiet($cmd, errmsg => "Failed to resume container");
}
sub phase3 {
my ($self, $vmid) = @_;
if (!$self->{target_initialized}) {
init_target_vm($self, $vmid);
}
}
sub phase3_cleanup {
my ($self, $vmid, $err) = @_;
my $conf = $self->{vmconf};
if (!$self->{shared}) {
# destroy local container data
$self->log('info', "removing container files on local node");
my $cmd = [ 'rm', '-rf', $self->{privatedir} ];
$self->cmd_logerr($cmd);
}
if ($self->{disk_quota}) {
my $cmd = [ 'vzquota', 'drop', $vmid];
$self->cmd_logerr($cmd, errmsg => "Failed to drop local quota");
}
}
sub final_cleanup {
my ($self, $vmid) = @_;
$self->log('info', "start final cleanup");
my $conf = $self->{vmconf};
unlink($self->{quotadumpfile}) if $self->{quotadumpfile};
unlink($self->{dumpfile}) if $self->{dumpfile};
if ($self->{undo_copy_dump} && $self->{dumpfile}) {
my $cmd = [ @{$self->{rem_ssh}}, 'rm', '-f', $self->{dumpfile} ];
$self->cmd_logerr($cmd, errmsg => "Failed to remove dump file");
}
if ($self->{undo_copy_quota_dump} && $self->{quotadumpfile}) {
my $cmd = [ @{$self->{rem_ssh}}, 'rm', '-f', $self->{quotadumpfile} ];
$self->cmd_logerr($cmd, errmsg => "Failed to remove 2nd level quota dump file");
}
}
1;
......@@ -13,7 +13,6 @@ use File::Path;
use PVE::RPCEnvironment;
use PVE::Storage;
use PVE::Cluster qw(cfs_read_file);
use PVE::VZDump::OpenVZ;
use Time::localtime;
use Time::Local;
use PVE::JSONSchema qw(get_standard_option);
......@@ -26,7 +25,7 @@ my $pidfile = '/var/run/vzdump.pid';
my $logdir = '/var/log/vzdump';
my @plugins = qw (PVE::VZDump::OpenVZ);
my @plugins = qw();
# Load available plugins
my $pveplug = "/usr/share/perl5/PVE/VZDump/QemuServer.pm";
......@@ -850,29 +849,12 @@ sub exec_backup_task {
$self->run_hook_script ('backup-start', $task, $logfd);
if ($vmtype eq 'openvz') {
# pre-suspend rsync
$plugin->copy_data_phase1 ($task, $vmid);
}
debugmsg ('info', "suspend vm", $logfd);
$vmstoptime = time ();
$self->run_hook_script ('pre-stop', $task, $logfd);
$plugin->suspend_vm ($task, $vmid);
$cleanup->{resume} = 1;
if ($vmtype eq 'openvz') {
# post-suspend rsync
$plugin->copy_data_phase2 ($task, $vmid);
debugmsg ('info', "resume vm", $logfd);
$cleanup->{resume} = 0;
$self->run_hook_script ('pre-restart', $task, $logfd);
$plugin->resume_vm ($task, $vmid);
my $delay = time () - $vmstoptime;
debugmsg ('info', "vm is online again after $delay seconds", $logfd);
}
} elsif ($mode eq 'snapshot') {
$self->run_hook_script ('backup-start', $task, $logfd);
......
include ../../defines.mk
PERLSOURCE = \
OpenVZ.pm \
Plugin.pm
all:
......
package PVE::VZDump::OpenVZ;
use strict;
use warnings;
use File::Path;
use File::Basename;
use PVE::INotify;
use PVE::VZDump;
use PVE::OpenVZ;
use base qw (PVE::VZDump::Plugin);
my $load_vz_conf = sub {
my ($self, $vmid) = @_;
my $conf = PVE::OpenVZ::load_config($vmid);
my $dir = $self->{privatedir};
if ($conf->{ve_private} && $conf->{ve_private}->{value}) {
$dir = $conf->{ve_private}->{value};
}
$dir =~ s/\$VEID/$vmid/;
$self->{vmlist}->{$vmid}->{dir} = $dir;
my $hostname = "CT $vmid";
if ($conf->{hostname} && $conf->{hostname}->{value}) {
$hostname = $conf->{hostname}->{value};
}
$self->{vmlist}->{$vmid}->{hostname} = $hostname;
};
my $rsync_vm = sub {
my ($self, $task, $from, $to, $text) = @_;
$self->loginfo ("starting $text sync $from to $to");
my $starttime = time();
my $opts = $self->{vzdump}->{opts};
my $rsyncopts = "--stats -x --numeric-ids";
$rsyncopts .= " --bwlimit=$opts->{bwlimit}" if $opts->{bwlimit};
$self->cmd ("rsync $rsyncopts -aH --delete --no-whole-file --inplace '$from' '$to'");
my $delay = time () - $starttime;
$self->loginfo ("$text sync finished ($delay seconds)");
};
sub new {
my ($class, $vzdump) = @_;
PVE::VZDump::check_bin ('vzctl');
my $self = bless PVE::OpenVZ::read_global_vz_config ();
$self->{vzdump} = $vzdump;
$self->{vmlist} = PVE::OpenVZ::config_list();
return $self;
};
sub type {
return 'openvz';
}
sub vm_status {
my ($self, $vmid) = @_;
my $status_text = '';
$self->cmd ("vzctl status $vmid", outfunc => sub {$status_text .= shift; });
chomp $status_text;
my $running = $status_text =~ m/running/ ? 1 : 0;
return wantarray ? ($running, $running ? 'running' : 'stopped') : $running;
}
sub prepare {
my ($self, $task, $vmid, $mode) = @_;
$self->$load_vz_conf ($vmid);
my $dir = $self->{vmlist}->{$vmid}->{dir};
my $diskinfo = { dir => $dir };
$task->{hostname} = $self->{vmlist}->{$vmid}->{hostname};
$task->{diskinfo} = $diskinfo;
my $hostname = PVE::INotify::nodename();
if ($mode eq 'snapshot') {
my $lvmmap = PVE::VZDump::get_lvm_mapping();
my ($srcdev, $lvmpath, $lvmvg, $lvmlv, $fstype) =
PVE::VZDump::get_lvm_device ($dir, $lvmmap);
my $targetdev = PVE::VZDump::get_lvm_device ($task->{dumpdir}, $lvmmap);
die ("mode failure - unable to detect lvm volume group\n") if !$lvmvg;
die ("mode failure - wrong lvm mount point '$lvmpath'\n") if $dir !~ m|/?$lvmpath/?|;
die ("mode failure - unable to dump into snapshot (use option --dumpdir)\n")
if $targetdev eq $srcdev;
$diskinfo->{snapname} = "vzsnap-$hostname-0";
$diskinfo->{snapdev} = "/dev/$lvmvg/$diskinfo->{snapname}";
$diskinfo->{srcdev} = $srcdev;
$diskinfo->{lvmvg} = $lvmvg;
$diskinfo->{lvmlv} = $lvmlv;
$diskinfo->{fstype} = $fstype;
$diskinfo->{lvmpath} = $lvmpath;
$diskinfo->{mountpoint} = "/mnt/vzsnap0";
$task->{snapdir} = $dir;
$task->{snapdir} =~ s|/?$lvmpath/?|$diskinfo->{mountpoint}/|;
} elsif ($mode eq 'suspend') {
$task->{snapdir} = $task->{tmpdir};
} else {
$task->{snapdir} = $dir;
}
}
sub lock_vm {
my ($self, $vmid) = @_;
my $filename = "$self->{lockdir}/103.lck";
my $lockmgr = PVE::OpenVZ::create_lock_manager();
$self->{lock} = $lockmgr->lock($filename) || die "can't lock VM $vmid\n";
}
sub unlock_vm {
my ($self, $vmid) = @_;
$self->{lock}->release();
}
sub copy_data_phase1 {
my ($self, $task) = @_;
$self->$rsync_vm ($task, "$task->{diskinfo}->{dir}/", $task->{snapdir}, "first");
}
# we use --skiplock for vzctl because we have already locked the VM
# by calling lock_vm()
sub stop_vm {
my ($self, $task, $vmid) = @_;
$self->cmd ("vzctl --skiplock stop $vmid");
}
sub start_vm {
my ($self, $task, $vmid) = @_;
$self->cmd ("vzctl --skiplock start $vmid");
}
sub suspend_vm {
my ($self, $task, $vmid) = @_;
$self->cmd ("vzctl --skiplock chkpnt $vmid --suspend");
}
sub snapshot {
my ($self, $task) = @_;
my $opts = $self->{vzdump}->{opts};
my $di = $task->{diskinfo};
mkpath $di->{mountpoint}; # create mount point for lvm snapshot
if (-b $di->{snapdev}) {
$self->loginfo ("trying to remove stale snapshot '$di->{snapdev}'");
$self->cmd_noerr ("umount $di->{mountpoint}");
$self->cmd_noerr ("lvremove -f $di->{snapdev}");
}
$self->loginfo ("creating lvm snapshot of $di->{srcdev} ('$di->{snapdev}')");
$task->{cleanup}->{lvm_snapshot} = 1;
$self->cmd ("lvcreate --size $opts->{size}M --snapshot" .
" --name $di->{snapname} /dev/$di->{lvmvg}/$di->{lvmlv}");
my $mopts = $di->{fstype} eq 'xfs' ? "-o nouuid" : '';
$task->{cleanup}->{snapshot_mount} = 1;
$self->cmd ("mount -n -t $di->{fstype} $mopts $di->{snapdev} $di->{mountpoint}");
}
sub copy_data_phase2 {
my ($self, $task) = @_;
$self->$rsync_vm ($task, "$task->{diskinfo}->{dir}/", $task->{snapdir}, "final");
}
sub resume_vm {
my ($self, $task, $vmid) = @_;
$self->cmd ("vzctl --skiplock chkpnt $vmid --resume");
}
sub assemble {
my ($self, $task, $vmid) = @_;
my $conffile = PVE::OpenVZ::config_file($vmid);
my $dir = $task->{snapdir};
$task->{cleanup}->{etc_vzdump} = 1;
mkpath "$dir/etc/vzdump/";
$self->cmd ("cp '$conffile' '$dir/etc/vzdump/vps.conf'");
my $cfgdir = dirname ($conffile);
foreach my $s (PVE::OpenVZ::SCRIPT_EXT) {
my $fn = "$cfgdir/$vmid.$s";
$self->cmd ("cp '$fn' '$dir/etc/vzdump/vps.$s'") if -f $fn;
}
}
sub archive {
my ($self, $task, $vmid, $filename, $comp) = @_;
my $findexcl = $self->{vzdump}->{findexcl};
my $findargs = join (' ', @$findexcl) . ' -print0';
my $opts = $self->{vzdump}->{opts};
my $srcdir = $self->{vmlist}->{$vmid}->{dir};
my $snapdir = $task->{snapdir};
my $taropts = "--totals --sparse --numeric-owner --no-recursion --one-file-system";
# note: --remove-files does not work because we do not
# backup all files (filters). tar complains:
# Cannot rmdir: Directory not empty
# we we disable this optimization for now
#if ($snapdir eq $task->{tmpdir} && $snapdir =~ m|^$opts->{dumpdir}/|) {
# $taropts .= " --remove-files"; # try to save space
#}
my $cmd = "(";
$cmd .= "cd $snapdir;find . $findargs|sed 's/\\\\/\\\\\\\\/g'|";
$cmd .= "tar cpf - $taropts --null -T -";
my $bwl = $opts->{bwlimit}*1024; # bandwidth limit for cstream
$cmd .= "|cstream -t $bwl" if $opts->{bwlimit};
$cmd .= "|$comp" if $comp;
$cmd .= ")";
if ($opts->{stdout}) {
$self->cmd ($cmd, output => ">&=" . fileno($opts->{stdout}));
} else {
$self->cmd ("$cmd >$filename");
}
}
sub cleanup {
my ($self, $task, $vmid) = @_;
my $di = $task->{diskinfo};
if ($task->{cleanup}->{snapshot_mount}) {
# Note: sleep to avoid 'device is busy' message.
# Seems Kernel need some time to cleanup open file list,
# fir example when we stop the tar with kill (stop task)
sleep(1);
$self->cmd_noerr ("umount $di->{mountpoint}");
}
if ($task->{cleanup}->{lvm_snapshot}) {
# loop, because we often get 'LV in use: not deactivating'
# we use run_command() because we do not want to log errors here
my $wait = 1;
while(-b $di->{snapdev}) {
eval {
my $cmd = ['lvremove', '-f', $di->{snapdev}];
PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {});
};
last if !$@;
if ($wait >= 64) {
$self->logerr($@);
last;
}
$self->loginfo("lvremove failed - trying again in $wait seconds") if $wait >= 8;
sleep($wait);
$wait = $wait*2;
}
}
if ($task->{cleanup}->{etc_vzdump}) {
my $dir = "$task->{snapdir}/etc/vzdump";
eval { rmtree $dir if -d $dir; };
$self->logerr ($@) if $@;
}
}
1;
......@@ -5,12 +5,10 @@ SUBDIRS = init.d cron ocf test
SCRIPTS = \
pveceph \
vzdump \
vzrestore \
pvestatd \
pvesh \
pveam \
pvebanner \
pvectl \
pvedaemon \
pveproxy \
spiceproxy \
......@@ -22,9 +20,7 @@ SCRIPTS = \
MANS = \
pveceph.1 \
pvectl.1 \
vzdump.1 \
vzrestore.1 \
pvestatd.1 \
pvedaemon.1 \
pveproxy.1 \
......@@ -82,10 +78,8 @@ install: ${SCRIPTS} ${MANS} pvemailforward
install -d ${MAN1DIR}
install -m 0644 ${MANS} ${MAN1DIR}
install -d ${PODDIR}
install -m 0644 pvectl.1.pod ${PODDIR}
install -m 0644 vzdump.1.pod ${PODDIR}
install -m 0644 pvesubscription.1.pod ${PODDIR}
install -m 0644 vzrestore.1.pod ${PODDIR}
set -e && for i in ${SUBDIRS}; do ${MAKE} -C $$i $@; done
.PHONY: distclean
......
#!/usr/bin/perl
use strict;
use warnings;
use PVE::Tools qw(extract_param);
use PVE::Cluster qw(cfs_register_file cfs_read_file);
use PVE::SafeSyslog;
use PVE::INotify;
use PVE::RPCEnvironment;
use PVE::CLIHandler;
use PVE::API2::OpenVZ;
use Data::Dumper; # fixme: remove
use base qw(PVE::CLIHandler);
$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
initlog('pvectl');
die "please run as root\n" if $> != 0;
PVE::INotify::inotify_init();
my $nodename = PVE::INotify::nodename();
my $rpcenv = PVE::RPCEnvironment->init('cli');
$rpcenv->init_request();
$rpcenv->set_language($ENV{LANG});
$rpcenv->set_user('root@pam');
my $upid_exit = sub {
my $upid = shift;
my $status = PVE::Tools::upid_read_status($upid);
exit($status eq 'OK' ? 0 : -1);
};
my $cmddef = {
list => [ "PVE::API2::OpenVZ", 'vmlist', [],
{ node => $nodename }, sub {
my $vmlist = shift;
exit 0 if (!scalar(@$vmlist));
printf "%10s %-20s %-10s %-10s %-12s\n",
qw(VMID NAME STATUS MEM(MB) DISK(GB));
foreach my $rec (sort { $a->{vmid} <=> $b->{vmid} } @$vmlist) {
printf "%10s %-20s %-10s %-10s %-12.2f\n", $rec->{vmid}, $rec->{name} || '',
$rec->{status},
($rec->{maxmem} || 0)/(1024*1024),
($rec->{maxdisk} || 0)/(1024*1024*1024);
}
} ],
create => [ 'PVE::API2::OpenVZ', 'create_vm', ['vmid', 'ostemplate'], { node => $nodename }, $upid_exit ],
destroy => [ 'PVE::API2::OpenVZ', 'destroy_vm', ['vmid'], { node => $nodename }, $upid_exit ],
set => [ "PVE::API2::OpenVZ", 'update_vm', ['vmid'], { node => $nodename } ],
config => [ "PVE::API2::OpenVZ", 'vm_config', ['vmid'],
{ node => $nodename }, sub {
my $config = shift;
foreach my $k (sort (keys %$config)) {
next if $k eq 'digest';
my $v = $config->{$k};
if ($k eq 'description') {
$v = PVE::Tools::encode_text($v);
}
print "$k: $v\n";
}
}],
start => [ 'PVE::API2::OpenVZ', 'vm_start', ['vmid'], { node => $nodename }, $upid_exit],
suspend => [ 'PVE::API2::OpenVZ', 'vm_suspend', ['vmid'], { node => $nodename }, $upid_exit],
resume => [ 'PVE::API2::OpenVZ', 'vm_resume', ['vmid'], { node => $nodename }, $upid_exit],
shutdown => [ 'PVE::API2::OpenVZ', 'vm_shutdown', ['vmid'], { node => $nodename }, $upid_exit],
stop => [ 'PVE::API2::OpenVZ', 'vm_stop', ['vmid'], { node => $nodename }, $upid_exit],
mount => [ 'PVE::API2::OpenVZ', 'vm_mount', ['vmid'], { node => $nodename }, $upid_exit],
umount => [ 'PVE::API2::OpenVZ', 'vm_umount', ['vmid'], { node => $nodename }, $upid_exit],
migrate => [ "PVE::API2::OpenVZ", 'migrate_vm', ['vmid', 'target'], { node => $nodename }, $upid_exit],
};
my $cmd = shift;
PVE::CLIHandler::handle_cmd($cmddef, "pvectl", $cmd, \@ARGV, undef, $0);
exit 0;
__END__
=head1 NAME
pvectl - vzctl wrapper to manage OpenVZ containers
=head1 SYNOPSIS
=include synopsis
=head1 DESCRIPTION
This is a small wrapper around vztl.
=include pve_copyright
......@@ -13,7 +13,6 @@ use PVE::INotify;
use PVE::Cluster qw(cfs_read_file);
use PVE::Storage;
use PVE::QemuServer;
use PVE::OpenVZ;
use PVE::RPCEnvironment;
use PVE::API2::Subscription;
use PVE::AutoBalloon;
......@@ -202,45 +201,6 @@ sub find_vzctl_console_pids {
return $res;
}
sub remove_stale_openvz_consoles {
my $vmstatus = PVE::OpenVZ::vmstatus();
my $pidhash = find_vzctl_console_pids();
foreach my $vmid (keys %$pidhash) {
next if defined($vmstatus->{$vmid});
syslog('info', "remove stale vzctl console for CT $vmid");
foreach my $pid (@{$pidhash->{$vmid}}) {
kill(9, $pid);
}
}
}
sub update_openvz_status {
my $ctime = time();
my $vmstatus = PVE::OpenVZ::vmstatus();
foreach my $vmid (keys %$vmstatus) {
my $d = $vmstatus->{$vmid};
my $data;
if ($d->{status} eq 'running') { # running
$data = "$d->{uptime}:$d->{name}:$d->{status}:0:$ctime:$d->{cpus}:$d->{cpu}:" .
"$d->{maxmem}:$d->{mem}:" .
"$d->{maxdisk}:$d->{disk}:" .
"$d->{netin}:$d->{netout}:" .
"$d->{diskread}:$d->{diskwrite}";
} else {
$data = "0:$d->{name}:$d->{status}:0:$ctime:$d->{cpus}::" .
"$d->{maxmem}::" .
"$d->{maxdisk}:$d->{disk}:" .
":::";
}
PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data);
}
}
sub update_storage_status {
my $cfg = cfs_read_file("storage.cfg");
......@@ -287,23 +247,11 @@ sub update_status {
$err = $@;
syslog('err', "qemu status update error: $err") if $err;
eval {
update_openvz_status();
};
$err = $@;
syslog('err', "openvz status update error: $err") if $err;
eval {
update_storage_status();
};
$err = $@;
syslog('err', "storage status update error: $err") if $err;
eval {
remove_stale_openvz_consoles();
};
$err = $@;
syslog('err', "openvz console cleanup error: $err") if $err;
}
my $next_update = 0;
......
#!/usr/bin/perl -w
use strict;
use PVE::SafeSyslog;
use PVE::Tools qw(extract_param);
use PVE::INotify;
use PVE::RPCEnvironment;
use PVE::CLIHandler;
use PVE::JSONSchema qw(get_standard_option);
use PVE::API2::OpenVZ;
use Data::Dumper; # fixme: remove
use base qw(PVE::CLIHandler);
$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
initlog('vzrestore');
die "please run as root\n" if $> != 0;
PVE::INotify::inotify_init();
my $rpcenv = PVE::RPCEnvironment->init('cli');
$rpcenv->init_request();
$rpcenv->set_language($ENV{LANG});
$rpcenv->set_user('root@pam');
__PACKAGE__->register_method({
name => 'vzrestore',
path => 'vzrestore',
method => 'POST',
description => "Restore OpenVZ containers.",
parameters => {
additionalProperties => 0,
properties => {
vmid => get_standard_option('pve-vmid'),
archive => {
description => "The backup file. You can pass '-' to read from standard input.",
type => 'string',
maxLength => 255,
},
storage => get_standard_option('pve-storage-id', {
description => "Target storage.",
default => 'local',
optional => 1,
}),
force => {
optional => 1,
type => 'boolean',
description => "Allow to overwrite existing container.",
},
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
$param->{ostemplate} = extract_param($param, 'archive');
$param->{node} = PVE::INotify::nodename();
$param->{restore} = 1;
return PVE::API2::OpenVZ->create_vm($param);
}});
my $cmddef = [ __PACKAGE__, 'vzrestore', ['archive', 'vmid'], undef,
sub {
my $upid = shift;
my $status = PVE::Tools::upid_read_status($upid);
exit($status eq 'OK' ? 0 : -1);
}];
push @ARGV, 'help' if !scalar(@ARGV);
PVE::CLIHandler::handle_simple_cmd($cmddef, \@ARGV, undef, $0);
exit 0;
__END__
=head1 NAME
vzrestore - restore OpenVZ vzdump backups
=head1 SYNOPSIS
=include synopsis
=head1 DESCRIPTION
Restores OpenVZ vzdump backups.
=head1 SEE ALSO
vzdump(1) qmrestore(1)
=include pve_copyright
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment