meta-google: dhcp-done: Adding status report
Previously dhcp-done only sends status code, this one provides the
capability to send status code + status message for better
troubleshooting.
Provide a way to let other process upgrade the status.
Tested: Unit test passed.
Change-Id: I9c689f90502a32b586c41e3491ad47ebc78fcc38
Signed-off-by: Yuxiao Zhang <yuxiaozhang@google.com>
diff --git a/subprojects/dhcp-done/dhcp-done.cpp b/subprojects/dhcp-done/dhcp-done.cpp
index d6c5c96..7ea8761 100644
--- a/subprojects/dhcp-done/dhcp-done.cpp
+++ b/subprojects/dhcp-done/dhcp-done.cpp
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "file-io.hpp"
+
#include <sdeventplus/event.hpp>
#include <sdeventplus/source/io.hpp>
#include <stdplus/fd/create.hpp>
@@ -23,11 +25,6 @@
// A privileged port that is reserved for querying BMC DHCP completion.
// This is well known by the clients querying the status.
constexpr uint16_t kListenPort = 23;
-enum : uint8_t
-{
- DONE = 0,
- POWERCYCLE = 1,
-};
stdplus::ManagedFd createListener()
{
@@ -43,30 +40,8 @@
return sock;
}
-int main(int argc, char* argv[])
+int main()
{
- if (argc != 2)
- {
- stdplus::println(stderr, "Invalid parameter count");
- return 1;
- }
-
- std::vector<uint8_t> data;
-
- if (argv[1] == "POWERCYCLE"sv)
- {
- data.push_back(POWERCYCLE);
- }
- else if (argv[1] == "DONE"sv)
- {
- data.push_back(DONE);
- }
- else
- {
- stdplus::println(stderr, "Invalid parameter");
- return 1;
- }
-
try
{
auto listener = createListener();
@@ -76,6 +51,20 @@
[&](sdeventplus::source::IO&, int, uint32_t) {
while (auto fd = stdplus::fd::accept(listener))
{
+ std::string data;
+ try
+ {
+ data = fileRead(statusFile);
+ }
+ catch (const std::exception& e)
+ {
+ // we don't want to fail the upgrade process, set the status
+ // to ONGOING
+ data.push_back(2);
+ data.append("Failed to read status ");
+ data.append(e.what());
+ }
+
stdplus::fd::sendExact(*fd, data, stdplus::fd::SendFlags(0));
}
});
diff --git a/subprojects/dhcp-done/dhcp-done.service.in b/subprojects/dhcp-done/dhcp-done.service.in
new file mode 100644
index 0000000..1c66551
--- /dev/null
+++ b/subprojects/dhcp-done/dhcp-done.service.in
@@ -0,0 +1,9 @@
+[Unit]
+Description=gBMC DHCP Status Daemon
+
+[Service]
+Restart=on-failure
+ExecStart=@@BIN@ dhcp-done
+
+[Install]
+WantedBy=multi-user.target
diff --git a/subprojects/dhcp-done/dhcp-done@.service.in b/subprojects/dhcp-done/dhcp-done@.service.in
deleted file mode 100644
index ece864b..0000000
--- a/subprojects/dhcp-done/dhcp-done@.service.in
+++ /dev/null
@@ -1,6 +0,0 @@
-[Unit]
-Description=gBMC DHCP Complete
-
-[Service]
-Restart=on-failure
-ExecStart=@@BIN@ dhcp-done %I
diff --git a/subprojects/dhcp-done/file-io.cpp b/subprojects/dhcp-done/file-io.cpp
new file mode 100644
index 0000000..321cc6d
--- /dev/null
+++ b/subprojects/dhcp-done/file-io.cpp
@@ -0,0 +1,39 @@
+#include "file-io.hpp"
+
+#include <fcntl.h>
+#include <sys/file.h>
+#include <unistd.h>
+
+#include <stdplus/fd/atomic.hpp>
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/fmt.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <stdplus/fd/ops.hpp>
+#include <stdplus/print.hpp>
+
+#include <cerrno>
+#include <fstream>
+#include <iostream>
+#include <string>
+
+using ::stdplus::fd::ManagedFd;
+using std::literals::string_view_literals::operator""sv;
+
+// Function to read contents from a file (with locking)
+std::string fileRead(const fs::path& filename)
+{
+ // Open the file in read mode
+ ManagedFd fd = stdplus::fd::open(std::string(filename).c_str(),
+ stdplus::fd::OpenAccess::ReadOnly);
+ return stdplus::fd::readAll<std::string>(fd);
+}
+
+// Function to write contents to a file atomically
+void fileWrite(const fs::path& filename, const std::string& data)
+{
+ stdplus::fd::AtomicWriter writer(filename, 0644);
+ stdplus::fd::FormatBuffer out(writer);
+ out.appends(data);
+ out.flush();
+ writer.commit();
+}
diff --git a/subprojects/dhcp-done/file-io.hpp b/subprojects/dhcp-done/file-io.hpp
new file mode 100644
index 0000000..93458ee
--- /dev/null
+++ b/subprojects/dhcp-done/file-io.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <filesystem>
+#include <string>
+
+namespace fs = std::filesystem;
+constexpr auto statusFile = "/run/dhcp_status";
+
+// Function to read contents from a file
+std::string fileRead(const fs::path& filename);
+
+// Function to write contents to a file atomically
+void fileWrite(const fs::path& filename, const std::string& data);
diff --git a/subprojects/dhcp-done/meson.build b/subprojects/dhcp-done/meson.build
index abc449c..0bc7198 100644
--- a/subprojects/dhcp-done/meson.build
+++ b/subprojects/dhcp-done/meson.build
@@ -30,21 +30,45 @@
]
libexecdir = get_option('prefix') / get_option('libexecdir')
+bindir = get_option('prefix') / get_option('bindir')
+
+fileio_lib = static_library(
+ 'fileio',
+ [
+ 'file-io.cpp',
+ ],
+ implicit_include_directories: false)
executable(
'dhcp-done',
'dhcp-done.cpp',
implicit_include_directories: false,
dependencies: deps,
+ link_with : fileio_lib,
install: true,
install_dir: libexecdir)
+executable(
+ 'update-dhcp-status',
+ 'update-dhcp-status.cpp',
+ implicit_include_directories: false,
+ dependencies: deps,
+ link_with : fileio_lib,
+ install: true,
+ install_dir: bindir)
+
systemd = dependency('systemd')
systemunitdir = systemd.get_variable('systemdsystemunitdir')
configure_file(
configuration: {'BIN': libexecdir / 'dhcp-done'},
- input: 'dhcp-done@.service.in',
- output: 'dhcp-done@.service',
+ input: 'dhcp-done.service.in',
+ output: 'dhcp-done.service',
install_mode: 'rw-r--r--',
install_dir: systemunitdir)
+
+build_tests = get_option('tests')
+
+#if not build_tests.disabled()
+subdir('test')
+#endif
diff --git a/subprojects/dhcp-done/meson.options b/subprojects/dhcp-done/meson.options
new file mode 100644
index 0000000..0fc2767
--- /dev/null
+++ b/subprojects/dhcp-done/meson.options
@@ -0,0 +1 @@
+option('tests', type: 'feature', description: 'Build tests')
diff --git a/subprojects/dhcp-done/test/fileio_test.cpp b/subprojects/dhcp-done/test/fileio_test.cpp
new file mode 100644
index 0000000..8a79cf2
--- /dev/null
+++ b/subprojects/dhcp-done/test/fileio_test.cpp
@@ -0,0 +1,34 @@
+#include "../file-io.hpp"
+
+#include <stdio.h>
+#include <sys/file.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+TEST(TestFileIO, TestFileReadWrite)
+{
+ std::string testFile = "./tmp_test_file";
+
+ std::string testStatus, testStatusUpdated;
+
+ testStatus.push_back(2);
+ testStatus.append("image downloading in progress");
+
+ testStatusUpdated.push_back(0);
+ testStatusUpdated.append("finished netboot");
+
+ fileWrite(testFile, testStatus);
+
+ EXPECT_TRUE(testStatus == fileRead(testFile));
+
+ fileWrite(testFile, testStatusUpdated);
+
+ EXPECT_TRUE(testStatusUpdated == fileRead(testFile));
+
+ remove(testFile.c_str());
+}
diff --git a/subprojects/dhcp-done/test/meson.build b/subprojects/dhcp-done/test/meson.build
new file mode 100644
index 0000000..de5354f
--- /dev/null
+++ b/subprojects/dhcp-done/test/meson.build
@@ -0,0 +1,39 @@
+# Copyright 2024 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+gtest = dependency('gtest', main: true, disabler: true, required: false)
+if not gtest.found()
+ gtest_proj = import('cmake').subproject(
+ 'googletest',
+ cmake_options: [
+ '-DCMAKE_CXX_FLAGS=-Wno-pedantic',
+ ],
+ required: false)
+ if gtest_proj.found()
+ gtest = declare_dependency(
+ dependencies: [
+ dependency('threads'),
+ gtest_proj.dependency('gtest'),
+ gtest_proj.dependency('gtest_main'),
+ ])
+ else
+ assert(not build_tests.allowed(), 'Googletest is required')
+ endif
+endif
+
+test('fileio_test', executable('fileio_test',
+ ['fileio_test.cpp'],
+ implicit_include_directories: false,
+ dependencies: [gtest, dependency('stdplus')],
+ link_with : fileio_lib))
diff --git a/subprojects/dhcp-done/update-dhcp-status.cpp b/subprojects/dhcp-done/update-dhcp-status.cpp
new file mode 100644
index 0000000..b23706e
--- /dev/null
+++ b/subprojects/dhcp-done/update-dhcp-status.cpp
@@ -0,0 +1,63 @@
+#include "file-io.hpp"
+
+#include <stdplus/print.hpp>
+
+#include <cstring>
+#include <iostream>
+
+static void printUsage()
+{
+ stdplus::println(stderr, "Usage: update_dhcp_status <state> <message>");
+ stdplus::println(stderr,
+ "<state> is one of 'DONE', 'POWERCYCLE' or 'ONGOING'");
+}
+
+static int genStatusCode(char* state)
+{
+ if (std::strcmp(state, "DONE") == 0)
+ {
+ return 0;
+ }
+ else if (std::strcmp(state, "POWERCYCLE") == 0)
+ {
+ return 1;
+ }
+ else if (std::strcmp(state, "ONGOING") == 0)
+ {
+ return 2;
+ }
+
+ return -1;
+}
+
+int main(int argc, char* argv[])
+{
+ if (argc != 3)
+ {
+ printUsage();
+ return 1;
+ }
+
+ int statusCode = genStatusCode(argv[1]);
+
+ if (statusCode == -1)
+ {
+ printUsage();
+ return 1;
+ }
+
+ try
+ {
+ std::string status;
+ status.push_back(statusCode);
+ status.append(argv[2]);
+ fileWrite(statusFile, status);
+ }
+ catch (const std::exception& e)
+ {
+ stdplus::println(stderr, "Failed to update status file {}", e.what());
+ return 1;
+ }
+
+ return 0;
+}