lxc-stop: use api, remove lxc_shutdown, extend lxc-stop functionality

implement c->reboot(c) in the api.

Also if the container is not running, return -2.  Currently
lxc-stop will return 0, so you cannot tell the difference
between successfull stopping and noop.

Per stgraber's email:

 - Remove lxc-shutdown
 - Change lxc-stop so that:
   * Default behaviour is to call shutdown(), wait 15s for STOPPED, if
not STOPPED, print a message to the user and call stop() [ NOTE:
actually 60 seconds per followup thread]
   * We have a -r option to reboot the container (with proper check that
the container indeed rebooted within the next 15s)
   * We have a -s option to shutdown the container without the automatic
fallback to stop()
   * Add a -k option allowing a user to just kill a container
(equivalent to old lxc-stop, no shutdown() call and no delay).

and update manpages.

Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com>
This commit is contained in:
Serge Hallyn 2013-05-16 23:03:47 +02:00
parent 5cee8c5040
commit 3e625e2d2e
10 changed files with 350 additions and 285 deletions

View File

@ -334,7 +334,6 @@ AC_CONFIG_FILES([
doc/lxc-netstat.sgml
doc/lxc-ps.sgml
doc/lxc-restart.sgml
doc/lxc-shutdown.sgml
doc/lxc-start-ephemeral.sgml
doc/lxc-start.sgml
doc/lxc-stop.sgml
@ -383,7 +382,6 @@ AC_CONFIG_FILES([
src/lxc/lxc-checkconfig
src/lxc/lxc-version
src/lxc/lxc-create
src/lxc/lxc-shutdown
src/lxc/lxc-start-ephemeral
src/lxc/lxc-destroy
src/lxc/legacy/lxc-ls

View File

@ -22,7 +22,6 @@ man_MANS = \
lxc-netstat.1 \
lxc-ps.1 \
lxc-restart.1 \
lxc-shutdown.1 \
lxc-start.1 \
lxc-stop.1 \
lxc-unfreeze.1 \

View File

@ -1,98 +0,0 @@
<!--
Copyright (C) 2012 Canonical, Inc
Authors: Serge Hallyn <serge.hallyn@canonical.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-->
<!DOCTYPE refentry PUBLIC @docdtd@ [
<!ENTITY commonoptions SYSTEM "@builddir@/common_options.sgml">
<!ENTITY seealso SYSTEM "@builddir@/see_also.sgml">
]>
<refentry>
<docinfo><date>@LXC_GENERATE_DATE@</date></docinfo>
<refmeta>
<refentrytitle>lxc-shutdown</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>lxc-shutdown</refname>
<refpurpose>
externally shut down or reboot a container
</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>lxc-shutdown</command>
<arg choice="req">-n <replaceable>name</replaceable></arg>
<arg choice="opt">-w</arg>
<arg choice="opt">-r</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<command>lxc-shutdown</command> sends a SIGPWR signal to the
specified container to request it to cleanly shut down. If
<optional>-w</optional> is specified, then <command>lxc-shutdown</command>
will wait until the container has shut down before exiting.
If <optional>-r</optional> is specified, the container will be
asked to reboot (using a SIGINT signal), and <optional>-w</optional>
will be ignored. If the container ignore these signals, then
nothing will happen. In that case, you can use <command>lxc-stop</command>
to force the container to stop.
</para>
</refsect1>
&commonoptions;
&seealso;
<refsect1>
<title>Author</title>
<para>Serge Hallyn <email>serge.hallyn@canonical.com</email></para>
</refsect1>
</refentry>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:2
sgml-indent-data:t
sgml-parent-document:nil
sgml-default-dtd-file:nil
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
-->

View File

@ -50,6 +50,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
<cmdsynopsis>
<command>lxc-stop</command>
<arg choice="req">-n <replaceable>name</replaceable></arg>
<arg choice="opt">-W</arg>
<arg choice="opt">-r</arg>
<arg choice="opt">-t <replaceable>timeout</replaceable></arg>
<arg choice="opt">-k</arg>
<arg choice="opt">-s</arg>
</cmdsynopsis>
</refsynopsisdiv>
@ -57,14 +62,90 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
<title>Description</title>
<para>
<command>lxc-stop</command> kills all the processes inside the
container. This command should be used if the processes are no
longer accessible and can no be exited normally.
<command>lxc-stop</command> reboots, cleanly shuts down, or kills
all the processes inside the container. By default, it will
request a clean shutdown of the container (by sending SIGPWR to
the container), wait 60 seconds for the container to exit, and
returns. If the container fails to cleanly exit, then after 60
seconds the container will be sent the
<command>lxc.stopsignal</command> to force it to shut down.
</para>
<para>
The <optional>-W</optional>, <optional>-r</optional>, <optional>-s</optional>
and <optional>-k</optional> options specify the action to perform.
<optional>-W</optional> indicates that after performing the specified
action, <command>lxc-stop</command> should immediately exit, while
<optional>-t TIMEOUT</optional> specifies the maximum amount of time
to wait for the container to complete the shutdown or reboot.
</para>
</refsect1>
&commonoptions;
<refsect1>
<title>Options</title>
<variablelist>
<varlistentry>
<term>
<option>-r,--reboot </option>
</term>
<listitem>
<para>
Request a reboot of the container.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-s,--shutdown </option>
</term>
<listitem>
<para>
Only request a clean shutdown, do not kill the container tasks if the
clean shutdown fails.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-k,--kill </option>
</term>
<listitem>
<para>
Rather than requesting a clean shutdown of the container, explicitly
kill all tasks in the container. This is the legacy
<command>lxc-stop</command> behavior.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-W,--nowait </option>
</term>
<listitem>
<para>
Simply perform the requestion action (reboot, shutdown, or hard
kill) and exit.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-t,--timeout <replaceable>TIMEOUT</replaceable></option>
</term>
<listitem>
<para>
Wait TIMEOUT seconds before hard-stopping the container of (in
the reboot case) returning failure.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Diagnostic</title>
@ -92,7 +173,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
</listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -123,7 +123,6 @@ bin_SCRIPTS = \
lxc-checkconfig \
lxc-version \
lxc-create \
lxc-shutdown \
lxc-destroy
EXTRA_DIST = \

View File

@ -61,9 +61,13 @@ struct lxc_arguments {
int ttynum;
char escape;
/* for lxc-wait */
/* for lxc-wait and lxc-shutdown */
char *states;
long timeout;
int nowait;
int reboot;
int hardstop;
int shutdown;
/* close fds from parent? */
int close_all_fds;

View File

@ -1,165 +0,0 @@
#!/bin/sh
# (C) Copyright Canonical 2011,2012
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library 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
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
set -e
. @DATADIR@/lxc/lxc.functions
usage() {
echo "usage: lxc-shutdown -n name [-w] [-r] [-P lxcpath]"
echo " Cleanly shut down a container."
echo " -w: wait for shutdown to complete."
echo " -r: reboot (ignore -w)."
echo " -t timeout: wait at most timeout seconds (implies -w), then kill"
echo " the container."
echo " -P lxcpath: path to the lxc container directories."
}
alarm() {
trap 'exit 0' TERM
pid=$1
timeout=$2
sleep $timeout
kill $pid
}
dolxcstop()
{
echo "Calling lxc-stop on $lxc_name"
lxc-stop -n $lxc_name -P "$lxc_path"
exit 0
}
usage_err() {
[ -n "$1" ] && echo "$1" >&2
usage
exit 1
}
optarg_check() {
[ -n "$2" ] || usage_err "option '$1' requires an argument"
}
timeout="-1"
reboot=0
dowait=0
while [ $# -gt 0 ]; do
opt="$1"
shift
case "$opt" in
-h|--help)
usage
exit 0
;;
-n|--name)
optarg_check $opt "$1"
lxc_name=$1
shift
;;
-w|--wait)
dowait=1
;;
-r|--reboot)
reboot=1
;;
-t|--timeout)
optarg_check $opt "$1"
timeout=$1
dowait=1
shift
;;
-P|--lxcpath)
optarg_check $opt "$1"
lxc_path=$1
dowait=1
shift
;;
--)
break;;
-?)
usage_err "unknown option '$opt'"
;;
-*)
# split opts -abc into -a -b -c
set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@"
;;
*)
usage_err "unknown option '$opt'"
exit 1
;;
esac
done
if [ -z "$lxc_name" ]; then
echo "no container name specified"
usage
exit 1
fi
if [ ! -d "$lxc_path" ]; then
echo "$lxc_path: no such directory"
exit 1
fi
if [ "$(id -u)" != "0" ]; then
echo "This command has to be run as root"
exit 1
fi
which lxc-info > /dev/null 2>&1 || { echo "lxc-info not found."; exit 1; }
which lxc-wait > /dev/null 2>&1 || { echo "lxc-wait not found."; exit 1; }
pid=`lxc-info -n $lxc_name -P "$lxc_path" -p 2>/dev/null | awk '{ print $2 }'`
if [ "$pid" = "-1" ]; then
echo "$lxc_name is not running"
exit 1
fi
if [ $reboot -eq 1 ]; then
kill -s INT $pid
exit 0
else
kill -s PWR $pid
fi
if [ $dowait -eq 0 ]; then
exit 0
fi
if [ $timeout != "-1" ]; then
trap dolxcstop EXIT
alarm $$ $timeout 2>/dev/null &
alarmpid=$!
fi
while ! lxc-info -n $lxc_name -P "$lxc_path" --state-is STOPPED; do
sleep 1
done
if [ $timeout != "-1" ]; then
trap - EXIT
# include subprocesses; otherwise, we may have to wait until sleep completes
# if called from a non-interactive context
kill $alarmpid $(ps --no-headers --ppid $alarmpid -o pid) 2>/dev/null || :
fi
echo "Container $lxc_name has shut down"
exit 0

