mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-25 23:25:24 +00:00
17722c169c
We still default to bhyveloader(1) if no explicit bootloader configuration is supplied in the domain. If the /domain/bootloader looks like grub-bhyve and the user doesn't supply /domain/bootloader_args, we make an intelligent guess and try chainloading the first partition on the disk (or a CD if one exists, under the assumption that for a VM a CD is likely an install source). Caveat: Assumes the HDD boots from the msdos1 partition. I think this is a pretty reasonable assumption for a VM. (DrvBhyve with Bhyveload already assumes that the first disk should be booted.) I've tested both HDD and CD boot and they seem to work.
420 lines
12 KiB
C
420 lines
12 KiB
C
/*
|
|
* bhyve_process.c: bhyve process management
|
|
*
|
|
* Copyright (C) 2014 Roman Bogorodskiy
|
|
*
|
|
* 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 <fcntl.h>
|
|
#include <kvm.h>
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/user.h>
|
|
#include <sys/ioctl.h>
|
|
#include <net/if.h>
|
|
#include <net/if_tap.h>
|
|
|
|
#include "bhyve_device.h"
|
|
#include "bhyve_process.h"
|
|
#include "bhyve_command.h"
|
|
#include "datatypes.h"
|
|
#include "virerror.h"
|
|
#include "virlog.h"
|
|
#include "virfile.h"
|
|
#include "viralloc.h"
|
|
#include "vircommand.h"
|
|
#include "virstring.h"
|
|
#include "virpidfile.h"
|
|
#include "virprocess.h"
|
|
#include "virnetdev.h"
|
|
#include "virnetdevbridge.h"
|
|
#include "virnetdevtap.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_BHYVE
|
|
|
|
VIR_LOG_INIT("bhyve.bhyve_process");
|
|
|
|
static virDomainObjPtr
|
|
bhyveProcessAutoDestroy(virDomainObjPtr vm,
|
|
virConnectPtr conn ATTRIBUTE_UNUSED,
|
|
void *opaque)
|
|
{
|
|
bhyveConnPtr driver = opaque;
|
|
|
|
virBhyveProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_DESTROYED);
|
|
|
|
if (!vm->persistent) {
|
|
virDomainObjListRemove(driver->domains, vm);
|
|
vm = NULL;
|
|
}
|
|
|
|
return vm;
|
|
}
|
|
|
|
static void
|
|
bhyveNetCleanup(virDomainObjPtr vm)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < vm->def->nnets; i++) {
|
|
virDomainNetDefPtr net = vm->def->nets[i];
|
|
int actualType = virDomainNetGetActualType(net);
|
|
|
|
if (actualType == VIR_DOMAIN_NET_TYPE_BRIDGE) {
|
|
if (net->ifname) {
|
|
ignore_value(virNetDevBridgeRemovePort(
|
|
virDomainNetGetActualBridgeName(net),
|
|
net->ifname));
|
|
ignore_value(virNetDevTapDelete(net->ifname, NULL));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
virBhyveFormatDevMapFile(const char *vm_name, char **fn_out)
|
|
{
|
|
|
|
return virAsprintf(fn_out, "%s/grub_bhyve-%s-device.map", BHYVE_STATE_DIR,
|
|
vm_name);
|
|
}
|
|
|
|
int
|
|
virBhyveProcessStart(virConnectPtr conn,
|
|
bhyveConnPtr driver,
|
|
virDomainObjPtr vm,
|
|
virDomainRunningReason reason,
|
|
unsigned int flags)
|
|
{
|
|
char *devmap_file = NULL;
|
|
char *devicemap = NULL;
|
|
char *logfile = NULL;
|
|
int logfd = -1;
|
|
off_t pos = -1;
|
|
char ebuf[1024];
|
|
virCommandPtr cmd = NULL;
|
|
virCommandPtr load_cmd = NULL;
|
|
bhyveConnPtr privconn = conn->privateData;
|
|
int ret = -1, rc;
|
|
|
|
if (virAsprintf(&logfile, "%s/%s.log",
|
|
BHYVE_LOG_DIR, vm->def->name) < 0)
|
|
return -1;
|
|
|
|
|
|
if ((logfd = open(logfile, O_WRONLY | O_APPEND | O_CREAT,
|
|
S_IRUSR | S_IWUSR)) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Failed to open '%s'"),
|
|
logfile);
|
|
goto cleanup;
|
|
}
|
|
|
|
VIR_FREE(privconn->pidfile);
|
|
if (!(privconn->pidfile = virPidFileBuildPath(BHYVE_STATE_DIR,
|
|
vm->def->name))) {
|
|
virReportSystemError(errno,
|
|
"%s", _("Failed to build pidfile path"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (unlink(privconn->pidfile) < 0 &&
|
|
errno != ENOENT) {
|
|
virReportSystemError(errno,
|
|
_("Cannot remove state PID file %s"),
|
|
privconn->pidfile);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (bhyveDomainAssignAddresses(vm->def, NULL) < 0)
|
|
goto cleanup;
|
|
|
|
/* Call bhyve to start the VM */
|
|
if (!(cmd = virBhyveProcessBuildBhyveCmd(conn,
|
|
vm->def,
|
|
false)))
|
|
goto cleanup;
|
|
|
|
virCommandSetOutputFD(cmd, &logfd);
|
|
virCommandSetErrorFD(cmd, &logfd);
|
|
virCommandWriteArgLog(cmd, logfd);
|
|
virCommandSetPidFile(cmd, privconn->pidfile);
|
|
virCommandDaemonize(cmd);
|
|
|
|
/* Now bhyve command is constructed, meaning the
|
|
* domain is ready to be started, so we can build
|
|
* and execute bhyveload command */
|
|
rc = virBhyveFormatDevMapFile(vm->def->name, &devmap_file);
|
|
if (rc < 0)
|
|
goto cleanup;
|
|
|
|
if (!(load_cmd = virBhyveProcessBuildLoadCmd(conn, vm->def, devmap_file,
|
|
&devicemap)))
|
|
goto cleanup;
|
|
virCommandSetOutputFD(load_cmd, &logfd);
|
|
virCommandSetErrorFD(load_cmd, &logfd);
|
|
|
|
if (devicemap != NULL) {
|
|
rc = virFileWriteStr(devmap_file, devicemap, 0644);
|
|
if (rc) {
|
|
virReportSystemError(errno,
|
|
_("Cannot write device.map '%s'"),
|
|
devmap_file);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* Log generated command line */
|
|
virCommandWriteArgLog(load_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)));
|
|
|
|
VIR_DEBUG("Loading domain '%s'", vm->def->name);
|
|
if (virCommandRun(load_cmd, NULL) < 0)
|
|
goto cleanup;
|
|
|
|
/* Now we can start the domain */
|
|
VIR_DEBUG("Starting domain '%s'", vm->def->name);
|
|
if (virCommandRun(cmd, NULL) < 0)
|
|
goto cleanup;
|
|
|
|
if (virPidFileReadPath(privconn->pidfile, &vm->pid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Domain %s didn't show up"), vm->def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (flags & VIR_BHYVE_PROCESS_START_AUTODESTROY &&
|
|
virCloseCallbacksSet(driver->closeCallbacks, vm,
|
|
conn, bhyveProcessAutoDestroy) < 0)
|
|
goto cleanup;
|
|
|
|
vm->def->id = vm->pid;
|
|
virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, reason);
|
|
|
|
if (virDomainSaveStatus(driver->xmlopt,
|
|
BHYVE_STATE_DIR,
|
|
vm) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
if (devicemap != NULL) {
|
|
rc = unlink(devmap_file);
|
|
if (rc < 0 && errno != ENOENT)
|
|
virReportSystemError(errno, _("cannot unlink file '%s'"),
|
|
devmap_file);
|
|
VIR_FREE(devicemap);
|
|
}
|
|
VIR_FREE(devmap_file);
|
|
|
|
if (ret < 0) {
|
|
int exitstatus; /* Needed to avoid logging non-zero status */
|
|
virCommandPtr destroy_cmd;
|
|
if ((destroy_cmd = virBhyveProcessBuildDestroyCmd(driver,
|
|
vm->def)) != NULL) {
|
|
virCommandSetOutputFD(load_cmd, &logfd);
|
|
virCommandSetErrorFD(load_cmd, &logfd);
|
|
ignore_value(virCommandRun(destroy_cmd, &exitstatus));
|
|
virCommandFree(destroy_cmd);
|
|
}
|
|
|
|
bhyveNetCleanup(vm);
|
|
}
|
|
|
|
virCommandFree(load_cmd);
|
|
virCommandFree(cmd);
|
|
VIR_FREE(logfile);
|
|
VIR_FORCE_CLOSE(logfd);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
virBhyveProcessStop(bhyveConnPtr driver,
|
|
virDomainObjPtr vm,
|
|
virDomainShutoffReason reason ATTRIBUTE_UNUSED)
|
|
{
|
|
int ret = -1;
|
|
virCommandPtr cmd = NULL;
|
|
|
|
if (!virDomainObjIsActive(vm)) {
|
|
VIR_DEBUG("VM '%s' not active", vm->def->name);
|
|
return 0;
|
|
}
|
|
|
|
if (vm->pid <= 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Invalid PID %d for VM"),
|
|
(int)vm->pid);
|
|
return -1;
|
|
}
|
|
|
|
/* First, try to kill 'bhyve' process */
|
|
if (virProcessKillPainfully(vm->pid, true) != 0)
|
|
VIR_WARN("Failed to gracefully stop bhyve VM '%s' (pid: %d)",
|
|
vm->def->name,
|
|
(int)vm->pid);
|
|
|
|
/* Cleanup network interfaces */
|
|
bhyveNetCleanup(vm);
|
|
|
|
/* No matter if shutdown was successful or not, we
|
|
* need to unload the VM */
|
|
if (!(cmd = virBhyveProcessBuildDestroyCmd(driver, vm->def)))
|
|
goto cleanup;
|
|
|
|
if (virCommandRun(cmd, NULL) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
|
|
virCloseCallbacksUnset(driver->closeCallbacks, vm,
|
|
bhyveProcessAutoDestroy);
|
|
|
|
virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF, reason);
|
|
vm->pid = -1;
|
|
vm->def->id = -1;
|
|
|
|
cleanup:
|
|
virCommandFree(cmd);
|
|
|
|
virPidFileDelete(BHYVE_STATE_DIR, vm->def->name);
|
|
virDomainDeleteConfig(BHYVE_STATE_DIR, NULL, vm);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
virBhyveGetDomainTotalCpuStats(virDomainObjPtr vm,
|
|
unsigned long long *cpustats)
|
|
{
|
|
struct kinfo_proc *kp;
|
|
kvm_t *kd;
|
|
char errbuf[_POSIX2_LINE_MAX];
|
|
int nprocs;
|
|
int ret = -1;
|
|
|
|
if ((kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL) {
|
|
virReportError(VIR_ERR_SYSTEM_ERROR,
|
|
_("Unable to get kvm descriptor: %s"),
|
|
errbuf);
|
|
return -1;
|
|
|
|
}
|
|
|
|
kp = kvm_getprocs(kd, KERN_PROC_PID, vm->pid, &nprocs);
|
|
if (kp == NULL || nprocs != 1) {
|
|
virReportError(VIR_ERR_SYSTEM_ERROR,
|
|
_("Unable to obtain information about pid: %d"),
|
|
(int)vm->pid);
|
|
goto cleanup;
|
|
}
|
|
|
|
*cpustats = kp->ki_runtime * 1000ull;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
kvm_close(kd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct bhyveProcessReconnectData {
|
|
bhyveConnPtr driver;
|
|
kvm_t *kd;
|
|
};
|
|
|
|
static int
|
|
virBhyveProcessReconnect(virDomainObjPtr vm,
|
|
void *opaque)
|
|
{
|
|
struct bhyveProcessReconnectData *data = opaque;
|
|
struct kinfo_proc *kp;
|
|
int nprocs;
|
|
char **proc_argv;
|
|
char *expected_proctitle = NULL;
|
|
int ret = -1;
|
|
|
|
if (!virDomainObjIsActive(vm))
|
|
return 0;
|
|
|
|
if (!vm->pid)
|
|
return 0;
|
|
|
|
virObjectLock(vm);
|
|
|
|
kp = kvm_getprocs(data->kd, KERN_PROC_PID, vm->pid, &nprocs);
|
|
if (kp == NULL || nprocs != 1)
|
|
goto cleanup;
|
|
|
|
if (virAsprintf(&expected_proctitle, "bhyve: %s", vm->def->name) < 0)
|
|
goto cleanup;
|
|
|
|
proc_argv = kvm_getargv(data->kd, kp, 0);
|
|
if (proc_argv && proc_argv[0])
|
|
if (STREQ(expected_proctitle, proc_argv[0]))
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
if (ret < 0) {
|
|
/* If VM is reported to be in active state, but we cannot find
|
|
* its PID, then we clear information about the PID and
|
|
* set state to 'shutdown' */
|
|
vm->pid = 0;
|
|
vm->def->id = -1;
|
|
virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF,
|
|
VIR_DOMAIN_SHUTOFF_UNKNOWN);
|
|
ignore_value(virDomainSaveStatus(data->driver->xmlopt,
|
|
BHYVE_STATE_DIR,
|
|
vm));
|
|
}
|
|
|
|
virObjectUnlock(vm);
|
|
VIR_FREE(expected_proctitle);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
virBhyveProcessReconnectAll(bhyveConnPtr driver)
|
|
{
|
|
kvm_t *kd;
|
|
struct bhyveProcessReconnectData data;
|
|
char errbuf[_POSIX2_LINE_MAX];
|
|
|
|
if ((kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL) {
|
|
virReportError(VIR_ERR_SYSTEM_ERROR,
|
|
_("Unable to get kvm descriptor: %s"),
|
|
errbuf);
|
|
return;
|
|
|
|
}
|
|
|
|
data.driver = driver;
|
|
data.kd = kd;
|
|
|
|
virDomainObjListForEach(driver->domains, virBhyveProcessReconnect, &data);
|
|
|
|
kvm_close(kd);
|
|
}
|