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