X-Git-Url: https://git.proxmox.com/?p=pve-common.git;a=blobdiff_plain;f=data%2FPVE%2FINotify.pm;h=30290ca38b7b158c9e8d2fe234aa439ab2667381;hp=110e0341b1c7cf106e31eb125f9ee31021b6fb52;hb=a68760b6b7843c9abd60d2a4d48d909d684060c8;hpb=ed4e6e0c5105791111f4c76a0ac5bfd6d63087c7 diff --git a/data/PVE/INotify.pm b/data/PVE/INotify.pm index 110e034..30290ca 100644 --- a/data/PVE/INotify.pm +++ b/data/PVE/INotify.pm @@ -3,6 +3,8 @@ package PVE::INotify; # todo: maybe we do not need update_file() ? use strict; +use warnings; + use POSIX; use IO::File; use IO::Dir; @@ -672,10 +674,77 @@ my $bond_modes = { 'balance-rr' => 0, 'balance-alb' => 6, }; +my $ovs_bond_modes = { + 'active-backup' => 1, + 'balance-slb' => 1, + 'lacp-balance-slb' => 1, + 'lacp-balance-tcp' => 1, +}; + #sub get_bond_modes { # return $bond_modes; #} +my $parse_ovs_option = sub { + my ($data) = @_; + + my $opts = {}; + foreach my $kv (split (/\s+/, $data || '')) { + my ($k, $v) = split('=', $kv, 2); + $opts->{$k} = $v if $k && $v; + } + return $opts; +}; + +my $set_ovs_option = sub { + my ($d, %params) = @_; + + my $opts = &$parse_ovs_option($d->{ovs_options}); + + foreach my $k (keys %params) { + my $v = $params{$k}; + if ($v) { + $opts->{$k} = $v; + } else { + delete $opts->{$k}; + } + } + + my $res = []; + foreach my $k (keys %$opts) { + push @$res, "$k=$opts->{$k}"; + } + + if (my $new = join(' ', @$res)) { + $d->{ovs_options} = $new; + return $d->{ovs_options}; + } else { + delete $d->{ovs_options}; + return undef; + } +}; + +my $extract_ovs_option = sub { + my ($d, $name) = @_; + + my $opts = &$parse_ovs_option($d->{ovs_options}); + + my $v = delete $opts->{$name}; + + my $res = []; + foreach my $k (keys %$opts) { + push @$res, "$k=$opts->{$k}"; + } + + if (my $new = join(' ', @$res)) { + $d->{ovs_options} = $new; + } else { + delete $d->{ovs_options}; + } + + return $v; +}; + sub read_etc_network_interfaces { my ($filename, $fh) = @_; @@ -685,7 +754,7 @@ sub read_etc_network_interfaces { if (my $fd2 = IO::File->new("/proc/net/dev", "r")) { while (defined ($line = <$fd2>)) { - if ($line =~ m/^\s*(eth[0-9]):.*/) { + if ($line =~ m/^\s*(eth\d+):.*/) { $ifaces->{$1}->{exists} = 1; } } @@ -698,15 +767,6 @@ sub read_etc_network_interfaces { # always add the vmbr0 bridge device $ifaces->{vmbr0}->{exists} = 1; - if (my $fd2 = IO::File->new("/proc/net/if_inet6", "r")) { - while (defined ($line = <$fd2>)) { - if ($line =~ m/^[a-f0-9]{32}\s+[a-f0-9]{2}\s+[a-f0-9]{2}\s+[a-f0-9]{2}\s+[a-f0-9]{2}\s+(lo|eth\d+|vmbr\d+|bond\d+)$/) { - $ifaces->{$1}->{active} = 1; - } - } - close ($fd2); - } - my $gateway = 0; while (defined ($line = <$fh>)) { @@ -740,6 +800,9 @@ sub read_etc_network_interfaces { } elsif ($id eq 'gateway') { $d->{$id} = $value; $gateway = 1; + } elsif ($id eq 'ovs_type' || $id eq 'ovs_options'|| $id eq 'ovs_bridge' || + $id eq 'ovs_bonds' || $id eq 'ovs_ports') { + $d->{$id} = $value; } elsif ($id eq 'slaves' || $id eq 'bridge_ports') { my $devs = {}; foreach my $p (split (/\s+/, $value)) { @@ -793,14 +856,39 @@ sub read_etc_network_interfaces { foreach my $iface (keys %$ifaces) { my $d = $ifaces->{$iface}; if ($iface =~ m/^bond\d+$/) { - $d->{type} = 'bond'; - } elsif ($iface =~ m/^vmbr\d+$/) { - $d->{type} = 'bridge'; - if (!defined ($d->{bridge_fd})) { - $d->{bridge_fd} = 0; + if (!$d->{ovs_type}) { + $d->{type} = 'bond'; + } elsif ($d->{ovs_type} eq 'OVSBond') { + $d->{type} = $d->{ovs_type}; + # translate: ovs_options => bond_mode + $d->{'bond_mode'} = &$extract_ovs_option($d, 'bond_mode'); + my $lacp = &$extract_ovs_option($d, 'lacp'); + if ($lacp && $lacp eq 'active') { + if ($d->{'bond_mode'} eq 'balance-slb') { + $d->{'bond_mode'} = 'lacp-balance-slb'; + } + } + # Note: balance-tcp needs lacp + if ($d->{'bond_mode'} eq 'balance-tcp') { + $d->{'bond_mode'} = 'lacp-balance-tcp'; + } + } else { + $d->{type} = 'unknown'; } - if (!defined ($d->{bridge_stp})) { - $d->{bridge_stp} = 'off'; + } elsif ($iface =~ m/^vmbr\d+$/) { + if (!$d->{ovs_type}) { + $d->{type} = 'bridge'; + + if (!defined ($d->{bridge_fd})) { + $d->{bridge_fd} = 0; + } + if (!defined ($d->{bridge_stp})) { + $d->{bridge_stp} = 'off'; + } + } elsif ($d->{ovs_type} eq 'OVSBridge') { + $d->{type} = $d->{ovs_type}; + } else { + $d->{type} = 'unknown'; } } elsif ($iface =~ m/^(\S+):\d+$/) { $d->{type} = 'alias'; @@ -810,17 +898,36 @@ sub read_etc_network_interfaces { $ifaces->{$1}->{exists} = 0; $d->{exists} = 0; } - } elsif ($iface =~ m/^eth[0-9]$/) { - $d->{type} = 'eth'; + } elsif ($iface =~ m/^eth\d+$/) { + if (!$d->{ovs_type}) { + $d->{type} = 'eth'; + } elsif ($d->{ovs_type} eq 'OVSPort') { + $d->{type} = $d->{ovs_type}; + } else { + $d->{type} = 'unknown'; + } } elsif ($iface =~ m/^lo$/) { $d->{type} = 'loopback'; } else { - $d->{type} = 'unknown'; + if (!$d->{ovs_type}) { + $d->{type} = 'unknown'; + } elsif ($d->{ovs_type} eq 'OVSIntPort') { + $d->{type} = $d->{ovs_type}; + } } $d->{method} = 'manual' if !$d->{method}; } + if (my $fd2 = IO::File->new("/proc/net/if_inet6", "r")) { + while (defined ($line = <$fd2>)) { + if ($line =~ m/^[a-f0-9]{32}\s+[a-f0-9]{2}\s+[a-f0-9]{2}\s+[a-f0-9]{2}\s+[a-f0-9]{2}\s+(\S+)$/) { + $ifaces->{$1}->{active} = 1 if defined($ifaces->{$1}); + } + } + close ($fd2); + } + return $ifaces; } @@ -831,47 +938,99 @@ sub __interface_to_string { my $raw = ''; - if ($d->{autostart}) { - $raw .= "auto $iface\n"; - } $raw .= "iface $iface inet $d->{method}\n"; $raw .= "\taddress $d->{address}\n" if $d->{address}; $raw .= "\tnetmask $d->{netmask}\n" if $d->{netmask}; $raw .= "\tgateway $d->{gateway}\n" if $d->{gateway}; $raw .= "\tbroadcast $d->{broadcast}\n" if $d->{broadcast}; - if ($d->{bridge_ports} || ($iface =~ m/^vmbr\d+$/)) { + my $done = { type => 1, priority => 1, method => 1, active => 1, exists => 1, + comments => 1, autostart => 1, options => 1, + address => 1, netmask => 1, gateway => 1, broadcast => 1 }; + + if ($d->{type} eq 'bridge') { + my $ports = $d->{bridge_ports} || 'none'; $raw .= "\tbridge_ports $ports\n"; - } + $done->{bridge_ports} = 1; - if ($d->{bridge_stp} || ($iface =~ m/^vmbr\d+$/)) { - my $v = $d->{bridge_stp}; - $v = defined ($v) ? $v : 'off'; + my $v = defined($d->{bridge_stp}) ? $d->{bridge_stp} : 'off'; $raw .= "\tbridge_stp $v\n"; - } + $done->{bridge_stp} = 1; - if (defined ($d->{bridge_fd}) || ($iface =~ m/^vmbr\d+$/)) { - my $v = $d->{bridge_fd}; - $v = defined ($v) ? $v : 0; + $v = defined($d->{bridge_fd}) ? $d->{bridge_fd} : 0; $raw .= "\tbridge_fd $v\n"; - } + $done->{bridge_fd} = 1; + + } elsif ($d->{type} eq 'bond') { - if ($d->{slaves} || ($iface =~ m/^bond\d+$/)) { my $slaves = $d->{slaves} || 'none'; $raw .= "\tslaves $slaves\n"; - } + $done->{slaves} = 1; - if (defined ($d->{'bond_miimon'}) || ($iface =~ m/^bond\d+$/)) { - my $v = $d->{'bond_miimon'}; - $v = defined ($v) ? $v : 100; + my $v = defined ($d->{'bond_miimon'}) ? $d->{'bond_miimon'} : 100; $raw .= "\tbond_miimon $v\n"; - } + $done->{'bond_miimon'} = 1; - if (defined ($d->{'bond_mode'}) || ($iface =~ m/^bond\d+$/)) { - my $v = $d->{'bond_mode'}; - $v = defined ($v) ? $v : 'balance-rr'; + $v = defined ($d->{'bond_mode'}) ? $d->{'bond_mode'} : 'balance-rr'; $raw .= "\tbond_mode $v\n"; + $done->{'bond_mode'} = 1; + + } elsif ($d->{type} eq 'OVSBridge') { + + $raw .= "\tovs_type $d->{type}\n"; + $done->{ovs_type} = 1; + + $raw .= "\tovs_ports $d->{ovs_ports}\n" if $d->{ovs_ports}; + $done->{ovs_ports} = 1; + + } elsif ($d->{type} eq 'OVSPort' || $d->{type} eq 'OVSIntPort' || + $d->{type} eq 'OVSBond') { + + $d->{autostart} = 0; # started by the bridge + + if ($d->{type} eq 'OVSBond') { + + $d->{bond_mode} = 'active-backup' if !$d->{bond_mode}; + + $ovs_bond_modes->{$d->{bond_mode}} || + die "OVS does not support bond mode '$d->{bond_mode}\n"; + + if ($d->{bond_mode} eq 'lacp-balance-slb') { + &$set_ovs_option($d, lacp => 'active'); + &$set_ovs_option($d, bond_mode => 'balance-slb'); + } elsif ($d->{bond_mode} eq 'lacp-balance-tcp') { + &$set_ovs_option($d, lacp => 'active'); + &$set_ovs_option($d, bond_mode => 'balance-tcp'); + } else { + &$set_ovs_option($d, lacp => undef); + &$set_ovs_option($d, bond_mode => $d->{bond_mode}); + } + $done->{bond_mode} = 1; + + $raw .= "\tovs_bonds $d->{ovs_bonds}\n" if $d->{ovs_bonds}; + $done->{ovs_bonds} = 1; + } + + if ($d->{ovs_bridge}) { + $raw = "allow-$d->{ovs_bridge} $iface\n$raw"; + } + + $raw .= "\tovs_type $d->{type}\n"; + $done->{ovs_type} = 1; + + if ($d->{ovs_bridge}) { + $raw .= "\tovs_bridge $d->{ovs_bridge}\n"; + $done->{ovs_bridge} = 1; + } + # fixme: use Data::Dumper; print Dumper($d); + } + + # print other settings + foreach my $k (keys %$d) { + next if $done->{$k}; + next if !$d->{$k}; + $raw .= "\t$k $d->{$k}\n"; } foreach my $option (@{$d->{options}}) { @@ -884,6 +1043,10 @@ sub __interface_to_string { $raw .= "#$cl\n"; } + if ($d->{autostart}) { + $raw = "auto $iface\n$raw"; + } + $raw .= "\n"; return $raw; @@ -892,16 +1055,87 @@ sub __interface_to_string { sub write_etc_network_interfaces { my ($filename, $fh, $ifaces) = @_; + my $used_ports = {}; + + foreach my $iface (keys %$ifaces) { + my $d = $ifaces->{$iface}; + + my $ports = ''; + foreach my $k (qw(bridge_ports ovs_ports slaves ovs_bonds)) { + $ports .= " $d->{$k}" if $d->{$k}; + } + + foreach my $p (PVE::Tools::split_list($ports)) { + die "port '$p' is already used on interface '$used_ports->{$p}'\n" + if $used_ports->{$p} && $used_ports->{$p} ne $iface; + $used_ports->{$p} = $iface; + } + } + + # delete unused OVS ports + foreach my $iface (keys %$ifaces) { + my $d = $ifaces->{$iface}; + if ($d->{type} eq 'OVSPort' || $d->{type} eq 'OVSIntPort' || + $d->{type} eq 'OVSBond') { + my $brname = $used_ports->{$iface}; + if (!$brname || !$ifaces->{$brname}) { + delete $ifaces->{$iface}; + next; + } + my $bd = $ifaces->{$brname}; + if ($bd->{type} ne 'OVSBridge') { + delete $ifaces->{$iface}; + next; + } + } + } + + # create OVS bridge ports + foreach my $iface (keys %$ifaces) { + my $d = $ifaces->{$iface}; + if ($d->{type} eq 'OVSBridge' && $d->{ovs_ports}) { + foreach my $p (split (/\s+/, $d->{ovs_ports})) { + my $n = $ifaces->{$p}; + die "OVS bridge '$iface' - unable to find port '$p'\n" + if !$n; + if ($n->{type} eq 'eth') { + $n->{type} = 'OVSPort'; + $n->{ovs_bridge} = $iface; + } elsif ($n->{type} eq 'OVSBond' || $n->{type} eq 'OVSPort' || + $n->{type} eq 'OVSIntPort') { + $n->{ovs_bridge} = $iface; + } else { + die "interface '$p' is not defined as OVS port/bond\n"; + } + } + } + } + + # check OVS bond ports + foreach my $iface (keys %$ifaces) { + my $d = $ifaces->{$iface}; + if ($d->{type} eq 'OVSBond' && $d->{ovs_bonds}) { + foreach my $p (split (/\s+/, $d->{ovs_bonds})) { + my $n = $ifaces->{$p}; + die "OVS bond '$iface' - unable to find slave '$p'\n" + if !$n; + die "OVS bond '$iface' - wrong interface type on slave '$p' " . + "('$n->{type}' != 'eth')\n" if $n->{type} ne 'eth'; + } + } + } + my $raw = "# network interface settings\n"; my $printed = {}; my $if_type_hash = { + unknown => 0, loopback => 10, eth => 20, bond => 30, bridge => 40, - }; + }; my $lookup_type_prio = sub { my $iface = shift; @@ -957,4 +1191,90 @@ register_file('interfaces', "/etc/network/interfaces", \&read_etc_network_interfaces, \&write_etc_network_interfaces); + +sub read_iscsi_initiatorname { + my ($filename, $fd) = @_; + + while (defined(my $line = <$fd>)) { + if ($line =~ m/^InitiatorName=(\S+)$/) { + return $1; + } + } + + return 'undefined'; +} + +register_file('initiatorname', "/etc/iscsi/initiatorname.iscsi", + \&read_iscsi_initiatorname); + +sub read_apt_auth { + my ($filename, $fd) = @_; + + local $/; + + my $raw = defined($fd) ? <$fd> : ''; + + $raw =~ s/^\s+//; + + + my @tokens = split(/\s+/, $raw); + + my $data = {}; + + my $machine; + while (defined(my $tok = shift @tokens)) { + + $machine = shift @tokens if $tok eq 'machine'; + next if !$machine; + $data->{$machine} = {} if !$data->{$machine}; + + $data->{$machine}->{login} = shift @tokens if $tok eq 'login'; + $data->{$machine}->{password} = shift @tokens if $tok eq 'password'; + }; + + return $data; +} + +my $format_apt_auth_data = sub { + my $data = shift; + + my $raw = ''; + + foreach my $machine (sort keys %$data) { + my $d = $data->{$machine}; + $raw .= "machine $machine\n"; + $raw .= " login $d->{login}\n" if $d->{login}; + $raw .= " password $d->{password}\n" if $d->{password}; + $raw .= "\n"; + } + + return $raw; +}; + +sub write_apt_auth { + my ($filename, $fh, $data) = @_; + + my $raw = &$format_apt_auth_data($data); + + die "write failed: $!" unless print $fh "$raw\n"; + + return $data; +} + +sub update_apt_auth { + my ($filename, $fh, $data) = @_; + + my $orig = read_apt_auth($filename, $fh); + + foreach my $machine (keys %$data) { + $orig->{$machine} = $data->{$machine}; + } + + return &$format_apt_auth_data($orig); +} + +register_file('apt-auth', "/etc/apt/auth.conf", + \&read_apt_auth, \&write_apt_auth, + \&update_apt_auth, perm => 0640); + 1;