View File

@ -28,11 +28,29 @@
#include <lxc/lxc.h>
#include <lxc/log.h>
#include <lxc/lxccontainer.h>
#include "arguments.h"
#include "commands.h"
#include "utils.h"
static int my_parser(struct lxc_arguments* args, int c, char* arg)
{
switch (c) {
case 'r': args->reboot = 1; break;
case 'W': args->nowait = 1; break;
case 't': args->timeout = atoi(arg); break;
case 'k': args->hardstop = 1; break;
case 's': args->shutdown = 1; break;
}
return 0;
}
static const struct option my_longopts[] = {
{"reboot", no_argument, 0, 'r'},
{"nowait", no_argument, 0, 'W'},
{"timeout", required_argument, 0, 't'},
{"kill", no_argument, 0, 'k'},
{"shutdown", no_argument, 0, 's'},
LXC_COMMON_OPTIONS
};
@ -44,14 +62,76 @@ static struct lxc_arguments my_args = {
lxc-stop stops a container with the identifier NAME\n\
\n\
Options :\n\
-n, --name=NAME NAME for name of the container\n",
-n, --name=NAME NAME for name of the container\n\
-r, --reboot reboot the container\n\
-W, --nowait don't wait for shutdown or reboot to complete\n\
-t, --timeout=T wait T seconds before hard-stopping\n\
-k, --kill kill container rather than request clean shutdown\n\
-s, --shutdown Only request clean shutdown, don't later force kill\n",
.options = my_longopts,
.parser = NULL,
.parser = my_parser,
.checker = NULL,
.timeout = 60,
};
/* returns -1 on failure, 0 on success */
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;
ret = gettimeofday(&tv, NULL);
if (ret)
break;
curtime = tv.tv_sec;
sleep(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;
bool s;
int ret = -1;
if (lxc_arguments_parse(&my_args, argc, argv))
return -1;
@ -59,5 +139,36 @@ int main(int argc, char *argv[])
my_args.progname, my_args.quiet, my_args.lxcpath[0]))
return -1;
return lxc_cmd_stop(my_args.name, my_args.lxcpath[0]);
c = lxc_container_new(my_args.name, my_args.lxcpath[0]);
if (!c) {
fprintf(stderr, "Error opening container\n");
goto out;
}
if (!c->is_running(c)) {
fprintf(stderr, "%s is not running\n", c->name);
ret = -2;
goto out;
}
if (my_args.hardstop) {
ret = c->stop(c) ? 0 : -1;
goto out;
}
if (my_args.reboot) {
ret = do_reboot_and_check(&my_args, c);
goto out;
}
s = c->shutdown(c, my_args.timeout);
if (!s) {
if (!my_args.shutdown)
ret = c->wait(c, "STOPPED", -1) ? 0 : -1;
else
ret = -1; // fail
}
out:
lxc_container_put(c);
return ret;
}

