libvirt/src/lxc_driver.c

1468 lines
38 KiB
C

/*
* Copyright IBM Corp. 2008
*
* lxc_driver.c: linux container driver functions
*
* Authors:
* David L. Leskovec <dlesko at linux.vnet.ibm.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
*/
#include <config.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/utsname.h>
#include <stdbool.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/poll.h>
#include <unistd.h>
#include <wait.h>
#include "virterror_internal.h"
#include "logging.h"
#include "datatypes.h"
#include "lxc_conf.h"
#include "lxc_container.h"
#include "lxc_driver.h"
#include "memory.h"
#include "util.h"
#include "bridge.h"
#include "veth.h"
#include "event.h"
#include "cgroup.h"
static int lxcStartup(void);
static int lxcShutdown(void);
static lxc_driver_t *lxc_driver = NULL;
/* Functions */
static void lxcDriverLock(lxc_driver_t *driver)
{
pthread_mutex_lock(&driver->lock);
}
static void lxcDriverUnlock(lxc_driver_t *driver)
{
pthread_mutex_unlock(&driver->lock);
}
static int lxcProbe(void)
{
if (getuid() != 0 ||
lxcContainerAvailable(0) < 0)
return 0;
return 1;
}
static virDrvOpenStatus lxcOpen(virConnectPtr conn,
virConnectAuthPtr auth ATTRIBUTE_UNUSED,
int flags ATTRIBUTE_UNUSED)
{
if (!lxcProbe())
goto declineConnection;
if (lxc_driver == NULL)
goto declineConnection;
/* Verify uri was specified */
if (conn->uri == NULL) {
conn->uri = xmlParseURI("lxc:///");
if (!conn->uri) {
lxcError(conn, NULL, VIR_ERR_NO_MEMORY, NULL);
return VIR_DRV_OPEN_ERROR;
}
} else if (conn->uri->scheme == NULL ||
STRNEQ(conn->uri->scheme, "lxc")) {
goto declineConnection;
}
conn->privateData = lxc_driver;
return VIR_DRV_OPEN_SUCCESS;
declineConnection:
return VIR_DRV_OPEN_DECLINED;
}
static int lxcClose(virConnectPtr conn)
{
conn->privateData = NULL;
return 0;
}
static virDomainPtr lxcDomainLookupByID(virConnectPtr conn,
int id)
{
lxc_driver_t *driver = conn->privateData;
virDomainObjPtr vm;
virDomainPtr dom = NULL;
lxcDriverLock(driver);
vm = virDomainFindByID(&driver->domains, id);
lxcDriverUnlock(driver);
if (!vm) {
lxcError(conn, NULL, VIR_ERR_NO_DOMAIN, NULL);
goto cleanup;
}
dom = virGetDomain(conn, vm->def->name, vm->def->uuid);
if (dom)
dom->id = vm->def->id;
cleanup:
if (vm)
virDomainObjUnlock(vm);
return dom;
}
static virDomainPtr lxcDomainLookupByUUID(virConnectPtr conn,
const unsigned char *uuid)
{
lxc_driver_t *driver = conn->privateData;
virDomainObjPtr vm;
virDomainPtr dom = NULL;
lxcDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, uuid);
lxcDriverUnlock(driver);
if (!vm) {
lxcError(conn, NULL, VIR_ERR_NO_DOMAIN, NULL);
goto cleanup;
}
dom = virGetDomain(conn, vm->def->name, vm->def->uuid);
if (dom)
dom->id = vm->def->id;
cleanup:
if (vm)
virDomainObjUnlock(vm);
return dom;
}
static virDomainPtr lxcDomainLookupByName(virConnectPtr conn,
const char *name)
{
lxc_driver_t *driver = conn->privateData;
virDomainObjPtr vm;
virDomainPtr dom = NULL;
lxcDriverLock(driver);
vm = virDomainFindByName(&driver->domains, name);
lxcDriverUnlock(driver);
if (!vm) {
lxcError(conn, NULL, VIR_ERR_NO_DOMAIN, NULL);
goto cleanup;
}
dom = virGetDomain(conn, vm->def->name, vm->def->uuid);
if (dom)
dom->id = vm->def->id;
cleanup:
if (vm)
virDomainObjUnlock(vm);
return dom;
}
static int lxcListDomains(virConnectPtr conn, int *ids, int nids) {
lxc_driver_t *driver = conn->privateData;
int got = 0, i;
lxcDriverLock(driver);
for (i = 0 ; i < driver->domains.count && got < nids ; i++) {
virDomainObjLock(driver->domains.objs[i]);
if (virDomainIsActive(driver->domains.objs[i]))
ids[got++] = driver->domains.objs[i]->def->id;
virDomainObjUnlock(driver->domains.objs[i]);
}
lxcDriverUnlock(driver);
return got;
}
static int lxcNumDomains(virConnectPtr conn) {
lxc_driver_t *driver = conn->privateData;
int n = 0, i;
lxcDriverLock(driver);
for (i = 0 ; i < driver->domains.count ; i++) {
virDomainObjLock(driver->domains.objs[i]);
if (virDomainIsActive(driver->domains.objs[i]))
n++;
virDomainObjUnlock(driver->domains.objs[i]);
}
lxcDriverUnlock(driver);
return n;
}
static int lxcListDefinedDomains(virConnectPtr conn,
char **const names, int nnames) {
lxc_driver_t *driver = conn->privateData;
int got = 0, i;
lxcDriverLock(driver);
for (i = 0 ; i < driver->domains.count && got < nnames ; i++) {
virDomainObjLock(driver->domains.objs[i]);
if (!virDomainIsActive(driver->domains.objs[i])) {
if (!(names[got++] = strdup(driver->domains.objs[i]->def->name))) {
lxcError(conn, NULL, VIR_ERR_NO_MEMORY,
"%s", _("failed to allocate space for VM name string"));
virDomainObjUnlock(driver->domains.objs[i]);
goto cleanup;
}
}
virDomainObjUnlock(driver->domains.objs[i]);
}
lxcDriverUnlock(driver);
return got;
cleanup:
for (i = 0 ; i < got ; i++)
VIR_FREE(names[i]);
lxcDriverUnlock(driver);
return -1;
}
static int lxcNumDefinedDomains(virConnectPtr conn) {
lxc_driver_t *driver = conn->privateData;
int n = 0, i;
lxcDriverLock(driver);
for (i = 0 ; i < driver->domains.count ; i++) {
virDomainObjLock(driver->domains.objs[i]);
if (!virDomainIsActive(driver->domains.objs[i]))
n++;
virDomainObjUnlock(driver->domains.objs[i]);
}
lxcDriverUnlock(driver);
return n;
}
static virDomainPtr lxcDomainDefine(virConnectPtr conn, const char *xml)
{
lxc_driver_t *driver = conn->privateData;
virDomainDefPtr def = NULL;
virDomainObjPtr vm = NULL;
virDomainPtr dom = NULL;
lxcDriverLock(driver);
if (!(def = virDomainDefParseString(conn, driver->caps, xml,
VIR_DOMAIN_XML_INACTIVE)))
goto cleanup;
if ((def->nets != NULL) && !(driver->have_netns)) {
lxcError(conn, NULL, VIR_ERR_NO_SUPPORT,
"%s", _("System lacks NETNS support"));
goto cleanup;
}
if (!(vm = virDomainAssignDef(conn, &driver->domains, def)))
goto cleanup;
def = NULL;
vm->persistent = 1;
if (virDomainSaveConfig(conn,
driver->configDir,
vm->newDef ? vm->newDef : vm->def) < 0) {
virDomainRemoveInactive(&driver->domains, vm);
vm = NULL;
goto cleanup;
}
dom = virGetDomain(conn, vm->def->name, vm->def->uuid);
if (dom)
dom->id = vm->def->id;
cleanup:
virDomainDefFree(def);
if (vm)
virDomainObjUnlock(vm);
lxcDriverUnlock(driver);
return dom;
}
static int lxcDomainUndefine(virDomainPtr dom)
{
lxc_driver_t *driver = dom->conn->privateData;
virDomainObjPtr vm;
int ret = -1;
lxcDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, dom->uuid);
if (!vm) {
lxcError(dom->conn, dom, VIR_ERR_INVALID_DOMAIN,
"%s", _("no domain with matching uuid"));
goto cleanup;
}
if (virDomainIsActive(vm)) {
lxcError(dom->conn, dom, VIR_ERR_INTERNAL_ERROR,
"%s", _("cannot delete active domain"));
goto cleanup;
}
if (!vm->persistent) {
lxcError(dom->conn, dom, VIR_ERR_INTERNAL_ERROR,
"%s", _("cannot undefine transient domain"));
goto cleanup;
}
if (virDomainDeleteConfig(dom->conn,
driver->configDir,
driver->autostartDir,
vm) < 0)
goto cleanup;
virDomainRemoveInactive(&driver->domains, vm);
vm = NULL;
ret = 0;
cleanup:
if (vm)
virDomainObjUnlock(vm);
lxcDriverUnlock(driver);
return ret;
}
static int lxcDomainGetInfo(virDomainPtr dom,
virDomainInfoPtr info)
{
lxc_driver_t *driver = dom->conn->privateData;
virDomainObjPtr vm;
int ret = -1;
lxcDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, dom->uuid);
lxcDriverUnlock(driver);
if (!vm) {
lxcError(dom->conn, dom, VIR_ERR_INVALID_DOMAIN,
"%s", _("no domain with matching uuid"));
goto cleanup;
}
info->state = vm->state;
if (!virDomainIsActive(vm)) {
info->cpuTime = 0;
} else {
info->cpuTime = 0;
}
info->maxMem = vm->def->maxmem;
info->memory = vm->def->memory;
info->nrVirtCpu = 1;
ret = 0;
cleanup:
if (vm)
virDomainObjUnlock(vm);
return ret;
}
static char *lxcGetOSType(virDomainPtr dom)
{
lxc_driver_t *driver = dom->conn->privateData;
virDomainObjPtr vm;
char *ret = NULL;
lxcDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, dom->uuid);
lxcDriverUnlock(driver);
if (!vm) {
lxcError(dom->conn, dom, VIR_ERR_INVALID_DOMAIN,
"%s", _("no domain with matching uuid"));
goto cleanup;
}
ret = strdup(vm->def->os.type);
cleanup:
if (vm)
virDomainObjUnlock(vm);
return ret;
}
static char *lxcDomainDumpXML(virDomainPtr dom,
int flags)
{
lxc_driver_t *driver = dom->conn->privateData;
virDomainObjPtr vm;
char *ret = NULL;
lxcDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, dom->uuid);
lxcDriverUnlock(driver);
if (!vm) {
lxcError(dom->conn, dom, VIR_ERR_INVALID_DOMAIN,
"%s", _("no domain with matching uuid"));
goto cleanup;
}
ret = virDomainDefFormat(dom->conn,
(flags & VIR_DOMAIN_XML_INACTIVE) &&
vm->newDef ? vm->newDef : vm->def,
flags);
cleanup:
if (vm)
virDomainObjUnlock(vm);
return ret;
}
/**
* lxcVmCleanup:
* @vm: Ptr to VM to clean up
*
* waitpid() on the container process. kill and wait the tty process
* This is called by both lxcDomainDestroy and lxcSigHandler when a
* container exits.
*
* Returns 0 on success or -1 in case of error
*/
static int lxcVMCleanup(virConnectPtr conn,
lxc_driver_t *driver,
virDomainObjPtr vm)
{
int rc = -1;
int waitRc;
int childStatus = -1;
virCgroupPtr cgroup;
int i;
while (((waitRc = waitpid(vm->pid, &childStatus, 0)) == -1) &&
errno == EINTR)
; /* empty */
if ((waitRc != vm->pid) && (errno != ECHILD)) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("waitpid failed to wait for container %d: %d %s"),
vm->pid, waitRc, strerror(errno));
}
rc = 0;
if (WIFEXITED(childStatus)) {
rc = WEXITSTATUS(childStatus);
DEBUG("container exited with rc: %d", rc);
}
virEventRemoveHandle(vm->monitorWatch);
close(vm->monitor);
virFileDeletePid(driver->stateDir, vm->def->name);
virDomainDeleteConfig(conn, driver->stateDir, NULL, vm);
vm->state = VIR_DOMAIN_SHUTOFF;
vm->pid = -1;
vm->def->id = -1;
vm->monitor = -1;
for (i = 0 ; i < vm->def->nnets ; i++) {
vethInterfaceUpOrDown(vm->def->nets[i]->ifname, 0);
vethDelete(vm->def->nets[i]->ifname);
}
if (virCgroupForDomain(vm->def, "lxc", &cgroup) == 0) {
virCgroupRemove(cgroup);
virCgroupFree(&cgroup);
}
return rc;
}
/**
* lxcSetupInterfaces:
* @def: pointer to virtual machine structure
*
* 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 lxcSetupInterfaces(virConnectPtr conn,
virDomainDefPtr def,
unsigned int *nveths,
char ***veths)
{
int rc = -1, i;
char *bridge = NULL;
char parentVeth[PATH_MAX] = "";
char containerVeth[PATH_MAX] = "";
brControl *brctl = NULL;
if (brInit(&brctl) != 0)
return -1;
for (i = 0 ; i < def->nnets ; i++) {
switch (def->nets[i]->type) {
case VIR_DOMAIN_NET_TYPE_NETWORK:
{
virNetworkPtr network = virNetworkLookupByName(conn,
def->nets[i]->data.network.name);
if (!network) {
goto error_exit;
}
bridge = virNetworkGetBridgeName(network);
virNetworkFree(network);
break;
}
case VIR_DOMAIN_NET_TYPE_BRIDGE:
bridge = def->nets[i]->data.bridge.brname;
break;
}
DEBUG("bridge: %s", bridge);
if (NULL == bridge) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("failed to get bridge for interface"));
goto error_exit;
}
DEBUG0("calling vethCreate()");
if (NULL != def->nets[i]->ifname) {
strcpy(parentVeth, def->nets[i]->ifname);
}
DEBUG("parentVeth: %s, containerVeth: %s", parentVeth, containerVeth);
if (0 != (rc = vethCreate(parentVeth, PATH_MAX, containerVeth, PATH_MAX))) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("failed to create veth device pair: %d"), rc);
goto error_exit;
}
if (NULL == def->nets[i]->ifname) {
def->nets[i]->ifname = strdup(parentVeth);
}
if (VIR_REALLOC_N(*veths, (*nveths)+1) < 0)
goto error_exit;
if (((*veths)[(*nveths)++] = strdup(containerVeth)) == NULL)
goto error_exit;
if (NULL == def->nets[i]->ifname) {
lxcError(NULL, NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("failed to allocate veth names"));
goto error_exit;
}
if (0 != (rc = brAddInterface(brctl, bridge, parentVeth))) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("failed to add %s device to %s: %s"),
parentVeth,
bridge,
strerror(rc));
goto error_exit;
}
if (0 != (rc = vethInterfaceUpOrDown(parentVeth, 1))) {
lxcError(NULL, NULL, VIR_ERR_INTERNAL_ERROR,
_("failed to enable parent ns veth device: %d"), rc);
goto error_exit;
}
}
rc = 0;
error_exit:
brShutdown(brctl);
return rc;
}
static int lxcMonitorClient(virConnectPtr conn,
lxc_driver_t * driver,
virDomainObjPtr vm)
{
char *sockpath = NULL;
int fd;
struct sockaddr_un addr;
if (virAsprintf(&sockpath, "%s/%s.sock",
driver->stateDir, vm->def->name) < 0) {
lxcError(conn, NULL, VIR_ERR_NO_MEMORY, NULL);
return -1;
}
if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("failed to create client socket: %s"),
strerror(errno));
goto error;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, sockpath, sizeof(addr.sun_path));
if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("failed to connect to client socket: %s"),
strerror(errno));
goto error;
}
VIR_FREE(sockpath);
return fd;
error:
VIR_FREE(sockpath);
if (fd != -1)
close(fd);
return -1;
}
static int lxcVmTerminate(virConnectPtr conn,
lxc_driver_t *driver,
virDomainObjPtr vm,
int signum)
{
if (signum == 0)
signum = SIGINT;
if (vm->pid <= 0) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("invalid PID %d for container"), vm->pid);
return -1;
}
if (kill(vm->pid, signum) < 0) {
if (errno != ESRCH) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("failed to kill pid %d: %s"),
vm->pid, strerror(errno));
return -1;
}
}
vm->state = VIR_DOMAIN_SHUTDOWN;
return lxcVMCleanup(conn, driver, vm);
}
static void lxcMonitorEvent(int watch,
int fd,
int events ATTRIBUTE_UNUSED,
void *data)
{
lxc_driver_t *driver = data;
virDomainObjPtr vm = NULL;
unsigned int i;
lxcDriverLock(driver);
for (i = 0 ; i < driver->domains.count ; i++) {
virDomainObjPtr tmpvm = driver->domains.objs[i];
virDomainObjLock(tmpvm);
if (tmpvm->monitorWatch == watch) {
vm = tmpvm;
break;
}
virDomainObjUnlock(tmpvm);
}
if (!vm) {
virEventRemoveHandle(watch);
goto cleanup;
}
if (vm->monitor != fd) {
virEventRemoveHandle(watch);
goto cleanup;
}
if (lxcVmTerminate(NULL, driver, vm, SIGINT) < 0)
virEventRemoveHandle(watch);
cleanup:
if (vm)
virDomainObjUnlock(vm);
lxcDriverUnlock(driver);
}
static int lxcControllerStart(virConnectPtr conn,
virDomainObjPtr vm,
int nveths,
char **veths,
int appPty,
int logfd)
{
int i;
int rc;
int ret = -1;
int largc = 0, larga = 0;
const char **largv = NULL;
pid_t child;
int status;
fd_set keepfd;
char appPtyStr[30];
const char *emulator;
lxc_driver_t *driver = conn->privateData;
FD_ZERO(&keepfd);
#define ADD_ARG_SPACE \
do { \
if (largc == larga) { \
larga += 10; \
if (VIR_REALLOC_N(largv, larga) < 0) \
goto no_memory; \
} \
} while (0)
#define ADD_ARG(thisarg) \
do { \
ADD_ARG_SPACE; \
largv[largc++] = thisarg; \
} while (0)
#define ADD_ARG_LIT(thisarg) \
do { \
ADD_ARG_SPACE; \
if ((largv[largc++] = strdup(thisarg)) == NULL) \
goto no_memory; \
} while (0)
snprintf(appPtyStr, sizeof(appPtyStr), "%d", appPty);
emulator = vm->def->emulator;
if (!emulator)
emulator = virDomainDefDefaultEmulator(conn, vm->def, driver->caps);
if (!emulator)
return -1;
ADD_ARG_LIT(emulator);
ADD_ARG_LIT("--name");
ADD_ARG_LIT(vm->def->name);
ADD_ARG_LIT("--console");
ADD_ARG_LIT(appPtyStr);
ADD_ARG_LIT("--background");
for (i = 0 ; i < nveths ; i++) {
ADD_ARG_LIT("--veth");
ADD_ARG_LIT(veths[i]);
}
ADD_ARG(NULL);
vm->stdin_fd = -1;
vm->stdout_fd = vm->stderr_fd = logfd;
FD_SET(appPty, &keepfd);
if (virExec(conn, largv, NULL, &keepfd, &child,
vm->stdin_fd, &vm->stdout_fd, &vm->stderr_fd,
VIR_EXEC_NONE) < 0)
goto cleanup;
/* We now wait for the process to exit - the controller
* will fork() itself into the background - waiting for
* it to exit thus guarentees it has written its pidfile
*/
while ((rc = waitpid(child, &status, 0) == -1) && errno == EINTR);
if (rc == -1) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("cannot wait for '%s': %s"),
largv[0], strerror(errno));
goto cleanup;
}
if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("container '%s' unexpectedly shutdown during startup"),
largv[0]);
goto cleanup;
}
#undef ADD_ARG
#undef ADD_ARG_LIT
#undef ADD_ARG_SPACE
ret = 0;
cleanup:
for (i = 0 ; i < largc ; i++)
VIR_FREE(largv[i]);
return ret;
no_memory:
lxcError(conn, NULL, VIR_ERR_NO_MEMORY, NULL);
goto cleanup;
}
/**
* lxcVmStart:
* @conn: pointer to connection
* @driver: pointer to driver structure
* @vm: pointer to virtual machine structure
*
* Starts a vm
*
* Returns 0 on success or -1 in case of error
*/
static int lxcVmStart(virConnectPtr conn,
lxc_driver_t * driver,
virDomainObjPtr vm)
{
int rc = -1;
unsigned int i;
int parentTty;
char *parentTtyPath = NULL;
char *logfile = NULL;
int logfd = -1;
unsigned int nveths = 0;
char **veths = NULL;
if (virFileMakePath(driver->logDir) < 0) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("cannot create log directory %s: %s"),
driver->logDir, strerror(rc));
return -1;
}
if (virAsprintf(&logfile, "%s/%s.log",
driver->logDir, vm->def->name) < 0) {
lxcError(conn, NULL, VIR_ERR_NO_MEMORY, NULL);
return -1;
}
/* open parent tty */
if (virFileOpenTty(&parentTty, &parentTtyPath, 1) < 0) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("failed to allocate tty: %s"),
strerror(errno));
goto cleanup;
}
if (vm->def->console &&
vm->def->console->type == VIR_DOMAIN_CHR_TYPE_PTY) {
VIR_FREE(vm->def->console->data.file.path);
vm->def->console->data.file.path = parentTtyPath;
} else {
VIR_FREE(parentTtyPath);
}
if (lxcSetupInterfaces(conn, vm->def, &nveths, &veths) != 0)
goto cleanup;
/* Persist the live configuration now we have veth & tty info */
if (virDomainSaveConfig(conn, driver->stateDir, vm->def) < 0) {
rc = -1;
goto cleanup;
}
if ((logfd = open(logfile, O_WRONLY | O_TRUNC | O_CREAT,
S_IRUSR|S_IWUSR)) < 0) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("failed to open %s: %s"), logfile,
strerror(errno));
goto cleanup;
}
if (lxcControllerStart(conn,
vm,
nveths, veths,
parentTty, logfd) < 0)
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 ((vm->monitor = lxcMonitorClient(conn, driver, vm)) < 0)
goto cleanup;
/* And get its pid */
if ((rc = virFileReadPid(driver->stateDir, vm->def->name, &vm->pid)) != 0) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("Failed to read pid file %s/%s.pid: %s"),
driver->stateDir, vm->def->name, strerror(rc));
rc = -1;
goto cleanup;
}
vm->def->id = vm->pid;
vm->state = VIR_DOMAIN_RUNNING;
if ((vm->monitorWatch = virEventAddHandle(
vm->monitor,
VIR_EVENT_HANDLE_ERROR | VIR_EVENT_HANDLE_HANGUP,
lxcMonitorEvent,
driver, NULL)) < 0) {
lxcVmTerminate(conn, driver, vm, 0);
goto cleanup;
}
rc = 0;
cleanup:
for (i = 0 ; i < nveths ; i++) {
if (rc != 0)
vethDelete(veths[i]);
VIR_FREE(veths[i]);
}
if (rc != 0 && vm->monitor != -1) {
close(vm->monitor);
vm->monitor = -1;
}
if (parentTty != -1)
close(parentTty);
if (logfd != -1)
close(logfd);
VIR_FREE(logfile);
return rc;
}
/**
* lxcDomainStart:
* @dom: domain to start
*
* Looks up domain and starts it.
*
* Returns 0 on success or -1 in case of error
*/
static int lxcDomainStart(virDomainPtr dom)
{
lxc_driver_t *driver = dom->conn->privateData;
virDomainObjPtr vm;
int ret = -1;
lxcDriverLock(driver);
vm = virDomainFindByName(&driver->domains, dom->name);
if (!vm) {
lxcError(dom->conn, dom, VIR_ERR_INVALID_DOMAIN,
_("no domain named %s"), dom->name);
goto cleanup;
}
if ((vm->def->nets != NULL) && !(driver->have_netns)) {
lxcError(dom->conn, NULL, VIR_ERR_NO_SUPPORT,
"%s", _("System lacks NETNS support"));
goto cleanup;
}
ret = lxcVmStart(dom->conn, driver, vm);
cleanup:
if (vm)
virDomainObjUnlock(vm);
lxcDriverUnlock(driver);
return ret;
}
/**
* lxcDomainCreateAndStart:
* @conn: pointer to connection
* @xml: XML definition of domain
* @flags: Unused
*
* Creates a domain based on xml and starts it
*
* Returns 0 on success or -1 in case of error
*/
static virDomainPtr
lxcDomainCreateAndStart(virConnectPtr conn,
const char *xml,
unsigned int flags ATTRIBUTE_UNUSED) {
lxc_driver_t *driver = conn->privateData;
virDomainObjPtr vm = NULL;
virDomainDefPtr def;
virDomainPtr dom = NULL;
lxcDriverLock(driver);
if (!(def = virDomainDefParseString(conn, driver->caps, xml,
VIR_DOMAIN_XML_INACTIVE)))
goto cleanup;
if ((def->nets != NULL) && !(driver->have_netns)) {
lxcError(conn, NULL, VIR_ERR_NO_SUPPORT,
"%s", _("System lacks NETNS support"));
goto cleanup;
}
if (!(vm = virDomainAssignDef(conn, &driver->domains, def)))
goto cleanup;
def = NULL;
if (lxcVmStart(conn, driver, vm) < 0) {
virDomainRemoveInactive(&driver->domains, vm);
vm = NULL;
goto cleanup;
}
dom = virGetDomain(conn, vm->def->name, vm->def->uuid);
if (dom)
dom->id = vm->def->id;
cleanup:
virDomainDefFree(def);
if (vm)
virDomainObjUnlock(vm);
lxcDriverUnlock(driver);
return dom;
}
/**
* lxcDomainShutdown:
* @dom: Ptr to domain to shutdown
*
* Sends SIGINT to container root process to request it to shutdown
*
* Returns 0 on success or -1 in case of error
*/
static int lxcDomainShutdown(virDomainPtr dom)
{
lxc_driver_t *driver = dom->conn->privateData;
virDomainObjPtr vm;
int ret = -1;
lxcDriverLock(driver);
vm = virDomainFindByID(&driver->domains, dom->id);
if (!vm) {
lxcError(dom->conn, dom, VIR_ERR_INVALID_DOMAIN,
_("no domain with id %d"), dom->id);
goto cleanup;
}
ret = lxcVmTerminate(dom->conn, driver, vm, 0);
if (!vm->persistent) {
virDomainRemoveInactive(&driver->domains, vm);
vm = NULL;
}
cleanup:
if (vm)
virDomainObjUnlock(vm);
lxcDriverUnlock(driver);
return ret;
}
/**
* lxcDomainDestroy:
* @dom: Ptr to domain to destroy
*
* Sends SIGKILL to container root process to terminate the container
*
* Returns 0 on success or -1 in case of error
*/
static int lxcDomainDestroy(virDomainPtr dom)
{
lxc_driver_t *driver = dom->conn->privateData;
virDomainObjPtr vm;
int ret = -1;
lxcDriverLock(driver);
vm = virDomainFindByID(&driver->domains, dom->id);
if (!vm) {
lxcError(dom->conn, dom, VIR_ERR_INVALID_DOMAIN,
_("no domain with id %d"), dom->id);
goto cleanup;
}
ret = lxcVmTerminate(dom->conn, driver, vm, SIGKILL);
if (!vm->persistent) {
virDomainRemoveInactive(&driver->domains, vm);
vm = NULL;
}
cleanup:
if (vm)
virDomainObjUnlock(vm);
lxcDriverUnlock(driver);
return ret;
}
static int lxcCheckNetNsSupport(void)
{
const char *argv[] = {"ip", "link", "set", "lo", "netns", "-1", NULL};
int ip_rc;
if (virRun(NULL, argv, &ip_rc) < 0 ||
!(WIFEXITED(ip_rc) && (WEXITSTATUS(ip_rc) != 255)))
return 0;
if (lxcContainerAvailable(LXC_CONTAINER_FEATURE_NET) < 0)
return 0;
return 1;
}
static int lxcStartup(void)
{
uid_t uid = getuid();
unsigned int i;
/* Check that the user is root */
if (0 != uid) {
return -1;
}
if (VIR_ALLOC(lxc_driver) < 0) {
return -1;
}
pthread_mutex_init(&lxc_driver->lock, NULL);
lxcDriverLock(lxc_driver);
/* Check that this is a container enabled kernel */
if(lxcContainerAvailable(0) < 0)
goto cleanup;
lxc_driver->have_netns = lxcCheckNetNsSupport();
/* Call function to load lxc driver configuration information */
if (lxcLoadDriverConfig(lxc_driver) < 0)
goto cleanup;
if ((lxc_driver->caps = lxcCapsInit()) == NULL)
goto cleanup;
if (virDomainLoadAllConfigs(NULL,
lxc_driver->caps,
&lxc_driver->domains,
lxc_driver->configDir,
lxc_driver->autostartDir,
NULL, NULL) < 0)
goto cleanup;
for (i = 0 ; i < lxc_driver->domains.count ; i++) {
virDomainObjPtr vm = lxc_driver->domains.objs[i];
char *config = NULL;
virDomainDefPtr tmp;
int rc;
virDomainObjLock(vm);
if ((vm->monitor = lxcMonitorClient(NULL, lxc_driver, vm)) < 0) {
virDomainObjUnlock(vm);
continue;
}
/* Read pid from controller */
if ((rc = virFileReadPid(lxc_driver->stateDir, vm->def->name, &vm->pid)) != 0) {
close(vm->monitor);
vm->monitor = -1;
virDomainObjUnlock(vm);
continue;
}
if ((config = virDomainConfigFile(NULL,
lxc_driver->stateDir,
vm->def->name)) == NULL) {
virDomainObjUnlock(vm);
continue;
}
/* Try and load the live config */
tmp = virDomainDefParseFile(NULL, lxc_driver->caps, config, 0);
VIR_FREE(config);
if (tmp) {
vm->newDef = vm->def;
vm->def = tmp;
}
if (vm->pid != 0) {
vm->def->id = vm->pid;
vm->state = VIR_DOMAIN_RUNNING;
} else {
vm->def->id = -1;
close(vm->monitor);
vm->monitor = -1;
}
virDomainObjUnlock(vm);
}
lxcDriverUnlock(lxc_driver);
return 0;
cleanup:
lxcDriverUnlock(lxc_driver);
lxcShutdown();
return -1;
}
static int lxcShutdown(void)
{
if (lxc_driver == NULL)
return(-1);
lxcDriverLock(lxc_driver);
virDomainObjListFree(&lxc_driver->domains);
virCapabilitiesFree(lxc_driver->caps);
VIR_FREE(lxc_driver->configDir);
VIR_FREE(lxc_driver->autostartDir);
VIR_FREE(lxc_driver->stateDir);
VIR_FREE(lxc_driver->logDir);
lxcDriverUnlock(lxc_driver);
VIR_FREE(lxc_driver);
lxc_driver = NULL;
return 0;
}
/**
* lxcActive:
*
* Checks if the LXC daemon is active, i.e. has an active domain
*
* Returns 1 if active, 0 otherwise
*/
static int
lxcActive(void) {
unsigned int i;
int active = 0;
if (lxc_driver == NULL)
return(0);
lxcDriverLock(lxc_driver);
for (i = 0 ; i < lxc_driver->domains.count ; i++) {
virDomainObjLock(lxc_driver->domains.objs[i]);
if (virDomainIsActive(lxc_driver->domains.objs[i]))
active = 1;
virDomainObjUnlock(lxc_driver->domains.objs[i]);
}
lxcDriverUnlock(lxc_driver);
return active;
}
static int lxcVersion(virConnectPtr conn, unsigned long *version)
{
struct utsname ver;
int maj;
int min;
int rev;
if (uname(&ver) != 0) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("uname(): %s"), strerror(errno));
return -1;
}
if (sscanf(ver.release, "%i.%i.%i", &maj, &min, &rev) != 3) {
lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR,
_("Unknown release: %s"), ver.release);
return -1;
}
*version = (maj * 1000 * 1000) + (min * 1000) + rev;
return 0;
}
static char *lxcGetSchedulerType(virDomainPtr domain ATTRIBUTE_UNUSED,
int *nparams)
{
if (nparams)
*nparams = 1;
return strdup("posix");
}
static int lxcSetSchedulerParameters(virDomainPtr domain,
virSchedParameterPtr params,
int nparams)
{
lxc_driver_t *driver = domain->conn->privateData;
int i;
virCgroupPtr group = NULL;
virDomainObjPtr vm = NULL;
int ret = -1;
if (virCgroupHaveSupport() != 0)
return -1;
lxcDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, domain->uuid);
lxcDriverUnlock(driver);
if (vm == NULL) {
lxcError(NULL, domain, VIR_ERR_INTERNAL_ERROR,
_("No such domain %s"), domain->uuid);
goto cleanup;
}
if (virCgroupForDomain(vm->def, "lxc", &group) != 0)
goto cleanup;
for (i = 0; i < nparams; i++) {
virSchedParameterPtr param = &params[i];
if (STREQ(param->field, "cpu_shares")) {
if (virCgroupSetCpuShares(group, params[i].value.ui) != 0)
goto cleanup;
} else {
lxcError(NULL, domain, VIR_ERR_INVALID_ARG,
_("Invalid parameter `%s'"), param->field);
goto cleanup;
}
}
ret = 0;
cleanup:
virCgroupFree(&group);
if (vm)
virDomainObjUnlock(vm);
return ret;
}
static int lxcGetSchedulerParameters(virDomainPtr domain,
virSchedParameterPtr params,
int *nparams)
{
lxc_driver_t *driver = domain->conn->privateData;
virCgroupPtr group = NULL;
virDomainObjPtr vm = NULL;
unsigned long val;
int ret = -1;
if (virCgroupHaveSupport() != 0)
return -1;
if ((*nparams) != 1) {
lxcError(NULL, domain, VIR_ERR_INVALID_ARG,
"%s", _("Invalid parameter count"));
return -1;
}
lxcDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, domain->uuid);
lxcDriverUnlock(driver);
if (vm == NULL) {
lxcError(NULL, domain, VIR_ERR_INTERNAL_ERROR,
_("No such domain %s"), domain->uuid);
goto cleanup;
}
if (virCgroupForDomain(vm->def, "lxc", &group) != 0)
goto cleanup;
if (virCgroupGetCpuShares(group, &val) != 0)
goto cleanup;
params[0].value.ul = val;
strncpy(params[0].field, "cpu_shares", sizeof(params[0].field));
params[0].type = VIR_DOMAIN_SCHED_FIELD_ULLONG;
ret = 0;
cleanup:
virCgroupFree(&group);
if (vm)
virDomainObjUnlock(vm);
return ret;
}
/* Function Tables */
static virDriver lxcDriver = {
VIR_DRV_LXC, /* the number virDrvNo */
"LXC", /* the name of the driver */
lxcOpen, /* open */
lxcClose, /* close */
NULL, /* supports_feature */
NULL, /* type */
lxcVersion, /* version */
NULL, /* getHostname */
NULL, /* getURI */
NULL, /* getMaxVcpus */
NULL, /* nodeGetInfo */
NULL, /* getCapabilities */
lxcListDomains, /* listDomains */
lxcNumDomains, /* numOfDomains */
lxcDomainCreateAndStart, /* domainCreateXML */
lxcDomainLookupByID, /* domainLookupByID */
lxcDomainLookupByUUID, /* domainLookupByUUID */
lxcDomainLookupByName, /* domainLookupByName */
NULL, /* domainSuspend */
NULL, /* domainResume */
lxcDomainShutdown, /* domainShutdown */
NULL, /* domainReboot */
lxcDomainDestroy, /* domainDestroy */
lxcGetOSType, /* domainGetOSType */
NULL, /* domainGetMaxMemory */
NULL, /* domainSetMaxMemory */
NULL, /* domainSetMemory */
lxcDomainGetInfo, /* domainGetInfo */
NULL, /* domainSave */
NULL, /* domainRestore */
NULL, /* domainCoreDump */
NULL, /* domainSetVcpus */
NULL, /* domainPinVcpu */
NULL, /* domainGetVcpus */
NULL, /* domainGetMaxVcpus */
lxcDomainDumpXML, /* domainDumpXML */
lxcListDefinedDomains, /* listDefinedDomains */
lxcNumDefinedDomains, /* numOfDefinedDomains */
lxcDomainStart, /* domainCreate */
lxcDomainDefine, /* domainDefineXML */
lxcDomainUndefine, /* domainUndefine */
NULL, /* domainAttachDevice */
NULL, /* domainDetachDevice */
NULL, /* domainGetAutostart */
NULL, /* domainSetAutostart */
lxcGetSchedulerType, /* domainGetSchedulerType */
lxcGetSchedulerParameters, /* domainGetSchedulerParameters */
lxcSetSchedulerParameters, /* domainSetSchedulerParameters */
NULL, /* domainMigratePrepare */
NULL, /* domainMigratePerform */
NULL, /* domainMigrateFinish */
NULL, /* domainBlockStats */
NULL, /* domainInterfaceStats */
NULL, /* domainBlockPeek */
NULL, /* domainMemoryPeek */
NULL, /* nodeGetCellsFreeMemory */
NULL, /* getFreeMemory */
NULL, /* domainEventRegister */
NULL, /* domainEventDeregister */
NULL, /* domainMigratePrepare2 */
NULL, /* domainMigrateFinish2 */
};
static virStateDriver lxcStateDriver = {
.initialize = lxcStartup,
.cleanup = lxcShutdown,
.active = lxcActive,
};
int lxcRegister(void)
{
virRegisterDriver(&lxcDriver);
virRegisterStateDriver(&lxcStateDriver);
return 0;
}