libvirt/src/lxc/lxc_process.c

1370 lines
43 KiB
C
Raw Normal View History

/*
* Copyright (C) 2010-2012 Red Hat, Inc.
* Copyright IBM Corp. 2008
*
* lxc_process.c: LXC process lifecycle management
*
* 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, see
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include "lxc_process.h"
#include "lxc_domain.h"
#include "lxc_container.h"
#include "lxc_fuse.h"
#include "datatypes.h"
#include "virfile.h"
#include "virpidfile.h"
#include "virnetdev.h"
#include "virnetdevveth.h"
#include "virnetdevbridge.h"
#include "virtime.h"
#include "domain_nwfilter.h"
#include "network/bridge_driver.h"
#include "memory.h"
#include "domain_audit.h"
#include "virterror_internal.h"
#include "logging.h"
#include "command.h"
#include "hooks.h"
#define VIR_FROM_THIS VIR_FROM_LXC
#define START_POSTFIX ": starting up\n"
int virLXCProcessAutoDestroyInit(virLXCDriverPtr driver)
{
if (!(driver->autodestroy = virHashCreate(5, NULL)))
return -1;
return 0;
}
struct virLXCProcessAutoDestroyData {
virLXCDriverPtr driver;
virConnectPtr conn;
};
static void virLXCProcessAutoDestroyDom(void *payload,
const void *name,
void *opaque)
{
struct virLXCProcessAutoDestroyData *data = opaque;
virConnectPtr conn = payload;
const char *uuidstr = name;
unsigned char uuid[VIR_UUID_BUFLEN];
virDomainObjPtr dom;
virDomainEventPtr event = NULL;
virLXCDomainObjPrivatePtr priv;
VIR_DEBUG("conn=%p uuidstr=%s thisconn=%p", conn, uuidstr, data->conn);
if (data->conn != conn)
return;
if (virUUIDParse(uuidstr, uuid) < 0) {
VIR_WARN("Failed to parse %s", uuidstr);
return;
}
if (!(dom = virDomainFindByUUID(&data->driver->domains,
uuid))) {
VIR_DEBUG("No domain object to kill");
return;
}
priv = dom->privateData;
VIR_DEBUG("Killing domain");
virLXCProcessStop(data->driver, dom, VIR_DOMAIN_SHUTOFF_DESTROYED);
virDomainAuditStop(dom, "destroyed");
event = virDomainEventNewFromObj(dom,
VIR_DOMAIN_EVENT_STOPPED,
VIR_DOMAIN_EVENT_STOPPED_DESTROYED);
priv->doneStopEvent = true;
if (dom && !dom->persistent)
virDomainRemoveInactive(&data->driver->domains, dom);
if (dom)
virDomainObjUnlock(dom);
if (event)
virDomainEventStateQueue(data->driver->domainEventState, event);
virHashRemoveEntry(data->driver->autodestroy, uuidstr);
}
/*
* Precondition: driver is locked
*/
void virLXCProcessAutoDestroyRun(virLXCDriverPtr driver, virConnectPtr conn)
{
struct virLXCProcessAutoDestroyData data = {
driver, conn
};
VIR_DEBUG("conn=%p", conn);
virHashForEach(driver->autodestroy, virLXCProcessAutoDestroyDom, &data);
}
void virLXCProcessAutoDestroyShutdown(virLXCDriverPtr driver)
{
virHashFree(driver->autodestroy);
}
int virLXCProcessAutoDestroyAdd(virLXCDriverPtr driver,
virDomainObjPtr vm,
virConnectPtr conn)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
virUUIDFormat(vm->def->uuid, uuidstr);
VIR_DEBUG("vm=%s uuid=%s conn=%p", vm->def->name, uuidstr, conn);
if (virHashAddEntry(driver->autodestroy, uuidstr, conn) < 0)
return -1;
return 0;
}
int virLXCProcessAutoDestroyRemove(virLXCDriverPtr driver,
virDomainObjPtr vm)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
virUUIDFormat(vm->def->uuid, uuidstr);
VIR_DEBUG("vm=%s uuid=%s", vm->def->name, uuidstr);
if (virHashRemoveEntry(driver->autodestroy, uuidstr) < 0)
return -1;
return 0;
}
static virConnectPtr
virLXCProcessAutoDestroyGetConn(virLXCDriverPtr driver,
virDomainObjPtr vm)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
virUUIDFormat(vm->def->uuid, uuidstr);
VIR_DEBUG("vm=%s uuid=%s", vm->def->name, uuidstr);
return virHashLookup(driver->autodestroy, uuidstr);
}
static int
virLXCProcessReboot(virLXCDriverPtr driver,
virDomainObjPtr vm)
{
virConnectPtr conn = virLXCProcessAutoDestroyGetConn(driver, vm);
int reason = vm->state.reason;
bool autodestroy = false;
int ret = -1;
virDomainDefPtr savedDef;
Fix deadlock in handling EOF in LXC monitor Depending on the scenario in which LXC containers exit, it is possible for the EOF callback of the LXC monitor to deadlock the driver. #0 0x00000038a0a0de4d in __lll_lock_wait () from /lib64/libpthread.so.0 #1 0x00000038a0a09ca6 in _L_lock_840 () from /lib64/libpthread.so.0 #2 0x00000038a0a09ba8 in pthread_mutex_lock () from /lib64/libpthread.so.0 #3 0x00007f4bd9579d55 in virMutexLock (m=<optimized out>) at util/threads-pthread.c:85 #4 0x00007f4bcacc7597 in lxcDriverLock (driver=0x7f4bc40c8290) at lxc/lxc_conf.h:81 #5 virLXCProcessMonitorEOFNotify (mon=<optimized out>, vm=0x7f4bb4000b00) at lxc/lxc_process.c:581 #6 0x00007f4bd9645c91 in virNetClientCloseLocked (client=client@entry=0x7f4bb4009e60) at rpc/virnetclient.c:554 #7 0x00007f4bd96460f8 in virNetClientIOEventLoopPassTheBuck (thiscall=0x0, client=0x7f4bb4009e60) at rpc/virnetclient.c:1306 #8 virNetClientIOEventLoopPassTheBuck (client=0x7f4bb4009e60, thiscall=0x0) at rpc/virnetclient.c:1287 #9 0x00007f4bd96467a2 in virNetClientCloseInternal (reason=3, client=0x7f4bb4009e60) at rpc/virnetclient.c:589 #10 virNetClientCloseInternal (client=0x7f4bb4009e60, reason=3) at rpc/virnetclient.c:561 #11 0x00007f4bcacc4a82 in virLXCMonitorClose (mon=0x7f4bb4000a00) at lxc/lxc_monitor.c:201 #12 0x00007f4bcacc55ac in virLXCProcessCleanup (reason=<optimized out>, vm=0x7f4bb4000b00, driver=0x7f4bc40c8290) at lxc/lxc_process.c:240 #13 virLXCProcessStop (driver=0x7f4bc40c8290, vm=vm@entry=0x7f4bb4000b00, reason=reason@entry=VIR_DOMAIN_SHUTOFF_DESTROYED) at lxc/lxc_process.c:735 #14 0x00007f4bcacc5bd2 in virLXCProcessAutoDestroyDom (payload=<optimized out>, name=0x7f4bb4003c80, opaque=0x7fff41af2df0) at lxc/lxc_process.c:94 #15 0x00007f4bd9586649 in virHashForEach (table=0x7f4bc409b270, iter=iter@entry=0x7f4bcacc5ab0 <virLXCProcessAutoDestroyDom>, data=data@entry=0x7fff41af2df0) at util/virhash.c:514 #16 0x00007f4bcacc52d7 in virLXCProcessAutoDestroyRun (driver=driver@entry=0x7f4bc40c8290, conn=conn@entry=0x7f4bb8000ab0) at lxc/lxc_process.c:120 #17 0x00007f4bcacca628 in lxcClose (conn=0x7f4bb8000ab0) at lxc/lxc_driver.c:128 #18 0x00007f4bd95e67ab in virReleaseConnect (conn=conn@entry=0x7f4bb8000ab0) at datatypes.c:114 When the driver calls virLXCMonitorClose, there is really no need for the EOF callback to be invoked in this case, since the caller can easily handle events itself. In changing this, the monitor needs to take a deep copy of the callback list, not merely a reference. Also adds debug statements in various places to aid troubleshooting Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2012-09-26 14:08:20 +00:00
VIR_DEBUG("Faking reboot");
if (conn) {
virConnectRef(conn);
autodestroy = true;
} else {
conn = virConnectOpen("lxc:///");
/* Ignoring NULL conn which is mostly harmless here */
}
/* In a reboot scenario, we need to make sure we continue
* to use the current 'def', and not switch to 'newDef'.
* So temporarily hide the newDef and then reinstate it
*/
savedDef = vm->newDef;
vm->newDef = NULL;
virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN);
vm->newDef = savedDef;
if (virLXCProcessStart(conn, driver, vm, autodestroy, reason) < 0) {
VIR_WARN("Unable to handle reboot of vm %s",
vm->def->name);
goto cleanup;
}
if (conn)
virConnectClose(conn);
ret = 0;
cleanup:
return ret;
}
/**
* virLXCProcessCleanup:
* @driver: pointer to driver structure
* @vm: pointer to VM to clean up
* @reason: reason for switching the VM to shutoff state
*
* Cleanout resources associated with the now dead VM
*
*/
static void virLXCProcessCleanup(virLXCDriverPtr driver,
virDomainObjPtr vm,
virDomainShutoffReason reason)
{
virCgroupPtr cgroup;
int i;
virLXCDomainObjPrivatePtr priv = vm->privateData;
virNetDevVPortProfilePtr vport = NULL;
Run an RPC protocol over the LXC controller monitor This defines a new RPC protocol to be used between the LXC controller and the libvirtd LXC driver. There is only a single RPC message defined thus far, an asynchronous "EXIT" event that is emitted just before the LXC controller process exits. This provides the LXC driver with details about how the container shutdown - normally, or abnormally (crashed), thus allowing the driver to emit better libvirt events. Emitting the event in the LXC controller requires a few little tricks with the RPC service. Simply calling the virNetServiceClientSendMessage does not work, since this merely queues the message for asynchronous processing. In addition the main event loop is no longer running at the point the event is emitted, so no I/O is processed. Thus after invoking virNetServiceClientSendMessage it is necessary to mark the client as being in "delayed close" mode. Then the event loop is run again, until the client completes its close - this happens only after the queued message has been fully transmitted. The final complexity is that it is not safe to run virNetServerQuit() from the client close callback, since that is invoked from a context where the server is locked. Thus a zero-second timer is used to trigger shutdown of the event loop, causing the controller to finally exit. * src/Makefile.am: Add rules for generating RPC protocol files and dispatch methods * src/lxc/lxc_controller.c: Emit an RPC event immediately before exiting * src/lxc/lxc_domain.h: Record the shutdown reason given by the controller * src/lxc/lxc_monitor.c, src/lxc/lxc_monitor.h: Register RPC program and event handler. Add callback to let driver receive EXIT event. * src/lxc/lxc_process.c: Use monitor exit event to decide what kind of domain event to emit * src/lxc/lxc_protocol.x: Define wire protocol for LXC controller monitor. Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2012-07-17 14:54:08 +00:00
VIR_DEBUG("Stopping VM name=%s pid=%d reason=%d",
vm->def->name, (int)vm->pid, (int)reason);
/* now that we know it's stopped call the hook if present */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
char *xml = virDomainDefFormat(vm->def, 0);
/* we can't stop the operation even if the script raised an error */
virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_STOPPED, VIR_HOOK_SUBOP_END,
NULL, xml, NULL);
VIR_FREE(xml);
}
/* Stop autodestroy in case guest is restarted */
virLXCProcessAutoDestroyRemove(driver, vm);
if (priv->monitor) {
virLXCMonitorClose(priv->monitor);
virObjectUnref(priv->monitor);
priv->monitor = NULL;
}
virPidFileDelete(driver->stateDir, vm->def->name);
virDomainDeleteConfig(driver->stateDir, NULL, vm);
virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF, reason);
vm->pid = -1;
vm->def->id = -1;
driver->nactive--;
if (!driver->nactive && driver->inhibitCallback)
driver->inhibitCallback(false, driver->inhibitOpaque);
for (i = 0 ; i < vm->def->nnets ; i++) {
virDomainNetDefPtr iface = vm->def->nets[i];
vport = virDomainNetGetActualVirtPortProfile(iface);
if (iface->ifname)
ignore_value(virNetDevSetOnline(iface->ifname, false));
if (vport && vport->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH)
ignore_value(virNetDevOpenvswitchRemovePort(
virDomainNetGetActualBridgeName(iface),
iface->ifname));
ignore_value(virNetDevVethDelete(iface->ifname));
networkReleaseActualDevice(iface);
}
virDomainConfVMNWFilterTeardown(vm);
if (driver->cgroup &&
virCgroupForDomain(driver->cgroup, vm->def->name, &cgroup, 0) == 0) {
virCgroupRemove(cgroup);
virCgroupFree(&cgroup);
}
/* now that we know it's stopped call the hook if present */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
char *xml = virDomainDefFormat(vm->def, 0);
/* we can't stop the operation even if the script raised an error */
virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_RELEASE, VIR_HOOK_SUBOP_END,
NULL, xml, NULL);
VIR_FREE(xml);
}
if (vm->newDef) {
virDomainDefFree(vm->def);
vm->def = vm->newDef;
vm->def->id = -1;
vm->newDef = NULL;
}
}
static int virLXCProcessSetupInterfaceBridged(virConnectPtr conn,
virDomainDefPtr vm,
virDomainNetDefPtr net,
const char *brname,
unsigned int *nveths,
char ***veths)
{
int ret = -1;
char *parentVeth;
char *containerVeth = NULL;
const virNetDevVPortProfilePtr vport = virDomainNetGetActualVirtPortProfile(net);
VIR_DEBUG("calling vethCreate()");
parentVeth = net->ifname;
if (virNetDevVethCreate(&parentVeth, &containerVeth) < 0)
goto cleanup;
VIR_DEBUG("parentVeth: %s, containerVeth: %s", parentVeth, containerVeth);
if (net->ifname == NULL)
net->ifname = parentVeth;
if (VIR_REALLOC_N(*veths, (*nveths)+1) < 0) {
virReportOOMError();
VIR_FREE(containerVeth);
goto cleanup;
}
(*veths)[(*nveths)] = containerVeth;
(*nveths)++;
if (virNetDevSetMAC(containerVeth, &net->mac) < 0)
goto cleanup;
if (vport && vport->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH)
ret = virNetDevOpenvswitchAddPort(brname, parentVeth, &net->mac,
vm->uuid, vport, virDomainNetGetActualVlan(net));
else
ret = virNetDevBridgeAddPort(brname, parentVeth);
if (ret < 0)
goto cleanup;
if (virNetDevSetOnline(parentVeth, true) < 0)
goto cleanup;
if (virNetDevBandwidthSet(net->ifname,
virDomainNetGetActualBandwidth(net)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot set bandwidth limits on %s"),
net->ifname);
goto cleanup;
}
if (net->filter &&
virDomainConfNWFilterInstantiate(conn, vm->uuid, net) < 0)
goto cleanup;
ret = 0;
cleanup:
return ret;
}
static int virLXCProcessSetupInterfaceDirect(virConnectPtr conn,
virDomainDefPtr def,
virDomainNetDefPtr net,
unsigned int *nveths,
char ***veths)
{
int ret = -1;
char *res_ifname = NULL;
virLXCDriverPtr driver = conn->privateData;
virNetDevBandwidthPtr bw;
virNetDevVPortProfilePtr prof;
/* XXX how todo bandwidth controls ?
* Since the 'net-ifname' is about to be moved to a different
* namespace & renamed, there will be no host side visible
* interface for the container to attach rules to
*/
bw = virDomainNetGetActualBandwidth(net);
if (bw) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Unable to set network bandwidth on direct interfaces"));
return -1;
}
/* XXX how todo port profiles ?
* Although we can do the association during container
* startup, at shutdown we are unable to disassociate
* because the macvlan device was moved to the container
* and automagically dies when the container dies. So
* we have no dev to perform disassociation with.
*/
prof = virDomainNetGetActualVirtPortProfile(net);
if (prof) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Unable to set port profile on direct interfaces"));
return -1;
}
if (VIR_REALLOC_N(*veths, (*nveths)+1) < 0) {
virReportOOMError();
return -1;
}
(*veths)[(*nveths)] = NULL;
if (virNetDevMacVLanCreateWithVPortProfile(
net->ifname, &net->mac,
virDomainNetGetActualDirectDev(net),
virDomainNetGetActualDirectMode(net),
false, false, def->uuid,
virDomainNetGetActualVirtPortProfile(net),
&res_ifname,
VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
driver->stateDir,
virDomainNetGetActualBandwidth(net)) < 0)
goto cleanup;
(*veths)[(*nveths)] = res_ifname;
(*nveths)++;
ret = 0;
cleanup:
return ret;
}
/**
* virLXCProcessSetupInterfaces:
* @conn: pointer to connection
* @def: pointer to virtual machine structure
* @nveths: number of interfaces
* @veths: interface names
*
* Sets up the container interfaces by creating the veth device pairs and
* attaching the parent end to the appropriate bridge. The container end
* will moved into the container namespace later after clone has been called.
*
* Returns 0 on success or -1 in case of error
*/
static int virLXCProcessSetupInterfaces(virConnectPtr conn,
virDomainDefPtr def,
unsigned int *nveths,
char ***veths)
{
int ret = -1;
size_t i;
for (i = 0 ; i < def->nnets ; i++) {
/* If appropriate, grab a physical device from the configured
* network's pool of devices, or resolve bridge device name
* to the one defined in the network definition.
*/
if (networkAllocateActualDevice(def->nets[i]) < 0)
goto cleanup;
switch (virDomainNetGetActualType(def->nets[i])) {
case VIR_DOMAIN_NET_TYPE_NETWORK: {
virNetworkPtr network;
char *brname = NULL;
bool fail = false;
int active;
virErrorPtr errobj;
if (!(network = virNetworkLookupByName(conn,
def->nets[i]->data.network.name)))
goto cleanup;
active = virNetworkIsActive(network);
if (active != 1) {
fail = true;
if (active == 0)
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Network '%s' is not active."),
def->nets[i]->data.network.name);
}
if (!fail) {
brname = virNetworkGetBridgeName(network);
if (brname == NULL)
fail = true;
}
/* Make sure any above failure is preserved */
errobj = virSaveLastError();
virNetworkFree(network);
virSetError(errobj);
virFreeError(errobj);
if (fail)
goto cleanup;
if (virLXCProcessSetupInterfaceBridged(conn,
def,
def->nets[i],
brname,
nveths,
veths) < 0) {
VIR_FREE(brname);
goto cleanup;
}
VIR_FREE(brname);
break;
}
case VIR_DOMAIN_NET_TYPE_BRIDGE: {
const char *brname = virDomainNetGetActualBridgeName(def->nets[i]);
if (!brname) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("No bridge name specified"));
goto cleanup;
}
if (virLXCProcessSetupInterfaceBridged(conn,
def,
def->nets[i],
brname,
nveths,
veths) < 0)
goto cleanup;
} break;
case VIR_DOMAIN_NET_TYPE_DIRECT:
if (virLXCProcessSetupInterfaceDirect(conn,
def,
def->nets[i],
nveths,
veths) < 0)
goto cleanup;
break;
case VIR_DOMAIN_NET_TYPE_USER:
case VIR_DOMAIN_NET_TYPE_ETHERNET:
case VIR_DOMAIN_NET_TYPE_SERVER:
case VIR_DOMAIN_NET_TYPE_CLIENT:
case VIR_DOMAIN_NET_TYPE_MCAST:
case VIR_DOMAIN_NET_TYPE_INTERNAL:
case VIR_DOMAIN_NET_TYPE_LAST:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unsupported network type %s"),
virDomainNetTypeToString(
virDomainNetGetActualType(def->nets[i])
));
goto cleanup;
}
}
ret = 0;
cleanup:
if (ret < 0) {
for (i = 0 ; i < def->nnets ; i++) {
virDomainNetDefPtr iface = def->nets[i];
virNetDevVPortProfilePtr vport = virDomainNetGetActualVirtPortProfile(iface);
if (vport && vport->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH)
ignore_value(virNetDevOpenvswitchRemovePort(
virDomainNetGetActualBridgeName(iface),
iface->ifname));
networkReleaseActualDevice(iface);
}
}
return ret;
}
extern virLXCDriverPtr lxc_driver;
Fix deadlock in handling EOF in LXC monitor Depending on the scenario in which LXC containers exit, it is possible for the EOF callback of the LXC monitor to deadlock the driver. #0 0x00000038a0a0de4d in __lll_lock_wait () from /lib64/libpthread.so.0 #1 0x00000038a0a09ca6 in _L_lock_840 () from /lib64/libpthread.so.0 #2 0x00000038a0a09ba8 in pthread_mutex_lock () from /lib64/libpthread.so.0 #3 0x00007f4bd9579d55 in virMutexLock (m=<optimized out>) at util/threads-pthread.c:85 #4 0x00007f4bcacc7597 in lxcDriverLock (driver=0x7f4bc40c8290) at lxc/lxc_conf.h:81 #5 virLXCProcessMonitorEOFNotify (mon=<optimized out>, vm=0x7f4bb4000b00) at lxc/lxc_process.c:581 #6 0x00007f4bd9645c91 in virNetClientCloseLocked (client=client@entry=0x7f4bb4009e60) at rpc/virnetclient.c:554 #7 0x00007f4bd96460f8 in virNetClientIOEventLoopPassTheBuck (thiscall=0x0, client=0x7f4bb4009e60) at rpc/virnetclient.c:1306 #8 virNetClientIOEventLoopPassTheBuck (client=0x7f4bb4009e60, thiscall=0x0) at rpc/virnetclient.c:1287 #9 0x00007f4bd96467a2 in virNetClientCloseInternal (reason=3, client=0x7f4bb4009e60) at rpc/virnetclient.c:589 #10 virNetClientCloseInternal (client=0x7f4bb4009e60, reason=3) at rpc/virnetclient.c:561 #11 0x00007f4bcacc4a82 in virLXCMonitorClose (mon=0x7f4bb4000a00) at lxc/lxc_monitor.c:201 #12 0x00007f4bcacc55ac in virLXCProcessCleanup (reason=<optimized out>, vm=0x7f4bb4000b00, driver=0x7f4bc40c8290) at lxc/lxc_process.c:240 #13 virLXCProcessStop (driver=0x7f4bc40c8290, vm=vm@entry=0x7f4bb4000b00, reason=reason@entry=VIR_DOMAIN_SHUTOFF_DESTROYED) at lxc/lxc_process.c:735 #14 0x00007f4bcacc5bd2 in virLXCProcessAutoDestroyDom (payload=<optimized out>, name=0x7f4bb4003c80, opaque=0x7fff41af2df0) at lxc/lxc_process.c:94 #15 0x00007f4bd9586649 in virHashForEach (table=0x7f4bc409b270, iter=iter@entry=0x7f4bcacc5ab0 <virLXCProcessAutoDestroyDom>, data=data@entry=0x7fff41af2df0) at util/virhash.c:514 #16 0x00007f4bcacc52d7 in virLXCProcessAutoDestroyRun (driver=driver@entry=0x7f4bc40c8290, conn=conn@entry=0x7f4bb8000ab0) at lxc/lxc_process.c:120 #17 0x00007f4bcacca628 in lxcClose (conn=0x7f4bb8000ab0) at lxc/lxc_driver.c:128 #18 0x00007f4bd95e67ab in virReleaseConnect (conn=conn@entry=0x7f4bb8000ab0) at datatypes.c:114 When the driver calls virLXCMonitorClose, there is really no need for the EOF callback to be invoked in this case, since the caller can easily handle events itself. In changing this, the monitor needs to take a deep copy of the callback list, not merely a reference. Also adds debug statements in various places to aid troubleshooting Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2012-09-26 14:08:20 +00:00
static void virLXCProcessMonitorEOFNotify(virLXCMonitorPtr mon,
virDomainObjPtr vm)
{
virLXCDriverPtr driver = lxc_driver;
virDomainEventPtr event = NULL;
virLXCDomainObjPrivatePtr priv;
Fix deadlock in handling EOF in LXC monitor Depending on the scenario in which LXC containers exit, it is possible for the EOF callback of the LXC monitor to deadlock the driver. #0 0x00000038a0a0de4d in __lll_lock_wait () from /lib64/libpthread.so.0 #1 0x00000038a0a09ca6 in _L_lock_840 () from /lib64/libpthread.so.0 #2 0x00000038a0a09ba8 in pthread_mutex_lock () from /lib64/libpthread.so.0 #3 0x00007f4bd9579d55 in virMutexLock (m=<optimized out>) at util/threads-pthread.c:85 #4 0x00007f4bcacc7597 in lxcDriverLock (driver=0x7f4bc40c8290) at lxc/lxc_conf.h:81 #5 virLXCProcessMonitorEOFNotify (mon=<optimized out>, vm=0x7f4bb4000b00) at lxc/lxc_process.c:581 #6 0x00007f4bd9645c91 in virNetClientCloseLocked (client=client@entry=0x7f4bb4009e60) at rpc/virnetclient.c:554 #7 0x00007f4bd96460f8 in virNetClientIOEventLoopPassTheBuck (thiscall=0x0, client=0x7f4bb4009e60) at rpc/virnetclient.c:1306 #8 virNetClientIOEventLoopPassTheBuck (client=0x7f4bb4009e60, thiscall=0x0) at rpc/virnetclient.c:1287 #9 0x00007f4bd96467a2 in virNetClientCloseInternal (reason=3, client=0x7f4bb4009e60) at rpc/virnetclient.c:589 #10 virNetClientCloseInternal (client=0x7f4bb4009e60, reason=3) at rpc/virnetclient.c:561 #11 0x00007f4bcacc4a82 in virLXCMonitorClose (mon=0x7f4bb4000a00) at lxc/lxc_monitor.c:201 #12 0x00007f4bcacc55ac in virLXCProcessCleanup (reason=<optimized out>, vm=0x7f4bb4000b00, driver=0x7f4bc40c8290) at lxc/lxc_process.c:240 #13 virLXCProcessStop (driver=0x7f4bc40c8290, vm=vm@entry=0x7f4bb4000b00, reason=reason@entry=VIR_DOMAIN_SHUTOFF_DESTROYED) at lxc/lxc_process.c:735 #14 0x00007f4bcacc5bd2 in virLXCProcessAutoDestroyDom (payload=<optimized out>, name=0x7f4bb4003c80, opaque=0x7fff41af2df0) at lxc/lxc_process.c:94 #15 0x00007f4bd9586649 in virHashForEach (table=0x7f4bc409b270, iter=iter@entry=0x7f4bcacc5ab0 <virLXCProcessAutoDestroyDom>, data=data@entry=0x7fff41af2df0) at util/virhash.c:514 #16 0x00007f4bcacc52d7 in virLXCProcessAutoDestroyRun (driver=driver@entry=0x7f4bc40c8290, conn=conn@entry=0x7f4bb8000ab0) at lxc/lxc_process.c:120 #17 0x00007f4bcacca628 in lxcClose (conn=0x7f4bb8000ab0) at lxc/lxc_driver.c:128 #18 0x00007f4bd95e67ab in virReleaseConnect (conn=conn@entry=0x7f4bb8000ab0) at datatypes.c:114 When the driver calls virLXCMonitorClose, there is really no need for the EOF callback to be invoked in this case, since the caller can easily handle events itself. In changing this, the monitor needs to take a deep copy of the callback list, not merely a reference. Also adds debug statements in various places to aid troubleshooting Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2012-09-26 14:08:20 +00:00
VIR_DEBUG("mon=%p vm=%p", mon, vm);
lxcDriverLock(driver);
virDomainObjLock(vm);
lxcDriverUnlock(driver);
priv = vm->privateData;
virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN);
if (!priv->wantReboot) {
virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN);
if (!priv->doneStopEvent) {
event = virDomainEventNewFromObj(vm,
VIR_DOMAIN_EVENT_STOPPED,
priv->stopReason);
virDomainAuditStop(vm, "shutdown");
} else {
VIR_DEBUG("Stop event has already been sent");
}
if (!vm->persistent) {
virDomainRemoveInactive(&driver->domains, vm);
vm = NULL;
}
} else {
int ret = virLXCProcessReboot(driver, vm);
virDomainAuditStop(vm, "reboot");
virDomainAuditStart(vm, "reboot", ret == 0);
if (ret == 0) {
event = virDomainEventRebootNewFromObj(vm);
} else {
event = virDomainEventNewFromObj(vm,
VIR_DOMAIN_EVENT_STOPPED,
priv->stopReason);
if (!vm->persistent) {
virDomainRemoveInactive(&driver->domains, vm);
vm = NULL;
}
}
}
if (vm)
virDomainObjUnlock(vm);
if (event) {
lxcDriverLock(driver);
virDomainEventStateQueue(driver->domainEventState, event);
lxcDriverUnlock(driver);
}
}
Run an RPC protocol over the LXC controller monitor This defines a new RPC protocol to be used between the LXC controller and the libvirtd LXC driver. There is only a single RPC message defined thus far, an asynchronous "EXIT" event that is emitted just before the LXC controller process exits. This provides the LXC driver with details about how the container shutdown - normally, or abnormally (crashed), thus allowing the driver to emit better libvirt events. Emitting the event in the LXC controller requires a few little tricks with the RPC service. Simply calling the virNetServiceClientSendMessage does not work, since this merely queues the message for asynchronous processing. In addition the main event loop is no longer running at the point the event is emitted, so no I/O is processed. Thus after invoking virNetServiceClientSendMessage it is necessary to mark the client as being in "delayed close" mode. Then the event loop is run again, until the client completes its close - this happens only after the queued message has been fully transmitted. The final complexity is that it is not safe to run virNetServerQuit() from the client close callback, since that is invoked from a context where the server is locked. Thus a zero-second timer is used to trigger shutdown of the event loop, causing the controller to finally exit. * src/Makefile.am: Add rules for generating RPC protocol files and dispatch methods * src/lxc/lxc_controller.c: Emit an RPC event immediately before exiting * src/lxc/lxc_domain.h: Record the shutdown reason given by the controller * src/lxc/lxc_monitor.c, src/lxc/lxc_monitor.h: Register RPC program and event handler. Add callback to let driver receive EXIT event. * src/lxc/lxc_process.c: Use monitor exit event to decide what kind of domain event to emit * src/lxc/lxc_protocol.x: Define wire protocol for LXC controller monitor. Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2012-07-17 14:54:08 +00:00
static void virLXCProcessMonitorExitNotify(virLXCMonitorPtr mon ATTRIBUTE_UNUSED,
virLXCProtocolExitStatus status,
virDomainObjPtr vm)
{
virLXCDomainObjPrivatePtr priv = vm->privateData;
switch (status) {
case VIR_LXC_PROTOCOL_EXIT_STATUS_SHUTDOWN:
priv->stopReason = VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN;
break;
case VIR_LXC_PROTOCOL_EXIT_STATUS_ERROR:
priv->stopReason = VIR_DOMAIN_EVENT_STOPPED_FAILED;
break;
case VIR_LXC_PROTOCOL_EXIT_STATUS_REBOOT:
priv->stopReason = VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN;
priv->wantReboot = true;
break;
Run an RPC protocol over the LXC controller monitor This defines a new RPC protocol to be used between the LXC controller and the libvirtd LXC driver. There is only a single RPC message defined thus far, an asynchronous "EXIT" event that is emitted just before the LXC controller process exits. This provides the LXC driver with details about how the container shutdown - normally, or abnormally (crashed), thus allowing the driver to emit better libvirt events. Emitting the event in the LXC controller requires a few little tricks with the RPC service. Simply calling the virNetServiceClientSendMessage does not work, since this merely queues the message for asynchronous processing. In addition the main event loop is no longer running at the point the event is emitted, so no I/O is processed. Thus after invoking virNetServiceClientSendMessage it is necessary to mark the client as being in "delayed close" mode. Then the event loop is run again, until the client completes its close - this happens only after the queued message has been fully transmitted. The final complexity is that it is not safe to run virNetServerQuit() from the client close callback, since that is invoked from a context where the server is locked. Thus a zero-second timer is used to trigger shutdown of the event loop, causing the controller to finally exit. * src/Makefile.am: Add rules for generating RPC protocol files and dispatch methods * src/lxc/lxc_controller.c: Emit an RPC event immediately before exiting * src/lxc/lxc_domain.h: Record the shutdown reason given by the controller * src/lxc/lxc_monitor.c, src/lxc/lxc_monitor.h: Register RPC program and event handler. Add callback to let driver receive EXIT event. * src/lxc/lxc_process.c: Use monitor exit event to decide what kind of domain event to emit * src/lxc/lxc_protocol.x: Define wire protocol for LXC controller monitor. Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2012-07-17 14:54:08 +00:00
default:
priv->stopReason = VIR_DOMAIN_EVENT_STOPPED_FAILED;
break;
}
VIR_DEBUG("Domain shutoff reason %d (from status %d)",
priv->stopReason, status);
}
/* XXX a little evil */
extern virLXCDriverPtr lxc_driver;
static void virLXCProcessMonitorInitNotify(virLXCMonitorPtr mon ATTRIBUTE_UNUSED,
pid_t initpid,
virDomainObjPtr vm)
{
virLXCDomainObjPrivatePtr priv = vm->privateData;
priv->initpid = initpid;
virDomainAuditInit(vm, initpid);
if (virDomainSaveStatus(lxc_driver->caps, lxc_driver->stateDir, vm) < 0)
VIR_WARN("Cannot update XML with PID for LXC %s", vm->def->name);
}
static virLXCMonitorCallbacks monitorCallbacks = {
.eofNotify = virLXCProcessMonitorEOFNotify,
Run an RPC protocol over the LXC controller monitor This defines a new RPC protocol to be used between the LXC controller and the libvirtd LXC driver. There is only a single RPC message defined thus far, an asynchronous "EXIT" event that is emitted just before the LXC controller process exits. This provides the LXC driver with details about how the container shutdown - normally, or abnormally (crashed), thus allowing the driver to emit better libvirt events. Emitting the event in the LXC controller requires a few little tricks with the RPC service. Simply calling the virNetServiceClientSendMessage does not work, since this merely queues the message for asynchronous processing. In addition the main event loop is no longer running at the point the event is emitted, so no I/O is processed. Thus after invoking virNetServiceClientSendMessage it is necessary to mark the client as being in "delayed close" mode. Then the event loop is run again, until the client completes its close - this happens only after the queued message has been fully transmitted. The final complexity is that it is not safe to run virNetServerQuit() from the client close callback, since that is invoked from a context where the server is locked. Thus a zero-second timer is used to trigger shutdown of the event loop, causing the controller to finally exit. * src/Makefile.am: Add rules for generating RPC protocol files and dispatch methods * src/lxc/lxc_controller.c: Emit an RPC event immediately before exiting * src/lxc/lxc_domain.h: Record the shutdown reason given by the controller * src/lxc/lxc_monitor.c, src/lxc/lxc_monitor.h: Register RPC program and event handler. Add callback to let driver receive EXIT event. * src/lxc/lxc_process.c: Use monitor exit event to decide what kind of domain event to emit * src/lxc/lxc_protocol.x: Define wire protocol for LXC controller monitor. Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2012-07-17 14:54:08 +00:00
.exitNotify = virLXCProcessMonitorExitNotify,
.initNotify = virLXCProcessMonitorInitNotify,
};
static virLXCMonitorPtr virLXCProcessConnectMonitor(virLXCDriverPtr driver,
virDomainObjPtr vm)
{
virLXCMonitorPtr monitor = NULL;
if (virSecurityManagerSetSocketLabel(driver->securityManager, vm->def) < 0)
goto cleanup;
/* Hold an extra reference because we can't allow 'vm' to be
* deleted while the monitor is active */
virObjectRef(vm);
monitor = virLXCMonitorNew(vm, driver->stateDir, &monitorCallbacks);
if (monitor == NULL)
virObjectUnref(vm);
if (virSecurityManagerClearSocketLabel(driver->securityManager, vm->def) < 0) {
if (monitor) {
virObjectUnref(monitor);
monitor = NULL;
}
goto cleanup;
}
cleanup:
return monitor;
}
int virLXCProcessStop(virLXCDriverPtr driver,
virDomainObjPtr vm,
virDomainShutoffReason reason)
{
virCgroupPtr group = NULL;
int rc;
Run an RPC protocol over the LXC controller monitor This defines a new RPC protocol to be used between the LXC controller and the libvirtd LXC driver. There is only a single RPC message defined thus far, an asynchronous "EXIT" event that is emitted just before the LXC controller process exits. This provides the LXC driver with details about how the container shutdown - normally, or abnormally (crashed), thus allowing the driver to emit better libvirt events. Emitting the event in the LXC controller requires a few little tricks with the RPC service. Simply calling the virNetServiceClientSendMessage does not work, since this merely queues the message for asynchronous processing. In addition the main event loop is no longer running at the point the event is emitted, so no I/O is processed. Thus after invoking virNetServiceClientSendMessage it is necessary to mark the client as being in "delayed close" mode. Then the event loop is run again, until the client completes its close - this happens only after the queued message has been fully transmitted. The final complexity is that it is not safe to run virNetServerQuit() from the client close callback, since that is invoked from a context where the server is locked. Thus a zero-second timer is used to trigger shutdown of the event loop, causing the controller to finally exit. * src/Makefile.am: Add rules for generating RPC protocol files and dispatch methods * src/lxc/lxc_controller.c: Emit an RPC event immediately before exiting * src/lxc/lxc_domain.h: Record the shutdown reason given by the controller * src/lxc/lxc_monitor.c, src/lxc/lxc_monitor.h: Register RPC program and event handler. Add callback to let driver receive EXIT event. * src/lxc/lxc_process.c: Use monitor exit event to decide what kind of domain event to emit * src/lxc/lxc_protocol.x: Define wire protocol for LXC controller monitor. Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2012-07-17 14:54:08 +00:00
VIR_DEBUG("Stopping VM name=%s pid=%d reason=%d",
vm->def->name, (int)vm->pid, (int)reason);
if (vm->pid <= 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Invalid PID %d for container"), vm->pid);
return -1;
}
virSecurityManagerRestoreAllLabel(driver->securityManager,
vm->def, false);
virSecurityManagerReleaseLabel(driver->securityManager, vm->def);
/* Clear out dynamically assigned labels */
if (vm->def->nseclabels &&
vm->def->seclabels[0]->type == VIR_DOMAIN_SECLABEL_DYNAMIC) {
VIR_FREE(vm->def->seclabels[0]->model);
VIR_FREE(vm->def->seclabels[0]->label);
VIR_FREE(vm->def->seclabels[0]->imagelabel);
}
if (virCgroupForDomain(driver->cgroup, vm->def->name, &group, 0) == 0) {
rc = virCgroupKillPainfully(group);
if (rc < 0) {
virReportSystemError(-rc, "%s",
_("Failed to kill container PIDs"));
rc = -1;
goto cleanup;
}
if (rc == 1) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Some container PIDs refused to die"));
rc = -1;
goto cleanup;
}
} else {
/* If cgroup doesn't exist, the VM pids must have already
* died and so we're just cleaning up stale state
*/
}
virLXCProcessCleanup(driver, vm, reason);
rc = 0;
cleanup:
virCgroupFree(&group);
return rc;
}
static virCommandPtr
virLXCProcessBuildControllerCmd(virLXCDriverPtr driver,
virDomainObjPtr vm,
int nveths,
char **veths,
int *ttyFDs,
size_t nttyFDs,
int handshakefd)
{
size_t i;
char *filterstr;
char *outputstr;
virCommandPtr cmd;
cmd = virCommandNew(vm->def->emulator);
/* The controller may call ip command, so we have to retain PATH. */
virCommandAddEnvPass(cmd, "PATH");
virCommandAddEnvFormat(cmd, "LIBVIRT_DEBUG=%d",
virLogGetDefaultPriority());
if (virLogGetNbFilters() > 0) {
filterstr = virLogGetFilters();
if (!filterstr) {
virReportOOMError();
goto cleanup;
}
virCommandAddEnvPair(cmd, "LIBVIRT_LOG_FILTERS", filterstr);
VIR_FREE(filterstr);
}
if (driver->log_libvirtd) {
if (virLogGetNbOutputs() > 0) {
outputstr = virLogGetOutputs();
if (!outputstr) {
virReportOOMError();
goto cleanup;
}
virCommandAddEnvPair(cmd, "LIBVIRT_LOG_OUTPUTS", outputstr);
VIR_FREE(outputstr);
}
} else {
virCommandAddEnvFormat(cmd,
"LIBVIRT_LOG_OUTPUTS=%d:stderr",
virLogGetDefaultPriority());
}
virCommandAddArgList(cmd, "--name", vm->def->name, NULL);
for (i = 0 ; i < nttyFDs ; i++) {
virCommandAddArg(cmd, "--console");
virCommandAddArgFormat(cmd, "%d", ttyFDs[i]);
virCommandPreserveFD(cmd, ttyFDs[i]);
}
virCommandAddArgPair(cmd, "--security",
virSecurityManagerGetModel(driver->securityManager));
virCommandAddArg(cmd, "--handshake");
virCommandAddArgFormat(cmd, "%d", handshakefd);
virCommandAddArg(cmd, "--background");
for (i = 0 ; i < nveths ; i++) {
virCommandAddArgList(cmd, "--veth", veths[i], NULL);
}
virCommandPreserveFD(cmd, handshakefd);
return cmd;
cleanup:
virCommandFree(cmd);
return NULL;
}
static int
virLXCProcessReadLogOutput(virDomainObjPtr vm,
char *logfile,
off_t pos,
char *buf,
size_t buflen)
{
int fd;
off_t off;
int whence;
int got = 0, ret = -1;
int retries = 10;
if ((fd = open(logfile, O_RDONLY)) < 0) {
virReportSystemError(errno, _("failed to open logfile %s"),
logfile);
goto cleanup;
}
if (pos < 0) {
off = 0;
whence = SEEK_END;
} else {
off = pos;
whence = SEEK_SET;
}
if (lseek(fd, off, whence) < 0) {
if (whence == SEEK_END)
virReportSystemError(errno,
_("unable to seek to end of log for %s"),
logfile);
else
virReportSystemError(errno,
_("unable to seek to %lld from start for %s"),
(long long)off, logfile);
goto cleanup;
}
while (retries) {
ssize_t bytes;
int isdead = 0;
if (kill(vm->pid, 0) == -1 && errno == ESRCH)
isdead = 1;
/* Any failures should be detected before we read the log, so we
* always have something useful to report on failure. */
bytes = saferead(fd, buf+got, buflen-got-1);
if (bytes < 0) {
virReportSystemError(errno, "%s",
_("Failure while reading guest log output"));
goto cleanup;
}
got += bytes;
buf[got] = '\0';
if ((got == buflen-1) || isdead) {
break;
}
usleep(100*1000);
retries--;
}
ret = got;
cleanup:
VIR_FORCE_CLOSE(fd);
return ret;
}
/**
* virLXCProcessStart:
* @conn: pointer to connection
* @driver: pointer to driver structure
* @vm: pointer to virtual machine structure
* @autoDestroy: mark the domain for auto destruction
* @reason: reason for switching vm to running state
*
* Starts a vm
*
* Returns 0 on success or -1 in case of error
*/
int virLXCProcessStart(virConnectPtr conn,
virLXCDriverPtr driver,
virDomainObjPtr vm,
bool autoDestroy,
virDomainRunningReason reason)
{
int rc = -1, r;
size_t nttyFDs = 0;
int *ttyFDs = NULL;
size_t i;
char *logfile = NULL;
int logfd = -1;
unsigned int nveths = 0;
char **veths = NULL;
int handshakefds[2] = { -1, -1 };
off_t pos = -1;
char ebuf[1024];
char *timestamp;
virCommandPtr cmd = NULL;
virLXCDomainObjPrivatePtr priv = vm->privateData;
virErrorPtr err = NULL;
if (!lxc_driver->cgroup) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("The 'cpuacct', 'devices' & 'memory' cgroups controllers must be mounted"));
return -1;
}
if (!virCgroupMounted(lxc_driver->cgroup,
VIR_CGROUP_CONTROLLER_CPUACCT)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to find 'cpuacct' cgroups controller mount"));
return -1;
}
if (!virCgroupMounted(lxc_driver->cgroup,
VIR_CGROUP_CONTROLLER_DEVICES)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to find 'devices' cgroups controller mount"));
return -1;
}
if (!virCgroupMounted(lxc_driver->cgroup,
VIR_CGROUP_CONTROLLER_MEMORY)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to find 'memory' cgroups controller mount"));
return -1;
}
if (virFileMakePath(driver->logDir) < 0) {
virReportSystemError(errno,
_("Cannot create log directory '%s'"),
driver->logDir);
return -1;
}
if (virAsprintf(&logfile, "%s/%s.log",
driver->logDir, vm->def->name) < 0) {
virReportOOMError();
return -1;
}
/* Do this up front, so any part of the startup process can add
* runtime state to vm->def that won't be persisted. This let's us
* report implicit runtime defaults in the XML, like vnc listen/socket
*/
VIR_DEBUG("Setting current domain def as transient");
if (virDomainObjSetDefTransient(driver->caps, vm, true) < 0)
goto cleanup;
/* Run an early hook to set-up missing devices */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
char *xml = virDomainDefFormat(vm->def, 0);
int hookret;
hookret = virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_PREPARE, VIR_HOOK_SUBOP_BEGIN,
NULL, xml, NULL);
VIR_FREE(xml);
/*
* If the script raised an error abort the launch
*/
if (hookret < 0)
goto cleanup;
}
/* Here we open all the PTYs we need on the host OS side.
* The LXC controller will open the guest OS side PTYs
* and forward I/O between them.
*/
nttyFDs = vm->def->nconsoles;
if (VIR_ALLOC_N(ttyFDs, nttyFDs) < 0) {
virReportOOMError();
goto cleanup;
}
/* If you are using a SecurityDriver with dynamic labelling,
then generate a security label for isolation */
VIR_DEBUG("Generating domain security label (if required)");
if (vm->def->nseclabels &&
vm->def->seclabels[0]->type == VIR_DOMAIN_SECLABEL_DEFAULT)
vm->def->seclabels[0]->type = VIR_DOMAIN_SECLABEL_NONE;
if (virSecurityManagerGenLabel(driver->securityManager, vm->def) < 0) {
virDomainAuditSecurityLabel(vm, false);
goto cleanup;
}
virDomainAuditSecurityLabel(vm, true);
VIR_DEBUG("Setting domain security labels");
if (virSecurityManagerSetAllLabel(driver->securityManager,
vm->def, NULL) < 0)
goto cleanup;
for (i = 0 ; i < vm->def->nconsoles ; i++)
ttyFDs[i] = -1;
for (i = 0 ; i < vm->def->nconsoles ; i++) {
char *ttyPath;
if (vm->def->consoles[i]->source.type != VIR_DOMAIN_CHR_TYPE_PTY) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Only PTY console types are supported"));
goto cleanup;
}
if (virFileOpenTty(&ttyFDs[i], &ttyPath, 1) < 0) {
virReportSystemError(errno, "%s",
_("Failed to allocate tty"));
goto cleanup;
}
VIR_FREE(vm->def->consoles[i]->source.data.file.path);
vm->def->consoles[i]->source.data.file.path = ttyPath;
VIR_FREE(vm->def->consoles[i]->info.alias);
if (virAsprintf(&vm->def->consoles[i]->info.alias, "console%zu", i) < 0) {
virReportOOMError();
goto cleanup;
}
}
if (virLXCProcessSetupInterfaces(conn, vm->def, &nveths, &veths) < 0)
goto cleanup;
/* Save the configuration for the controller */
if (virDomainSaveConfig(driver->stateDir, vm->def) < 0)
goto cleanup;
if ((logfd = open(logfile, O_WRONLY | O_APPEND | O_CREAT,
S_IRUSR|S_IWUSR)) < 0) {
virReportSystemError(errno,
_("Failed to open '%s'"),
logfile);
goto cleanup;
}
if (pipe(handshakefds) < 0) {
virReportSystemError(errno, "%s",
_("Unable to create pipe"));
goto cleanup;
}
if (!(cmd = virLXCProcessBuildControllerCmd(driver,
vm,
nveths, veths,
ttyFDs, nttyFDs,
handshakefds[1])))
goto cleanup;
virCommandSetOutputFD(cmd, &logfd);
virCommandSetErrorFD(cmd, &logfd);
/* now that we know it is about to start call the hook if present */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
char *xml = virDomainDefFormat(vm->def, 0);
int hookret;
hookret = virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_START, VIR_HOOK_SUBOP_BEGIN,
NULL, xml, NULL);
VIR_FREE(xml);
/*
* If the script raised an error abort the launch
*/
if (hookret < 0)
goto cleanup;
}
/* Log timestamp */
if ((timestamp = virTimeStringNow()) == NULL) {
virReportOOMError();
goto cleanup;
}
if (safewrite(logfd, timestamp, strlen(timestamp)) < 0 ||
safewrite(logfd, START_POSTFIX, strlen(START_POSTFIX)) < 0) {
VIR_WARN("Unable to write timestamp to logfile: %s",
virStrerror(errno, ebuf, sizeof(ebuf)));
}
VIR_FREE(timestamp);
/* Log generated command line */
virCommandWriteArgLog(cmd, logfd);
if ((pos = lseek(logfd, 0, SEEK_END)) < 0)
VIR_WARN("Unable to seek to end of logfile: %s",
virStrerror(errno, ebuf, sizeof(ebuf)));
if (virCommandRun(cmd, NULL) < 0)
goto cleanup;
if (VIR_CLOSE(handshakefds[1]) < 0) {
virReportSystemError(errno, "%s", _("could not close handshake fd"));
goto cleanup;
}
/* Connect to the controller as a client *first* because
* this will block until the child has written their
* pid file out to disk */
if (!(priv->monitor = virLXCProcessConnectMonitor(driver, vm)))
goto cleanup;
/* And get its pid */
if ((r = virPidFileRead(driver->stateDir, vm->def->name, &vm->pid)) < 0) {
virReportSystemError(-r,
_("Failed to read pid file %s/%s.pid"),
driver->stateDir, vm->def->name);
goto cleanup;
}
Run an RPC protocol over the LXC controller monitor This defines a new RPC protocol to be used between the LXC controller and the libvirtd LXC driver. There is only a single RPC message defined thus far, an asynchronous "EXIT" event that is emitted just before the LXC controller process exits. This provides the LXC driver with details about how the container shutdown - normally, or abnormally (crashed), thus allowing the driver to emit better libvirt events. Emitting the event in the LXC controller requires a few little tricks with the RPC service. Simply calling the virNetServiceClientSendMessage does not work, since this merely queues the message for asynchronous processing. In addition the main event loop is no longer running at the point the event is emitted, so no I/O is processed. Thus after invoking virNetServiceClientSendMessage it is necessary to mark the client as being in "delayed close" mode. Then the event loop is run again, until the client completes its close - this happens only after the queued message has been fully transmitted. The final complexity is that it is not safe to run virNetServerQuit() from the client close callback, since that is invoked from a context where the server is locked. Thus a zero-second timer is used to trigger shutdown of the event loop, causing the controller to finally exit. * src/Makefile.am: Add rules for generating RPC protocol files and dispatch methods * src/lxc/lxc_controller.c: Emit an RPC event immediately before exiting * src/lxc/lxc_domain.h: Record the shutdown reason given by the controller * src/lxc/lxc_monitor.c, src/lxc/lxc_monitor.h: Register RPC program and event handler. Add callback to let driver receive EXIT event. * src/lxc/lxc_process.c: Use monitor exit event to decide what kind of domain event to emit * src/lxc/lxc_protocol.x: Define wire protocol for LXC controller monitor. Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2012-07-17 14:54:08 +00:00
priv->stopReason = VIR_DOMAIN_EVENT_STOPPED_FAILED;
priv->wantReboot = false;
vm->def->id = vm->pid;
virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, reason);
priv->doneStopEvent = false;
if (!driver->nactive && driver->inhibitCallback)
driver->inhibitCallback(true, driver->inhibitOpaque);
driver->nactive++;
if (lxcContainerWaitForContinue(handshakefds[0]) < 0) {
char out[1024];
if (!(virLXCProcessReadLogOutput(vm, logfile, pos, out, 1024) < 0)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("guest failed to start: %s"), out);
}
goto error;
}
if (autoDestroy &&
virLXCProcessAutoDestroyAdd(driver, vm, conn) < 0)
goto error;
if (virDomainObjSetDefTransient(driver->caps, vm, false) < 0)
goto error;
/* Write domain status to disk.
*
* XXX: Earlier we wrote the plain "live" domain XML to this
* location for the benefit of libvirt_lxc. We're now overwriting
* it with the live status XML instead. This is a (currently
* harmless) inconsistency we should fix one day */
if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0)
goto error;
/* finally we can call the 'started' hook script if any */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
char *xml = virDomainDefFormat(vm->def, 0);
int hookret;
hookret = virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_STARTED, VIR_HOOK_SUBOP_BEGIN,
NULL, xml, NULL);
VIR_FREE(xml);
/*
* If the script raised an error abort the launch
*/
if (hookret < 0)
goto error;
}
rc = 0;
cleanup:
if (rc != 0 && !err)
err = virSaveLastError();
virCommandFree(cmd);
if (VIR_CLOSE(logfd) < 0) {
virReportSystemError(errno, "%s", _("could not close logfile"));
rc = -1;
}
for (i = 0 ; i < nveths ; i++) {
if (rc != 0)
ignore_value(virNetDevVethDelete(veths[i]));
VIR_FREE(veths[i]);
}
if (rc != 0) {
if (vm->newDef) {
virDomainDefFree(vm->newDef);
vm->newDef = NULL;
}
if (priv->monitor) {
virObjectUnref(priv->monitor);
priv->monitor = NULL;
}
virDomainConfVMNWFilterTeardown(vm);
virSecurityManagerRestoreAllLabel(driver->securityManager,
vm->def, false);
virSecurityManagerReleaseLabel(driver->securityManager, vm->def);
/* Clear out dynamically assigned labels */
if (vm->def->nseclabels &&
vm->def->seclabels[0]->type == VIR_DOMAIN_SECLABEL_DYNAMIC) {
VIR_FREE(vm->def->seclabels[0]->model);
VIR_FREE(vm->def->seclabels[0]->label);
VIR_FREE(vm->def->seclabels[0]->imagelabel);
}
}
for (i = 0 ; i < nttyFDs ; i++)
VIR_FORCE_CLOSE(ttyFDs[i]);
VIR_FREE(ttyFDs);
VIR_FORCE_CLOSE(handshakefds[0]);
VIR_FORCE_CLOSE(handshakefds[1]);
VIR_FREE(logfile);
if (err) {
virSetError(err);
virFreeError(err);
}
return rc;
error:
err = virSaveLastError();
virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED);
goto cleanup;
}
struct virLXCProcessAutostartData {
virLXCDriverPtr driver;
virConnectPtr conn;
};
static void
virLXCProcessAutostartDomain(void *payload, const void *name ATTRIBUTE_UNUSED, void *opaque)
{
virDomainObjPtr vm = payload;
const struct virLXCProcessAutostartData *data = opaque;
virDomainObjLock(vm);
if (vm->autostart &&
!virDomainObjIsActive(vm)) {
int ret = virLXCProcessStart(data->conn, data->driver, vm, false,
VIR_DOMAIN_RUNNING_BOOTED);
virDomainAuditStart(vm, "booted", ret >= 0);
if (ret < 0) {
virErrorPtr err = virGetLastError();
VIR_ERROR(_("Failed to autostart VM '%s': %s"),
vm->def->name,
err ? err->message : "");
} else {
virDomainEventPtr event =
virDomainEventNewFromObj(vm,
VIR_DOMAIN_EVENT_STARTED,
VIR_DOMAIN_EVENT_STARTED_BOOTED);
if (event)
virDomainEventStateQueue(data->driver->domainEventState, event);
}
}
virDomainObjUnlock(vm);
}
void
virLXCProcessAutostartAll(virLXCDriverPtr driver)
{
/* XXX: Figure out a better way todo this. The domain
* startup code needs a connection handle in order
* to lookup the bridge associated with a virtual
* network
*/
virConnectPtr conn = virConnectOpen("lxc:///");
/* Ignoring NULL conn which is mostly harmless here */
struct virLXCProcessAutostartData data = { driver, conn };
lxcDriverLock(driver);
virHashForEach(driver->domains.objs, virLXCProcessAutostartDomain, &data);
lxcDriverUnlock(driver);
if (conn)
virConnectClose(conn);
}
static void
virLXCProcessReconnectDomain(void *payload, const void *name ATTRIBUTE_UNUSED, void *opaque)
{
virDomainObjPtr vm = payload;
virLXCDriverPtr driver = opaque;
virLXCDomainObjPrivatePtr priv;
virDomainObjLock(vm);
VIR_DEBUG("Reconnect id=%d pid=%d state=%d", vm->def->id, vm->pid, vm->state.state);
priv = vm->privateData;
if (vm->pid != 0) {
vm->def->id = vm->pid;
virDomainObjSetState(vm, VIR_DOMAIN_RUNNING,
VIR_DOMAIN_RUNNING_UNKNOWN);
if (!driver->nactive && driver->inhibitCallback)
driver->inhibitCallback(true, driver->inhibitOpaque);
driver->nactive++;
if (!(priv->monitor = virLXCProcessConnectMonitor(driver, vm)))
goto error;
if (virSecurityManagerReserveLabel(driver->securityManager,
vm->def, vm->pid) < 0)
goto error;
/* now that we know it's reconnected call the hook if present */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
char *xml = virDomainDefFormat(vm->def, 0);
int hookret;
/* we can't stop the operation even if the script raised an error */
hookret = virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_RECONNECT, VIR_HOOK_SUBOP_BEGIN,
NULL, xml, NULL);
VIR_FREE(xml);
if (hookret < 0)
goto error;
}
} else {
vm->def->id = -1;
}
cleanup:
virDomainObjUnlock(vm);
return;
error:
virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED);
virDomainAuditStop(vm, "failed");
goto cleanup;
}
int virLXCProcessReconnectAll(virLXCDriverPtr driver,
virDomainObjListPtr doms)
{
virHashForEach(doms->objs, virLXCProcessReconnectDomain, driver);
return 0;
}