View File

@ -45,6 +45,105 @@
lxc_log_define(lxc_container, lxc);
static bool file_exists(char *f)
{
struct stat statbuf;
return stat(f, &statbuf) == 0;
}
/*
* A few functions to help detect when a container creation failed.
* If a container creation was killed partway through, then trying
* to actually start that container could harm the host. We detect
* this by creating a 'partial' file under the container directory,
* and keeping an advisory lock. When container creation completes,
* we remove that file. When we load or try to start a container, if
* we find that file, without a flock, we remove the container.
*/
int ongoing_create(struct lxc_container *c)
{
int len = strlen(c->config_path) + strlen(c->name) + 10;
char *path = alloca(len);
int fd, ret;
ret = snprintf(path, len, "%s/%s/partial", c->config_path, c->name);
if (ret < 0 || ret >= len) {
ERROR("Error writing partial pathname");
return -1;
}
if (!file_exists(path))
return 0;
if (process_lock())
return -1;
if ((fd = open(path, O_RDWR)) < 0) {
// give benefit of the doubt
SYSERROR("Error opening partial file");
process_unlock();
return 0;
}
if ((ret = flock(fd, LOCK_EX | LOCK_NB)) == -1 &&
errno == EWOULDBLOCK) {
// create is still ongoing
close(fd);
process_unlock();
return 1;
}
// create completed but partial is still there.
close(fd);
process_unlock();
return 2;
}
int create_partial(struct lxc_container *c)
{
// $lxcpath + '/' + $name + '/partial' + \0
int len = strlen(c->config_path) + strlen(c->name) + 10;
char *path = alloca(len);
int fd, ret;
ret = snprintf(path, len, "%s/%s/partial", c->config_path, c->name);
if (ret < 0 || ret >= len) {
ERROR("Error writing partial pathname");
return -1;
}
if (process_lock() < 0)
return -1;
if ((fd=open(path, O_CREAT | O_EXCL, 0755)) < 0) {
SYSERROR("Erorr creating partial file");
process_unlock();
return -1;
}
if (flock(fd, LOCK_EX) < 0) {
SYSERROR("Error locking partial file %s", path);
close(fd);
process_unlock();
return -1;
}
process_unlock();
return fd;
}
void remove_partial(struct lxc_container *c, int fd)
{
// $lxcpath + '/' + $name + '/partial' + \0
int len = strlen(c->config_path) + strlen(c->name) + 10;
char *path = alloca(len);
int ret;
close(fd);
ret = snprintf(path, len, "%s/%s/partial", c->config_path, c->name);
if (ret < 0 || ret >= len) {
ERROR("Error writing partial pathname");
return;
}
if (process_lock())
return;
if (unlink(path) < 0)
SYSERROR("Error unlink partial file %s", path);
process_unlock();
}
/* LOCKING
* 1. c->privlock protects the struct lxc_container from multiple threads.
* 2. c->slock protects the on-disk container data
@ -163,13 +262,6 @@ int lxc_container_put(struct lxc_container *c)
return 0;
}
static bool file_exists(char *f)
{
struct stat statbuf;
return stat(f, &statbuf) == 0;
}
static bool lxcapi_is_defined(struct lxc_container *c)
{
struct stat statbuf;
@ -349,6 +441,19 @@ static bool lxcapi_start(struct lxc_container *c, int useinit, char * const argv
if (!c->lxc_conf)
return false;
if ((ret = ongoing_create(c)) < 0) {
ERROR("Error checking for incomplete creation");
return false;
}
if (ret == 2) {
ERROR("Error: %s creation was not completed", c->name);
c->destroy(c);
return false;
} else if (ret == 1) {
ERROR("Error: creation of %s is ongoing", c->name);
return false;
}
/* is this app meant to be run through lxcinit, as in lxc-execute? */
if (useinit && !argv)
return false;
@ -550,7 +655,7 @@ static bool lxcapi_create(struct lxc_container *c, const char *t, char *const ar
bool bret = false;
pid_t pid;
char *tpath = NULL, **newargv;
int ret, len, nargs = 0;
int partial_fd, ret, len, nargs = 0;
if (!c)
return false;
@ -576,6 +681,10 @@ static bool lxcapi_create(struct lxc_container *c, const char *t, char *const ar
if (lxcapi_is_defined(c) && c->lxc_conf && c->lxc_conf->rootfs.path && access(c->lxc_conf->rootfs.path, F_OK) == 0)
goto out;
/* Mark that this container is being created */
if ((partial_fd = create_partial(c)) < 0)
goto out;
/* we're going to fork. but since we'll wait for our child, we
* don't need to lxc_container_get */
@ -659,6 +768,8 @@ static bool lxcapi_create(struct lxc_container *c, const char *t, char *const ar
bret = load_config_locked(c, c->configfile);
out_unlock:
if (partial_fd >= 0)
remove_partial(c, partial_fd);
container_disk_unlock(c);
out:
if (tpath)
@ -666,6 +777,23 @@ out:
return bret;
}
static bool lxcapi_reboot(struct lxc_container *c)
{
pid_t pid;
if (!c)
return false;
if (!c->is_running(c))
return false;
pid = c->init_pid(c);
if (pid <= 0)
return false;
if (kill(pid, SIGINT) < 0)
return false;
return true;
}
static bool lxcapi_shutdown(struct lxc_container *c, int timeout)
{
bool retv;
@ -1699,6 +1827,12 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath
if (file_exists(c->configfile))
lxcapi_load_config(c, NULL);
if (ongoing_create(c) == 2) {
ERROR("Error: %s creation was not completed", c->name);
c->destroy(c);
goto err;
}
// assign the member functions
c->is_defined = lxcapi_is_defined;
c->state = lxcapi_state;
@ -1720,6 +1854,7 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath
c->create = lxcapi_create;
c->createl = lxcapi_createl;
c->shutdown = lxcapi_shutdown;
c->reboot = lxcapi_reboot;
c->clear_config_item = lxcapi_clear_config_item;
c->get_config_item = lxcapi_get_config_item;
c->get_cgroup_item = lxcapi_get_cgroup_item;

View File

@ -50,6 +50,8 @@ struct lxc_container {
bool (*save_config)(struct lxc_container *c, const char *alt_file);
bool (*create)(struct lxc_container *c, const char *t, char *const argv[]);
bool (*createl)(struct lxc_container *c, const char *t, ...);
/* send SIGINT to ask container to reboot */
bool (*reboot)(struct lxc_container *c);
/* send SIGPWR. if timeout is not 0 or -1, do a hard stop after timeout seconds */
bool (*shutdown)(struct lxc_container *c, int timeout);
/* clear all network or capability items in the in-memory configuration */