Merge changes from topic "persistent-network"

* changes:
  system: Configure eth1 interface (persist the configuration)
  system: Configure eth1 interface (on running system)
  yang: add network settings into czechlight-system
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a627405..36a4d5e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,6 +77,7 @@
     src/utils/libyang.cpp
     src/utils/libyang.h
     src/utils/log.h
+    src/utils/log.cpp
     src/utils/log-fwd.h
     src/utils/log-init.cpp
     src/utils/log-init.h
@@ -87,7 +88,7 @@
     src/utils/waitUntilSignalled.cpp
     src/utils/waitUntilSignalled.h
     )
-target_link_libraries(velia-utils PUBLIC spdlog::spdlog PRIVATE PkgConfig::SYSTEMD PkgConfig::SYSREPO fmt::fmt)
+target_link_libraries(velia-utils PUBLIC spdlog::spdlog PRIVATE PkgConfig::SYSTEMD PkgConfig::SYSREPO fmt::fmt docopt)
 
 # - health
 add_library(velia-health STATIC
diff --git a/src/main-firewall.cpp b/src/main-firewall.cpp
index d9d0066..82a0ff6 100644
--- a/src/main-firewall.cpp
+++ b/src/main-firewall.cpp
@@ -11,25 +11,9 @@
 #include "utils/exec.h"
 #include "utils/journal.h"
 #include "utils/log-init.h"
+#include "utils/log.h"
 #include "utils/waitUntilSignalled.h"
 
-/** @short Extract log level from a CLI option */
-spdlog::level::level_enum parseLogLevel(const std::string& name, const docopt::value& option)
-{
-    long x;
-    try {
-        x = option.asLong();
-    } catch (std::invalid_argument&) {
-        throw std::runtime_error(name + " log level: expecting integer");
-    }
-    static_assert(spdlog::level::trace < spdlog::level::off, "spdlog::level levels have changed");
-    static_assert(spdlog::level::off == 6, "spdlog::level levels have changed");
-    if (x < 0 || x > 5)
-        throw std::runtime_error(name + " log level invalid or out-of-range");
-
-    return static_cast<spdlog::level::level_enum>(5 - x);
-}
-
 static const char usage[] =
     R"(Bridge between sysrepo and nftables.
 
diff --git a/src/main-hardware.cpp b/src/main-hardware.cpp
index 7b0bd2e..e357119 100644
--- a/src/main-hardware.cpp
+++ b/src/main-hardware.cpp
@@ -8,26 +8,10 @@
 #include "ietf-hardware/sysrepo/Sysrepo.h"
 #include "utils/exceptions.h"
 #include "utils/journal.h"
+#include "utils/log.h"
 #include "utils/log-init.h"
 #include "utils/waitUntilSignalled.h"
 
-/** @short Extract log level from a CLI option */
-spdlog::level::level_enum parseLogLevel(const std::string& name, const docopt::value& option)
-{
-    long x;
-    try {
-        x = option.asLong();
-    } catch (std::invalid_argument&) {
-        throw std::runtime_error(name + " log level: expecting integer");
-    }
-    static_assert(spdlog::level::trace < spdlog::level::off, "spdlog::level levels have changed");
-    static_assert(spdlog::level::off == 6, "spdlog::level levels have changed");
-    if (x < 0 || x > 5)
-        throw std::runtime_error(name + " log level invalid or out-of-range");
-
-    return static_cast<spdlog::level::level_enum>(5 - x);
-}
-
 static const char usage[] =
     R"(Hardware monitoring via Sysrepo.
 
diff --git a/src/main-health.cpp b/src/main-health.cpp
index fcf96fc..be829af 100644
--- a/src/main-health.cpp
+++ b/src/main-health.cpp
@@ -11,25 +11,9 @@
 #include "main.h"
 #include "utils/exceptions.h"
 #include "utils/journal.h"
+#include "utils/log.h"
 #include "utils/log-init.h"
 
-/** @short Extract log level from a CLI option */
-spdlog::level::level_enum parseLogLevel(const std::string& name, const docopt::value& option)
-{
-    long x;
-    try {
-        x = option.asLong();
-    } catch (std::invalid_argument&) {
-        throw std::runtime_error(name + " log level: expecting integer");
-    }
-    static_assert(spdlog::level::trace < spdlog::level::off, "spdlog::level levels have changed");
-    static_assert(spdlog::level::off == 6, "spdlog::level levels have changed");
-    if (x < 0 || x > 5)
-        throw std::runtime_error(name + " log level invalid or out-of-range");
-
-    return static_cast<spdlog::level::level_enum>(5 - x);
-}
-
 static const char usage[] =
     R"(Monitor system health status.
 
diff --git a/src/main-system.cpp b/src/main-system.cpp
index 2f2eda0..742c5bf 100644
--- a/src/main-system.cpp
+++ b/src/main-system.cpp
@@ -12,25 +12,9 @@
 #include "utils/exceptions.h"
 #include "utils/exec.h"
 #include "utils/journal.h"
+#include "utils/log.h"
 #include "utils/log-init.h"
 
-/** @short Extract log level from a CLI option */
-spdlog::level::level_enum parseLogLevel(const std::string& name, const docopt::value& option)
-{
-    long x;
-    try {
-        x = option.asLong();
-    } catch (std::invalid_argument&) {
-        throw std::runtime_error(name + " log level: expecting integer");
-    }
-    static_assert(spdlog::level::trace < spdlog::level::off, "spdlog::level levels have changed");
-    static_assert(spdlog::level::off == 6, "spdlog::level levels have changed");
-    if (x < 0 || x > 5)
-        throw std::runtime_error(name + " log level invalid or out-of-range");
-
-    return static_cast<spdlog::level::level_enum>(5 - x);
-}
-
 static const char usage[] =
     R"(Sysrepo-powered system management.
 
diff --git a/src/system/Authentication.cpp b/src/system/Authentication.cpp
index 2404a08..5c4c0e7 100644
--- a/src/system/Authentication.cpp
+++ b/src/system/Authentication.cpp
@@ -43,10 +43,10 @@
 }
 
 namespace impl {
-void changePassword(const std::string& name, const std::string& password)
+void changePassword(const std::string& name, const std::string& password, const std::string& etc_shadow)
 {
     utils::execAndWait(spdlog::get("system"), CHPASSWD_EXECUTABLE, {}, name + ":" + password);
-    auto shadow = velia::utils::readFileToString("/etc/shadow");
+    auto shadow = velia::utils::readFileToString(etc_shadow);
     utils::safeWriteFile(BACKUP_ETC_SHADOW_FILE, shadow);
 }
 }
