1 // Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 #include <config.h>
8
9 #include <exceptions/exceptions.h>
10 #include <cc/data.h>
11 #include <process/daemon.h>
12 #include <process/config_base.h>
13 #include <process/log_parser.h>
14 #include <log/logger_support.h>
15
16 #include <gtest/gtest.h>
17
18 #include <sys/wait.h>
19
20 using namespace isc;
21 using namespace isc::process;
22 using namespace isc::data;
23
24 namespace isc {
25 namespace process {
26
27 // @brief Derived Daemon class
28 class DaemonImpl : public Daemon {
29 public:
30 static std::string getVersion(bool extended);
31
32 using Daemon::makePIDFileName;
33 };
34
getVersion(bool extended)35 std::string DaemonImpl::getVersion(bool extended) {
36 if (extended) {
37 return (std::string("EXTENDED"));
38 } else {
39 return (std::string("BASIC"));
40 }
41 }
42
43 };
44 };
45
46 namespace {
47
48 /// @brief Daemon Test test fixture class
49 class DaemonTest : public ::testing::Test {
50 public:
51 /// @brief Constructor
DaemonTest()52 DaemonTest() : env_copy_() {
53 // Take a copy of KEA_PIDFILE_DIR environment variable value
54 char *env_value = getenv("KEA_PIDFILE_DIR");
55 if (env_value) {
56 env_copy_ = std::string(env_value);
57 }
58 }
59
60 /// @brief Destructor
61 ///
62 /// As some of the tests have the side-effect of altering the logging
63 /// settings (when configureLogger is called), the logging is reset to
64 /// the default after each test completes.
~DaemonTest()65 ~DaemonTest() {
66 isc::log::setDefaultLoggingOutput();
67 // Restore KEA_PIDFILE_DIR environment variable value
68 if (env_copy_.empty()) {
69 static_cast<void>(unsetenv("KEA_PIDFILE_DIR"));
70 } else {
71 static_cast<void>(setenv("KEA_PIDFILE_DIR", env_copy_.c_str(), 1));
72 }
73 }
74
75 private:
76 /// @brief copy of KEA_PIDFILE_DIR environment variable value
77 std::string env_copy_;
78 };
79
80
81 // Very simple test. Checks whether Daemon can be instantiated and its
82 // default parameters are sane
TEST_F(DaemonTest,constructor)83 TEST_F(DaemonTest, constructor) {
84 // Disable KEA_PIDFILE_DIR
85 EXPECT_EQ(0, unsetenv("KEA_PIDFILE_DIR"));
86
87 EXPECT_NO_THROW(Daemon instance1);
88
89 // Check only instance values.
90 Daemon instance2;
91 EXPECT_TRUE(instance2.getConfigFile().empty());
92 EXPECT_EQ(std::string(DATA_DIR), instance2.getPIDFileDir());
93 EXPECT_TRUE(instance2.getPIDFileName().empty());
94 }
95
96 // Verify config file accessors
TEST_F(DaemonTest,getSetConfigFile)97 TEST_F(DaemonTest, getSetConfigFile) {
98 Daemon instance;
99
100 EXPECT_NO_THROW(instance.setConfigFile("test.txt"));
101 EXPECT_EQ("test.txt", instance.getConfigFile());
102 EXPECT_NO_THROW(instance.checkConfigFile());
103 }
104
105 // Verify config file checker.
TEST_F(DaemonTest,checkConfigFile)106 TEST_F(DaemonTest, checkConfigFile) {
107 Daemon instance;
108
109 EXPECT_THROW(instance.checkConfigFile(), BadValue);
110 EXPECT_NO_THROW(instance.setConfigFile("/tmp/"));
111 EXPECT_THROW(instance.checkConfigFile(), BadValue);
112 EXPECT_NO_THROW(instance.setConfigFile("/tmp/test.txt"));
113 EXPECT_NO_THROW(instance.checkConfigFile());
114 }
115
116 // Verify process name accessors
TEST_F(DaemonTest,getSetProcName)117 TEST_F(DaemonTest, getSetProcName) {
118 Daemon instance;
119
120 EXPECT_NO_THROW(instance.setProcName("myproc"));
121 EXPECT_EQ("myproc", instance.getProcName());
122 }
123
124 // Verify PID file directory name accessors
TEST_F(DaemonTest,getSetPIDFileDir)125 TEST_F(DaemonTest, getSetPIDFileDir) {
126 Daemon instance;
127
128 EXPECT_NO_THROW(instance.setPIDFileDir("/tmp"));
129 EXPECT_EQ("/tmp", instance.getPIDFileDir());
130 }
131
132 // Verify PID file name accessors.
TEST_F(DaemonTest,setPIDFileName)133 TEST_F(DaemonTest, setPIDFileName) {
134 Daemon instance;
135
136 // Verify that PID file name may not be set to empty
137 EXPECT_THROW(instance.setPIDFileName(""), BadValue);
138
139 EXPECT_NO_THROW(instance.setPIDFileName("myproc"));
140 EXPECT_EQ("myproc", instance.getPIDFileName());
141
142 // Verify that setPIDFileName cannot be called twice on the same instance.
143 EXPECT_THROW(instance.setPIDFileName("again"), InvalidOperation);
144 }
145
146 // Test the getVersion() redefinition
TEST_F(DaemonTest,getVersion)147 TEST_F(DaemonTest, getVersion) {
148 EXPECT_THROW(Daemon::getVersion(false), NotImplemented);
149
150 ASSERT_NO_THROW(DaemonImpl::getVersion(false));
151
152 EXPECT_EQ(DaemonImpl::getVersion(false), "BASIC");
153
154 ASSERT_NO_THROW(DaemonImpl::getVersion(true));
155
156 EXPECT_EQ(DaemonImpl::getVersion(true), "EXTENDED");
157 }
158
159 // Verify makePIDFileName method
TEST_F(DaemonTest,makePIDFileName)160 TEST_F(DaemonTest, makePIDFileName) {
161 DaemonImpl instance;
162
163 // Verify that config file cannot be blank
164 instance.setProcName("notblank");
165 EXPECT_THROW(instance.makePIDFileName(), InvalidOperation);
166
167 // Verify that proc name cannot be blank
168 instance.setProcName("");
169 instance.setConfigFile("notblank");
170 EXPECT_THROW(instance.makePIDFileName(), InvalidOperation);
171
172 // Verify that config file must contain a file name
173 instance.setProcName("myproc");
174 instance.setConfigFile(".txt");
175 EXPECT_THROW(instance.makePIDFileName(), BadValue);
176 instance.setConfigFile("/tmp/");
177 EXPECT_THROW(instance.makePIDFileName(), BadValue);
178
179 // Given a valid config file name and proc name we should good to go
180 instance.setConfigFile("/tmp/test.conf");
181 std::string name;
182 EXPECT_NO_THROW(name = instance.makePIDFileName());
183
184 // Make sure the name is as we expect
185 std::ostringstream stream;
186 stream << instance.getPIDFileDir() << "/test.myproc.pid";
187 EXPECT_EQ(stream.str(), name);
188
189 // Verify that the default directory can be overridden
190 instance.setPIDFileDir("/tmp");
191 EXPECT_NO_THROW(name = instance.makePIDFileName());
192 EXPECT_EQ("/tmp/test.myproc.pid", name);
193 }
194
195 // Verifies the creation a PID file and that a pre-existing PID file
196 // which points to a live PID causes a throw.
TEST_F(DaemonTest,createPIDFile)197 TEST_F(DaemonTest, createPIDFile) {
198 DaemonImpl instance;
199
200 instance.setConfigFile("test.conf");
201 instance.setProcName("daemon_test");
202 instance.setPIDFileDir(TEST_DATA_BUILDDIR);
203
204 EXPECT_NO_THROW(instance.createPIDFile());
205
206 std::ostringstream stream;
207 stream << TEST_DATA_BUILDDIR << "/test.daemon_test.pid";
208 EXPECT_EQ(stream.str(), instance.getPIDFileName());
209
210 // If we try again, we should see our own PID file and fail
211 EXPECT_THROW(instance.createPIDFile(), DaemonPIDExists);
212 }
213
214 // Verifies that a pre-existing PID file which points to a dead PID
215 // is overwritten.
TEST_F(DaemonTest,createPIDFileOverwrite)216 TEST_F(DaemonTest, createPIDFileOverwrite) {
217 DaemonImpl instance;
218
219 // We're going to use fork to generate a PID we KNOW is dead.
220 int pid = fork();
221 ASSERT_GE(pid, 0);
222
223 if (pid == 0) {
224 // This is the child, die right away. Tragic, no?
225 _exit (0);
226 }
227
228 // Back in the parent test, we need to wait for the child to die
229 // As with debugging waitpid() can be interrupted ignore EINTR.
230 int stat;
231 int ret;
232 do {
233 ret = waitpid(pid, &stat, 0);
234 } while ((ret == -1) && (errno == EINTR));
235 ASSERT_EQ(ret, pid);
236
237 // Ok, so we should now have a PID that we know to be dead.
238 // Let's use it to create a PID file.
239 instance.setConfigFile("test.conf");
240 instance.setProcName("daemon_test");
241 instance.setPIDFileDir(TEST_DATA_BUILDDIR);
242 EXPECT_NO_THROW(instance.createPIDFile(pid));
243
244 // If we try to create the PID file again, this should work.
245 EXPECT_NO_THROW(instance.createPIDFile());
246 }
247
248 // Verifies that Daemon destruction deletes the PID file
TEST_F(DaemonTest,PIDFileCleanup)249 TEST_F(DaemonTest, PIDFileCleanup) {
250 boost::shared_ptr<DaemonImpl> instance;
251 instance.reset(new DaemonImpl);
252
253 instance->setConfigFile("test.conf");
254 instance->setProcName("daemon_test");
255 instance->setPIDFileDir(TEST_DATA_BUILDDIR);
256 EXPECT_NO_THROW(instance->createPIDFile());
257
258 // If we try again, we should see our own PID file
259 EXPECT_THROW(instance->createPIDFile(), DaemonPIDExists);
260
261 // Save the pid file name
262 std::string pid_file_name = instance->getPIDFileName();
263
264 // Now delete the Daemon instance. This should remove the
265 // PID file.
266 instance.reset();
267
268 struct stat stat_buf;
269 ASSERT_EQ(-1, stat(pid_file_name.c_str(), &stat_buf));
270 EXPECT_EQ(errno, ENOENT);
271 }
272
273 // Checks that configureLogger method is behaving properly.
274 // More dedicated tests are available for LogConfigParser class.
275 // See logger_unittest.cc
TEST_F(DaemonTest,parsingConsoleOutput)276 TEST_F(DaemonTest, parsingConsoleOutput) {
277 Daemon::setVerbose(false);
278
279 // Storage - parsed configuration will be stored here
280 ConfigPtr storage(new ConfigBase());
281
282 const char* config_txt =
283 "{ \"loggers\": ["
284 " {"
285 " \"name\": \"kea\","
286 " \"output_options\": ["
287 " {"
288 " \"output\": \"stdout\""
289 " }"
290 " ],"
291 " \"debuglevel\": 99,"
292 " \"severity\": \"DEBUG\""
293 " }"
294 "]}";
295 ConstElementPtr config = Element::fromJSON(config_txt);
296
297 // Spawn a daemon and tell it to configure logger
298 Daemon x;
299 EXPECT_NO_THROW(x.configureLogger(config, storage));
300
301 // The parsed configuration should be processed by the daemon and
302 // stored in configuration storage.
303 ASSERT_EQ(1, storage->getLoggingInfo().size());
304
305 EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
306 EXPECT_EQ(99, storage->getLoggingInfo()[0].debuglevel_);
307 EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[0].severity_);
308
309 ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
310 EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
311 }
312
TEST_F(DaemonTest,exitValue)313 TEST_F(DaemonTest, exitValue) {
314 DaemonImpl instance;
315
316 EXPECT_EQ(EXIT_SUCCESS, instance.getExitValue());
317 instance.setExitValue(77);
318 EXPECT_EQ(77, instance.getExitValue());
319 }
320
321
322 // More tests will appear here as we develop Daemon class.
323
324 };
325