Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ncm-network: nmstate enhancements #1647

Merged
merged 3 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ncm-network/src/main/pan/components/network/core-schema.pan
Original file line number Diff line number Diff line change
Expand Up @@ -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");
jrha marked this conversation as resolved.
Show resolved Hide resolved
if (length(SELF) != 1) error("Cannot use command and any of the other attributes as rule");
} else {
if (!exists(SELF['to']) && !exists(SELF['from'])) {
Expand Down
113 changes: 71 additions & 42 deletions ncm-network/src/main/perl/nmstate.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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}'");
Expand All @@ -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.
jrha marked this conversation as resolved.
Show resolved Hide resolved
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;
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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";
Expand All @@ -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.
Expand All @@ -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 = [];
Expand Down
181 changes: 181 additions & 0 deletions ncm-network/src/test/perl/nmstate_advance.t
Original file line number Diff line number Diff line change
@@ -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 => <<EOF;
# File generated by NCM::Component::nmstate. Do not edit
---
interfaces:
- ipv4:
address:
- ip: 4.3.2.1
prefix-length: 24
dhcp: false
enabled: true
mac-address: 6e:a5:1b:55:77:0a
name: eth0
profile-name: eth0
state: up
type: ethernet
route-rules:
config:
- family: ipv4
ip-to: 1.2.3.4/24
priority: 100
route-table: '4'
- route-table: '4'
state: absent
routes:
config:
- next-hop-interface: eth0
state: absent
- destination: 1.2.3.4/32
next-hop-interface: eth0
- destination: 1.2.3.5/24
next-hop-interface: eth0
- destination: 1.2.3.6/8
next-hop-address: 4.3.2.1
next-hop-interface: eth0
- destination: 1.2.3.7/32
next-hop-address: 4.3.2.2
next-hop-interface: eth0
- destination: 0.0.0.0/0
next-hop-address: 4.3.2.3
next-hop-interface: eth0
table-id: '3'
EOF

Readonly my $VLAN_YML => <<EOF;
# File generated by NCM::Component::nmstate. Do not edit
---
interfaces:
- ipv4:
address:
- ip: 4.3.2.1
prefix-length: 24
dhcp: false
enabled: true
name: eth0.123
profile-name: eth0.123
state: up
type: vlan
vlan:
base-iface: eth0
id: '123'
routes:
config:
- next-hop-interface: eth0.123
state: absent
- destination: 1.2.3.4/32
next-hop-interface: eth0.123
EOF

Readonly my $DHCP_YML => <<EOF;
# File generated by NCM::Component::nmstate. Do not edit
---
interfaces:
- ipv4:
dhcp: true
enabled: true
mac-address: 6e:a5:1b:55:77:0b
name: eth1
profile-name: eth1
state: up
type: ethernet
routes:
config:
- next-hop-interface: eth1
state: absent
EOF

Readonly my $BOND_YML => <<EOF;
# File generated by NCM::Component::nmstate. Do not edit
---
interfaces:
- ipv4:
address:
- ip: 4.3.2.1
prefix-length: 24
dhcp: false
enabled: true
link-aggregation:
port:
- eth2
- eth3
name: bond0
profile-name: bond0
state: up
type: bond
routes:
config:
- next-hop-interface: bond0
state: absent
EOF


Readonly my $RT => <<EOF;
#
# reserved values
#
255 local
254 main
253 default
0 unspec
#
# local
#
#1 inr.ruhep
EOF

Readonly my $RT_NEW => <<EOF;
#
# reserved values
#
255 local
254 main
253 default
0 unspec
#
# local
#
#1 inr.ruhep
3 outside # managed by Quattor
4 space # managed by Quattor
EOF

# File must exist, set with correct content
set_file_contents("/etc/iproute2/rt_tables", $RT);

is($cmp->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();
16 changes: 13 additions & 3 deletions ncm-network/src/test/perl/nmstate_simple.t
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,21 @@ Readonly my $ETH0_YML => <<EOF;
# File generated by NCM::Component::nmstate. Do not edit
---
interfaces:
- link-aggregation:
port: []
- ipv4:
address:
- ip: 4.3.2.1
prefix-length: 24
dhcp: false
enabled: true
mac-address: 6e:a5:1b:55:77:0a
name: eth0
profile-name: eth0
type: bond
state: up
type: ethernet
routes:
config:
- next-hop-interface: eth0
state: absent
EOF

Readonly my $NOTTOREMOVE => <<EOF;
Expand Down
Loading
Loading