constexpr static auto REG = dependency_type::REGULAR;
constexpr static auto WAITS = dependency_type::WAITS_FOR;
-constexpr static auto MS = dependency_type::MILESTONE;
// Friend interface to access base_process_service private/protected members.
class base_process_service_test
bsp->exec_succeeded();
}
+ static void exec_failed(base_process_service *bsp, int errcode)
+ {
+ bsp->waiting_for_execstat = false;
+ bsp->exec_failed(errcode);
+ }
+
static void handle_exit(base_process_service *bsp, int exit_status)
{
bsp->pid = -1;
bsp->pid = -1;
bsp->handle_exit_status(bp_sys::exit_status(false, true, signo));
}
+
+ static int get_notification_fd(base_process_service *bsp)
+ {
+ return bsp->notification_fd;
+ }
};
namespace bp_sys {
ps.set_restart_interval(time_val(10,0), 3);
ps.set_restart_delay(time_val(0, 200000000)); // 200 milliseconds
ps.set_stop_timeout(time_val(10,0));
- ps.set_start_interruptible(false);
}
// Regular service start
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
init_service_defaults(p);
sset.add_service(&p);
sset.remove_service(&p);
}
+// Test start with readiness notification
+void test_proc_notify_start()
+{
+ using namespace std;
+
+ service_set sset;
+
+ string command = "test-command";
+ list<pair<unsigned,unsigned>> command_offsets;
+ command_offsets.emplace_back(0, command.length());
+ std::list<prelim_dep> depends;
+
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
+ init_service_defaults(p);
+ p.set_notification_fd(3);
+ sset.add_service(&p);
+
+ p.start(true);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTING);
+
+ base_process_service_test::exec_succeeded(&p);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTING);
+
+ int nfd = base_process_service_test::get_notification_fd(&p);
+ assert(nfd > 0);
+
+ char notifystr[] = "ok started\n";
+ std::vector<char> rnotifystr;
+ rnotifystr.insert(rnotifystr.end(), notifystr, notifystr + sizeof(notifystr));
+ bp_sys::supply_read_data(nfd, std::move(rnotifystr));
+
+ event_loop.regd_fd_watchers[nfd]->fd_event(event_loop, nfd, dasynq::IN_EVENTS);
+
+ assert(p.get_state() == service_state_t::STARTED);
+ assert(event_loop.active_timers.size() == 0);
+
+ sset.remove_service(&p);
+}
+
// Unexpected termination
void test_proc_unexpected_term()
{
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
init_service_defaults(p);
sset.add_service(&p);
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::TERMINATED);
+ assert(event_loop.active_timers.size() == 0);
+
+ sset.remove_service(&p);
+}
+
+// Unexpected termination
+void test_proc_term_restart()
+{
+ using namespace std;
+
+ service_set sset;
+
+ string command = "test-command";
+ list<pair<unsigned,unsigned>> command_offsets;
+ command_offsets.emplace_back(0, command.length());
+ std::list<prelim_dep> depends;
+
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
+ init_service_defaults(p);
+ p.set_auto_restart(true);
+ sset.add_service(&p);
+
+ p.start(true);
+ sset.process_queues();
+
+ base_process_service_test::exec_succeeded(&p);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTED);
+ assert(event_loop.active_timers.size() == 0);
+
+ base_process_service_test::handle_exit(&p, 0);
+ sset.process_queues();
+
+ // Starting, restart timer should be armed:
+ assert(p.get_state() == service_state_t::STARTING);
+ assert(event_loop.active_timers.size() == 1);
+
+ event_loop.advance_time(time_val(0, 200000000));
+ assert(event_loop.active_timers.size() == 0);
+
+ sset.process_queues();
+ base_process_service_test::exec_succeeded(&p);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTED);
assert(event_loop.active_timers.size() == 0);
sset.remove_service(&p);
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
init_service_defaults(p);
sset.add_service(&p);
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
+ assert(event_loop.active_timers.size() == 0);
+
+ sset.remove_service(&p);
+}
+
+// Termination via stop request, ensure reason is reset:
+void test_term_via_stop2()
+{
+ using namespace std;
+
+ service_set sset;
+
+ string command = "test-command";
+ list<pair<unsigned,unsigned>> command_offsets;
+ command_offsets.emplace_back(0, command.length());
+ std::list<prelim_dep> depends;
+
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
+ init_service_defaults(p);
+ sset.add_service(&p);
+
+ p.start(true);
+ sset.process_queues();
+
+ // first set it up with failure reason:
+
+ base_process_service_test::exec_failed(&p, ENOENT);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::EXECFAILED);
+
+ // now restart clean:
+
+ p.start(true);
+ sset.process_queues();
+
+ base_process_service_test::exec_succeeded(&p);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTED);
+ assert(event_loop.active_timers.size() == 0);
+
+ p.stop(true);
+ sset.process_queues();
+ assert(p.get_state() == service_state_t::STOPPING);
+
+ base_process_service_test::handle_exit(&p, 0);
+ sset.process_queues();
+ assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
assert(event_loop.active_timers.size() == 0);
sset.remove_service(&p);
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
+ scripted_service p {&sset, "testproc", std::move(command), command_offsets, depends};
init_service_defaults(p);
+ p.set_start_timeout(time_val(10,0));
sset.add_service(&p);
p.start(true);
assert(p.get_state() == service_state_t::STARTING);
- p.timer_expired();
+ event_loop.advance_time(time_val(10,0));
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPING);
- base_process_service_test::handle_exit(&p, 0);
+ base_process_service_test::handle_signal_exit(&p, SIGTERM);
sset.process_queues();
+ // We set no stop script, so state should now be STOPPED with no timer set
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::TIMEDOUT);
assert(event_loop.active_timers.size() == 0);
sset.remove_service(&p);
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
+ scripted_service p {&sset, "testproc", std::move(command), command_offsets, depends};
+ p.set_start_timeout(time_val {1,0});
init_service_defaults(p);
sset.add_service(&p);
- service_record ts {&sset, "test-service-1", service_type_t::INTERNAL, {{&p, dependency_type::WAITS_FOR}} };
+ service_record ts {&sset, "test-service-1", service_type_t::INTERNAL,
+ {{&p, dependency_type::WAITS_FOR}} };
ts.start(true);
sset.process_queues();
assert(p.get_state() == service_state_t::STARTING);
assert(ts.get_state() == service_state_t::STARTING);
- p.timer_expired();
+ event_loop.advance_time(time_val {1,0}); // start timer should expire
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPING);
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::TIMEDOUT);
assert(ts.get_state() == service_state_t::STARTED);
assert(event_loop.active_timers.size() == 0);
sset.remove_service(&p);
}
+// Test exec() failure for process service start.
+void test_proc_start_execfail()
+{
+ using namespace std;
+
+ service_set sset;
+
+ string command = "test-command";
+ list<pair<unsigned,unsigned>> command_offsets;
+ command_offsets.emplace_back(0, command.length());
+ std::list<prelim_dep> depends;
+
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
+ init_service_defaults(p);
+ sset.add_service(&p);
+
+ p.start(true);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTING);
+
+ base_process_service_test::exec_failed(&p, ENOENT);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::EXECFAILED);
+ assert(event_loop.active_timers.size() == 0);
+
+ sset.remove_service(&p);
+}
+
+// Test no ready notification before process terminates
+void test_proc_notify_fail()
+{
+ using namespace std;
+
+ service_set sset;
+
+ string command = "test-command";
+ list<pair<unsigned,unsigned>> command_offsets;
+ command_offsets.emplace_back(0, command.length());
+ std::list<prelim_dep> depends;
+
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
+ init_service_defaults(p);
+ p.set_notification_fd(3);
+ sset.add_service(&p);
+
+ p.start(true);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTING);
+
+ base_process_service_test::exec_succeeded(&p);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTING);
+
+ int nfd = base_process_service_test::get_notification_fd(&p);
+ assert(nfd > 0);
+
+ // Signal EOF on notify fd:
+ event_loop.regd_fd_watchers[nfd]->fd_event(event_loop, nfd, dasynq::IN_EVENTS);
+
+ assert(p.get_state() == service_state_t::STOPPING);
+
+ base_process_service_test::handle_exit(&p, 0);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STOPPED);
+ assert(event_loop.active_timers.size() == 0);
+
+ sset.remove_service(&p);
+}
+
// Test stop timeout
void test_proc_stop_timeout()
{
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
init_service_defaults(p);
+ p.set_stop_timeout(time_val {10, 0});
sset.add_service(&p);
p.start(true);
assert(p.get_state() == service_state_t::STOPPING);
assert(bp_sys::last_sig_sent == SIGTERM);
- p.timer_expired();
+ event_loop.advance_time(time_val {10, 0}); // expire stop timer
sset.process_queues();
// kill signal (SIGKILL) should have been sent; process not dead until it's dead, however
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
+
+ assert(event_loop.active_timers.size() == 0);
- // Note that timer is still active as we faked its expiry above
- //assert(event_loop.active_timers.size() == 0);
- event_loop.active_timers.clear();
sset.remove_service(&p);
}
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
init_service_defaults(p);
p.set_smooth_recovery(true);
+ p.set_restart_delay(time_val {0, 1000});
sset.add_service(&p);
p.start(true);
assert(first_instance == bp_sys::last_forked_pid);
assert(p.get_state() == service_state_t::STARTED);
- p.timer_expired();
+ event_loop.advance_time(time_val {0, 1000});
sset.process_queues();
// Now a new process should've been launched:
assert(first_instance + 1 == bp_sys::last_forked_pid);
assert(p.get_state() == service_state_t::STARTED);
- event_loop.active_timers.clear();
+
+ assert(event_loop.active_timers.size() == 0);
sset.remove_service(&p);
}
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- process_service p = process_service(&sset, "testproc", std::move(command), command_offsets, depends);
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
init_service_defaults(p);
p.set_smooth_recovery(true);
p.set_restart_delay(time_val(0, 0));
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
+ scripted_service p {&sset, "testscripted", std::move(command), command_offsets, depends};
init_service_defaults(p);
p.set_stop_command(stopcommand, command_offsets);
+ p.set_stop_timeout(time_val {10, 0});
sset.add_service(&p);
p.start(true);
// should still be stopping:
assert(p.get_state() == service_state_t::STOPPING);
- p.timer_expired();
+ event_loop.advance_time(time_val {10, 0}); // expire stop timer
sset.process_queues();
// kill signal (SIGKILL) should have been sent; process not dead until it's dead, however
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
+
+ assert(event_loop.active_timers.size() == 0);
- event_loop.active_timers.clear();
sset.remove_service(&p);
}
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
+ scripted_service p {&sset, "testscripted", std::move(command), command_offsets, depends};
init_service_defaults(p);
p.set_stop_command(stopcommand, command_offsets);
sset.add_service(&p);
service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{&p, REG}});
- service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{&p, REG}, {s2, REG}});
+ service_record *s3 = new service_record(&sset, "test-service-3",
+ service_type_t::INTERNAL, {{&p, REG}, {s2, REG}});
sset.add_service(s2);
sset.add_service(s3);
assert(p.get_state() == service_state_t::STOPPED);
assert(s2->get_state() == service_state_t::STOPPED);
assert(s3->get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::FAILED);
+ assert(s2->get_stop_reason() == stopped_reason_t::DEPFAILED);
+ assert(s3->get_stop_reason() == stopped_reason_t::DEPFAILED);
event_loop.active_timers.clear();
sset.remove_service(&p);
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
+ scripted_service p {&sset, "testscripted", std::move(command), command_offsets, depends};
init_service_defaults(p);
p.set_stop_command(stopcommand, command_offsets);
sset.add_service(&p);
service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {});
- service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{s2, REG}, {&p, REG}});
- service_record *s4 = new service_record(&sset, "test-service-4", service_type_t::INTERNAL, {{&p, REG}, {s3, REG}});
+ service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL,
+ {{s2, REG}, {&p, REG}});
+ service_record *s4 = new service_record(&sset, "test-service-4", service_type_t::INTERNAL,
+ {{&p, REG}, {s3, REG}});
sset.add_service(s2);
sset.add_service(s3);
sset.add_service(s4);
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
+ scripted_service p {&sset, "testscripted", std::move(command), command_offsets, depends};
init_service_defaults(p);
- onstart_flags_t sflags;
+ service_flags_t sflags;
sflags.skippable = true;
p.set_flags(sflags);
sset.add_service(&p);
assert(p.get_state() == service_state_t::STOPPED);
assert(s2->get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
+ assert(s2->get_stop_reason() == stopped_reason_t::NORMAL);
assert(sset.count_active_services() == 0);
event_loop.active_timers.clear();
command_offsets.emplace_back(0, command.length());
std::list<prelim_dep> depends;
- scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
+ scripted_service p {&sset, "testscripted", std::move(command), command_offsets, depends};
init_service_defaults(p);
- onstart_flags_t sflags;
+ service_flags_t sflags;
sflags.skippable = true;
+ sflags.start_interruptible = true;
p.set_flags(sflags);
- p.set_start_interruptible(true);
sset.add_service(&p);
service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{&p, REG}});
assert(p.get_state() == service_state_t::STOPPED);
assert(s2->get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
+ assert(s2->get_stop_reason() == stopped_reason_t::NORMAL);
assert(sset.count_active_services() == 0);
event_loop.active_timers.clear();
sset.remove_service(&p);
}
+// Test that starting a service with a waits-for dependency on another - currently stopping - service,
+// causes that service to re-start.
+void test_waitsfor_restart()
+{
+ using namespace std;
+
+ service_set sset;
+
+ string command = "test-command";
+ list<pair<unsigned,unsigned>> command_offsets;
+ command_offsets.emplace_back(0, command.length());
+ std::list<prelim_dep> depends;
+
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
+ init_service_defaults(p);
+ sset.add_service(&p);
+
+ service_record tp {&sset, "test-service", service_type_t::INTERNAL, {{&p, WAITS}}};
+ sset.add_service(&tp);
+
+ // start p:
+
+ p.start(true);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTING);
+
+ base_process_service_test::exec_succeeded(&p);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTED);
+ assert(event_loop.active_timers.size() == 0);
+
+ // begin stopping p:
+
+ p.stop(true);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STOPPING);
+
+ // start tp (which waits-for p):
+
+ tp.start(true);
+ sset.process_queues();
+
+ assert(tp.get_state() == service_state_t::STARTING);
+ assert(p.get_state() == service_state_t::STOPPING);
+
+ base_process_service_test::handle_signal_exit(&p, SIGTERM);
+ sset.process_queues();
+
+ assert(tp.get_state() == service_state_t::STARTING);
+ assert(p.get_state() == service_state_t::STARTING);
+
+ base_process_service_test::exec_succeeded(&p);
+ sset.process_queues();
+
+ assert(tp.get_state() == service_state_t::STARTED);
+ assert(p.get_state() == service_state_t::STARTED);
+
+ sset.remove_service(&tp);
+ sset.remove_service(&p);
+}
+
+
#define RUN_TEST(name, spacing) \
- std::cout << #name "..." spacing; \
+ std::cout << #name "..." spacing << std::flush; \
name(); \
std::cout << "PASSED" << std::endl;
int main(int argc, char **argv)
{
RUN_TEST(test_proc_service_start, " ");
+ RUN_TEST(test_proc_notify_start, " ");
RUN_TEST(test_proc_unexpected_term, " ");
+ RUN_TEST(test_proc_term_restart, " ");
RUN_TEST(test_term_via_stop, " ");
+ RUN_TEST(test_term_via_stop2, " ");
RUN_TEST(test_proc_start_timeout, " ");
RUN_TEST(test_proc_start_timeout2, " ");
+ RUN_TEST(test_proc_start_execfail, " ");
+ RUN_TEST(test_proc_notify_fail, " ");
RUN_TEST(test_proc_stop_timeout, " ");
RUN_TEST(test_proc_smooth_recovery1, "");
RUN_TEST(test_proc_smooth_recovery2, "");
RUN_TEST(test_scripted_stop_fail, " ");
RUN_TEST(test_scripted_start_skip, " ");
RUN_TEST(test_scripted_start_skip2, " ");
+ RUN_TEST(test_waitsfor_restart, " ");
}