@@ -273,7 +273,7 @@
         auto password = getValueAsString(getSubtree(userNode, "change-password/password-cleartext"));
         m_log->debug("Changing password for {}", name);
         try {
-            changePassword(name, password);
+            changePassword(name, password, m_etc_shadow);
             output->new_path(session->get_context(), "result", "success", LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_OUTPUT);
             m_log->info("Changed password for {}", name);
         } catch (std::runtime_error& ex) {
diff --git a/src/system/Authentication.h b/src/system/Authentication.h
index f57d86f..37362bb 100644
--- a/src/system/Authentication.h
+++ b/src/system/Authentication.h
@@ -23,12 +23,12 @@
 };
 
 namespace impl {
-void changePassword(const std::string& name, const std::string& password);
+void changePassword(const std::string& name, const std::string& password, const std::string& etc_shadow);
 }
 
 class Authentication {
 public:
-    using ChangePassword = std::function<void(const std::string& name, const std::string& password)>;
+    using ChangePassword = std::function<void(const std::string& name, const std::string& password, const std::string& etc_shadow)>;
 
     Authentication(sysrepo::S_Session srSess, const std::string& etc_passwd, const std::string& etc_shadow, const std::string& authorized_keys_format, ChangePassword changePassword);
 
diff --git a/src/utils/log.cpp b/src/utils/log.cpp
new file mode 100644
index 0000000..f6fe716
--- /dev/null
+++ b/src/utils/log.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+
+#include <docopt.h>
+#include "log.h"
+
+spdlog::level::level_enum parseLogLevel(const std::string& name, const docopt::value& option)
+{
+    long x;
+    try {
+        x = option.asLong();
+    } catch (std::invalid_argument&) {
+        throw std::runtime_error(name + " log level: expecting integer");
+    }
+    static_assert(spdlog::level::trace < spdlog::level::off, "spdlog::level levels have changed");
+    static_assert(spdlog::level::off == 6, "spdlog::level levels have changed");
+    if (x < 0 || x > 5)
+        throw std::runtime_error(name + " log level invalid or out-of-range");
+
+    return static_cast<spdlog::level::level_enum>(5 - x);
+}
diff --git a/src/utils/log.h b/src/utils/log.h
index 4ae7bca..1e2ed63 100644
--- a/src/utils/log.h
+++ b/src/utils/log.h
@@ -13,3 +13,10 @@
 
 //#define SPDLOG_ENABLE_SYSLOG
 #include <spdlog/spdlog.h>
+
+namespace docopt {
+    class value;
+}
+
+/** @short Extract log level from a CLI option */
+spdlog::level::level_enum parseLogLevel(const std::string& name, const docopt::value& option);
diff --git a/tests/mock/system.h b/tests/mock/system.h
index a191b02..c404ae9 100644
--- a/tests/mock/system.h
+++ b/tests/mock/system.h
@@ -22,5 +22,5 @@
 
 struct FakeAuthentication {
 public:
-    MAKE_CONST_MOCK2(changePassword, void(const std::string&, const std::string&));
+    MAKE_CONST_MOCK3(changePassword, void(const std::string&, const std::string&, const std::string&));
 };
diff --git a/tests/sysrepo_authentication.cpp b/tests/sysrepo_authentication.cpp
index 01c81a4..fef8295 100644
--- a/tests/sysrepo_authentication.cpp
+++ b/tests/sysrepo_authentication.cpp
@@ -33,7 +33,7 @@
     std::string authorized_keys_format = testDir / "authorized_keys/{USER}";
     std::string etc_passwd = testDir / "etc_passwd";
     std::string etc_shadow = testDir / "etc_shadow";
-    velia::system::Authentication auth(srSess, etc_passwd, etc_shadow, authorized_keys_format, [&mock] (const auto& user, const auto& password) { mock.changePassword(user, password); });
+    velia::system::Authentication auth(srSess, etc_passwd, etc_shadow, authorized_keys_format, [&mock] (const auto& user, const auto& password, const auto& etc_shadow) { mock.changePassword(user, password, etc_shadow); });
 
     auto test_srConn = std::make_shared<sysrepo::Connection>();
     auto test_srSess = std::make_shared<sysrepo::Session>(test_srConn, SR_DS_OPERATIONAL);
@@ -80,7 +80,7 @@
             SECTION("changePassword is successful")
             {
                 rpcPath = "/czechlight-system:authentication/users[name='root']/change-password";
-                expectation = NAMED_REQUIRE_CALL(mock, changePassword("root", "new-password"));
+                expectation = NAMED_REQUIRE_CALL(mock, changePassword("root", "new-password", etc_shadow));
                 expected = {
                     {"/result", "success"}
                 };
@@ -92,7 +92,7 @@
             SECTION("changePassword throws")
             {
                 rpcPath = "/czechlight-system:authentication/users[name='root']/change-password";
-                expectation = NAMED_REQUIRE_CALL(mock, changePassword("root", "new-password")).THROW(std::runtime_error("Task failed succesfully."));
+                expectation = NAMED_REQUIRE_CALL(mock, changePassword("root", "new-password", etc_shadow)).THROW(std::runtime_error("Task failed succesfully."));
                 expected = {
                     {"/result", "failure"},
                     {"/message", "Task failed succesfully."}