]> git.proxmox.com Git - pve-cluster.git/blobdiff - data/PVE/Corosync.pm
fix #2479: use correct sub in create_conf
[pve-cluster.git] / data / PVE / Corosync.pm
index 0d2a85f7dda7d31771461a89bf267725049bdfac..3208a6bf1925ac6166d90d57d991047d5b1b14e9 100644 (file)
@@ -3,11 +3,16 @@ package PVE::Corosync;
 use strict;
 use warnings;
 
-use Digest::SHA;
 use Clone 'clone';
+use Digest::SHA;
 use Net::IP qw(ip_is_ipv6);
+use Scalar::Util qw(weaken);
+use Socket qw(AF_INET AF_INET6 inet_ntop);
 
 use PVE::Cluster;
+use PVE::JSONSchema;
+use PVE::Tools;
+use PVE::Tools qw($IPV4RE $IPV6RE);
 
 my $basedir = "/etc/pve";
 
@@ -16,6 +21,37 @@ my $conf_array_sections = {
     interface => 1,
 };
 
+my $corosync_link_format = {
+    address => {
+       default_key => 1,
+       type => 'string', format => 'address',
+       format_description => 'IP',
+       description => "Hostname (or IP) of this corosync link address.",
+    },
+    priority => {
+       optional => 1,
+       type => 'integer',
+       minimum => 0,
+       maximum => 255,
+       default => 0,
+       description => "The priority for the link when knet is used in 'passive' mode. Lower value means higher priority.",
+    },
+};
+my $corosync_link_desc = {
+    type => 'string', format => $corosync_link_format,
+    description => "Address and priority information of a single corosync link.",
+    optional => 1,
+};
+PVE::JSONSchema::register_standard_option("corosync-link", $corosync_link_desc);
+
+sub parse_corosync_link {
+    my ($value) = @_;
+
+    return undef if !defined($value);
+
+    return PVE::JSONSchema::parse_property_string($corosync_link_format, $value);
+}
+
 # a very simply parser ...
 sub parse_conf {
     my ($filename, $raw) = @_;
@@ -90,36 +126,6 @@ sub parse_conf {
     return $conf;
 }
 
-my $dump_section;
-$dump_section = sub {
-    my ($section, $prefix) = @_;
-
-    my $raw = '';
-
-    foreach my $k (sort keys %$section) {
-       my $v = $section->{$k};
-       if (ref($v) eq 'HASH') {
-           $raw .= $prefix . "$k {\n";
-           $raw .= &$dump_section($v, "$prefix  ");
-           $raw .=  $prefix . "}\n";
-           $raw .= "\n" if !$prefix; # add extra newline at 1st level only
-       } elsif (ref($v) eq 'ARRAY') {
-           foreach my $child (@$v) {
-               $raw .= $prefix . "$k {\n";
-               $raw .= &$dump_section($child, "$prefix  ");
-               $raw .=  $prefix . "}\n";
-           }
-       } elsif (!ref($v)) {
-           die "got undefined value for key '$k'!\n" if !defined($v);
-           $raw .= $prefix . "$k: $v\n";
-       } else {
-           die "unexpected reference in config hash: $k => ". ref($v) ."\n";
-       }
-    }
-
-    return $raw;
-};
-
 sub write_conf {
     my ($filename, $conf) = @_;
 
@@ -134,7 +140,39 @@ sub write_conf {
     $c->{nodelist}->{node} = &$hash_to_array($c->{nodelist}->{node});
     $c->{totem}->{interface} = &$hash_to_array($c->{totem}->{interface});
 
-    my $raw = &$dump_section($c, '');
+    my $dump_section_weak;
+    $dump_section_weak = sub {
+       my ($section, $prefix) = @_;
+
+       my $raw = '';
+
+       foreach my $k (sort keys %$section) {
+           my $v = $section->{$k};
+           if (ref($v) eq 'HASH') {
+               $raw .= $prefix . "$k {\n";
+               $raw .= $dump_section_weak->($v, "$prefix  ");
+               $raw .=  $prefix . "}\n";
+               $raw .= "\n" if !$prefix; # add extra newline at 1st level only
+           } elsif (ref($v) eq 'ARRAY') {
+               foreach my $child (@$v) {
+                   $raw .= $prefix . "$k {\n";
+                   $raw .= $dump_section_weak->($child, "$prefix  ");
+                   $raw .=  $prefix . "}\n";
+               }
+           } elsif (!ref($v)) {
+               die "got undefined value for key '$k'!\n" if !defined($v);
+               $raw .= $prefix . "$k: $v\n";
+           } else {
+               die "unexpected reference in config hash: $k => ". ref($v) ."\n";
+           }
+       }
+
+       return $raw;
+    };
+    my $dump_section = $dump_section_weak;
+    weaken($dump_section_weak);
+
+    my $raw = $dump_section->($c, '');
 
     return $raw;
 }
@@ -146,14 +184,12 @@ PVE::Cluster::cfs_register_file('corosync.conf.new', \&parse_conf,
                                \&write_conf);
 
 sub check_conf_exists {
-    my ($silent) = @_;
-
-    $silent = $silent // 0;
+    my ($noerr) = @_;
 
     my $exists = -f "$basedir/corosync.conf";
 
-    warn "Corosync config '$basedir/corosync.conf' does not exist - is this node part of a cluster?\n"
-       if !$silent && !$exists;
+    die "Error: Corosync config '$basedir/corosync.conf' does not exist - is this node part of a cluster?\n"
+       if !$noerr && !$exists;
 
     return $exists;
 }
@@ -203,7 +239,7 @@ sub create_conf {
 
     my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
 
-    my $link0 = PVE::Cluster::parse_corosync_link($param{link0});
+    my $link0 = parse_corosync_link($param{link0});
     $link0->{address} //= $local_ip_address;
 
     my $conf = {
@@ -237,18 +273,110 @@ sub create_conf {
            debug => 'off',
        },
     };
+    my $totem = $conf->{totem};
 
-    my $link1 = PVE::Cluster::parse_corosync_link($param{link1});
+    $totem->{interface}->{0}->{knet_link_priority} = $link0->{priority}
+       if defined($link0->{priority});
 
+    my $link1 = parse_corosync_link($param{link1});
     if ($link1->{address}) {
        $conf->{totem}->{interface}->{1} = {
            linknumber => 1,
        };
-       $conf->{totem}->{link_mode} = 'passive';
+       $totem->{link_mode} = 'passive';
+       $totem->{interface}->{1}->{knet_link_priority} = $link1->{priority}
+           if defined($link1->{priority});
        $conf->{nodelist}->{node}->{$nodename}->{ring1_addr} = $link1->{address};
     }
 
     return { main => $conf };
 }
 
+sub for_all_corosync_addresses {
+    my ($corosync_conf, $ip_version, $func) = @_;
+
+    my $nodelist = nodelist($corosync_conf);
+    return if !defined($nodelist);
+
+    # iterate sorted to make rules deterministic (for change detection)
+    foreach my $node_name (sort keys %$nodelist) {
+       my $node_config = $nodelist->{$node_name};
+       foreach my $node_key (sort keys %$node_config) {
+           if ($node_key =~ /^(ring|link)\d+_addr$/) {
+               my $node_address = $node_config->{$node_key};
+
+               my($ip, $version) = resolve_hostname_like_corosync($node_address, $corosync_conf);
+               next if !defined($ip);
+               next if defined($version) && defined($ip_version) && $version != $ip_version;
+
+               $func->($node_name, $ip, $version, $node_key);
+           }
+       }
+    }
+}
+
+# NOTE: Corosync actually only resolves on startup or config change, but we
+# currently do not have an easy way to synchronize our behaviour to that.
+sub resolve_hostname_like_corosync {
+    my ($hostname, $corosync_conf) = @_;
+
+    my $corosync_strategy = $corosync_conf->{main}->{totem}->{ip_version};
+    $corosync_strategy = lc ($corosync_strategy // "ipv6-4");
+
+    my $match_ip_and_version = sub {
+       my ($addr) = @_;
+
+       return undef if !defined($addr);
+
+       if ($addr =~ m/^$IPV4RE$/) {
+           return ($addr, 4);
+       } elsif ($addr =~ m/^$IPV6RE$/) {
+           return ($addr, 6);
+       }
+
+       return undef;
+    };
+
+    my ($resolved_ip, $ip_version) = $match_ip_and_version->($hostname);
+
+    return ($resolved_ip, $ip_version) if defined($resolved_ip);
+
+    my $resolved_ip4;
+    my $resolved_ip6;
+
+    my @resolved_raw;
+    eval { @resolved_raw = PVE::Tools::getaddrinfo_all($hostname); };
+
+    return undef if ($@ || !@resolved_raw);
+
+    foreach my $socket_info (@resolved_raw) {
+       next if !$socket_info->{addr};
+
+       my ($family, undef, $host) = PVE::Tools::unpack_sockaddr_in46($socket_info->{addr});
+
+       if ($family == AF_INET && !defined($resolved_ip4)) {
+           $resolved_ip4 = inet_ntop(AF_INET, $host);
+       } elsif ($family == AF_INET6 && !defined($resolved_ip6)) {
+           $resolved_ip6 = inet_ntop(AF_INET6, $host);
+       }
+
+       last if defined($resolved_ip4) && defined($resolved_ip6);
+    }
+
+    # corosync_strategy specifies the order in which IP addresses are resolved
+    # by corosync. We need to match that order, to ensure we create firewall
+    # rules for the correct address family.
+    if ($corosync_strategy eq "ipv4") {
+       $resolved_ip = $resolved_ip4;
+    } elsif ($corosync_strategy eq "ipv6") {
+       $resolved_ip = $resolved_ip6;
+    } elsif ($corosync_strategy eq "ipv6-4") {
+       $resolved_ip = $resolved_ip6 // $resolved_ip4;
+    } elsif ($corosync_strategy eq "ipv4-6") {
+       $resolved_ip = $resolved_ip4 // $resolved_ip6;
+    }
+
+    return $match_ip_and_version->($resolved_ip);
+}
+
 1;