From e3245ccaea386ba1b68fe4c28e9d962abcade716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gerard?= Date: Mon, 5 Feb 2024 16:46:17 +0100 Subject: [PATCH] applying PR #1647 --- .../pan/components/network/core-schema.pan | 4 +- ncm-network/src/main/perl/nmstate.pm | 113 +++++++---- ncm-network/src/test/perl/nmstate_advance.t | 181 ++++++++++++++++++ ncm-network/src/test/perl/nmstate_simple.t | 16 +- .../src/test/resources/boundinterface.pan | 6 + .../src/test/resources/dhcpinterface.pan | 2 + .../src/test/resources/nmstate_advance.pan | 34 ++++ .../src/test/resources/nmstate_simple.pan | 1 + .../src/test/resources/vlaninterface.pan | 7 + 9 files changed, 317 insertions(+), 47 deletions(-) create mode 100644 ncm-network/src/test/perl/nmstate_advance.t create mode 100644 ncm-network/src/test/resources/boundinterface.pan create mode 100644 ncm-network/src/test/resources/dhcpinterface.pan create mode 100644 ncm-network/src/test/resources/nmstate_advance.pan create mode 100644 ncm-network/src/test/resources/vlaninterface.pan diff --git a/ncm-network/src/main/pan/components/network/core-schema.pan b/ncm-network/src/main/pan/components/network/core-schema.pan index 5ce4940bb2..0839ff6e3a 100644 --- a/ncm-network/src/main/pan/components/network/core-schema.pan +++ b/ncm-network/src/main/pan/components/network/core-schema.pan @@ -80,9 +80,9 @@ type structure_rule = { @{rule add options to use (cannot be combined with other options)} "command" ? string with !match(SELF, '[;]') } with { + module = value('/software/components/network/ncm-module', ''); if (exists(SELF['command'])) { - module = value('/software/components/network/ncm-module', ''); - if (module == 'nmstate') error("Command routes are not supported by the nmstate backend"); + if (module == 'nmstate') error("Command rule are not supported by the nmstate backend"); if (length(SELF) != 1) error("Cannot use command and any of the other attributes as rule"); } else { if (!exists(SELF['to']) && !exists(SELF['from'])) { diff --git a/ncm-network/src/main/perl/nmstate.pm b/ncm-network/src/main/perl/nmstate.pm index d2be9ef2b2..0bcbd3379a 100644 --- a/ncm-network/src/main/perl/nmstate.pm +++ b/ncm-network/src/main/perl/nmstate.pm @@ -107,6 +107,7 @@ sub make_nm_ip_rule my ($self, $device, $rules, $routing_table_hash) = @_; my @rule_entry; + my %rule_entry_absent; foreach my $rule (@$rules) { if ($rule->{command}){ $self->warn("Rule command entry not supported with nmstate, ignoring '$rule->{command}'"); @@ -121,7 +122,14 @@ sub make_nm_ip_rule $thisrule{'ip-to'} = $rule->{to} if $rule->{to}; $thisrule{'ip-from'} = $rule->{from} if $rule->{from}; push (@rule_entry, \%thisrule); + + # Add a default absent rule to match table defined. This will clear any existing rules for this table, instead of merging. + if ($rule->{table}) { + $rule_entry_absent{'state'} = "absent"; + $rule_entry_absent{'route-table'} = $routing_table_hash->{$rule->{table}}; + }; } + push (@rule_entry, \%rule_entry_absent) if %rule_entry_absent; return \@rule_entry; } @@ -159,6 +167,22 @@ sub make_nm_ip_route return \@rt_entry; } +# create an absent route entry. +# if you prepend the routes with the 'absent', then nmstate will clear the existing matches and apply the routes +# This will allow nmstate to clear all routes for the interface and only apply routes defined in config. +# useful when routes are changed later on in profile once host is built. +# return arrayref +sub make_nm_route_absent { + my ($self, $device) = @_; + + my @rt_entry; + my %rt; + $rt{'state'} = "absent"; + $rt{'next-hop-interface'} = $device; + push (@rt_entry, \%rt); + return \@rt_entry; +} + # group all eth bound to a bond together in a hashref for to be used as # - port in nmstate config file sub get_bonded_eth @@ -281,11 +305,11 @@ sub generate_nmstate_config my $iface = $net->{interfaces}->{$name}; my $device = $iface->{device} || $name; - my $is_eth = $iface->{set_hwaddr}; - my $eth_bootproto = $iface->{bootproto}; + my $is_eth = $iface->{hwaddr} ? 1 : 0; + my $eth_bootproto = $iface->{bootproto} || 'static'; my $is_ip = exists $iface->{ip} ? 1 : 0; my $is_vlan_eth = exists $iface->{vlan} ? 1 : 0; - my $is_bond_eth = exists $iface->{master} ? 1 : 0; + my $is_partof_bond = exists $iface->{master} ? 1 : 0; my $iface_changed = 0; # create hash of interface entries that will be used by nmstate config. @@ -294,9 +318,13 @@ sub generate_nmstate_config $ifaceconfig->{mtu} = $iface->{mtu} if $iface->{mtu}; $ifaceconfig->{'mac-address'} = $iface->{hwaddr} if $iface->{hwaddr}; $ifaceconfig->{'profile-name'} = $name; + + # this will be empty if the interface isnt a bond interface. + # we can use this to determine if this interface is bond interface. + my $bonded_eth = get_bonded_eth($self, $name, $net->{interfaces}); if ($is_eth) { $ifaceconfig->{type} = "ethernet"; - if ($is_bond_eth) { + if ($is_partof_bond) { # no ipv4 address for bonded eth, plus in nmstate bonded eth is controlled by controller. no config is required. $ifaceconfig->{ipv4}->{enabled} = "false"; $ifaceconfig->{state} = "up"; @@ -309,51 +337,52 @@ sub generate_nmstate_config $ifaceconfig->{type} = "vlan"; $ifaceconfig->{vlan}->{'base-iface'} = $iface->{physdev}; $ifaceconfig->{vlan}->{'id'} = $vlan_id; - } else { + } elsif ($bonded_eth) { # if bond device $ifaceconfig->{type} = "bond"; $ifaceconfig->{'link-aggregation'} = $iface->{link_aggregation}; - my $bonded_eth = get_bonded_eth($self, $name, $net->{interfaces}); if ($bonded_eth){ $ifaceconfig->{'link-aggregation'}->{port} = $bonded_eth; } } - if (defined($eth_bootproto)) { - if ($eth_bootproto eq 'static') { - $ifaceconfig->{state} = "up"; - if ($is_ip) { - # if device has manual ip assigned - my $ip_list = {}; - if ($iface->{netmask}) { - my $ip = NetAddr::IP->new($iface->{ip}."/".$iface->{netmask}); - $ip_list->{ip} = $ip->addr; - $ip_list->{'prefix-length'} = $ip->masklen; - } else { - $self->error("$name with (IPv4) ip and no netmask configured"); - } - - # TODO: append alias ip to ip_list as array, providing ips as array of hashref. - $ifaceconfig->{ipv4}->{address} = [$ip_list]; - $ifaceconfig->{ipv4}->{dhcp} = $YFALSE; - $ifaceconfig->{ipv4}->{enabled} = $YTRUE; + if ($eth_bootproto eq 'static') { + $ifaceconfig->{state} = "up"; + if ($is_ip) { + # if device has manual ip assigned + my $ip_list = {}; + if ($iface->{netmask}) { + my $ip = NetAddr::IP->new($iface->{ip}."/".$iface->{netmask}); + $ip_list->{ip} = $ip->addr; + $ip_list->{'prefix-length'} = $ip->masklen; } else { - # TODO: configure IPV6 enteries - if ($iface->{ipv6addr}) { - $self->warn("ipv6 addr found but not supported"); - $ifaceconfig->{ipv6}->{enabled} = $YFALSE; - # TODO create ipv6.address entries here. i.e - #$ifaceconfig->{ipv6}->{address} = [$ipv6_list]; - } else { - $self->verbose("no ipv6 entries"); - } + $self->error("$name with (IPv4) ip and no netmask configured"); } - } elsif (($eth_bootproto eq "none") && (!$is_bond_eth)) { - # no ip on interface and is not a bond eth, assume not managed so disable eth. - $ifaceconfig->{ipv4}->{enabled} = "false"; - $ifaceconfig->{ipv6}->{enabled} = "false"; - $ifaceconfig->{state} = "down"; + + # TODO: append alias ip to ip_list as array, providing ips as array of hashref. + $ifaceconfig->{ipv4}->{address} = [$ip_list]; + $ifaceconfig->{ipv4}->{dhcp} = $YFALSE; + $ifaceconfig->{ipv4}->{enabled} = $YTRUE; + } elsif ($iface->{ipv6addr}) { + $self->warn("ipv6 addr found but not supported"); + $ifaceconfig->{ipv6}->{enabled} = $YFALSE; + # TODO create ipv6.address entries here. i.e + #$ifaceconfig->{ipv6}->{address} = [$ipv6_list]; + } else { + $self->error("No ip address defined for static bootproto"); } + } elsif (($eth_bootproto eq "dhcp") && (!$is_partof_bond)) { + # dhcp configuration + $ifaceconfig->{state} = "up"; + $ifaceconfig->{ipv4}->{dhcp} = $YTRUE; + $ifaceconfig->{ipv4}->{enabled} = $YTRUE; + } elsif (($eth_bootproto eq "none") && (!$is_partof_bond)) { + # no ip on interface and is not a part of a bonded interface, assume not managed so disable eth. + $ifaceconfig->{ipv4}->{enabled} = "false"; + $ifaceconfig->{ipv6}->{enabled} = "false"; + $ifaceconfig->{state} = "down"; + } elsif ($eth_bootproto eq "bootp"){ + $self->error("bootp bootproto not supported by nmstate"); } # create default route entry. @@ -377,13 +406,13 @@ sub generate_nmstate_config # next-hop-interface: # and so on. my $routes = []; + push @$routes, @{$self->make_nm_route_absent($name)}; + push @$routes, \%default_rt if scalar %default_rt; if (defined($iface->{route})) { $self->verbose("policy route found, nmstate will manage it"); my $route = $iface->{route}; - $routes = $self->make_nm_ip_route($name, $route, $routing_table); - push @$routes, \%default_rt if scalar %default_rt; - } elsif (scalar %default_rt){ - push @$routes, \%default_rt if scalar %default_rt; + my $policyroutes = $self->make_nm_ip_route($name, $route, $routing_table); + push @$routes, @{$policyroutes}; } my $policy_rule = []; diff --git a/ncm-network/src/test/perl/nmstate_advance.t b/ncm-network/src/test/perl/nmstate_advance.t new file mode 100644 index 0000000000..5705775d28 --- /dev/null +++ b/ncm-network/src/test/perl/nmstate_advance.t @@ -0,0 +1,181 @@ +use strict; +use warnings; + +BEGIN { + *CORE::GLOBAL::sleep = sub {}; +} + +use Test::More; +use Test::Quattor qw(nmstate_advance); +use Test::MockModule; +use Readonly; + +use NCM::Component::nmstate; +my $mock = Test::MockModule->new('NCM::Component::nmstate'); +my %executables; +$mock->mock('_is_executable', sub {diag "executables $_[1] ",explain \%executables;return $executables{$_[1]};}); + +my $cfg = get_config_for_profile('nmstate_advance'); +my $cmp = NCM::Component::nmstate->new('network'); + +Readonly my $ETH0_YML => < < < < < <Configure($cfg), 1, "Component runs correctly with a test profile"); + +is(get_file_contents("/etc/iproute2/rt_tables"), $RT_NEW, "Exact routing table"); + +my $eth0yml = get_file_contents("/etc/nmstate/eth0.yml"); +is($eth0yml, $ETH0_YML, "Exact eth0 route yml config"); + +my $dhcpyml = get_file_contents("/etc/nmstate/eth1.yml"); +is($dhcpyml, $DHCP_YML, "Exact eth1 dhcp yml config"); + +my $bondyml = get_file_contents("/etc/nmstate/bond0.yml"); +is($bondyml, $BOND_YML, "Exact bond0 yml config"); + +my $vlanyml = get_file_contents("/etc/nmstate/eth0.123.yml"); +is($vlanyml, $VLAN_YML, "Exact eth0.123 vlan yml config"); + +done_testing(); diff --git a/ncm-network/src/test/perl/nmstate_simple.t b/ncm-network/src/test/perl/nmstate_simple.t index 6b62c9a0dc..e313938c4c 100644 --- a/ncm-network/src/test/perl/nmstate_simple.t +++ b/ncm-network/src/test/perl/nmstate_simple.t @@ -46,11 +46,21 @@ Readonly my $ETH0_YML => < <