blob: c654543837ff83c39d4ccd315bc76c15b36d2afa [file] [log] [blame]
Tomáš Peckaf10b9302021-02-23 19:02:02 +01001/*
2 * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
5 *
6*/
7
8#include "trompeloeil_doctest.h"
Tomáš Pecka39bbb512021-05-23 22:07:22 +02009#include <filesystem>
Václav Kubernát7efd6d52021-11-09 01:31:11 +010010#include <sysrepo-cpp/utils/exception.hpp>
Tomáš Peckaf10b9302021-02-23 19:02:02 +010011#include "pretty_printers.h"
12#include "system/IETFInterfaces.h"
Tomáš Pecka39bbb512021-05-23 22:07:22 +020013#include "system/IETFInterfacesConfig.h"
Tomáš Peckaf10b9302021-02-23 19:02:02 +010014#include "test_log_setup.h"
Tomáš Pecka39bbb512021-05-23 22:07:22 +020015#include "tests/configure.cmake.h"
Tomáš Peckaf10b9302021-02-23 19:02:02 +010016#include "tests/mock/system.h"
Tomáš Peckac164ca62024-01-24 13:38:03 +010017#include "tests/sysrepo-helpers/common.h"
Tomáš Pecka39bbb512021-05-23 22:07:22 +020018#include "utils/io.h"
Tomáš Peckaf10b9302021-02-23 19:02:02 +010019
Tomáš Pecka71a98b02021-05-17 15:59:40 +020020using namespace std::string_literals;
21
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +020022struct FakeNetworkReload {
23public:
24 MAKE_CONST_MOCK1(cb, void(const std::vector<std::string>&));
25};
26
27TEST_CASE("Config data in ietf-interfaces")
28{
29 TEST_SYSREPO_INIT_LOGS;
30 TEST_SYSREPO_INIT;
31 TEST_SYSREPO_INIT_CLIENT;
32 trompeloeil::sequence seq1;
33
Tomáš Pecka9cb218c2022-06-13 11:48:20 +020034 sysrepo::Connection{}.sessionStart(sysrepo::Datastore::Running).copyConfig(sysrepo::Datastore::Startup, "ietf-interfaces");
35
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +020036 auto fake = FakeNetworkReload();
37
38 const auto fakeConfigDir = std::filesystem::path(CMAKE_CURRENT_BINARY_DIR) / "tests/network/"s;
39 std::filesystem::remove_all(fakeConfigDir);
40 std::filesystem::create_directories(fakeConfigDir);
41
Jan Kundrát80113ed2022-04-27 18:10:52 +020042 REQUIRE_CALL(fake, cb(std::vector<std::string>{})).IN_SEQUENCE(seq1);
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +020043 auto network = std::make_shared<velia::system::IETFInterfacesConfig>(srSess, fakeConfigDir, std::vector<std::string>{"br0", "eth0", "eth1"}, [&fake](const std::vector<std::string>& updatedInterfaces) { fake.cb(updatedInterfaces); });
Tomáš Pecka71a98b02021-05-17 15:59:40 +020044
45 SECTION("Link changes disabled")
46 {
Tomáš Pecka71a98b02021-05-17 15:59:40 +020047 SECTION("Only specified link names can appear in configurable datastore")
48 {
49 for (const auto& [name, type] : {std::pair<const char*, const char*>{"eth0", "iana-if-type:ethernetCsmacd"},
50 {"eth1", "iana-if-type:ethernetCsmacd"},
Tomáš Peckac8523852023-08-30 16:59:32 +020051 {"eth2", "iana-if-type:ethernetCsmacd"},
Tomáš Pecka71a98b02021-05-17 15:59:40 +020052 {"br0", "iana-if-type:bridge"},
53 {"osc", "iana-if-type:ethernetCsmacd"},
54 {"oscW", "iana-if-type:ethernetCsmacd"},
55 {"oscE", "iana-if-type:ethernetCsmacd"}}) {
Jan Kundrátb3e99982022-03-18 17:38:20 +010056 client.setItem("/ietf-interfaces:interfaces/interface[name='"s + name + "']/type", type);
57 client.setItem("/ietf-interfaces:interfaces/interface[name='"s + name + "']/enabled", "false");
Tomáš Pecka71a98b02021-05-17 15:59:40 +020058 }
Tomáš Pecka9cb218c2022-06-13 11:48:20 +020059
60 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0", "eth0", "eth1"})).IN_SEQUENCE(seq1); // only these are monitored by the test
Václav Kubernát7efd6d52021-11-09 01:31:11 +010061 client.applyChanges();
Tomáš Pecka71a98b02021-05-17 15:59:40 +020062 }
63
64 SECTION("Invalid type for a valid link")
65 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +010066 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:softwareLoopback");
67 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Pecka71a98b02021-05-17 15:59:40 +020068 }
69
70 SECTION("Invalid name")
71 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +010072 client.setItem("/ietf-interfaces:interfaces/interface[name='blah0']/type", "iana-if-type:ethernetCsmacd");
73 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Pecka71a98b02021-05-17 15:59:40 +020074 }
75 }
Tomáš Peckade0ef2f2021-07-08 13:23:50 +020076
77 SECTION("There must always be enabled protocol or the interface must be explicitely disabled")
78 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +010079 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
Tomáš Peckade0ef2f2021-07-08 13:23:50 +020080
81 SECTION("Disabled protocols; enabled link")
82 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +010083 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
84 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
85 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
86 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckade0ef2f2021-07-08 13:23:50 +020087 }
88
89 SECTION("Active protocols; disabled link")
90 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +010091 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "false");
92 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
93 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
94 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +020095 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +010096 client.applyChanges();
Tomáš Peckade0ef2f2021-07-08 13:23:50 +020097 }
98
99 SECTION("IPv4 only; enabled link")
100 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100101 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
102 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
103 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
104 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +0200105 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100106 client.applyChanges();
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200107 }
108 }
109
110 SECTION("Every active protocol must have at least one IP address assigned")
111 {
Tomáš Pecka9cb218c2022-06-13 11:48:20 +0200112 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100113 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "false");
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +0200114 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1).TIMES(AT_MOST(1));
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100115 client.applyChanges();
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200116
117 SECTION("Enabled IPv4 protocol with some IPs assigned is valid")
118 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100119 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
120 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
121 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
122 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.2']/ietf-ip:prefix-length", "24");
123 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +0200124 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100125 client.applyChanges();
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200126 }
127
128 SECTION("Enabled IPv6 protocol with some IPs assigned is valid")
129 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100130 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
131 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
132 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
133 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +0200134 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100135 client.applyChanges();
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200136 }
137
Tomáš Peckabd43a942021-08-04 10:12:49 +0200138 SECTION("Enabled IPv4 protocol must have at least one IP or the autoconfiguration must be on")
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200139 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100140 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
141 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
142 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
143 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
144 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200145 }
146
Tomáš Peckabd43a942021-08-04 10:12:49 +0200147 SECTION("Enabled IPv6 protocol must have at least one IP or the autoconfiguration must be on")
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200148 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100149 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
150 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
151 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
152 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "false");
153 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200154 }
155 }
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200156
157 std::string expectedContents;
Tomáš Peckabd43a942021-08-04 10:12:49 +0200158
Tomáš Pecka85f77752021-06-03 10:09:58 +0200159 SECTION("Setting IPs to eth0")
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200160 {
Tomáš Pecka85f77752021-06-03 10:09:58 +0200161 const auto expectedFilePath = fakeConfigDir / "eth0.network";
162
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100163 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200164
165 SECTION("Single IPv4 address")
166 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100167 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/description", "Hello world");
168 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
169 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200170 expectedContents = R"([Match]
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200171Name=eth0
172
173[Network]
174Description=Hello world
Tomáš Pecka85f77752021-06-03 10:09:58 +0200175Address=192.0.2.1/24
176LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200177IPv6AcceptRA=false
Tomáš Pecka85f77752021-06-03 10:09:58 +0200178DHCP=no
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200179LLDP=true
180EmitLLDP=nearest-bridge
181)";
Tomáš Pecka85f77752021-06-03 10:09:58 +0200182 }
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200183
Tomáš Pecka85f77752021-06-03 10:09:58 +0200184 SECTION("Two IPv4 addresses")
185 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100186 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
187 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.2']/ietf-ip:prefix-length", "24");
188 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
189 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200190 expectedContents = R"([Match]
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200191Name=eth0
192
193[Network]
Tomáš Pecka85f77752021-06-03 10:09:58 +0200194Address=192.0.2.1/24
195Address=192.0.2.2/24
196LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200197IPv6AcceptRA=false
Tomáš Pecka85f77752021-06-03 10:09:58 +0200198DHCP=no
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200199LLDP=true
200EmitLLDP=nearest-bridge
201)";
Tomáš Pecka85f77752021-06-03 10:09:58 +0200202 }
203
204 SECTION("IPv4 and IPv6 addresses")
205 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100206 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
207 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
208 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200209 expectedContents = R"([Match]
210Name=eth0
211
212[Network]
213Address=192.0.2.1/24
214Address=2001:db8::1/32
Tomáš Peckabd43a942021-08-04 10:12:49 +0200215IPv6AcceptRA=true
Tomáš Pecka85f77752021-06-03 10:09:58 +0200216DHCP=no
217LLDP=true
218EmitLLDP=nearest-bridge
219)";
220 }
221
222 SECTION("IPv4 and IPv6 addresses but IPv6 disabled")
223 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100224 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
225 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
226 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
227 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/enabled", "false");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200228 expectedContents = R"([Match]
229Name=eth0
230
231[Network]
232Address=192.0.2.1/24
233LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200234IPv6AcceptRA=false
Tomáš Pecka85f77752021-06-03 10:09:58 +0200235DHCP=no
236LLDP=true
237EmitLLDP=nearest-bridge
238)";
239 }
240
241 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100242 client.applyChanges();
Tomáš Pecka85f77752021-06-03 10:09:58 +0200243 REQUIRE(std::filesystem::exists(expectedFilePath));
244 REQUIRE(velia::utils::readFileToString(expectedFilePath) == expectedContents);
245
246 // reset the contents
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100247 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200248 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100249 client.applyChanges();
Tomáš Pecka85f77752021-06-03 10:09:58 +0200250 REQUIRE(!std::filesystem::exists(expectedFilePath));
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200251 }
252
Tomáš Peckac2929a62021-06-05 17:21:03 +0200253 SECTION("Two links")
Tomáš Pecka85f77752021-06-03 10:09:58 +0200254 {
255 const auto expectedFilePathEth0 = fakeConfigDir / "eth0.network";
256 const auto expectedFilePathEth1 = fakeConfigDir / "eth1.network";
Tomáš Peckac2929a62021-06-05 17:21:03 +0200257
258 std::string expectedContentsEth0 = R"([Match]
Tomáš Pecka85f77752021-06-03 10:09:58 +0200259Name=eth0
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200260
Tomáš Pecka85f77752021-06-03 10:09:58 +0200261[Network]
262Address=192.0.2.1/24
263LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200264IPv6AcceptRA=false
Tomáš Pecka85f77752021-06-03 10:09:58 +0200265DHCP=no
266LLDP=true
267EmitLLDP=nearest-bridge
268)";
Tomáš Peckac2929a62021-06-05 17:21:03 +0200269 std::string expectedContentsEth1 = R"([Match]
Tomáš Pecka85f77752021-06-03 10:09:58 +0200270Name=eth1
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200271
Tomáš Pecka85f77752021-06-03 10:09:58 +0200272[Network]
273Address=2001:db8::1/32
Tomáš Peckabd43a942021-08-04 10:12:49 +0200274IPv6AcceptRA=true
Tomáš Pecka85f77752021-06-03 10:09:58 +0200275DHCP=no
276LLDP=true
277EmitLLDP=nearest-bridge
278)";
279
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100280 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
281 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
282 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
283 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/type", "iana-if-type:ethernetCsmacd");
284 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200285
286 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0", "eth1"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100287 client.applyChanges();
Tomáš Pecka85f77752021-06-03 10:09:58 +0200288 REQUIRE(std::filesystem::exists(expectedFilePathEth0));
289 REQUIRE(std::filesystem::exists(expectedFilePathEth1));
290 REQUIRE(velia::utils::readFileToString(expectedFilePathEth0) == expectedContentsEth0);
291 REQUIRE(velia::utils::readFileToString(expectedFilePathEth1) == expectedContentsEth1);
292
293 // reset the contents
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100294 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']");
295 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth1']");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200296 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0", "eth1"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100297 client.applyChanges();
Tomáš Pecka85f77752021-06-03 10:09:58 +0200298 REQUIRE(!std::filesystem::exists(expectedFilePathEth0));
299 REQUIRE(!std::filesystem::exists(expectedFilePathEth1));
300 }
Tomáš Peckac2929a62021-06-05 17:21:03 +0200301
302 SECTION("Setup a bridge br0 over eth0 and eth1")
303 {
304 const auto expectedFilePathBr0 = fakeConfigDir / "br0.network";
305 const auto expectedFilePathEth0 = fakeConfigDir / "eth0.network";
306 const auto expectedFilePathEth1 = fakeConfigDir / "eth1.network";
307
308 std::string expectedContentsBr0 = R"([Match]
309Name=br0
310
311[Network]
312LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200313IPv6AcceptRA=false
Tomáš Peckac2929a62021-06-05 17:21:03 +0200314DHCP=no
315LLDP=true
316EmitLLDP=nearest-bridge
317)";
318
319 std::string expectedContentsEth0 = R"([Match]
320Name=eth0
321
322[Network]
323Bridge=br0
Tomáš Peckabd43a942021-08-04 10:12:49 +0200324IPv6AcceptRA=false
Tomáš Peckac2929a62021-06-05 17:21:03 +0200325DHCP=no
326LLDP=true
327EmitLLDP=nearest-bridge
328)";
329
330 std::string expectedContentsEth1 = R"([Match]
331Name=eth1
332
333[Network]
334Bridge=br0
Tomáš Peckabd43a942021-08-04 10:12:49 +0200335IPv6AcceptRA=false
Tomáš Peckac2929a62021-06-05 17:21:03 +0200336DHCP=no
337LLDP=true
338EmitLLDP=nearest-bridge
339)";
340
341 // create br0 bridge over eth0 and eth1 with no IP
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100342 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/enabled", "false");
343 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/type", "iana-if-type:bridge");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200344
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100345 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
346 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/czechlight-network:bridge", "br0");
347 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
348 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200349
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100350 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/type", "iana-if-type:ethernetCsmacd");
351 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/czechlight-network:bridge", "br0");
352 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/ietf-ip:ipv4/ietf-ip:enabled", "false");
353 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth1']/ietf-ip:ipv6");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200354
355 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0", "eth0", "eth1"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100356 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200357 REQUIRE(std::filesystem::exists(expectedFilePathBr0));
358 REQUIRE(std::filesystem::exists(expectedFilePathEth0));
359 REQUIRE(std::filesystem::exists(expectedFilePathEth1));
360 REQUIRE(velia::utils::readFileToString(expectedFilePathBr0) == expectedContentsBr0);
361 REQUIRE(velia::utils::readFileToString(expectedFilePathEth0) == expectedContentsEth0);
362 REQUIRE(velia::utils::readFileToString(expectedFilePathEth1) == expectedContentsEth1);
363
364 // assign an IPv4 address to br0
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100365 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/enabled", "true");
366 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
367 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200368 expectedContentsBr0 = R"([Match]
369Name=br0
370
371[Network]
372Address=192.0.2.1/24
373LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200374IPv6AcceptRA=false
Tomáš Peckac2929a62021-06-05 17:21:03 +0200375DHCP=no
376LLDP=true
377EmitLLDP=nearest-bridge
378)";
379
380 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100381 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200382 REQUIRE(std::filesystem::exists(expectedFilePathBr0));
383 REQUIRE(std::filesystem::exists(expectedFilePathEth0));
384 REQUIRE(std::filesystem::exists(expectedFilePathEth1));
385 REQUIRE(velia::utils::readFileToString(expectedFilePathBr0) == expectedContentsBr0);
386 REQUIRE(velia::utils::readFileToString(expectedFilePathEth0) == expectedContentsEth0);
387 REQUIRE(velia::utils::readFileToString(expectedFilePathEth1) == expectedContentsEth1);
388
389 // assign also an IPv6 address to br0
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100390 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200391 expectedContentsBr0 = R"([Match]
392Name=br0
393
394[Network]
395Address=192.0.2.1/24
396Address=2001:db8::1/32
Tomáš Peckabd43a942021-08-04 10:12:49 +0200397IPv6AcceptRA=true
Tomáš Peckac2929a62021-06-05 17:21:03 +0200398DHCP=no
399LLDP=true
400EmitLLDP=nearest-bridge
401)";
402
403 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100404 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200405 REQUIRE(std::filesystem::exists(expectedFilePathBr0));
406 REQUIRE(std::filesystem::exists(expectedFilePathEth0));
407 REQUIRE(std::filesystem::exists(expectedFilePathEth1));
408 REQUIRE(velia::utils::readFileToString(expectedFilePathBr0) == expectedContentsBr0);
409 REQUIRE(velia::utils::readFileToString(expectedFilePathEth0) == expectedContentsEth0);
410 REQUIRE(velia::utils::readFileToString(expectedFilePathEth1) == expectedContentsEth1);
411
412 // remove eth1 from bridge
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100413 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth1']/czechlight-network:bridge");
414 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::2']/ietf-ip:prefix-length", "32");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200415
416 expectedContentsEth1 = R"([Match]
417Name=eth1
418
419[Network]
420Address=2001:db8::2/32
Tomáš Peckabd43a942021-08-04 10:12:49 +0200421IPv6AcceptRA=true
Tomáš Peckac2929a62021-06-05 17:21:03 +0200422DHCP=no
423LLDP=true
424EmitLLDP=nearest-bridge
425)";
426
427 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth1"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100428 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200429 REQUIRE(std::filesystem::exists(expectedFilePathBr0));
430 REQUIRE(std::filesystem::exists(expectedFilePathEth0));
431 REQUIRE(std::filesystem::exists(expectedFilePathEth1));
432 REQUIRE(velia::utils::readFileToString(expectedFilePathBr0) == expectedContentsBr0);
433 REQUIRE(velia::utils::readFileToString(expectedFilePathEth0) == expectedContentsEth0);
434 REQUIRE(velia::utils::readFileToString(expectedFilePathEth1) == expectedContentsEth1);
435
436 // reset the contents
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100437 client.deleteItem("/ietf-interfaces:interfaces/interface[name='br0']");
438 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']");
439 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth1']");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200440 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0", "eth0", "eth1"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100441 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200442 REQUIRE(!std::filesystem::exists(expectedFilePathBr0));
443 REQUIRE(!std::filesystem::exists(expectedFilePathEth0));
444 REQUIRE(!std::filesystem::exists(expectedFilePathEth1));
445 }
446
447 SECTION("Slave interface and enabled/disabled IP protocols")
448 {
449 const auto expectedFilePathBr0 = fakeConfigDir / "br0.network";
450 const auto expectedFilePathEth0 = fakeConfigDir / "eth0.network";
451
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100452 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/type", "iana-if-type:bridge");
453 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
454 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
455 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/czechlight-network:bridge", "br0");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200456
457 SECTION("Can't be a slave when IPv4 enabled")
458 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100459 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
460 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckac2929a62021-06-05 17:21:03 +0200461 }
462
463 SECTION("Can't be a slave when IPv6 enabled")
464 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100465 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
466 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckac2929a62021-06-05 17:21:03 +0200467 }
468
469 SECTION("Can't be a slave when both IPv4 and IPv6 enabled")
470 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100471 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
472 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
473 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckac2929a62021-06-05 17:21:03 +0200474 }
475
476 SECTION("Can be a slave when addresses present but protocol is disabled")
477 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100478 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
479 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/enabled", "false");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200480
481 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0", "eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100482 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200483
484 // reset the contents
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100485 client.deleteItem("/ietf-interfaces:interfaces/interface[name='br0']");
486 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200487 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0", "eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100488 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200489 REQUIRE(!std::filesystem::exists(expectedFilePathBr0));
490 REQUIRE(!std::filesystem::exists(expectedFilePathEth0));
491 }
492 }
Tomáš Peckabd43a942021-08-04 10:12:49 +0200493
494 SECTION("Network autoconfiguration")
495 {
496 const auto expectedFilePath = fakeConfigDir / "eth0.network";
497
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100498 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
Tomáš Peckabd43a942021-08-04 10:12:49 +0200499
500 SECTION("IPv4 on with address, IPv6 disabled, DHCPv4 off, RA off")
501 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100502 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
503 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24"); // in case DHCP is disabled an IP must be present
Tomáš Peckabd43a942021-08-04 10:12:49 +0200504
505 expectedContents = R"([Match]
506Name=eth0
507
508[Network]
509Address=192.0.2.1/24
510LinkLocalAddressing=no
511IPv6AcceptRA=false
512DHCP=no
513LLDP=true
514EmitLLDP=nearest-bridge
515)";
516 }
517
518 SECTION("IPv4 on with address, IPv6 disabled, DHCPv4 on, RA on")
519 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100520 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "true");
521 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24"); // in case DHCP is disabled an IP must be present
522 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
523 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "true");
Tomáš Peckabd43a942021-08-04 10:12:49 +0200524
525 expectedContents = R"([Match]
526Name=eth0
527
528[Network]
529Address=192.0.2.1/24
530LinkLocalAddressing=no
531IPv6AcceptRA=false
532DHCP=ipv4
533LLDP=true
534EmitLLDP=nearest-bridge
535)";
536 }
537
538 SECTION("IPv4 disabled, IPv6 enabled, DHCPv4 on, RA on")
539 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100540 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
541 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "true");
542 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
543 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "true");
Tomáš Peckabd43a942021-08-04 10:12:49 +0200544
545 expectedContents = R"([Match]
546Name=eth0
547
548[Network]
549IPv6AcceptRA=true
550DHCP=no
551LLDP=true
552EmitLLDP=nearest-bridge
553)";
554 }
555
556 SECTION("IPv4 enabled, IPv6 enabled, DHCPv4 on, RA on")
557 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100558 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
559 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "true");
560 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
561 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "true");
Tomáš Peckabd43a942021-08-04 10:12:49 +0200562
563 expectedContents = R"([Match]
564Name=eth0
565
566[Network]
567IPv6AcceptRA=true
568DHCP=ipv4
569LLDP=true
570EmitLLDP=nearest-bridge
571)";
572 }
573
574 SECTION("IPv4 enabled, IPv6 enabled, DHCPv4 off, RA on")
575 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100576 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
577 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/prefix-length", "24");
578 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
579 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
580 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "true");
Tomáš Peckabd43a942021-08-04 10:12:49 +0200581
582 expectedContents = R"([Match]
583Name=eth0
584
585[Network]
586Address=192.0.2.1/24
587IPv6AcceptRA=true
588DHCP=no
589LLDP=true
590EmitLLDP=nearest-bridge
591)";
592 }
593
594 SECTION("IPv4 disabled, IPv6 disabled, DHCPv4 off, RA off")
595 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100596 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/prefix-length", "24");
597 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
598 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/prefix-length", "32");
599 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "false");
Tomáš Peckabd43a942021-08-04 10:12:49 +0200600
601 expectedContents = R"([Match]
602Name=eth0
603
604[Network]
605Address=192.0.2.1/24
606Address=2001:db8::1/32
607IPv6AcceptRA=false
608DHCP=no
609LLDP=true
610EmitLLDP=nearest-bridge
611)";
612 }
613
614 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100615 client.applyChanges();
Tomáš Peckabd43a942021-08-04 10:12:49 +0200616 REQUIRE(std::filesystem::exists(expectedFilePath));
617 REQUIRE(velia::utils::readFileToString(expectedFilePath) == expectedContents);
618
619 // reset the contents
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100620 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']");
Tomáš Peckabd43a942021-08-04 10:12:49 +0200621 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100622 client.applyChanges();
Tomáš Peckabd43a942021-08-04 10:12:49 +0200623 REQUIRE(!std::filesystem::exists(expectedFilePath));
624 }
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200625}