Skip to content

Commit

Permalink
applying PR quattor#1647
Browse files Browse the repository at this point in the history
  • Loading branch information
StephaneGerardVUB committed Feb 5, 2024
1 parent 14a5f7d commit e3245cc
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 47 deletions.
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");
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.
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

0 comments on commit e3245cc

Please sign in to comment.