blob: 3117c88cbd3ff85766a7770ddcbced80f38be723 [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"
15#include "test_sysrepo_helpers.h"
Tomáš Pecka39bbb512021-05-23 22:07:22 +020016#include "tests/configure.cmake.h"
Tomáš Peckaf10b9302021-02-23 19:02:02 +010017#include "tests/mock/system.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"},
51 {"br0", "iana-if-type:bridge"},
52 {"osc", "iana-if-type:ethernetCsmacd"},
53 {"oscW", "iana-if-type:ethernetCsmacd"},
54 {"oscE", "iana-if-type:ethernetCsmacd"}}) {
Jan Kundrátb3e99982022-03-18 17:38:20 +010055 client.setItem("/ietf-interfaces:interfaces/interface[name='"s + name + "']/type", type);
56 client.setItem("/ietf-interfaces:interfaces/interface[name='"s + name + "']/enabled", "false");
Tomáš Pecka71a98b02021-05-17 15:59:40 +020057 }
Tomáš Pecka9cb218c2022-06-13 11:48:20 +020058
59 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 +010060 client.applyChanges();
Tomáš Pecka71a98b02021-05-17 15:59:40 +020061 }
62
63 SECTION("Invalid type for a valid link")
64 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +010065 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:softwareLoopback");
66 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Pecka71a98b02021-05-17 15:59:40 +020067 }
68
69 SECTION("Invalid name")
70 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +010071 client.setItem("/ietf-interfaces:interfaces/interface[name='blah0']/type", "iana-if-type:ethernetCsmacd");
72 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Pecka71a98b02021-05-17 15:59:40 +020073 }
74 }
Tomáš Peckade0ef2f2021-07-08 13:23:50 +020075
76 SECTION("There must always be enabled protocol or the interface must be explicitely disabled")
77 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +010078 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
Tomáš Peckade0ef2f2021-07-08 13:23:50 +020079
80 SECTION("Disabled protocols; enabled link")
81 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +010082 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
83 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
84 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
85 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckade0ef2f2021-07-08 13:23:50 +020086 }
87
88 SECTION("Active protocols; disabled link")
89 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +010090 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "false");
91 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
92 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
93 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 +020094 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +010095 client.applyChanges();
Tomáš Peckade0ef2f2021-07-08 13:23:50 +020096 }
97
98 SECTION("IPv4 only; enabled link")
99 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100100 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
101 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
102 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
103 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +0200104 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100105 client.applyChanges();
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200106 }
107 }
108
109 SECTION("Every active protocol must have at least one IP address assigned")
110 {
Tomáš Pecka9cb218c2022-06-13 11:48:20 +0200111 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100112 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "false");
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +0200113 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 +0100114 client.applyChanges();
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200115
116 SECTION("Enabled IPv4 protocol with some IPs assigned is valid")
117 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100118 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
119 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
120 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
121 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.2']/ietf-ip:prefix-length", "24");
122 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
Tomáš Pecka4c7ff2a2021-08-06 15:05:54 +0200123 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100124 client.applyChanges();
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200125 }
126
127 SECTION("Enabled IPv6 protocol with some IPs assigned is valid")
128 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100129 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
130 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
131 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
132 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 +0200133 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100134 client.applyChanges();
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200135 }
136
Tomáš Peckabd43a942021-08-04 10:12:49 +0200137 SECTION("Enabled IPv4 protocol must have at least one IP or the autoconfiguration must be on")
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200138 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100139 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
140 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
141 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
142 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
143 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200144 }
145
Tomáš Peckabd43a942021-08-04 10:12:49 +0200146 SECTION("Enabled IPv6 protocol must have at least one IP or the autoconfiguration must be on")
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200147 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100148 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
149 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
150 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
151 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "false");
152 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckade0ef2f2021-07-08 13:23:50 +0200153 }
154 }
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200155
156 std::string expectedContents;
Tomáš Peckabd43a942021-08-04 10:12:49 +0200157
Tomáš Pecka85f77752021-06-03 10:09:58 +0200158 SECTION("Setting IPs to eth0")
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200159 {
Tomáš Pecka85f77752021-06-03 10:09:58 +0200160 const auto expectedFilePath = fakeConfigDir / "eth0.network";
161
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100162 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200163
164 SECTION("Single IPv4 address")
165 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100166 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/description", "Hello world");
167 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
168 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200169 expectedContents = R"([Match]
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200170Name=eth0
171
172[Network]
173Description=Hello world
Tomáš Pecka85f77752021-06-03 10:09:58 +0200174Address=192.0.2.1/24
175LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200176IPv6AcceptRA=false
Tomáš Pecka85f77752021-06-03 10:09:58 +0200177DHCP=no
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200178LLDP=true
179EmitLLDP=nearest-bridge
180)";
Tomáš Pecka85f77752021-06-03 10:09:58 +0200181 }
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200182
Tomáš Pecka85f77752021-06-03 10:09:58 +0200183 SECTION("Two IPv4 addresses")
184 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100185 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
186 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.2']/ietf-ip:prefix-length", "24");
187 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
188 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200189 expectedContents = R"([Match]
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200190Name=eth0
191
192[Network]
Tomáš Pecka85f77752021-06-03 10:09:58 +0200193Address=192.0.2.1/24
194Address=192.0.2.2/24
195LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200196IPv6AcceptRA=false
Tomáš Pecka85f77752021-06-03 10:09:58 +0200197DHCP=no
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200198LLDP=true
199EmitLLDP=nearest-bridge
200)";
Tomáš Pecka85f77752021-06-03 10:09:58 +0200201 }
202
203 SECTION("IPv4 and IPv6 addresses")
204 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100205 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
206 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
207 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200208 expectedContents = R"([Match]
209Name=eth0
210
211[Network]
212Address=192.0.2.1/24
213Address=2001:db8::1/32
Tomáš Peckabd43a942021-08-04 10:12:49 +0200214IPv6AcceptRA=true
Tomáš Pecka85f77752021-06-03 10:09:58 +0200215DHCP=no
216LLDP=true
217EmitLLDP=nearest-bridge
218)";
219 }
220
221 SECTION("IPv4 and IPv6 addresses but IPv6 disabled")
222 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100223 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
224 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
225 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
226 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/enabled", "false");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200227 expectedContents = R"([Match]
228Name=eth0
229
230[Network]
231Address=192.0.2.1/24
232LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200233IPv6AcceptRA=false
Tomáš Pecka85f77752021-06-03 10:09:58 +0200234DHCP=no
235LLDP=true
236EmitLLDP=nearest-bridge
237)";
238 }
239
240 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100241 client.applyChanges();
Tomáš Pecka85f77752021-06-03 10:09:58 +0200242 REQUIRE(std::filesystem::exists(expectedFilePath));
243 REQUIRE(velia::utils::readFileToString(expectedFilePath) == expectedContents);
244
245 // reset the contents
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100246 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200247 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100248 client.applyChanges();
Tomáš Pecka85f77752021-06-03 10:09:58 +0200249 REQUIRE(!std::filesystem::exists(expectedFilePath));
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200250 }
251
Tomáš Peckac2929a62021-06-05 17:21:03 +0200252 SECTION("Two links")
Tomáš Pecka85f77752021-06-03 10:09:58 +0200253 {
254 const auto expectedFilePathEth0 = fakeConfigDir / "eth0.network";
255 const auto expectedFilePathEth1 = fakeConfigDir / "eth1.network";
Tomáš Peckac2929a62021-06-05 17:21:03 +0200256
257 std::string expectedContentsEth0 = R"([Match]
Tomáš Pecka85f77752021-06-03 10:09:58 +0200258Name=eth0
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200259
Tomáš Pecka85f77752021-06-03 10:09:58 +0200260[Network]
261Address=192.0.2.1/24
262LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200263IPv6AcceptRA=false
Tomáš Pecka85f77752021-06-03 10:09:58 +0200264DHCP=no
265LLDP=true
266EmitLLDP=nearest-bridge
267)";
Tomáš Peckac2929a62021-06-05 17:21:03 +0200268 std::string expectedContentsEth1 = R"([Match]
Tomáš Pecka85f77752021-06-03 10:09:58 +0200269Name=eth1
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200270
Tomáš Pecka85f77752021-06-03 10:09:58 +0200271[Network]
272Address=2001:db8::1/32
Tomáš Peckabd43a942021-08-04 10:12:49 +0200273IPv6AcceptRA=true
Tomáš Pecka85f77752021-06-03 10:09:58 +0200274DHCP=no
275LLDP=true
276EmitLLDP=nearest-bridge
277)";
278
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100279 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
280 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
281 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
282 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/type", "iana-if-type:ethernetCsmacd");
283 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 +0200284
285 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0", "eth1"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100286 client.applyChanges();
Tomáš Pecka85f77752021-06-03 10:09:58 +0200287 REQUIRE(std::filesystem::exists(expectedFilePathEth0));
288 REQUIRE(std::filesystem::exists(expectedFilePathEth1));
289 REQUIRE(velia::utils::readFileToString(expectedFilePathEth0) == expectedContentsEth0);
290 REQUIRE(velia::utils::readFileToString(expectedFilePathEth1) == expectedContentsEth1);
291
292 // reset the contents
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100293 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']");
294 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth1']");
Tomáš Pecka85f77752021-06-03 10:09:58 +0200295 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0", "eth1"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100296 client.applyChanges();
Tomáš Pecka85f77752021-06-03 10:09:58 +0200297 REQUIRE(!std::filesystem::exists(expectedFilePathEth0));
298 REQUIRE(!std::filesystem::exists(expectedFilePathEth1));
299 }
Tomáš Peckac2929a62021-06-05 17:21:03 +0200300
301 SECTION("Setup a bridge br0 over eth0 and eth1")
302 {
303 const auto expectedFilePathBr0 = fakeConfigDir / "br0.network";
304 const auto expectedFilePathEth0 = fakeConfigDir / "eth0.network";
305 const auto expectedFilePathEth1 = fakeConfigDir / "eth1.network";
306
307 std::string expectedContentsBr0 = R"([Match]
308Name=br0
309
310[Network]
311LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200312IPv6AcceptRA=false
Tomáš Peckac2929a62021-06-05 17:21:03 +0200313DHCP=no
314LLDP=true
315EmitLLDP=nearest-bridge
316)";
317
318 std::string expectedContentsEth0 = R"([Match]
319Name=eth0
320
321[Network]
322Bridge=br0
Tomáš Peckabd43a942021-08-04 10:12:49 +0200323IPv6AcceptRA=false
Tomáš Peckac2929a62021-06-05 17:21:03 +0200324DHCP=no
325LLDP=true
326EmitLLDP=nearest-bridge
327)";
328
329 std::string expectedContentsEth1 = R"([Match]
330Name=eth1
331
332[Network]
333Bridge=br0
Tomáš Peckabd43a942021-08-04 10:12:49 +0200334IPv6AcceptRA=false
Tomáš Peckac2929a62021-06-05 17:21:03 +0200335DHCP=no
336LLDP=true
337EmitLLDP=nearest-bridge
338)";
339
340 // create br0 bridge over eth0 and eth1 with no IP
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100341 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/enabled", "false");
342 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/type", "iana-if-type:bridge");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200343
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100344 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
345 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/czechlight-network:bridge", "br0");
346 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
347 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200348
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100349 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/type", "iana-if-type:ethernetCsmacd");
350 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/czechlight-network:bridge", "br0");
351 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/ietf-ip:ipv4/ietf-ip:enabled", "false");
352 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth1']/ietf-ip:ipv6");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200353
354 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0", "eth0", "eth1"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100355 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200356 REQUIRE(std::filesystem::exists(expectedFilePathBr0));
357 REQUIRE(std::filesystem::exists(expectedFilePathEth0));
358 REQUIRE(std::filesystem::exists(expectedFilePathEth1));
359 REQUIRE(velia::utils::readFileToString(expectedFilePathBr0) == expectedContentsBr0);
360 REQUIRE(velia::utils::readFileToString(expectedFilePathEth0) == expectedContentsEth0);
361 REQUIRE(velia::utils::readFileToString(expectedFilePathEth1) == expectedContentsEth1);
362
363 // assign an IPv4 address to br0
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100364 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/enabled", "true");
365 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
366 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200367 expectedContentsBr0 = R"([Match]
368Name=br0
369
370[Network]
371Address=192.0.2.1/24
372LinkLocalAddressing=no
Tomáš Peckabd43a942021-08-04 10:12:49 +0200373IPv6AcceptRA=false
Tomáš Peckac2929a62021-06-05 17:21:03 +0200374DHCP=no
375LLDP=true
376EmitLLDP=nearest-bridge
377)";
378
379 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100380 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200381 REQUIRE(std::filesystem::exists(expectedFilePathBr0));
382 REQUIRE(std::filesystem::exists(expectedFilePathEth0));
383 REQUIRE(std::filesystem::exists(expectedFilePathEth1));
384 REQUIRE(velia::utils::readFileToString(expectedFilePathBr0) == expectedContentsBr0);
385 REQUIRE(velia::utils::readFileToString(expectedFilePathEth0) == expectedContentsEth0);
386 REQUIRE(velia::utils::readFileToString(expectedFilePathEth1) == expectedContentsEth1);
387
388 // assign also an IPv6 address to br0
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100389 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 +0200390 expectedContentsBr0 = R"([Match]
391Name=br0
392
393[Network]
394Address=192.0.2.1/24
395Address=2001:db8::1/32
Tomáš Peckabd43a942021-08-04 10:12:49 +0200396IPv6AcceptRA=true
Tomáš Peckac2929a62021-06-05 17:21:03 +0200397DHCP=no
398LLDP=true
399EmitLLDP=nearest-bridge
400)";
401
402 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100403 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200404 REQUIRE(std::filesystem::exists(expectedFilePathBr0));
405 REQUIRE(std::filesystem::exists(expectedFilePathEth0));
406 REQUIRE(std::filesystem::exists(expectedFilePathEth1));
407 REQUIRE(velia::utils::readFileToString(expectedFilePathBr0) == expectedContentsBr0);
408 REQUIRE(velia::utils::readFileToString(expectedFilePathEth0) == expectedContentsEth0);
409 REQUIRE(velia::utils::readFileToString(expectedFilePathEth1) == expectedContentsEth1);
410
411 // remove eth1 from bridge
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100412 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth1']/czechlight-network:bridge");
413 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 +0200414
415 expectedContentsEth1 = R"([Match]
416Name=eth1
417
418[Network]
419Address=2001:db8::2/32
Tomáš Peckabd43a942021-08-04 10:12:49 +0200420IPv6AcceptRA=true
Tomáš Peckac2929a62021-06-05 17:21:03 +0200421DHCP=no
422LLDP=true
423EmitLLDP=nearest-bridge
424)";
425
426 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth1"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100427 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200428 REQUIRE(std::filesystem::exists(expectedFilePathBr0));
429 REQUIRE(std::filesystem::exists(expectedFilePathEth0));
430 REQUIRE(std::filesystem::exists(expectedFilePathEth1));
431 REQUIRE(velia::utils::readFileToString(expectedFilePathBr0) == expectedContentsBr0);
432 REQUIRE(velia::utils::readFileToString(expectedFilePathEth0) == expectedContentsEth0);
433 REQUIRE(velia::utils::readFileToString(expectedFilePathEth1) == expectedContentsEth1);
434
435 // reset the contents
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100436 client.deleteItem("/ietf-interfaces:interfaces/interface[name='br0']");
437 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']");
438 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth1']");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200439 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0", "eth0", "eth1"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100440 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200441 REQUIRE(!std::filesystem::exists(expectedFilePathBr0));
442 REQUIRE(!std::filesystem::exists(expectedFilePathEth0));
443 REQUIRE(!std::filesystem::exists(expectedFilePathEth1));
444 }
445
446 SECTION("Slave interface and enabled/disabled IP protocols")
447 {
448 const auto expectedFilePathBr0 = fakeConfigDir / "br0.network";
449 const auto expectedFilePathEth0 = fakeConfigDir / "eth0.network";
450
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100451 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/type", "iana-if-type:bridge");
452 client.setItem("/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
453 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
454 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/czechlight-network:bridge", "br0");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200455
456 SECTION("Can't be a slave when IPv4 enabled")
457 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100458 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
459 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckac2929a62021-06-05 17:21:03 +0200460 }
461
462 SECTION("Can't be a slave when IPv6 enabled")
463 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100464 client.setItem("/ietf-interfaces:interfaces/interface[name='eth1']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
465 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckac2929a62021-06-05 17:21:03 +0200466 }
467
468 SECTION("Can't be a slave when both IPv4 and IPv6 enabled")
469 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100470 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
471 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
472 REQUIRE_THROWS_AS(client.applyChanges(), sysrepo::ErrorWithCode);
Tomáš Peckac2929a62021-06-05 17:21:03 +0200473 }
474
475 SECTION("Can be a slave when addresses present but protocol is disabled")
476 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100477 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
478 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/enabled", "false");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200479
480 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0", "eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100481 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200482
483 // reset the contents
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100484 client.deleteItem("/ietf-interfaces:interfaces/interface[name='br0']");
485 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']");
Tomáš Peckac2929a62021-06-05 17:21:03 +0200486 REQUIRE_CALL(fake, cb(std::vector<std::string>{"br0", "eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100487 client.applyChanges();
Tomáš Peckac2929a62021-06-05 17:21:03 +0200488 REQUIRE(!std::filesystem::exists(expectedFilePathBr0));
489 REQUIRE(!std::filesystem::exists(expectedFilePathEth0));
490 }
491 }
Tomáš Peckabd43a942021-08-04 10:12:49 +0200492
493 SECTION("Network autoconfiguration")
494 {
495 const auto expectedFilePath = fakeConfigDir / "eth0.network";
496
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100497 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
Tomáš Peckabd43a942021-08-04 10:12:49 +0200498
499 SECTION("IPv4 on with address, IPv6 disabled, DHCPv4 off, RA off")
500 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100501 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
502 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 +0200503
504 expectedContents = R"([Match]
505Name=eth0
506
507[Network]
508Address=192.0.2.1/24
509LinkLocalAddressing=no
510IPv6AcceptRA=false
511DHCP=no
512LLDP=true
513EmitLLDP=nearest-bridge
514)";
515 }
516
517 SECTION("IPv4 on with address, IPv6 disabled, DHCPv4 on, RA on")
518 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100519 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "true");
520 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
521 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
522 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 +0200523
524 expectedContents = R"([Match]
525Name=eth0
526
527[Network]
528Address=192.0.2.1/24
529LinkLocalAddressing=no
530IPv6AcceptRA=false
531DHCP=ipv4
532LLDP=true
533EmitLLDP=nearest-bridge
534)";
535 }
536
537 SECTION("IPv4 disabled, IPv6 enabled, DHCPv4 on, RA on")
538 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100539 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
540 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "true");
541 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
542 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 +0200543
544 expectedContents = R"([Match]
545Name=eth0
546
547[Network]
548IPv6AcceptRA=true
549DHCP=no
550LLDP=true
551EmitLLDP=nearest-bridge
552)";
553 }
554
555 SECTION("IPv4 enabled, IPv6 enabled, DHCPv4 on, RA on")
556 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100557 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
558 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "true");
559 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
560 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 +0200561
562 expectedContents = R"([Match]
563Name=eth0
564
565[Network]
566IPv6AcceptRA=true
567DHCP=ipv4
568LLDP=true
569EmitLLDP=nearest-bridge
570)";
571 }
572
573 SECTION("IPv4 enabled, IPv6 enabled, DHCPv4 off, RA on")
574 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100575 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
576 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/prefix-length", "24");
577 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
578 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
579 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 +0200580
581 expectedContents = R"([Match]
582Name=eth0
583
584[Network]
585Address=192.0.2.1/24
586IPv6AcceptRA=true
587DHCP=no
588LLDP=true
589EmitLLDP=nearest-bridge
590)";
591 }
592
593 SECTION("IPv4 disabled, IPv6 disabled, DHCPv4 off, RA off")
594 {
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100595 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/prefix-length", "24");
596 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
597 client.setItem("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/prefix-length", "32");
598 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 +0200599
600 expectedContents = R"([Match]
601Name=eth0
602
603[Network]
604Address=192.0.2.1/24
605Address=2001:db8::1/32
606IPv6AcceptRA=false
607DHCP=no
608LLDP=true
609EmitLLDP=nearest-bridge
610)";
611 }
612
613 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100614 client.applyChanges();
Tomáš Peckabd43a942021-08-04 10:12:49 +0200615 REQUIRE(std::filesystem::exists(expectedFilePath));
616 REQUIRE(velia::utils::readFileToString(expectedFilePath) == expectedContents);
617
618 // reset the contents
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100619 client.deleteItem("/ietf-interfaces:interfaces/interface[name='eth0']");
Tomáš Peckabd43a942021-08-04 10:12:49 +0200620 REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100621 client.applyChanges();
Tomáš Peckabd43a942021-08-04 10:12:49 +0200622 REQUIRE(!std::filesystem::exists(expectedFilePath));
623 }
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200624}