Merge pull request #1934 from brauner/2017-11-21/implement_do_lxc_reboot_correctly

commands: improve and simplify locking + lxccontainer: add reboot2() API extension
This commit is contained in:
Serge Hallyn 2017-12-06 16:31:50 -06:00 committed by GitHub
commit 49be8a144a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 646 additions and 202 deletions

View File

@ -128,6 +128,9 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd)
if (ret < 0) {
WARN("%s - Failed to receive response for command \"%s\"",
strerror(errno), lxc_cmd_str(cmd->req.cmd));
if (errno == ECONNRESET)
return -ECONNRESET;
return -1;
}
TRACE("Command \"%s\" received response", lxc_cmd_str(cmd->req.cmd));
@ -318,6 +321,8 @@ static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped,
}
ret = lxc_cmd_rsp_recv(client_fd, cmd);
if (ret == -ECONNRESET)
*stopped = 1;
out:
if (!stay_connected || ret <= 0)
close(client_fd);
@ -893,9 +898,8 @@ int lxc_cmd_add_state_client(const char *name, const char *lxcpath,
lxc_state_t states[MAX_STATE],
int *state_client_fd)
{
int stopped;
int state, stopped;
ssize_t ret;
int state = -1;
struct lxc_cmd_rr cmd = {
.req = {
.cmd = LXC_CMD_ADD_STATE_CLIENT,
@ -904,46 +908,10 @@ int lxc_cmd_add_state_client(const char *name, const char *lxcpath,
},
};
/* Lock the whole lxc_cmd_add_state_client_callback() call to ensure
* that lxc_set_state() doesn't cause us to miss a state.
*/
process_lock();
/* Check if already in requested state. */
state = lxc_getstate(name, lxcpath);
if (state < 0) {
process_unlock();
TRACE("%s - Failed to retrieve state of container", strerror(errno));
return -1;
} else if (states[state]) {
process_unlock();
TRACE("Container is %s state", lxc_state2str(state));
return state;
}
if ((state == STARTING) && !states[RUNNING] && !states[STOPPING] && !states[STOPPED]) {
process_unlock();
TRACE("Container is in %s state and caller requested to be "
"informed about a previous state", lxc_state2str(state));
return state;
} else if ((state == RUNNING) && !states[STOPPING] && !states[STOPPED]) {
process_unlock();
TRACE("Container is in %s state and caller requested to be "
"informed about a previous state", lxc_state2str(state));
return state;
} else if ((state == STOPPING) && !states[STOPPED]) {
process_unlock();
TRACE("Container is in %s state and caller requested to be "
"informed about a previous state", lxc_state2str(state));
return state;
} else if ((state == STOPPED) || (state == ABORTING)) {
process_unlock();
TRACE("Container is in %s state and caller requested to be "
"informed about a previous state", lxc_state2str(state));
return state;
}
ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
process_unlock();
if (states[STOPPED] != 0 && stopped != 0)
return STOPPED;
if (ret < 0) {
ERROR("%s - Failed to execute command", strerror(errno));
return -1;
@ -952,37 +920,55 @@ int lxc_cmd_add_state_client(const char *name, const char *lxcpath,
/* We should now be guaranteed to get an answer from the state sending
* function.
*/
if (cmd.rsp.ret < 0) {
ERROR("Failed to receive socket fd");
ERROR("%s - Failed to receive socket fd", strerror(-cmd.rsp.ret));
return -1;
}
state = PTR_TO_INT(cmd.rsp.data);
if (state < MAX_STATE) {
TRACE("Container is already in requested state %s", lxc_state2str(state));
close(cmd.rsp.ret);
return state;
}
*state_client_fd = cmd.rsp.ret;
TRACE("Added state client %d to state client list", cmd.rsp.ret);
return MAX_STATE;
}
static int lxc_cmd_add_state_client_callback(int fd, struct lxc_cmd_req *req,
struct lxc_handler *handler)
{
int ret;
struct lxc_cmd_rsp rsp = {0};
if (req->datalen < 0)
return -1;
goto reap_client_fd;
if (req->datalen > (sizeof(lxc_state_t) * MAX_STATE))
return -1;
goto reap_client_fd;
if (!req->data)
return -1;
goto reap_client_fd;
rsp.ret = lxc_add_state_client(fd, handler, (lxc_state_t *)req->data);
if (rsp.ret < 0)
ERROR("Failed to add state client %d to state client list", fd);
else
TRACE("Added state client %d to state client list", fd);
goto reap_client_fd;
return lxc_cmd_rsp_send(fd, &rsp);
rsp.data = INT_TO_PTR(rsp.ret);
ret = lxc_cmd_rsp_send(fd, &rsp);
if (ret < 0)
goto reap_client_fd;
return 0;
reap_client_fd:
/* Special indicator to lxc_cmd_handler() to close the fd and do related
* cleanup.
*/
return 1;
}
int lxc_cmd_console_log(const char *name, const char *lxcpath,
@ -1155,7 +1141,7 @@ static void lxc_cmd_fd_cleanup(int fd, struct lxc_handler *handler,
struct lxc_epoll_descr *descr,
const lxc_cmd_t cmd)
{
struct state_client *client;
struct lxc_state_client *client;
struct lxc_list *cur, *next;
lxc_console_free(handler->conf, fd);
@ -1166,7 +1152,7 @@ static void lxc_cmd_fd_cleanup(int fd, struct lxc_handler *handler,
}
process_lock();
lxc_list_for_each_safe(cur, &handler->state_clients, next) {
lxc_list_for_each_safe(cur, &handler->conf->state_clients, next) {
client = cur->elem;
if (client->clientfd != fd)
continue;
@ -1176,6 +1162,10 @@ static void lxc_cmd_fd_cleanup(int fd, struct lxc_handler *handler,
lxc_list_del(cur);
free(cur->elem);
free(cur);
/* No need to walk the whole list. If we found the state client
* fd there can't be a second one.
*/
break;
}
process_unlock();
}

View File

@ -33,6 +33,7 @@
#include "commands_utils.h"
#include "initutils.h"
#include "log.h"
#include "lxclock.h"
#include "monitor.h"
#include "state.h"
#include "utils.h"
@ -192,7 +193,8 @@ int lxc_cmd_connect(const char *name, const char *lxcpath,
int lxc_add_state_client(int state_client_fd, struct lxc_handler *handler,
lxc_state_t states[MAX_STATE])
{
struct state_client *newclient;
int state;
struct lxc_state_client *newclient;
struct lxc_list *tmplist;
newclient = malloc(sizeof(*newclient));
@ -209,10 +211,19 @@ int lxc_add_state_client(int state_client_fd, struct lxc_handler *handler,
return -ENOMEM;
}
lxc_list_add_elem(tmplist, newclient);
lxc_list_add_tail(&handler->state_clients, tmplist);
process_lock();
state = handler->state;
if (states[state] != 1) {
lxc_list_add_elem(tmplist, newclient);
lxc_list_add_tail(&handler->conf->state_clients, tmplist);
process_unlock();
} else {
process_unlock();
free(newclient);
free(tmplist);
return state;
}
TRACE("added state client %d to state client list", state_client_fd);
return 0;
return MAX_STATE;
}

View File

@ -2419,6 +2419,7 @@ struct lxc_conf *lxc_conf_init(void)
for (i = 0; i < NUM_LXC_HOOKS; i++)
lxc_list_init(&new->hooks[i]);
lxc_list_init(&new->groups);
lxc_list_init(&new->state_clients);
new->lsm_aa_profile = NULL;
new->lsm_se_context = NULL;
new->tmp_umount_proc = 0;

View File

@ -248,6 +248,11 @@ enum lxchooks {
extern char *lxchook_names[NUM_LXC_HOOKS];
struct lxc_state_client {
int clientfd;
lxc_state_t states[MAX_STATE];
};
struct lxc_conf {
int is_execute;
char *fstab;
@ -363,6 +368,8 @@ struct lxc_conf {
/* init working directory */
char* init_cwd;
/* A list of clients registered to be informed about a container state. */
struct lxc_list state_clients;
};
int write_id_mapping(enum idtype idtype, pid_t pid, const char *buf,

View File

@ -77,6 +77,11 @@ static int execute_start(struct lxc_handler *handler, void* data)
argv[i++] = (char *)lxc_log_priority_to_string(lxc_log_get_level());
}
if (handler->conf->logfile) {
argv[i++] = "-o";
argv[i++] = (char *)handler->conf->logfile;
}
if (my_args->quiet)
argv[i++] = "--quiet";

View File

@ -828,10 +828,6 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a
return false;
}
/* Is this app meant to be run through lxcinit, as in lxc-execute? */
if (useinit && !argv)
return false;
if (container_mem_lock(c))
return false;
conf = c->lxc_conf;
@ -843,15 +839,20 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a
if (!handler)
return false;
/* If no argv was passed in, use lxc.init_cmd if provided in the
* configuration
*/
if (!argv)
argv = init_cmd = split_init_cmd(conf->init_cmd);
if (!argv) {
if (useinit && conf->execute_cmd)
argv = init_cmd = split_init_cmd(conf->execute_cmd);
else
argv = init_cmd = split_init_cmd(conf->init_cmd);
}
/* ... otherwise use default_args. */
if (!argv)
argv = default_args;
if (!argv) {
if (useinit)
return false;
else
argv = default_args;
}
/* I'm not sure what locks we want here.Any? Is liblxc's locking enough
* here to protect the on disk container? We don't want to exclude
@ -1793,12 +1794,11 @@ static bool do_lxcapi_reboot(struct lxc_container *c)
WRAP_API(bool, lxcapi_reboot)
static bool do_lxcapi_shutdown(struct lxc_container *c, int timeout)
static bool do_lxcapi_reboot2(struct lxc_container *c, int timeout)
{
int ret, state_client_fd = -1;
bool retv = false;
int killret, ret;
pid_t pid;
int haltsignal = SIGPWR;
int rebootsignal = SIGINT, state_client_fd = -1;
lxc_state_t states[MAX_STATE] = {0};
if (!c)
@ -1806,6 +1806,74 @@ static bool do_lxcapi_shutdown(struct lxc_container *c, int timeout)
if (!do_lxcapi_is_running(c))
return true;
pid = do_lxcapi_init_pid(c);
if (pid <= 0)
return true;
if (c->lxc_conf && c->lxc_conf->rebootsignal)
rebootsignal = c->lxc_conf->rebootsignal;
/* Add a new state client before sending the shutdown signal so that we
* don't miss a state.
*/
if (timeout != 0) {
states[RUNNING] = 2;
ret = lxc_cmd_add_state_client(c->name, c->config_path, states,
&state_client_fd);
if (ret < 0)
return false;
if (state_client_fd < 0)
return false;
if (ret == RUNNING)
return true;
if (ret < MAX_STATE)
return false;
}
/* Send reboot signal to container. */
killret = kill(pid, rebootsignal);
if (killret < 0) {
WARN("Could not send signal %d to pid %d", rebootsignal, pid);
if (state_client_fd >= 0)
close(state_client_fd);
return false;
}
TRACE("Sent signal %d to pid %d", rebootsignal, pid);
if (timeout == 0)
return true;
ret = lxc_cmd_sock_rcv_state(state_client_fd, timeout);
close(state_client_fd);
if (ret < 0)
return false;
TRACE("Received state \"%s\"", lxc_state2str(ret));
if (ret != RUNNING)
return false;
return true;
}
WRAP_API_1(bool, lxcapi_reboot2, int)
static bool do_lxcapi_shutdown(struct lxc_container *c, int timeout)
{
int killret, ret;
pid_t pid;
int haltsignal = SIGPWR, state_client_fd = -1;
lxc_state_t states[MAX_STATE] = {0};
if (!c)
return false;
if (!do_lxcapi_is_running(c))
return true;
pid = do_lxcapi_init_pid(c);
if (pid <= 0)
return true;
@ -1817,37 +1885,49 @@ static bool do_lxcapi_shutdown(struct lxc_container *c, int timeout)
if (c->lxc_conf && c->lxc_conf->haltsignal)
haltsignal = c->lxc_conf->haltsignal;
INFO("Using signal number '%d' as halt signal", haltsignal);
/* Add a new state client before sending the shutdown signal so that we
* don't miss a state.
*/
states[STOPPED] = 1;
ret = lxc_cmd_add_state_client(c->name, c->config_path, states,
&state_client_fd);
/* Send shutdown signal to container. */
if (kill(pid, haltsignal) < 0)
WARN("Could not send signal %d to pid %d", haltsignal, pid);
/* Retrieve the state. */
if (state_client_fd >= 0) {
int state;
state = lxc_cmd_sock_rcv_state(state_client_fd, timeout);
close(state_client_fd);
TRACE("Received state \"%s\"", lxc_state2str(state));
if (state != STOPPED)
if (timeout != 0) {
states[STOPPED] = 1;
ret = lxc_cmd_add_state_client(c->name, c->config_path, states,
&state_client_fd);
if (ret < 0)
return false;
if (state_client_fd < 0)
return false;
if (ret == STOPPED)
return true;
if (ret < MAX_STATE)
return false;
retv = true;
} else if (ret == STOPPED) {
TRACE("Container is already stopped");
retv = true;
} else {
TRACE("Received state \"%s\" instead of expected \"STOPPED\"",
lxc_state2str(ret));
}
return retv;
/* Send shutdown signal to container. */
killret = kill(pid, haltsignal);
if (killret < 0) {
WARN("Could not send signal %d to pid %d", haltsignal, pid);
if (state_client_fd >= 0)
close(state_client_fd);
return false;
}
TRACE("Sent signal %d to pid %d", haltsignal, pid);
if (timeout == 0)
return true;
ret = lxc_cmd_sock_rcv_state(state_client_fd, timeout);
close(state_client_fd);
if (ret < 0)
return false;
TRACE("Received state \"%s\"", lxc_state2str(ret));
if (ret != STOPPED)
return false;
return true;
}
WRAP_API_1(bool, lxcapi_shutdown, int)
@ -4595,6 +4675,7 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath
c->createl = lxcapi_createl;
c->shutdown = lxcapi_shutdown;
c->reboot = lxcapi_reboot;
c->reboot2 = lxcapi_reboot2;
c->clear_config = lxcapi_clear_config;
c->clear_config_item = lxcapi_clear_config_item;
c->get_config_item = lxcapi_get_config_item;

View File

@ -846,6 +846,17 @@ struct lxc_container {
* \return \c 0 on success, nonzero on failure.
*/
int (*console_log)(struct lxc_container *c, struct lxc_console_log *log);
/*!
* \brief Request the container reboot by sending it \c SIGINT.
*
* \param c Container.
* \param timeout Seconds to wait before returning false.
* (-1 to wait forever, 0 to avoid waiting).
*
* \return \c true if the container was rebooted successfully, else \c false.
*/
bool (*reboot2)(struct lxc_container *c, int timeout);
};
/*!

View File

@ -200,6 +200,9 @@ restart:
fddir = dirfd(dir);
while ((direntp = readdir(dir))) {
struct lxc_list *cur;
bool matched = false;
if (!direntp)
break;
@ -222,6 +225,20 @@ restart:
(i < len_fds && fd == fds_to_ignore[i]))
continue;
/* Keep state clients that wait on reboots. */
lxc_list_for_each(cur, &conf->state_clients) {
struct lxc_state_client *client = cur->elem;
if (client->clientfd != fd)
continue;
matched = true;
break;
}
if (matched)
continue;
if (current_config && fd == current_config->logfd)
continue;
@ -338,18 +355,14 @@ static int lxc_serve_state_clients(const char *name,
{
ssize_t ret;
struct lxc_list *cur, *next;
struct state_client *client;
struct lxc_state_client *client;
struct lxc_msg msg = {.type = lxc_msg_state, .value = state};
process_lock();
/* Only set state under process lock held so that we don't cause
* lxc_cmd_add_state_client() to miss a state.
*/
handler->state = state;
TRACE("Set container state to %s", lxc_state2str(state));
if (lxc_list_empty(&handler->state_clients)) {
if (lxc_list_empty(&handler->conf->state_clients)) {
TRACE("No state clients registered");
process_unlock();
lxc_monitor_send_state(name, state, handler->lxcpath);
@ -359,10 +372,10 @@ static int lxc_serve_state_clients(const char *name,
strncpy(msg.name, name, sizeof(msg.name));
msg.name[sizeof(msg.name) - 1] = 0;
lxc_list_for_each_safe(cur, &handler->state_clients, next) {
lxc_list_for_each_safe(cur, &handler->conf->state_clients, next) {
client = cur->elem;
if (!client->states[state]) {
if (client->states[state] == 0) {
TRACE("State %s not registered for state client %d",
lxc_state2str(state), client->clientfd);
continue;
@ -531,7 +544,8 @@ struct lxc_handler *lxc_init_handler(const char *name, struct lxc_conf *conf,
handler->lxcpath = lxcpath;
handler->pinfd = -1;
handler->state_socket_pair[0] = handler->state_socket_pair[1] = -1;
lxc_list_init(&handler->state_clients);
if (handler->conf->reboot == 0)
lxc_list_init(&handler->conf->state_clients);
for (i = 0; i < LXC_NS_MAX; i++)
handler->nsfd[i] = -1;
@ -554,10 +568,12 @@ struct lxc_handler *lxc_init_handler(const char *name, struct lxc_conf *conf,
handler->state_socket_pair[1]);
}
handler->conf->maincmd_fd = lxc_cmd_init(name, lxcpath, "command");
if (handler->conf->maincmd_fd < 0) {
ERROR("Failed to set up command socket");
goto on_error;
if (handler->conf->reboot == 0) {
handler->conf->maincmd_fd = lxc_cmd_init(name, lxcpath, "command");
if (handler->conf->maincmd_fd < 0) {
ERROR("Failed to set up command socket");
goto on_error;
}
}
TRACE("Unix domain socket %d for command server is ready",
handler->conf->maincmd_fd);
@ -715,9 +731,11 @@ void lxc_fini(const char *name, struct lxc_handler *handler)
lxc_set_state(name, handler, STOPPED);
/* close command socket */
close(handler->conf->maincmd_fd);
handler->conf->maincmd_fd = -1;
if (handler->conf->reboot == 0) {
/* close command socket */
close(handler->conf->maincmd_fd);
handler->conf->maincmd_fd = -1;
}
if (run_lxc_hooks(name, "post-stop", handler->conf, handler->lxcpath, NULL)) {
ERROR("Failed to run lxc.hook.post-stop for container \"%s\".", name);
@ -739,8 +757,13 @@ void lxc_fini(const char *name, struct lxc_handler *handler)
/* The command socket is now closed, no more state clients can register
* themselves from now on. So free the list of state clients.
*/
lxc_list_for_each_safe(cur, &handler->state_clients, next) {
struct state_client *client = cur->elem;
lxc_list_for_each_safe(cur, &handler->conf->state_clients, next) {
struct lxc_state_client *client = cur->elem;
/* Keep state clients that want to be notified about reboots. */
if ((handler->conf->reboot > 0) && (client->states[RUNNING] == 2))
continue;
/* close state client socket */
close(client->clientfd);
lxc_list_del(cur);
@ -801,9 +824,8 @@ static int do_start(void *data)
lxc_sync_fini_parent(handler);
/* Don't leak the pinfd to the container. */
if (handler->pinfd >= 0) {
if (handler->pinfd >= 0)
close(handler->pinfd);
}
if (lxc_sync_wait_parent(handler, LXC_SYNC_STARTUP))
return -1;

View File

@ -85,9 +85,6 @@ struct lxc_handler {
/* The container's in-memory configuration. */
struct lxc_conf *conf;
/* A list of clients registered to be informed about a container state. */
struct lxc_list state_clients;
/* A set of operations to be performed at various stages of the
* container's life.
*/
@ -110,11 +107,6 @@ struct lxc_operations {
int (*post_start)(struct lxc_handler *, void *);
};
struct state_client {
int clientfd;
lxc_state_t states[MAX_STATE];
};
extern int lxc_poll(const char *name, struct lxc_handler *handler);
extern int lxc_set_state(const char *name, struct lxc_handler *handler, lxc_state_t state);
extern void lxc_abort(const char *name, struct lxc_handler *handler);

View File

@ -45,9 +45,9 @@
lxc_log_define(lxc_state, lxc);
static const char * const strstate[] = {
"STOPPED", "STARTING", "RUNNING", "STOPPING",
"ABORTING", "FREEZING", "FROZEN", "THAWED",
static const char *const strstate[] = {
"STOPPED", "STARTING", "RUNNING", "STOPPING",
"ABORTING", "FREEZING", "FROZEN", "THAWED",
};
const char *lxc_state2str(lxc_state_t state)

View File

@ -94,62 +94,6 @@ Options :\n\
.timeout = -2,
};
/* returns -1 on failure, 0 on success */
static int do_reboot_and_check(struct lxc_arguments *a, struct lxc_container *c)
{
int ret;
pid_t pid;
pid_t newpid;
int timeout = a->timeout;
pid = c->init_pid(c);
if (pid == -1)
return -1;
if (!c->reboot(c))
return -1;
if (a->nowait)
return 0;
if (timeout == 0)
goto out;
for (;;) {
/* can we use c-> wait for this, assuming it will
* re-enter RUNNING? For now just sleep */
int elapsed_time, curtime = 0;
struct timeval tv;
newpid = c->init_pid(c);
if (newpid != -1 && newpid != pid)
return 0;
if (timeout != -1) {
ret = gettimeofday(&tv, NULL);
if (ret)
break;
curtime = tv.tv_sec;
}
sleep(1);
if (timeout != -1) {
ret = gettimeofday(&tv, NULL);
if (ret)
break;
elapsed_time = tv.tv_sec - curtime;
if (timeout - elapsed_time <= 0)
break;
timeout -= elapsed_time;
}
}
out:
newpid = c->init_pid(c);
if (newpid == -1 || newpid == pid) {
printf("Reboot did not complete before timeout\n");
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
struct lxc_container *c;
@ -259,7 +203,11 @@ int main(int argc, char *argv[])
/* reboot */
if (my_args.reboot) {
ret = do_reboot_and_check(&my_args, c) < 0 ? EXIT_SUCCESS : EXIT_FAILURE;
ret = c->reboot2(c, my_args.timeout);
if (ret < 0)
ret = EXIT_FAILURE;
else
ret = EXIT_SUCCESS;
goto out;
}

View File

@ -20,6 +20,7 @@ lxc_test_snapshot_SOURCES = snapshot.c
lxc_test_concurrent_SOURCES = concurrent.c
lxc_test_may_control_SOURCES = may_control.c
lxc_test_reboot_SOURCES = reboot.c
lxc_test_api_reboot_SOURCES = api_reboot.c
lxc_test_list_SOURCES = list.c
lxc_test_attach_SOURCES = attach.c
lxc_test_device_add_remove_SOURCES = device_add_remove.c
@ -29,6 +30,7 @@ lxc_test_parse_config_file_SOURCES = parse_config_file.c lxctest.h
lxc_test_config_jump_table_SOURCES = config_jump_table.c lxctest.h
lxc_test_shortlived_SOURCES = shortlived.c
lxc_test_livepatch_SOURCES = livepatch.c lxctest.h
lxc_test_state_server_SOURCES = state_server.c lxctest.h
AM_CFLAGS=-DLXCROOTFSMOUNT=\"$(LXCROOTFSMOUNT)\" \
-DLXCPATH=\"$(LXCPATH)\" \
@ -57,7 +59,8 @@ bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \
lxc-test-snapshot lxc-test-concurrent lxc-test-may-control \
lxc-test-reboot lxc-test-list lxc-test-attach lxc-test-device-add-remove \
lxc-test-apparmor lxc-test-utils lxc-test-parse-config-file \
lxc-test-config-jump-table lxc-test-shortlived lxc-test-livepatch
lxc-test-config-jump-table lxc-test-shortlived lxc-test-livepatch \
lxc-test-api-reboot lxc-test-state-server
bin_SCRIPTS = lxc-test-automount \
lxc-test-autostart \
@ -117,7 +120,8 @@ EXTRA_DIST = \
shortlived.c \
shutdowntest.c \
snapshot.c \
startone.c
startone.c \
state_server.c
clean-local:
rm -f lxc-test-utils-*

125
src/tests/api_reboot.c Normal file
View File

@ -0,0 +1,125 @@
/* liblxcapi
*
* Copyright © 2017 Christian Brauner <christian.brauner@ubuntu.com>.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <alloca.h>
#include <stdio.h>
#include <sched.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <sys/reboot.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "lxc/lxccontainer.h"
#include "lxctest.h"
int main(int argc, char *argv[])
{
int i;
struct lxc_container *c;
int ret = EXIT_FAILURE;
/* Test that the reboot() API function properly waits for containers to
* restart.
*/
c = lxc_container_new("reboot", NULL);
if (!c) {
lxc_error("%s", "Failed to create container \"reboot\"");
exit(ret);
}
if (c->is_defined(c)) {
lxc_error("%s\n", "Container \"reboot\" is defined");
goto on_error_put;
}
if (!c->createl(c, "busybox", NULL, NULL, 0, NULL)) {
lxc_error("%s\n", "Failed to create busybox container \"reboot\"");
goto on_error_put;
}
if (!c->is_defined(c)) {
lxc_error("%s\n", "Container \"reboot\" is not defined");
goto on_error_put;
}
c->clear_config(c);
if (!c->load_config(c, NULL)) {
lxc_error("%s\n", "Failed to load config for container \"reboot\"");
goto on_error_stop;
}
if (!c->want_daemonize(c, true)) {
lxc_error("%s\n", "Failed to mark container \"reboot\" daemonized");
goto on_error_stop;
}
if (!c->startl(c, 0, NULL)) {
lxc_error("%s\n", "Failed to start container \"reboot\" daemonized");
goto on_error_stop;
}
/* reboot 10 times */
for (i = 0; i < 10; i++) {
/* Give the init system some time to setup it's signal handlers
* otherwise we will hang indefinitely.
*/
sleep(5);
if (!c->reboot2(c, -1)) {
lxc_error("%s\n", "Failed to reboot container \"reboot\"");
goto on_error_stop;
}
if (!c->is_running(c)) {
lxc_error("%s\n", "Failed to reboot container \"reboot\"");
goto on_error_stop;
}
lxc_debug("%s\n", "Container \"reboot\" rebooted successfully");
}
/* Give the init system some time to setup it's signal handlers
* otherwise we will hang indefinitely.
*/
sleep(5);
/* Test non-blocking reboot2() */
if (!c->reboot2(c, 0)) {
lxc_error("%s\n", "Failed to request non-blocking reboot of container \"reboot\"");
goto on_error_stop;
}
lxc_debug("%s\n", "Non-blocking reboot of container \"reboot\" succeeded");
ret = EXIT_SUCCESS;
on_error_stop:
if (c->is_running(c) && !c->stop(c))
lxc_error("%s\n", "Failed to stop container \"reboot\"");
if (!c->destroy(c))
lxc_error("%s\n", "Failed to destroy container \"reboot\"");
on_error_put:
lxc_container_put(c);
if (ret == EXIT_SUCCESS)
lxc_debug("%s\n", "All reboot tests passed");
exit(ret);
}

View File

@ -203,14 +203,11 @@ int main(int argc, char *argv[])
}
free(value);
if (!c->reboot(c)) {
if (!c->reboot2(c, -1)) {
lxc_error("%s", "Failed to create container \"livepatch\"");
goto on_error_stop;
}
/* Busybox shouldn't take long to reboot. Sleep for 5s. */
sleep(5);
if (!c->is_running(c)) {
lxc_error("%s\n", "Failed to reboot container \"livepatch\"");
goto on_error_destroy;

View File

@ -28,6 +28,9 @@
#include <errno.h>
#include <fcntl.h>
#include "lxctest.h"
#include "utils.h"
#define MYNAME "shortlived"
static int destroy_container(void)
@ -92,12 +95,33 @@ again:
int main(int argc, char *argv[])
{
struct lxc_container *c;
int i;
const char *s;
bool b;
int i;
struct lxc_container *c;
struct lxc_log log;
char template[sizeof(P_tmpdir"/shortlived_XXXXXX")];
int ret = 0;
strcpy(template, P_tmpdir"/shortlived_XXXXXX");
i = lxc_make_tmpfile(template, false);
if (i < 0) {
lxc_error("Failed to create temporary log file for container %s\n", MYNAME);
exit(EXIT_FAILURE);
} else {
lxc_debug("Using \"%s\" as temporary log file for container %s\n", template, MYNAME);
close(i);
}
log.name = MYNAME;
log.file = template;
log.level = "TRACE";
log.prefix = "shortlived";
log.quiet = false;
log.lxcpath = NULL;
if (lxc_log_init(&log))
exit(EXIT_FAILURE);
ret = 1;
/* test a real container */
c = lxc_container_new(MYNAME, NULL);
@ -141,16 +165,51 @@ int main(int argc, char *argv[])
goto out;
}
if (!c->set_config_item(c, "lxc.execute.cmd", "echo hello")) {
fprintf(stderr, "%d: failed setting lxc.init.cmd\n", __LINE__);
goto out;
}
c->want_daemonize(c, true);
/* Test whether we can start a really short-lived daemonized container.
*/
/* Test whether we can start a really short-lived daemonized container. */
for (i = 0; i < 10; i++) {
if (!c->startl(c, 0, NULL)) {
fprintf(stderr, "%d: %s failed to start\n", __LINE__, c->name);
fprintf(stderr, "%d: %s failed to start on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
/* The container needs to exit with a successful error code. */
if (c->error_num != 0) {
fprintf(stderr, "%d: %s exited successfully on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
fprintf(stderr, "%d: %s exited correctly with error code %d on %dth iteration\n", __LINE__, c->name, c->error_num, i);
if (!c->wait(c, "STOPPED", 30)) {
fprintf(stderr, "%d: %s failed to wait on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
}
/* Test whether we can start a really short-lived daemonized container with lxc-init. */
for (i = 0; i < 10; i++) {
if (!c->startl(c, 1, NULL)) {
fprintf(stderr, "%d: %s failed to start on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
/* The container needs to exit with a successful error code. */
if (c->error_num != 0) {
fprintf(stderr, "%d: %s exited successfully on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
fprintf(stderr, "%d: %s exited correctly with error code %d on %dth iteration\n", __LINE__, c->name, c->error_num, i);
if (!c->wait(c, "STOPPED", 30)) {
fprintf(stderr, "%d: %s failed to wait on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
sleep(1);
}
if (!c->set_config_item(c, "lxc.init.cmd", "you-shall-fail")) {
@ -158,15 +217,52 @@ int main(int argc, char *argv[])
goto out;
}
/* Test whether we catch the start failure of a really short-lived
* daemonized container.
*/
if (!c->set_config_item(c, "lxc.execute.cmd", "you-shall-fail")) {
fprintf(stderr, "%d: failed setting lxc.init.cmd\n", __LINE__);
goto out;
}
/* Test whether we can start a really short-lived daemonized container. */
for (i = 0; i < 10; i++) {
if (c->startl(c, 0, NULL)) {
fprintf(stderr, "%d: %s failed to start\n", __LINE__, c->name);
fprintf(stderr, "%d: %s failed to start on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
/* The container needs to exit with an error code.*/
if (c->error_num == 0) {
fprintf(stderr, "%d: %s exited successfully on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
fprintf(stderr, "%d: %s exited correctly with error code %d on %dth iteration\n", __LINE__, c->name, c->error_num, i);
if (!c->wait(c, "STOPPED", 30)) {
fprintf(stderr, "%d: %s failed to wait on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
}
/* Test whether we can start a really short-lived daemonized container with lxc-init. */
for (i = 0; i < 10; i++) {
/* An container started with lxc-init will always start
* succesfully unless lxc-init has a bug.
*/
if (!c->startl(c, 1, NULL)) {
fprintf(stderr, "%d: %s failed to start on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
/* The container needs to exit with an error code.*/
if (c->error_num == 0) {
fprintf(stderr, "%d: %s exited successfully on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
fprintf(stderr, "%d: %s exited correctly with error code %d on %dth iteration\n", __LINE__, c->name, c->error_num, i);
if (!c->wait(c, "STOPPED", 30)) {
fprintf(stderr, "%d: %s failed to wait on %dth iteration\n", __LINE__, c->name, i);
goto out;
}
sleep(1);
}
c->stop(c);
@ -180,5 +276,6 @@ out:
destroy_container();
}
lxc_container_put(c);
unlink(template);
exit(ret);
}

153
src/tests/state_server.c Normal file
View File

@ -0,0 +1,153 @@
/* liblxcapi
*
* Copyright © 2017 Christian Brauner <christian.brauner@ubuntu.com>.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <alloca.h>
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/reboot.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "lxc/lxccontainer.h"
#include "lxctest.h"
struct thread_args {
int thread_id;
int timeout;
bool success;
struct lxc_container *c;
};
void *state_wrapper(void *data)
{
struct thread_args *args = data;
lxc_debug("Starting state server thread %d\n", args->thread_id);
args->success = args->c->shutdown(args->c, args->timeout);
lxc_debug("State server thread %d with shutdown timeout %d returned \"%s\"\n",
args->thread_id, args->timeout, args->success ? "SUCCESS" : "FAILED");
pthread_exit(NULL);
return NULL;
}
int main(int argc, char *argv[])
{
int i, j;
pthread_attr_t attr;
pthread_t threads[10];
struct thread_args args[10];
struct lxc_container *c;
int ret = EXIT_FAILURE;
c = lxc_container_new("state-server", NULL);
if (!c) {
lxc_error("%s", "Failed to create container \"state-server\"");
exit(ret);
}
if (c->is_defined(c)) {
lxc_error("%s\n", "Container \"state-server\" is defined");
goto on_error_put;
}
if (!c->createl(c, "busybox", NULL, NULL, 0, NULL)) {
lxc_error("%s\n", "Failed to create busybox container \"state-server\"");
goto on_error_put;
}
if (!c->is_defined(c)) {
lxc_error("%s\n", "Container \"state-server\" is not defined");
goto on_error_put;
}
c->clear_config(c);
if (!c->load_config(c, NULL)) {
lxc_error("%s\n", "Failed to load config for container \"state-server\"");
goto on_error_stop;
}
if (!c->want_daemonize(c, true)) {
lxc_error("%s\n", "Failed to mark container \"state-server\" daemonized");
goto on_error_stop;
}
pthread_attr_init(&attr);
for (j = 0; j < 10; j++) {
lxc_debug("Starting state server test iteration %d\n", j);
if (!c->startl(c, 0, NULL)) {
lxc_error("%s\n", "Failed to start container \"state-server\" daemonized");
goto on_error_stop;
}
sleep(5);
for (i = 0; i < 10; i++) {
int ret;
args[i].thread_id = i;
args[i].c = c;
args[i].timeout = -1;
/* test non-blocking shutdown request */
if (i == 0)
args[i].timeout = 0;
ret = pthread_create(&threads[i], &attr, state_wrapper, (void *) &args[i]);
if (ret != 0)
goto on_error_stop;
}
for (i = 0; i < 10; i++) {
int ret;
ret = pthread_join(threads[i], NULL);
if (ret != 0)
goto on_error_stop;
if (!args[i].success) {
lxc_error("State server thread %d failed\n", args[i].thread_id);
goto on_error_stop;
}
}
}
ret = EXIT_SUCCESS;
on_error_stop:
if (c->is_running(c) && !c->stop(c))
lxc_error("%s\n", "Failed to stop container \"state-server\"");
if (!c->destroy(c))
lxc_error("%s\n", "Failed to destroy container \"state-server\"");
on_error_put:
lxc_container_put(c);
if (ret == EXIT_SUCCESS)
lxc_debug("%s\n", "All state server tests passed");
exit(ret);
}