diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index a1e102759e6e..a730d4e9ad3e 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -47,6 +47,7 @@ DOCKER_CTL_SCRIPT="$DOCKER_CTL_DIR/docker.sh" FILESYSTEM_ROOT_USR="$FILESYSTEM_ROOT/usr" FILESYSTEM_ROOT_USR_LIB="$FILESYSTEM_ROOT/usr/lib/" FILESYSTEM_ROOT_USR_LIB_SYSTEMD_SYSTEM="$FILESYSTEM_ROOT_USR_LIB/systemd/system" +FILESYSTEM_ROOT_USR_LIB_SYSTEMD_NETWORK="$FILESYSTEM_ROOT_USR_LIB/systemd/network" FILESYSTEM_ROOT_USR_SHARE="$FILESYSTEM_ROOT_USR/share" FILESYSTEM_ROOT_USR_SHARE_SONIC="$FILESYSTEM_ROOT_USR_SHARE/sonic" FILESYSTEM_ROOT_USR_SHARE_SONIC_SCRIPTS="$FILESYSTEM_ROOT_USR_SHARE_SONIC/scripts" @@ -637,6 +638,34 @@ sudo cp $IMAGE_CONFIGS/config-chassisdb/config-chassisdb $FILESYSTEM_ROOT/usr/bi echo "config-chassisdb.service" | sudo tee -a $GENERATED_SERVICE_FILE sudo LANG=C chroot $FILESYSTEM_ROOT systemctl enable config-chassisdb.service +# Copy midplane network service file for smart switch +sudo cp $IMAGE_CONFIGS/midplane-network/bridge-midplane.netdev $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_NETWORK/bridge-midplane.netdev +sudo cp $IMAGE_CONFIGS/midplane-network/bridge-midplane.network $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_NETWORK/bridge-midplane.network +sudo cp $IMAGE_CONFIGS/midplane-network/dummy-midplane.netdev $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_NETWORK/dummy-midplane.netdev +sudo cp $IMAGE_CONFIGS/midplane-network/dummy-midplane.network $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_NETWORK/dummy-midplane.network +sudo cp $IMAGE_CONFIGS/midplane-network/midplane-network-npu.network $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_NETWORK/midplane-network-npu.network +sudo cp $IMAGE_CONFIGS/midplane-network/midplane-network-dpu.network $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_NETWORK/midplane-network-dpu.network +sudo cp $IMAGE_CONFIGS/midplane-network/midplane-network-npu.service $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_SYSTEM/midplane-network-npu.service +sudo cp $IMAGE_CONFIGS/midplane-network/midplane-network-dpu.service $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_SYSTEM/midplane-network-dpu.service + +# Disable smart switch unit by default, these units will be controlled by systemd-sonic-generator +sudo ln -s /dev/null $FILESYSTEM_ROOT/etc/systemd/network/bridge-midplane.netdev +sudo ln -s /dev/null $FILESYSTEM_ROOT/etc/systemd/network/bridge-midplane.network +sudo ln -s /dev/null $FILESYSTEM_ROOT/etc/systemd/network/dummy-midplane.netdev +sudo ln -s /dev/null $FILESYSTEM_ROOT/etc/systemd/network/dummy-midplane.network +sudo ln -s /dev/null $FILESYSTEM_ROOT/etc/systemd/network/midplane-network-npu.network +sudo ln -s /dev/null $FILESYSTEM_ROOT/etc/systemd/network/midplane-network-dpu.network +echo "midplane-network-npu.service" | sudo tee -a $GENERATED_SERVICE_FILE +sudo LANG=C chroot $FILESYSTEM_ROOT systemctl disable midplane-network-npu.service +echo "midplane-network-dpu.service" | sudo tee -a $GENERATED_SERVICE_FILE +sudo LANG=C chroot $FILESYSTEM_ROOT systemctl disable midplane-network-dpu.service + +# According to the issue: https://github.com/systemd/systemd/issues/19106, To disable ManageForeignRoutingPolicyRules to avoid the ip rules being deleted by systemd-networkd +sudo sed -i 's/#ManageForeignRoutingPolicyRules=yes/ManageForeignRoutingPolicyRules=no/g' $FILESYSTEM_ROOT/etc/systemd/networkd.conf + +sudo LANG=C chroot $FILESYSTEM_ROOT systemctl enable systemd-networkd +sudo LANG=C chroot $FILESYSTEM_ROOT systemctl disable systemd-networkd-wait-online.service + # Copy backend-acl script and service file sudo cp $IMAGE_CONFIGS/backend_acl/backend-acl.service $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_SYSTEM/backend-acl.service sudo cp $IMAGE_CONFIGS/backend_acl/backend_acl.py $FILESYSTEM_ROOT/usr/bin/backend_acl.py diff --git a/files/image_config/midplane-network/bridge-midplane.netdev b/files/image_config/midplane-network/bridge-midplane.netdev new file mode 100644 index 000000000000..829d65989342 --- /dev/null +++ b/files/image_config/midplane-network/bridge-midplane.netdev @@ -0,0 +1,5 @@ +# Bridge interface for midplane network + +[NetDev] +Name=bridge-midplane +Kind=bridge diff --git a/files/image_config/midplane-network/bridge-midplane.network b/files/image_config/midplane-network/bridge-midplane.network new file mode 100644 index 000000000000..5a7cabf8fb31 --- /dev/null +++ b/files/image_config/midplane-network/bridge-midplane.network @@ -0,0 +1,8 @@ +# Network configuration for bridge midplane + +[Match] +Name=bridge-midplane + +[Network] +Address=169.254.200.254/24 +LinkLocalAddressing=no diff --git a/files/image_config/midplane-network/dummy-midplane.netdev b/files/image_config/midplane-network/dummy-midplane.netdev new file mode 100644 index 000000000000..ea92c87532ef --- /dev/null +++ b/files/image_config/midplane-network/dummy-midplane.netdev @@ -0,0 +1,5 @@ +# Dummy interface for midplane network + +[NetDev] +Name=dummy-midplane +Kind=dummy diff --git a/files/image_config/midplane-network/dummy-midplane.network b/files/image_config/midplane-network/dummy-midplane.network new file mode 100644 index 000000000000..dd4b8d6559ae --- /dev/null +++ b/files/image_config/midplane-network/dummy-midplane.network @@ -0,0 +1,9 @@ +# The systemd-networkd requires that the bridge interface(bridge-midplane) has at least one member interface, otherwise the IP address will not be configured. +# This dummy interface will be added into the bridge-midplane as a member to guarantee the IP address configuration correctly. +# Refer: https://github.com/systemd/systemd/issues/9252#issuecomment-771540028 + +[Match] +Name=dummy-midplane + +[Network] +Bridge=bridge-midplane diff --git a/files/image_config/midplane-network/midplane-network-dpu.network b/files/image_config/midplane-network/midplane-network-dpu.network new file mode 100644 index 000000000000..bb4c58ed9a54 --- /dev/null +++ b/files/image_config/midplane-network/midplane-network-dpu.network @@ -0,0 +1,5 @@ +[Match] +Name=eth0-midplane + +[Network] +DHCP=yes diff --git a/files/image_config/midplane-network/midplane-network-dpu.service b/files/image_config/midplane-network/midplane-network-dpu.service new file mode 100644 index 000000000000..5750850b731d --- /dev/null +++ b/files/image_config/midplane-network/midplane-network-dpu.service @@ -0,0 +1,15 @@ +# Oneshot midplane network service + +[Unit] +Description=Midplane network service +Requires=systemd-networkd.service +After=systemd-networkd.service +Before=database.service + +[Service] +Type=oneshot +User=root +ExecStart=/usr/lib/systemd/systemd-networkd-wait-online -i eth0-midplane --timeout=600 + +[Install] +WantedBy=multi-user.target diff --git a/files/image_config/midplane-network/midplane-network-npu.network b/files/image_config/midplane-network/midplane-network-npu.network new file mode 100644 index 000000000000..8cc4a2264989 --- /dev/null +++ b/files/image_config/midplane-network/midplane-network-npu.network @@ -0,0 +1,5 @@ +[Match] +Name=dpu* + +[Network] +Bridge=bridge-midplane diff --git a/files/image_config/midplane-network/midplane-network-npu.service b/files/image_config/midplane-network/midplane-network-npu.service new file mode 100644 index 000000000000..785f048ac795 --- /dev/null +++ b/files/image_config/midplane-network/midplane-network-npu.service @@ -0,0 +1,14 @@ +# Oneshot midplane network service + +[Unit] +Description=Midplane network service +Requires=systemd-networkd.service +After=systemd-networkd.service + +[Service] +Type=oneshot +User=root +ExecStart=/usr/lib/systemd/systemd-networkd-wait-online -i bridge-midplane + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd-sonic-generator/Makefile b/src/systemd-sonic-generator/Makefile index 0e6fb9095dce..ecfc19e0b26a 100644 --- a/src/systemd-sonic-generator/Makefile +++ b/src/systemd-sonic-generator/Makefile @@ -3,14 +3,14 @@ CFLAGS += -std=gnu99 -D_GNU_SOURCE CXX=g++ CXXFLAGS += -std=c++11 -D_GNU_SOURCE -LDFLAGS += -lpthread -lboost_filesystem -lboost_system -lgtest +LDFLAGS += -lpthread -lboost_filesystem -lboost_system -lgtest -ljson-c BINARY = systemd-sonic-generator $(BINARY): systemd-sonic-generator.c rm -f ./systemd-sonic-generator - $(CC) $(CFLAGS) -o $@ $^ + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) install: $(BINARY) mkdir -p $(DESTDIR) diff --git a/src/systemd-sonic-generator/ssg-test.cc b/src/systemd-sonic-generator/ssg-test.cc index 52706358d175..bcc661a9df2a 100644 --- a/src/systemd-sonic-generator/ssg-test.cc +++ b/src/systemd-sonic-generator/ssg-test.cc @@ -9,9 +9,11 @@ #include #include #include +#include #include #include #include +#include #include "systemd-sonic-generator.h" namespace fs = boost::filesystem; @@ -19,7 +21,7 @@ namespace fs = boost::filesystem; namespace SSGTest { #define IS_MULTI_ASIC(x) ((x) > 1) #define IS_SINGLE_ASIC(x) ((x) <= 1) -#define NUM_UNIT_FILES 6 +#define NUM_UNIT_FILES 9 /* * This test class uses following directory hierarchy for input and output @@ -40,27 +42,36 @@ namespace SSGTest { */ const std::string TEST_ROOT_DIR = "tests/ssg-test/"; const std::string TEST_UNIT_FILE_PREFIX = TEST_ROOT_DIR + "systemd/"; +const std::string TEST_LIB_NETWORK = TEST_UNIT_FILE_PREFIX + "network/"; const std::string TEST_ASIC_CONF_FORMAT = TEST_ROOT_DIR + "%s/asic.conf"; +const std::string TEST_PLATFORM_CONF_FORMAT = TEST_ROOT_DIR + "%s/platform.json"; const std::string TEST_MACHINE_CONF = TEST_ROOT_DIR + "machine.conf"; const std::string TEST_PLATFORM_DIR = TEST_ROOT_DIR + "test_platform/"; const std::string TEST_ASIC_CONF = TEST_PLATFORM_DIR + "asic.conf"; +const std::string TEST_PLATFORM_CONF = TEST_PLATFORM_DIR + "platform.json"; const std::string TEST_OUTPUT_DIR = TEST_ROOT_DIR + "generator/"; +const std::string TEST_ETC_NETWORK = TEST_OUTPUT_DIR + "network/"; const std::string TEST_CONFIG_FILE = TEST_ROOT_DIR + "generated_services.conf"; const std::string TEST_UNIT_FILES = "tests/testfiles/"; + /* Input data for generated_services.conf */ const std::vector generated_services = { - "multi_inst_a.service", /* Single instance of a multi asic service a */ - "multi_inst_a@.service", /* Multi-instance of a multi asic service a */ - "multi_inst_b@.service", /* Multi-instance of a multi asic service b */ - "single_inst.service", /* A single instance service */ - "test.service", /* A single instance test service - to test dependency creation */ - "test.timer", /* A timer service */ + "multi_inst_a.service", /* Single instance of a multi asic service a */ + "multi_inst_a@.service", /* Multi-instance of a multi asic service a */ + "multi_inst_b@.service", /* Multi-instance of a multi asic service b */ + "single_inst.service", /* A single instance service */ + "test.service", /* A single instance test service + to test dependency creation */ + "test.timer", /* A timer service */ + "midplane-network-npu.service", /* A midplane network service for smart switch NPU*/ + "midplane-network-dpu.service", /* A midplane network service for smart switch DPU*/ + "database.service", /* A database service*/ + "database@.service", /* A database service for multi instances */ }; static std::mutex g_ssg_test_mutex; @@ -120,8 +131,13 @@ class SsgFunctionTest : public SystemdSonicGeneratorFixture { try { fs::path current(file->path()); if(!fs::is_directory(current)) { + std::string ext = boost::filesystem::extension(current); + fs::path dest_path = dest_dir; + if (ext == ".netdev" || ext == ".network" || ext == ".link") { + dest_path = dest_path / "network"; + } /* Copy file */ - fs::copy_file( current, dest_dir / current.filename()); + fs::copy_file( current, dest_path / current.filename()); } } catch(fs::filesystem_error const & e) { @@ -142,6 +158,10 @@ class SsgFunctionTest : public SystemdSonicGeneratorFixture { fs::create_directories(path); path = fs::path(TEST_PLATFORM_DIR.c_str()); fs::create_directories(path); + path = fs::path(TEST_LIB_NETWORK.c_str()); + fs::create_directories(path); + path = fs::path(TEST_ETC_NETWORK.c_str()); + fs::create_directories(path); fp = fopen(TEST_MACHINE_CONF.c_str(), "w"); ASSERT_NE(fp, nullptr); fputs("onie_platform=test_platform", fp); @@ -163,6 +183,15 @@ class SsgFunctionTest : public SystemdSonicGeneratorFixture { private: }; + +struct SsgMainConfig { + int num_asics = 0; + bool is_smart_switch_npu = false; + bool is_smart_switch_dpu = false; + int num_dpus = 0; +}; + + /* * class SsgMainTest * Implements functions to test ssg_main routine. @@ -228,10 +257,10 @@ class SsgMainTest : public SsgFunctionTest { void validate_output_unit_files(std::vector strs, std::string target, bool expected_result, - int num_asics) { + int num_instances) { for (std::string str : strs) { bool finished = false; - for (int i = 0 ; i < num_asics && !finished; ++i) { + for (int i = 0 ; i < num_instances && !finished; ++i) { auto str_t = str; if (is_multi_instance(str)) { /* insert instance id in string */ @@ -241,7 +270,10 @@ class SsgMainTest : public SsgFunctionTest { finished = true; } fs::path path{TEST_OUTPUT_DIR + target + "/" + str_t}; - EXPECT_EQ(fs::exists(path), expected_result) + char resolved_path[PATH_MAX]; + realpath(path.c_str(), resolved_path); + bool exist = fs::exists(path) && strcmp(resolved_path, "/dev/null") != 0; + EXPECT_EQ(exist, expected_result) << "Failed validation: " << path; } } @@ -250,7 +282,7 @@ class SsgMainTest : public SsgFunctionTest { /* * This function validates the generated dependencies in a Unit File. */ - void validate_depedency_in_unit_file(int num_asics) { + void validate_depedency_in_unit_file(const SsgMainConfig &cfg) { std::string test_service = "test.service"; /* Validate Unit file dependency creation for multi instance @@ -258,53 +290,83 @@ class SsgMainTest : public SsgFunctionTest { * system but not present for single asic system. */ validate_output_dependency_list(multi_asic_dependency_list, - test_service, IS_MULTI_ASIC(num_asics), num_asics); + test_service, IS_MULTI_ASIC(cfg.num_asics), cfg.num_asics); - /* Validate Unit file dependency creation for single instance - * services. These entries should not be present for multi asic - * system but present for single asic system. + /* This section handles a tricky scenario. + * When the number of DPUs (Data Processing Units) is greater than 0, + * the dependency list will be split. Otherwise, it remains in one line. + * Despite the split, the final result remains equivalent. */ - validate_output_dependency_list(single_asic_dependency_list, - test_service, IS_SINGLE_ASIC(num_asics), num_asics); + if (cfg.num_dpus > 0) { + /* Validate Unit file dependency creation for single instance + * services. These entries should not be present for multi asic + * system but present for single asic system. + */ + validate_output_dependency_list(single_asic_dependency_list_split, + test_service, IS_SINGLE_ASIC(cfg.num_asics), cfg.num_asics); + validate_output_dependency_list(npu_dependency_list, + "midplane-network-npu.service", true, cfg.num_dpus); + } else { + /* Validate Unit file dependency creation for single instance + * services. These entries should not be present for multi asic + * system but present for single asic system. + */ + validate_output_dependency_list(single_asic_dependency_list, + test_service, IS_SINGLE_ASIC(cfg.num_asics), cfg.num_asics); + } /* Validate Unit file dependency creation for single instance * common services. These entries should not be present for multi * and single asic system. */ validate_output_dependency_list(common_dependency_list, - test_service, true, num_asics); + test_service, true, cfg.num_asics); } /* * This function validates the list of generated Service Unit Files. */ - void validate_service_file_generated_list(int num_asics) { + void validate_service_file_generated_list(const SsgMainConfig &cfg) { std::string test_target = "multi-user.target.wants"; validate_output_unit_files(multi_asic_service_list, - test_target, IS_MULTI_ASIC(num_asics), num_asics); + test_target, IS_MULTI_ASIC(cfg.num_asics), cfg.num_asics); validate_output_unit_files(single_asic_service_list, - test_target, IS_SINGLE_ASIC(num_asics), num_asics); + test_target, IS_SINGLE_ASIC(cfg.num_asics), cfg.num_asics); validate_output_unit_files(common_service_list, - test_target, true, num_asics); + test_target, true, cfg.num_asics); + validate_output_unit_files(npu_service_list, + test_target, cfg.is_smart_switch_npu, cfg.num_dpus); + validate_output_unit_files(npu_network_service_list, + "network", cfg.is_smart_switch_npu, cfg.num_dpus); + validate_output_unit_files(dpu_service_list, + test_target, cfg.is_smart_switch_dpu, cfg.num_dpus); + validate_output_unit_files(dpu_network_service_list, + "network", cfg.is_smart_switch_dpu, cfg.num_dpus); } /* ssg_main test routine. * input: num_asics number of asics */ - void ssg_main_test(int num_asics) { + void ssg_main_test(const SsgMainConfig &cfg) { FILE* fp; std::vector argv_; std::vector arguments = { "ssg_main", TEST_OUTPUT_DIR.c_str() }; - std::string num_asic_str = "NUM_ASIC=" + std::to_string(num_asics); + std::string num_asic_str = "NUM_ASIC=" + std::to_string(cfg.num_asics); std::string unit_file_path = fs::current_path().string() + "/" +TEST_UNIT_FILE_PREFIX; g_unit_file_prefix = unit_file_path.c_str(); g_config_file = TEST_CONFIG_FILE.c_str(); g_machine_config_file = TEST_MACHINE_CONF.c_str(); g_asic_conf_format = TEST_ASIC_CONF_FORMAT.c_str(); + g_platform_file_format = TEST_PLATFORM_CONF_FORMAT.c_str(); + std::string lib_systemd = fs::current_path().string() + "/" + TEST_UNIT_FILE_PREFIX; + g_lib_systemd = lib_systemd.c_str(); + std::string etc_systemd = fs::current_path().string() + "/" + TEST_OUTPUT_DIR; + g_etc_systemd = etc_systemd.c_str(); + /* Set NUM_ASIC value in asic.conf */ fp = fopen(TEST_ASIC_CONF.c_str(), "w"); @@ -312,6 +374,29 @@ class SsgMainTest : public SsgFunctionTest { fputs(num_asic_str.c_str(), fp); fclose(fp); + /* Set platform file for smart switch */ + if (cfg.is_smart_switch_dpu || cfg.is_smart_switch_npu) { + nlohmann::json platform_config; + if (cfg.is_smart_switch_dpu) { + ASSERT_EQ(cfg.num_dpus, 0); + ASSERT_EQ(cfg.is_smart_switch_npu, false); + + platform_config["DPU"] = nlohmann::json::object(); + } + else if (cfg.is_smart_switch_npu) { + ASSERT_EQ(cfg.is_smart_switch_dpu, false); + nlohmann::json dpus; + for (int i = 0; i < cfg.num_dpus; i++) { + dpus["dpu" + std::to_string(i)] = nlohmann::json::object(); + } + platform_config["DPUS"] = dpus; + } + fp = fopen(TEST_PLATFORM_CONF.c_str(), "w"); + ASSERT_NE(fp, nullptr); + fputs(platform_config.dump().c_str(), fp); + fclose(fp); + } + /* Create argv list for ssg_main. */ for (const auto& arg : arguments) { argv_.push_back((char*)arg.data()); @@ -322,15 +407,22 @@ class SsgMainTest : public SsgFunctionTest { EXPECT_EQ(ssg_main(argv_.size(), argv_.data()), 0); /* Validate systemd service template creation. */ - validate_service_file_generated_list(num_asics); + validate_service_file_generated_list(cfg); /* Validate Test Unit file for dependency creation. */ - validate_depedency_in_unit_file(num_asics); + validate_depedency_in_unit_file(cfg); } /* Save global variables before running tests */ virtual void SetUp() { SsgFunctionTest::SetUp(); + // Create /dev/null symlink for simulation disabled service + std::vector disabled_service; + disabled_service.insert(disabled_service.end(), npu_network_service_list.begin(), npu_network_service_list.end()); + disabled_service.insert(disabled_service.end(), dpu_network_service_list.begin(), dpu_network_service_list.end()); + for (const auto &service : disabled_service) { + fs::create_symlink("/dev/null", TEST_ETC_NETWORK + service); + } } /* Restore global vars */ @@ -343,8 +435,14 @@ class SsgMainTest : public SsgFunctionTest { static const std::vector single_asic_service_list; static const std::vector multi_asic_service_list; static const std::vector common_service_list; + static const std::vector npu_service_list; + static const std::vector npu_network_service_list; + static const std::vector dpu_service_list; + static const std::vector dpu_network_service_list; static const std::vector single_asic_dependency_list; + static const std::vector single_asic_dependency_list_split; static const std::vector multi_asic_dependency_list; + static const std::vector npu_dependency_list; static const std::vector common_dependency_list; }; @@ -368,6 +466,7 @@ const std::vector SsgMainTest::multi_asic_service_list = { "multi_inst_a@%1%.service", "multi_inst_b@%1%.service", + "database@%1%.service", }; /* Common Systemd service Unit file list for single and multi asic system. */ @@ -376,7 +475,36 @@ SsgMainTest::common_service_list = { "multi_inst_a.service", "single_inst.service", "test.service", + "database.service", +}; +/* Systemd service Unit file list for Smart Switch NPU. */ +const std::vector +SsgMainTest::npu_service_list = { + "database@dpu%1%.service", + "midplane-network-npu.service", +}; + +/* Systemd service Unit file list for Smart Switch NPU. */ +const std::vector +SsgMainTest::npu_network_service_list = { + "bridge-midplane.netdev", + "bridge-midplane.network", + "dummy-midplane.netdev", + "dummy-midplane.network", + "midplane-network-npu.network", +}; + +/* Systemd service Unit file list for Smart Switch DPU. */ +const std::vector +SsgMainTest::dpu_service_list = { + "midplane-network-dpu.service", +}; + +/* Systemd service Unit file list for Smart Switch DPU. */ +const std::vector +SsgMainTest::dpu_network_service_list = { + "midplane-network-dpu.network", }; /* @@ -398,6 +526,13 @@ SsgMainTest::single_asic_dependency_list = { "After=multi_inst_a.service multi_inst_b.service", }; +/* Systemd service Unit file dependency entries for Single asic system. */ +const std::vector +SsgMainTest::single_asic_dependency_list_split = { + "After=multi_inst_a.service", + "After=multi_inst_b.service", +}; + /* Systemd service Unit file dependency entries for multi asic system. */ const std::vector SsgMainTest::multi_asic_dependency_list = { @@ -413,6 +548,11 @@ SsgMainTest::common_dependency_list = { "Before=single_inst.service", }; +const std::vector +SsgMainTest::npu_dependency_list = { + "Before=database@dpu%1%.service", +}; + /* Test get functions for global vasr*/ TEST_F(SystemdSonicGeneratorFixture, get_global_vars) { EXPECT_EQ(g_unit_file_prefix, nullptr); @@ -460,7 +600,7 @@ TEST_F(SsgFunctionTest, insert_instance_number) { char input[] = "test@.service"; for (int i = 0; i <= 100; ++i) { std::string out = "test@" + std::to_string(i) + ".service"; - char* ret = insert_instance_number(input, i); + char* ret = insert_instance_number(input, i, ""); ASSERT_NE(ret, nullptr); EXPECT_STREQ(ret, out.c_str()); } @@ -495,13 +635,30 @@ TEST_F(SsgFunctionTest, get_num_of_asic) { /* TEST get_unit_files()*/ TEST_F(SsgFunctionTest, get_unit_files) { g_unit_file_prefix = TEST_UNIT_FILE_PREFIX.c_str(); + g_lib_systemd = TEST_UNIT_FILE_PREFIX.c_str(); + g_etc_systemd = TEST_OUTPUT_DIR.c_str(); g_config_file = TEST_CONFIG_FILE.c_str(); - char* unit_files[NUM_UNIT_FILES]; + char* unit_files[NUM_UNIT_FILES] = { NULL }; int num_unit_files = get_unit_files(unit_files); - EXPECT_EQ(num_unit_files, NUM_UNIT_FILES); - for (std::string service : generated_services) { + // Exclude the midplane-network-{npu/dpu}.service which is only used for smart switch + auto non_smart_switch_generated_services = generated_services; + non_smart_switch_generated_services.erase( + std::remove(non_smart_switch_generated_services.begin(), + non_smart_switch_generated_services.end(), + "midplane-network-npu.service"), + non_smart_switch_generated_services.end()); + non_smart_switch_generated_services.erase( + std::remove(non_smart_switch_generated_services.begin(), + non_smart_switch_generated_services.end(), + "midplane-network-dpu.service"), + non_smart_switch_generated_services.end()); + EXPECT_EQ(num_unit_files, non_smart_switch_generated_services.size()); + for (std::string service : non_smart_switch_generated_services) { bool found = false; for (auto& unit_file : unit_files) { + if (unit_file == NULL) { + continue; + } if(unit_file == service) { found = true; break; @@ -530,18 +687,49 @@ TEST_F(SsgMainTest, ssg_main_argv) { /* TEST ssg_main() single asic */ TEST_F(SsgMainTest, ssg_main_single_npu) { - ssg_main_test(1); + SsgMainConfig cfg; + cfg.num_asics = 1; + ssg_main_test(cfg); } /* TEST ssg_main() multi(10) asic */ TEST_F(SsgMainTest, ssg_main_10_npu) { - ssg_main_test(10); + SsgMainConfig cfg; + cfg.num_asics = 10; + ssg_main_test(cfg); } /* TEST ssg_main() multi(40) asic */ TEST_F(SsgMainTest, ssg_main_40_npu) { - ssg_main_test(40); + SsgMainConfig cfg; + cfg.num_asics = 40; + ssg_main_test(cfg); +} + +TEST_F(SsgMainTest, ssg_main_smart_switch_npu) { + SsgMainConfig cfg; + cfg.num_asics = 1; + cfg.is_smart_switch_npu = true; + cfg.num_dpus = 8; + ssg_main_test(cfg); } + +TEST_F(SsgMainTest, ssg_main_smart_switch_dpu) { + SsgMainConfig cfg; + cfg.num_asics = 1; + cfg.is_smart_switch_dpu = true; + ssg_main_test(cfg); +} + +TEST_F(SsgMainTest, ssg_main_smart_switch_double_execution) { + SsgMainConfig cfg; + cfg.num_asics = 1; + cfg.is_smart_switch_npu = true; + cfg.num_dpus = 8; + ssg_main_test(cfg); + ssg_main_test(cfg); +} + } int main(int argc, char** argv) { diff --git a/src/systemd-sonic-generator/systemd-sonic-generator.c b/src/systemd-sonic-generator/systemd-sonic-generator.c index d9bb75fea2b6..f5cac8e942f2 100644 --- a/src/systemd-sonic-generator/systemd-sonic-generator.c +++ b/src/systemd-sonic-generator/systemd-sonic-generator.c @@ -8,16 +8,35 @@ #include #include #include +#include #define MAX_NUM_TARGETS 48 #define MAX_NUM_INSTALL_LINES 48 #define MAX_NUM_UNITS 128 #define MAX_BUF_SIZE 512 +#define MAX_PLATFORM_NAME_LEN 64 + + +const char* LIB_SYSTEMD = "/usr/lib/systemd"; +const char* ETC_SYSTEMD = "/etc/systemd"; const char* UNIT_FILE_PREFIX = "/usr/lib/systemd/system/"; const char* CONFIG_FILE = "/etc/sonic/generated_services.conf"; const char* MACHINE_CONF_FILE = "/host/machine.conf"; const char* ASIC_CONF_FORMAT = "/usr/share/sonic/device/%s/asic.conf"; +const char* PLATFORM_FILE_FORMAT = "/usr/share/sonic/device/%s/platform.json"; +const char* DPU_PREFIX = "dpu"; + + +const char* g_lib_systemd = NULL; +const char* get_lib_systemd () { + return (g_lib_systemd) ? g_lib_systemd : LIB_SYSTEMD; +} + +const char* g_etc_systemd = NULL; +const char* get_etc_systemd () { + return (g_etc_systemd) ? g_etc_systemd : ETC_SYSTEMD; +} const char* g_unit_file_prefix = NULL; const char* get_unit_file_prefix() { @@ -39,9 +58,64 @@ const char* get_asic_conf_format() { return (g_asic_conf_format) ? g_asic_conf_format : ASIC_CONF_FORMAT; } +const char* g_platform_file_format = NULL; +const char* get_platform_file_format() { + return (g_platform_file_format) ? g_platform_file_format : PLATFORM_FILE_FORMAT; +} + static int num_asics; static char** multi_instance_services; static int num_multi_inst; +static bool smart_switch_npu; +static bool smart_switch_dpu; +static bool smart_switch; +static size_t num_dpus; +static char* platform = NULL; +static struct json_object *platform_info = NULL; + + +#ifdef _SSG_UNITTEST +/** + * @brief Cleans up the cache by resetting cache pointers. + */ +void clean_up_cache() { + platform = NULL; + platform_info = NULL; +} +#endif + +/** + * Sets the value of a pointer to an invalid memory address. + * + * @param pointer A pointer to a pointer variable. + */ +void set_invalid_pointer(void **pointer) { + *pointer = (void *)-1; +} + + +/** + * @brief Checks if a pointer is valid. + * + * This function checks if a pointer is valid by verifying that it is not NULL and not equal to (void *)-1. + * + * @param pointer The pointer to be checked. + * @return true if the pointer is valid, false otherwise. + */ +bool is_valid_pointer(void *pointer) { + return pointer != NULL && pointer != (void *)-1; +} + + +/** + * Checks if a pointer is initialized. + * + * @param pointer The pointer to check. + * @return true if the pointer is not NULL, false otherwise. + */ +bool is_initialized_pointer(void *pointer) { + return pointer != NULL; +} void strip_trailing_newline(char* str) { /*** @@ -54,6 +128,22 @@ void strip_trailing_newline(char* str) { } +/** + * Checks if the given path is "/dev/null". + * + * @param path The path to check. + * @return true if the path is "/dev/null", false otherwise. + */ +static bool is_devnull(const char* path) +{ + char resolved_path[PATH_MAX]; + if (realpath(path, resolved_path) == NULL) { + return false; + } + return strcmp(resolved_path, "/dev/null") == 0; +} + + static int get_target_lines(char* unit_file, char* target_lines[]) { /*** Gets installation information for a given unit file @@ -128,6 +218,48 @@ static bool is_multi_instance_service(char *service_name){ } + +/** + * Checks if a service is a multi-instance service for DPU. + * + * @param service_name The name of the service to check. + * @return true if the service is a multi-instance service for DPU, false otherwise. + */ +static bool is_multi_instance_service_for_dpu(const char *service_name) { + if (!smart_switch_npu) { + return false; + } + + const static char* multi_instance_services_for_dpu[] = {"database"}; + char *tmp_service_name = strdup(service_name); + if (tmp_service_name == NULL) { + fprintf(stderr, "Error: Failed to allocate memory for tmp_service_name\n"); + exit(EXIT_FAILURE); + } + + for (size_t i = 0; i < sizeof(multi_instance_services_for_dpu) / + sizeof(multi_instance_services_for_dpu[0]); + i++) { + char* saveptr; + char* token = strtok_r(tmp_service_name, "@", &saveptr); + if (token) { + if (strstr(token, ".service") != NULL) { + /* If we are here, service_name did not have '@' delimiter but + * contains '.service' */ + token = strtok_r(tmp_service_name, ".", &saveptr); + } + } + if (strcmp(tmp_service_name, multi_instance_services_for_dpu[i]) == 0) { + free(tmp_service_name); + return true; + } + } + + free(tmp_service_name); + return false; +} + + static int get_install_targets_from_line(char* target_string, char* install_type, char* targets[], int existing_targets) { /*** Helper fuction for get_install_targets @@ -231,12 +363,18 @@ static void replace_multi_inst_dep(char *src) { service_name = strdup(word); service_name = strtok_r(service_name, ".", &save_ptr2); type = strtok_r(NULL, "\n", &save_ptr2); - if (is_multi_instance_service(word)) { + if (num_asics > 1 && is_multi_instance_service(word)) { for(i = 0; i < num_asics; i++) { snprintf(buf, MAX_BUF_SIZE, "%s=%s@%d.%s\n", token, service_name, i, type); fputs(buf,fp_tmp); } + } else if (smart_switch_npu && is_multi_instance_service_for_dpu(word)) { + for(i = 0; i < num_dpus; i++) { + snprintf(buf, MAX_BUF_SIZE, "%s=%s@%s%d.%s\n", + token, service_name, DPU_PREFIX, i, type); + fputs(buf,fp_tmp); + } } else { snprintf(buf, MAX_BUF_SIZE,"%s=%s.%s\n",token, service_name, type); fputs(buf, fp_tmp); @@ -283,7 +421,8 @@ int get_install_targets(char* unit_file, char* targets[]) { dot_ptr = strchr(instance_name, '.'); *dot_ptr = '\0'; - if((num_asics > 1) && (!is_multi_instance_service(instance_name))) { + if(((num_asics > 1) && (!is_multi_instance_service(instance_name))) + || ((num_dpus > 0) && (!is_multi_instance_service_for_dpu(instance_name)))) { replace_multi_inst_dep(file_path); } free(instance_name); @@ -366,7 +505,14 @@ int get_unit_files(char* unit_files[]) { if ((strcmp(line, "topology.service") == 0) && (num_asics == 1)) { continue; + } else if ((strcmp(line, "midplane-network-dpu.service") == 0) && + !smart_switch_dpu) { + continue; + } else if ((strcmp(line, "midplane-network-npu.service") == 0) && + !smart_switch_npu) { + continue; } + unit_files[num_unit_files] = strdup(line); num_unit_files++; } @@ -379,7 +525,7 @@ int get_unit_files(char* unit_files[]) { } -char* insert_instance_number(char* unit_file, int instance) { +char* insert_instance_number(char* unit_file, int instance, const char *instance_prefix) { /*** Adds an instance number to a systemd template name @@ -395,12 +541,16 @@ char* insert_instance_number(char* unit_file, int instance) { return NULL; } + if (instance_prefix == NULL) { + instance_prefix = ""; + } + /*** suffix is "@.service", set suffix=".service" prefix_len is length of "example@" ***/ prefix_len = ++suffix - unit_file; - ret = asprintf(&instance_name, "%.*s%d%s", prefix_len, unit_file, instance, suffix); + ret = asprintf(&instance_name, "%.*s%s%d%s", prefix_len, unit_file, instance_prefix, instance, suffix); if (ret == -1) { fprintf(stderr, "Error creating instance %d of %s\n", instance, unit_file); return NULL; @@ -410,7 +560,7 @@ char* insert_instance_number(char* unit_file, int instance) { } -static int create_symlink(char* unit, char* target, char* install_dir, int instance) { +static int create_symlink(char* unit, char* target, char* install_dir, int instance, const char *instance_prefix) { struct stat st; char src_path[PATH_MAX]; char dest_path[PATH_MAX]; @@ -425,7 +575,7 @@ static int create_symlink(char* unit, char* target, char* install_dir, int insta unit_instance = strdup(unit); } else { - unit_instance = insert_instance_number(unit, instance); + unit_instance = insert_instance_number(unit, instance, instance_prefix); } strcpy(final_install_dir, install_dir); @@ -467,6 +617,13 @@ static int create_symlink(char* unit, char* target, char* install_dir, int insta } } + if (is_devnull(dest_path)) { + if (remove(dest_path) != 0) { + fprintf(stderr, "Unable to remove existing symlink %s\n", dest_path); + return -1; + } + } + r = symlink(src_path, dest_path); if (r < 0) { @@ -506,22 +663,31 @@ static int install_unit_file(char* unit_file, char* target, char* install_dir) { for (int i = 0; i < num_asics; i++) { if (strstr(target, "@") != NULL) { - target_instance = insert_instance_number(target, i); + target_instance = insert_instance_number(target, i, ""); } else { target_instance = strdup(target); } - r = create_symlink(unit_file, target_instance, install_dir, i); + r = create_symlink(unit_file, target_instance, install_dir, i, ""); if (r < 0) fprintf(stderr, "Error installing %s for target %s\n", unit_file, target_instance); free(target_instance); } - } - else { - r = create_symlink(unit_file, target, install_dir, -1); + } else if (num_dpus > 0 && strstr(unit_file, "@") != NULL) { + // If multi-instance service for DPU + // Install each DPU units to the host main instance only, + // E.g. install database@dpu0.service, database@dpu1.service to multi-user.target.wants + // We don't have case like to install xxx@dpu0.service to swss@dpu0.service.wants + for (int i = 0; i < num_dpus; i++) { + r = create_symlink(unit_file, target, install_dir, i, DPU_PREFIX); + if (r < 0) + fprintf(stderr, "Error installing %s for target %s\n", unit_file, target); + } + } else { + r = create_symlink(unit_file, target, install_dir, -1, ""); if (r < 0) fprintf(stderr, "Error installing %s for target %s\n", unit_file, target); } @@ -530,25 +696,32 @@ static int install_unit_file(char* unit_file, char* target, char* install_dir) { } -int get_num_of_asic() { - /*** - Determines if the current platform is single or multi-ASIC - ***/ - FILE *fp; - char *line = NULL; +/** + * Retrieves the platform name from the machine configuration file. + * If the platform name is already cached, it returns the cached value. + * If the platform name is not found in the configuration file, it sets the platform pointer to NULL. + * + * @return The platform name if found, otherwise NULL. + */ +const char* get_platform() { + if (is_initialized_pointer(platform)) { + if (is_valid_pointer(platform)) { + return platform; + } else { + return NULL; + } + } + + FILE* fp; + char* line = NULL; char* token; - char* platform = NULL; char* saveptr; + char *tmp_platform = NULL; + static char platform_buffer[MAX_PLATFORM_NAME_LEN + 1]; size_t len = 0; ssize_t nread; - bool ans; - char asic_file[512]; - char* str_num_asic; - int num_asic = 1; const char* machine_config_file = get_machine_config_file(); - fp = fopen(machine_config_file, "r"); - if (fp == NULL) { fprintf(stderr, "Failed to open %s\n", machine_config_file); exit(EXIT_FAILURE); @@ -558,12 +731,43 @@ int get_num_of_asic() { if ((strstr(line, "onie_platform") != NULL) || (strstr(line, "aboot_platform") != NULL)) { token = strtok_r(line, "=", &saveptr); - platform = strtok_r(NULL, "=", &saveptr); - strip_trailing_newline(platform); + tmp_platform = strtok_r(NULL, "=", &saveptr); + strip_trailing_newline(tmp_platform); break; } } + if (tmp_platform == NULL) { + set_invalid_pointer((void **)&platform); + fclose(fp); + free(line); + return NULL; + } + strncpy(platform_buffer, tmp_platform, sizeof(platform_buffer) - 1); fclose(fp); + free(line); + + platform = platform_buffer; + return platform; +} + + +int get_num_of_asic() { + /*** + Determines if the current platform is single or multi-ASIC + ***/ + FILE *fp; + char *line = NULL; + char* token; + const char* platform = NULL; + char* saveptr; + size_t len = 0; + ssize_t nread; + char asic_file[512]; + char* str_num_asic; + int num_asic = 1; + + platform = get_platform(); + if(platform != NULL) { snprintf(asic_file, 512, get_asic_conf_format(), platform); fp = fopen(asic_file, "r"); @@ -587,6 +791,331 @@ int get_num_of_asic() { } + +/** + * Retrieves the platform information. + * + * This function reads the platform information from a JSON file and returns it as a JSON object. + * If the platform information has already been retrieved, it returns the cached value. + * + * @return The platform information as a JSON object, or NULL if it fails to retrieve or parse the information. + */ +const struct json_object* get_platform_info() { + if (is_initialized_pointer(platform_info)) { + if (is_valid_pointer(platform_info)) { + return platform_info; + } else { + return NULL; + } + } + + char platform_file_path[PATH_MAX]; + const char* platform = get_platform(); + if (platform == NULL) { + set_invalid_pointer((void **)&platform_info); + return NULL; + } + snprintf(platform_file_path, sizeof(platform_file_path), get_platform_file_format(), platform); + + FILE *fp = fopen(platform_file_path, "r"); + if (fp == NULL) { + fprintf(stdout, "Failed to open %s\n", platform_file_path); + set_invalid_pointer((void **)&platform_info); + return NULL; + } + if (fseek(fp, 0, SEEK_END) != 0) { + fprintf(stdout, "Failed to seek to end of %s\n", platform_file_path); + fclose(fp); + exit(EXIT_FAILURE); + } + size_t fsize = ftell(fp); + if (fseek(fp, 0, SEEK_SET) != 0) { + fprintf(stdout, "Failed to seek to beginning of %s\n", platform_file_path); + fclose(fp); + exit(EXIT_FAILURE); + } + char *platform_json = malloc(fsize + 1); + if (platform_json == NULL) { + fprintf(stdout, "Failed to allocate memory for %s\n", platform_file_path); + fclose(fp); + exit(EXIT_FAILURE); + } + if (fread(platform_json, fsize, 1, fp) != 1) { + fprintf(stdout, "Failed to read %s\n", platform_file_path); + free(platform_json); + fclose(fp); + exit(EXIT_FAILURE); + } + fclose(fp); + platform_json[fsize] = '\0'; + + platform_info = json_tokener_parse(platform_json); + if (platform_info == NULL) { + fprintf(stderr, "Failed to parse %s\n", platform_file_path); + free(platform_json); + return NULL; + } + free(platform_json); + return platform_info; +} + + +/** + * Checks if the platform is a smart switch with an NPU (Network Processing Unit). + * + * @return true if the platform is a smart switch with an NPU, false otherwise. + */ +static bool is_smart_switch_npu() { + struct json_object *dpus; + const struct json_object *platform_info = get_platform_info(); + if (platform_info == NULL) { + return false; + } + return json_object_object_get_ex(platform_info, "DPUS", &dpus); +} + + +/** + * Checks if the current platform is a smart switch with a DPU (Data Processing Unit). + * + * @return true if the platform is a smart switch with a DPU, false otherwise. + */ +static bool is_smart_switch_dpu() { + struct json_object *dpu; + const struct json_object *platform_info = get_platform_info(); + if (platform_info == NULL) { + return false; + } + return json_object_object_get_ex(platform_info, "DPU", &dpu); +} + + +/** + * @brief Retrieves the number of DPUs (Data Processing Units). + * + * This function retrieves the number of DPUs by accessing the platform information + * and extracting the "DPUS" array from it. If the platform information is not available + * or the "DPUS" array does not exist, the function returns 0. + * + * @return The number of DPUs. + */ +static int get_num_of_dpu() { + struct json_object *dpus; + const struct json_object *platform_info = get_platform_info(); + if (platform_info == NULL) { + return 0; + } + if (!json_object_object_get_ex(platform_info, "DPUS", &dpus)) { + return 0; + } + size_t num_dpu = 0; + json_object_object_foreach(dpus, key, val) { + num_dpu++; + } + return num_dpu; +} + + +/** + * Installs the network service. + * + * This function installs the network service by creating a symlink + * to the network service file in the appropriate directory. + * + * @param unit_name The name of the network unit to install. + * @return 0 if the network unit is installed successfully, or -1 if an error occurs. + */ +static int install_network_unit(const char* unit_name) { + assert(unit_name); + + const char* unit_type = strrchr(unit_name, '.'); + if (unit_type == NULL) { + fprintf(stderr, "Invalid network unit %s\n", unit_name); + return -1; + } + unit_type++; + + char install_path[PATH_MAX] = {0}; + char original_path[PATH_MAX] = {0}; + const char* subdir; + if (strcmp(unit_type, "netdev") == 0 || strcmp(unit_type, "network") == 0) { + subdir = "/network/"; + } else { + fprintf(stderr, "Invalid network unit %s\n", unit_type); + return -1; + } + + strcpy(install_path, get_etc_systemd()); + strcat(install_path, subdir); + strcat(install_path, unit_name); + strcpy(original_path, get_lib_systemd()); + strcat(original_path, subdir); + strcat(original_path, unit_name); + + struct stat st; + + if (stat((const char *)install_path, &st) == 0) { + // If the file already exists, remove it + if (S_ISDIR(st.st_mode)) { + fprintf(stderr, "Error: %s is a directory\n", install_path); + return -1; + } + if (remove(install_path) != 0) { + fprintf(stderr, "Error removing existing file %s\n", install_path); + return -1; + } + } + + if (is_devnull(install_path)) { + if (remove(install_path) != 0) { + fprintf(stderr, "Unable to remove existing symlink %s\n", install_path); + return -1; + } + } + + if (symlink(original_path, install_path) != 0) { + if (errno == EEXIST) + return 0; + fprintf(stderr, "Error creating symlink %s -> %s (%s)\n", install_path, original_path, strerror(errno)); + return -1; + } + + return 0; +} + + +static int render_network_service_for_smart_switch() { + if (!smart_switch_npu) { + return 0; + } + + // Render Before instruction for midplane network with database service + if (num_dpus == 0) { + return 0; + } + + char buffer_instruction[MAX_BUF_SIZE] = {0}; + strcpy(buffer_instruction, "\nBefore="); + for (size_t i = 0; i < num_dpus; i++) { + char *unit; + asprintf(&unit, "database@dpu%ld.service", i); + strcat(buffer_instruction, unit); + free(unit); + if (i != num_dpus - 1) { + strcat(buffer_instruction, " "); + } + } + + char unit_path[PATH_MAX] = { 0 }; + strcpy(unit_path, get_unit_file_prefix()); + strcat(unit_path, "/midplane-network-npu.service"); + + FILE *fp = fopen(unit_path, "r"); + if (fp == NULL) { + fprintf(stderr, "Failed to open %s\n", unit_path); + return -1; + } + fseek(fp, 0, SEEK_END); + size_t file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + size_t len = file_size + strlen(buffer_instruction) + 1; + char *unit_content = malloc(len); + if (unit_content == NULL) { + fprintf(stderr, "Failed to allocate memory for %s\n", unit_path); + fclose(fp); + exit(EXIT_FAILURE); + } + if (fread(unit_content, file_size, 1, fp) != 1) { + fprintf(stderr, "Failed to read %s\n", unit_path); + free(unit_content); + fclose(fp); + exit(EXIT_FAILURE); + } + fclose(fp); + + // Find insert point for Before instruction + char *insert_point = strstr(unit_content, "[Unit]"); + insert_point += strlen("[Unit]"); + // Move the rest of the file to make room for the Before instruction + memmove(insert_point + strlen(buffer_instruction), insert_point, file_size - (insert_point - unit_content)); + // Insert the Before instruction + memcpy(insert_point, buffer_instruction, strlen(buffer_instruction)); + // Remove original Before instruction + insert_point += strlen(buffer_instruction); + char *before_start = strstr(insert_point, "Before="); + while (before_start != NULL) { + char *before_end = strchr(before_start, '\n'); + if (before_end == NULL) { + before_end = before_start + strlen(before_start); + } else { + // Include newline character + before_end += 1; + } + const char *target_service = strstr(before_start, "database@dpu"); + if (target_service != NULL && target_service < before_end) { + memmove(before_start, before_end, strlen(before_end) + 1); + } else { + before_start = before_end; + } + before_start = strstr(before_start, "Before="); + } + // Write the modified unit file + fp = fopen(unit_path, "w"); + if (fp == NULL) { + fprintf(stderr, "Failed to open %s\n", unit_path); + free(unit_content); + exit(EXIT_FAILURE); + } + if (fwrite(unit_content, strlen(unit_content), 1, fp) != 1) { + fprintf(stderr, "Failed to write %s\n", unit_path); + free(unit_content); + fclose(fp); + exit(EXIT_FAILURE); + } + fclose(fp); + free(unit_content); + + return 0; +} + + +static int install_network_service_for_smart_switch() { + const char** network_units = NULL; + if (smart_switch_npu) { + static const char* npu_network_units[] = { + "bridge-midplane.netdev", + "bridge-midplane.network", + "dummy-midplane.netdev", + "dummy-midplane.network", + "midplane-network-npu.network", + NULL + }; + network_units = npu_network_units; + } else if (smart_switch_dpu) { + static const char* dpu_network_units[] = { + "midplane-network-dpu.network", + NULL + }; + network_units = dpu_network_units; + } else { + return -1; + } + + if (network_units == NULL) { + return 0; + } + + while(*network_units) { + if (install_network_unit(*network_units) != 0) { + return -1; + } + network_units++; + } + + return 0; +} + + int ssg_main(int argc, char **argv) { char* unit_files[MAX_NUM_UNITS]; char install_dir[PATH_MAX]; @@ -599,20 +1128,41 @@ int ssg_main(int argc, char **argv) { int num_targets; int r; +#ifdef _SSG_UNITTEST + clean_up_cache(); +#endif + if (argc <= 1) { fputs("Installation directory required as argument\n", stderr); return 1; } num_asics = get_num_of_asic(); + smart_switch_npu = is_smart_switch_npu(); + smart_switch_dpu = is_smart_switch_dpu(); + smart_switch = smart_switch_npu || smart_switch_dpu; + num_dpus = get_num_of_dpu(); + strcpy(install_dir, argv[1]); strcat(install_dir, "/"); num_unit_files = get_unit_files(unit_files); + // Install and render midplane network service for smart switch + if (smart_switch) { + if (render_network_service_for_smart_switch() != 0) { + return -1; + } + if (install_network_service_for_smart_switch() != 0) { + return -1; + } + } + // For each unit file, get the installation targets and install the unit for (int i = 0; i < num_unit_files; i++) { unit_instance = strdup(unit_files[i]); - if ((num_asics == 1) && strstr(unit_instance, "@") != NULL) { + if ((num_asics == 1 && + !is_multi_instance_service_for_dpu(unit_instance)) && + strstr(unit_instance, "@") != NULL) { prefix = strdup(strtok_r(unit_instance, "@", &saveptr)); suffix = strdup(strtok_r(NULL, "@", &saveptr)); @@ -647,6 +1197,10 @@ int ssg_main(int argc, char **argv) { } free(multi_instance_services); + if (is_valid_pointer(platform_info)) { + json_object_put(platform_info); + } + return 0; } diff --git a/src/systemd-sonic-generator/systemd-sonic-generator.h b/src/systemd-sonic-generator/systemd-sonic-generator.h index 25c179caa0bf..8695951fb211 100644 --- a/src/systemd-sonic-generator/systemd-sonic-generator.h +++ b/src/systemd-sonic-generator/systemd-sonic-generator.h @@ -15,17 +15,21 @@ extern const char* UNIT_FILE_PREFIX; extern const char* CONFIG_FILE; extern const char* MACHINE_CONF_FILE; extern const char* ASIC_CONF_FORMAT; -extern const char* g_unit_file_prefix; +extern const char* PLATFORM_FILE_FORMAT; +extern const char* g_lib_systemd; +extern const char* g_etc_systemd; +extern const char* g_unit_file_prefix; extern const char* g_config_file; extern const char* g_machine_config_file; extern const char* g_asic_conf_format; +extern const char* g_platform_file_format; /* C-functions under test */ extern const char* get_unit_file_prefix(); extern const char* get_config_file(); extern const char* get_machine_config_file(); extern const char* get_asic_conf_format(); -extern char* insert_instance_number(char* unit_file, int instance); +extern char* insert_instance_number(char* unit_file, int instance, const char *instance_prefix); extern int ssg_main(int argc, char** argv); extern int get_num_of_asic(); extern int get_install_targets(char* unit_file, char* targets[]); diff --git a/src/systemd-sonic-generator/tests/testfiles/bridge-midplane.netdev b/src/systemd-sonic-generator/tests/testfiles/bridge-midplane.netdev new file mode 120000 index 000000000000..a866bb529edc --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/bridge-midplane.netdev @@ -0,0 +1 @@ +../../../../files/image_config/midplane-network/bridge-midplane.netdev \ No newline at end of file diff --git a/src/systemd-sonic-generator/tests/testfiles/bridge-midplane.network b/src/systemd-sonic-generator/tests/testfiles/bridge-midplane.network new file mode 120000 index 000000000000..cb7ad4965151 --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/bridge-midplane.network @@ -0,0 +1 @@ +../../../../files/image_config/midplane-network/bridge-midplane.network \ No newline at end of file diff --git a/src/systemd-sonic-generator/tests/testfiles/database.service b/src/systemd-sonic-generator/tests/testfiles/database.service new file mode 100644 index 000000000000..c730d07c950f --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/database.service @@ -0,0 +1,14 @@ +[Unit] +Description=Database container +StartLimitIntervalSec=1200 +StartLimitBurst=3 + +[Service] +User=root +ExecStartPre=/usr/local/bin/database.sh start +ExecStart=/usr/local/bin/database.sh wait +ExecStop=/usr/local/bin/database.sh stop +RestartSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd-sonic-generator/tests/testfiles/database@.service b/src/systemd-sonic-generator/tests/testfiles/database@.service new file mode 100644 index 000000000000..8a1719e05632 --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/database@.service @@ -0,0 +1,14 @@ +[Unit] +Description=Database container +StartLimitIntervalSec=1200 +StartLimitBurst=3 + +[Service] +User=root +ExecStartPre=/usr/local/bin/database.sh start %i +ExecStart=/usr/local/bin/database.sh wait %i +ExecStop=/usr/local/bin/database.sh stop %i +RestartSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd-sonic-generator/tests/testfiles/dummy-midplane.netdev b/src/systemd-sonic-generator/tests/testfiles/dummy-midplane.netdev new file mode 120000 index 000000000000..2d4fec18f2a2 --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/dummy-midplane.netdev @@ -0,0 +1 @@ +../../../../files/image_config/midplane-network/dummy-midplane.netdev \ No newline at end of file diff --git a/src/systemd-sonic-generator/tests/testfiles/dummy-midplane.network b/src/systemd-sonic-generator/tests/testfiles/dummy-midplane.network new file mode 120000 index 000000000000..e8278cf8eff8 --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/dummy-midplane.network @@ -0,0 +1 @@ +../../../../files/image_config/midplane-network/dummy-midplane.network \ No newline at end of file diff --git a/src/systemd-sonic-generator/tests/testfiles/midplane-network-dpu.network b/src/systemd-sonic-generator/tests/testfiles/midplane-network-dpu.network new file mode 120000 index 000000000000..e042f502c78f --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/midplane-network-dpu.network @@ -0,0 +1 @@ +../../../../files/image_config/midplane-network/midplane-network-dpu.network \ No newline at end of file diff --git a/src/systemd-sonic-generator/tests/testfiles/midplane-network-dpu.service b/src/systemd-sonic-generator/tests/testfiles/midplane-network-dpu.service new file mode 120000 index 000000000000..e11f6138fbca --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/midplane-network-dpu.service @@ -0,0 +1 @@ +../../../../files/image_config/midplane-network/midplane-network-dpu.service \ No newline at end of file diff --git a/src/systemd-sonic-generator/tests/testfiles/midplane-network-npu.network b/src/systemd-sonic-generator/tests/testfiles/midplane-network-npu.network new file mode 120000 index 000000000000..66921589156e --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/midplane-network-npu.network @@ -0,0 +1 @@ +../../../../files/image_config/midplane-network/midplane-network-npu.network \ No newline at end of file diff --git a/src/systemd-sonic-generator/tests/testfiles/midplane-network-npu.service b/src/systemd-sonic-generator/tests/testfiles/midplane-network-npu.service new file mode 120000 index 000000000000..0a8b423a4688 --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/midplane-network-npu.service @@ -0,0 +1 @@ +../../../../files/image_config/midplane-network/midplane-network-npu.service \ No newline at end of file