use Storable qw(dclone);
use IO::File;
use MIME::Base64;
-use XML::Parser;
use Digest::SHA;
use Digest::HMAC_SHA1;
use Net::SSLeay;
'vzdump.cron' => 1,
'storage.cfg' => 1,
'datacenter.cfg' => 1,
+ 'replication.cfg' => 1,
'corosync.conf' => 1,
'corosync.conf.new' => 1,
'user.cfg' => 1,
};
eval {
- PVE::Tools::run_command($cmd, outfunc => $record_output,
+ PVE::Tools::run_command($cmd, outfunc => $record_output,
errfunc => $record_output);
};
my @required_dirs = (
"$basedir/priv",
- "$basedir/nodes",
+ "$basedir/nodes",
"$basedir/nodes/$nodename",
"$basedir/nodes/$nodename/lxc",
"$basedir/nodes/$nodename/qemu-server",
"$basedir/nodes/$nodename/openvz",
"$basedir/nodes/$nodename/priv");
-
+
foreach my $dir (@required_dirs) {
if (! -d $dir) {
mkdir($dir) || $! == EEXIST || die "unable to create directory '$dir' - $!\n";
my $rc = PVE::INotify::read_file('resolvconf');
$names .= ",IP:$ip";
-
+
my $fqdn = $nodename;
$names .= ",DNS:$nodename";
eval {
my $ver = $kvstore->{$node}->{tasklist} if $kvstore->{$node};
my $cd = $tasklistcache->{$node};
- if (!$cd || !$ver || !$cd->{version} ||
+ if (!$cd || !$ver || !$cd->{version} ||
($cd->{version} != $ver)) {
my $raw = &$ipcc_get_status("tasklist", $node) || '[]';
my $data = decode_json($raw);
my ($key, @ela) = split(/:/, $1);
next if !$key;
next if !(scalar(@ela) > 1);
- $res->{$key} = \@ela;
+ $res->{$key} = [ map { $_ eq 'U' ? undef : $_ } @ela ];
}
}
my $err = RRDs::error;
die "RRD error: $err\n" if $err;
-
- die "got wrong time resolution ($step != $reso)\n"
+
+ die "got wrong time resolution ($step != $reso)\n"
if $step != $reso;
my $res = [];
# Using RRD graph is clumsy - maybe it
# is better to simply fetch the data, and do all display
# related things with javascript (new extjs html5 graph library).
-
+
my $rrddir = "/var/lib/rrdcached/db";
my $rrd = "$rrddir/$rrdname";
sub cfs_read_file {
my ($filename) = @_;
- my ($version, $info) = cfs_file_version($filename);
+ my ($version, $info) = cfs_file_version($filename);
my $parser = $info->{parser};
return &$ccache_read($filename, $parser, $version);
sub cfs_write_file {
my ($filename, $data) = @_;
- my ($version, $info) = cfs_file_version($filename);
+ my ($version, $info) = cfs_file_version($filename);
my $writer = $info->{writer} || die "no writer defined";
if ($err && ($err eq "got lock request timeout\n") &&
!check_cfs_quorum()){
$err = "$msg: no quorum!\n";
- }
+ }
if (!$err || $err !~ /^got lock timeout -/) {
rmdir $filename; # cfs unlock
sub check_vmid_unused {
my ($vmid, $noerr) = @_;
-
+
my $vmlist = get_vmlist();
my $d = $vmlist->{ids}->{$vmid};
return 1 if !defined($d);
-
+
return undef if $noerr;
my $vmtypestr = $d->{type} eq 'qemu' ? 'VM' : 'CT';
$family =
PVE::Tools::get_host_address_family($ip);
}
- return ($ip, $family);
+ return wantarray ? ($ip, $family) : $ip;
}
}
# fallback: try to get IP by other means
- my ($family, $packed_ip);
-
- eval {
- my @res = PVE::Tools::getaddrinfo_all($nodename);
- $family = $res[0]->{family};
- $packed_ip = (PVE::Tools::unpack_sockaddr_in46($res[0]->{addr}))[2];
- };
-
- if ($@) {
- die "hostname lookup failed:\n$@" if !$noerr;
- return undef;
- }
-
- my $ip = Socket::inet_ntop($family, $packed_ip);
- if ($ip =~ m/^127\.|^::1$/) {
- die "hostname lookup failed - got local IP address ($nodename = $ip)\n" if !$noerr;
- return undef;
- }
-
- return wantarray ? ($ip, $family) : $ip;
+ return PVE::Network::get_ip_from_hostname($nodename, $noerr);
}
sub get_local_migration_ip {
my ($start_sshd) = @_;
my $conf = PVE::Tools::file_get_contents($sshd_config_fn);
-
+
return if $conf =~ m/^PermitRootLogin\s+yes\s*$/m;
if ($conf !~ s/^#?PermitRootLogin.*$/PermitRootLogin yes/m) {
chomp $conf;
$conf .= "\nPermitRootLogin yes\n";
- }
+ }
PVE::Tools::file_set_contents($sshd_config_fn, $conf);
}
}
- warn "can't create shared ssh key database '$sshauthkeys'\n"
+ warn "can't create shared ssh key database '$sshauthkeys'\n"
if ! -f $sshauthkeys;
if (-f $rootsshauthkeys && ! -l $rootsshauthkeys) {
die "no node name specified" if !$nodename;
die "no ip address specified" if !$ip_address;
-
+
mkdir $authdir;
if (! -f $sshknownhosts) {
}
}
- my $old = PVE::Tools::file_get_contents($sshknownhosts, 128*1024);
-
+ my $old = PVE::Tools::file_get_contents($sshknownhosts, 128*1024);
+
my $new = '';
-
+
if ((! -l $sshglobalknownhosts) && (-f $sshglobalknownhosts)) {
$new = PVE::Tools::file_get_contents($sshglobalknownhosts, 128*1024);
}
unlink $sshglobalknownhosts;
symlink $sshknownhosts, $sshglobalknownhosts;
-
- warn "can't create symlink for ssh known hosts '$sshglobalknownhosts' -> '$sshknownhosts'\n"
+
+ warn "can't create symlink for ssh known hosts '$sshglobalknownhosts' -> '$sshknownhosts'\n"
if ! -l $sshglobalknownhosts;
}
return PVE::JSONSchema::dump_config($datacenter_schema, $filename, $cfg);
}
-cfs_register_file('datacenter.cfg',
- \&parse_datacenter_config,
+cfs_register_file('datacenter.cfg',
+ \&parse_datacenter_config,
\&write_datacenter_config);
# a very simply parser ...
$raw =~ s/\s+/ /g;
$raw =~ s/^\s+//;
$raw =~ s/\s*$//;
-
+
my @tokens = split(/\s/, $raw);
-
+
my $conf = { section => 'main', children => [] };
my $stack = [];
my $section = $conf;
-
+
while (defined(my $token = shift @tokens)) {
my $nexttok = $tokens[0];
my $key = $token;
die "missing ':' after key '$key'\n" if ! ($key =~ s/:$//);
-
+
die "parse error - no value for '$key'\n" if !defined($nexttok);
my $value = shift @tokens;
my ($section, $prefix) = @_;
my $raw = $prefix . $section->{section} . " {\n";
-
+
my @list = grep { defined($_->{key}) } @{$section->{children}};
foreach my $child (sort {$a->{key} cmp $b->{key}} @list) {
$raw .= $prefix . " $child->{key}: $child->{value}\n";
}
-
+
@list = grep { defined($_->{section}) } @{$section->{children}};
foreach my $child (sort {$a->{section} cmp $b->{section}} @list) {
$raw .= &$dump_corosync_section($child, "$prefix ");
}
$raw .= $prefix . "}\n\n";
-
+
return $raw;
-
+
};
sub write_corosync_conf {
my $raw = '';
my $prefix = '';
-
+
die "no main section" if $conf->{section} ne 'main';
my @list = grep { defined($_->{key}) } @{$conf->{children}};
}
}
}
-
+
return undef if $noerr;
die "invalid corosync config - unable to read version\n";
# read only - use "rename corosync.conf.new corosync.conf" to write
PVE::Cluster::cfs_register_file('corosync.conf', \&parse_corosync_conf);
# this is read/write
-PVE::Cluster::cfs_register_file('corosync.conf.new', \&parse_corosync_conf,
+PVE::Cluster::cfs_register_file('corosync.conf.new', \&parse_corosync_conf,
\&write_corosync_conf);
sub check_corosync_conf_exists {
return $exists;
}
+sub corosync_update_nodelist {
+ my ($conf, $nodelist) = @_;
+
+ delete $conf->{digest};
+
+ my $version = corosync_conf_version($conf);
+ corosync_conf_version($conf, undef, $version + 1);
+
+ my $children = [];
+ foreach my $v (values %$nodelist) {
+ next if !($v->{ring0_addr} || $v->{name});
+ my $kv = [];
+ foreach my $k (keys %$v) {
+ push @$kv, { key => $k, value => $v->{$k} };
+ }
+ my $ns = { section => 'node', children => $kv };
+ push @$children, $ns;
+ }
+
+ foreach my $main (@{$conf->{children}}) {
+ next if !defined($main->{section});
+ if ($main->{section} eq 'nodelist') {
+ $main->{children} = $children;
+ last;
+ }
+ }
+
+
+ cfs_write_file("corosync.conf.new", $conf);
+
+ rename("/etc/pve/corosync.conf.new", "/etc/pve/corosync.conf")
+ || die "activate corosync.conf.new failed - $!\n";
+}
+
+sub corosync_nodelist {
+ my ($conf) = @_;
+
+ my $nodelist = {};
+
+ foreach my $main (@{$conf->{children}}) {
+ next if !defined($main->{section});
+ if ($main->{section} eq 'nodelist') {
+ foreach my $ne (@{$main->{children}}) {
+ next if !defined($ne->{section}) || ($ne->{section} ne 'node');
+ my $node = { quorum_votes => 1 };
+ my $name;
+ foreach my $child (@{$ne->{children}}) {
+ next if !defined($child->{key});
+ $node->{$child->{key}} = $child->{value};
+ # use 'name' over 'ring0_addr' if set
+ if ($child->{key} eq 'name') {
+ delete $nodelist->{$name} if $name;
+ $name = $child->{value};
+ $nodelist->{$name} = $node;
+ } elsif(!$name && $child->{key} eq 'ring0_addr') {
+ $name = $child->{value};
+ $nodelist->{$name} = $node;
+ }
+ }
+ }
+ }
+ }
+
+ return $nodelist;
+}
+
+# get a hash representation of the corosync config totem section
+sub corosync_totem_config {
+ my ($conf) = @_;
+
+ my $res = {};
+
+ foreach my $main (@{$conf->{children}}) {
+ next if !defined($main->{section}) ||
+ $main->{section} ne 'totem';
+
+ foreach my $e (@{$main->{children}}) {
+
+ if ($e->{section} && $e->{section} eq 'interface') {
+ my $entry = {};
+
+ $res->{interface} = {};
+
+ foreach my $child (@{$e->{children}}) {
+ next if !defined($child->{key});
+ $entry->{$child->{key}} = $child->{value};
+ if($child->{key} eq 'ringnumber') {
+ $res->{interface}->{$child->{value}} = $entry;
+ }
+ }
+
+ } elsif ($e->{key}) {
+ $res->{$e->{key}} = $e->{value};
+ }
+ }
+ }
+
+ return $res;
+}
+
# X509 Certificate cache helper
my $cert_cache_nodes = {};
return $res;
}
+sub get_ssh_info {
+ my ($node, $network_cidr) = @_;
+
+ my $ip;
+ if (defined($network_cidr)) {
+ # Use mtunnel via to get the remote node's ip inside $network_cidr.
+ # This goes over the regular network (iow. uses get_ssh_info() with
+ # $network_cidr undefined.
+ # FIXME: Use the REST API client for this after creating an API entry
+ # for get_migration_ip.
+ my $default_remote = get_ssh_info($node, undef);
+ my $default_ssh = ssh_info_to_command($default_remote);
+ my $cmd =[@$default_ssh, 'pvecm', 'mtunnel',
+ '-migration_network', $network_cidr,
+ '-get_migration_ip'
+ ];
+ PVE::Tools::run_command($cmd, outfunc => sub {
+ my ($line) = @_;
+ chomp $line;
+ die "internal error: unexpected output from mtunnel\n"
+ if defined($ip);
+ if ($line =~ /^ip: '(.*)'$/) {
+ $ip = $1;
+ } else {
+ die "internal error: bad output from mtunnel\n"
+ if defined($ip);
+ }
+ });
+ die "failed to get ip for node '$node' in network '$network_cidr'\n"
+ if !defined($ip);
+ } else {
+ $ip = remote_node_ip($node);
+ }
+
+ return {
+ ip => $ip,
+ name => $node
+ };
+}
+
+sub ssh_info_to_command {
+ my ($info, @extra_options) = @_;
+ return [
+ '/usr/bin/ssh',
+ '-o', 'BatchMode=yes',
+ '-o', 'HostKeyAlias='.$info->{name},
+ @extra_options,
+ "root\@$info->{ip}"
+ ];
+}
+
1;