qemu: add support for running QEMU driver in embedded mode

This enables support for running QEMU embedded to the calling
application process using a URI:

   qemu:///embed?root=/some/path

Note that it is important to keep the path reasonably short to
avoid risk of hitting the limit on UNIX socket path names
which is 108 characters.

When using the embedded mode with a root=/var/tmp/embed, the
driver will use the following paths:

                logDir: /var/tmp/embed/log/qemu
           swtpmLogDir: /var/tmp/embed/log/swtpm
         configBaseDir: /var/tmp/embed/etc/qemu
              stateDir: /var/tmp/embed/run/qemu
         swtpmStateDir: /var/tmp/embed/run/swtpm
              cacheDir: /var/tmp/embed/cache/qemu
                libDir: /var/tmp/embed/lib/qemu
       swtpmStorageDir: /var/tmp/embed/lib/swtpm
 defaultTLSx509certdir: /var/tmp/embed/etc/pki/qemu

These are identical whether the embedded driver is privileged
or unprivileged.

This compares with the system instance which uses

                logDir: /var/log/libvirt/qemu
           swtpmLogDir: /var/log/swtpm/libvirt/qemu
         configBaseDir: /etc/libvirt/qemu
              stateDir: /run/libvirt/qemu
         swtpmStateDir: /run/libvirt/qemu/swtpm
              cacheDir: /var/cache/libvirt/qemu
                libDir: /var/lib/libvirt/qemu
       swtpmStorageDir: /var/lib/libvirt/swtpm
 defaultTLSx509certdir: /etc/pki/qemu

At this time all features present in the QEMU driver are available when
running in embedded mode, availability matching whether the embedded
driver is privileged or unprivileged.

Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrangé 2019-05-17 12:35:57 +01:00
parent 88446e07b2
commit 068efae5b1
7 changed files with 186 additions and 25 deletions

View File

@ -63,6 +63,105 @@ qemu+tcp://example.com/system (remote access, SASl/Kerberos)
qemu+ssh://root@example.com/system (remote access, SSH tunnelled)
</pre>
<h3><a id="uriembedded">Embedded driver</a></h3>
<p>
Since 6.0.0 the QEMU driver has experimental support for operating
in an embedded mode. In this scenario, rather than connecting to
the libvirtd daemon, the QEMU driver runs in the client application
process directly. To use this the client application must have
registered &amp; be running an instance of the event loop. To open
the driver in embedded mode the app use the new URI path and specify
a virtual root directory under which the driver will create content.
</p>
<pre>
qemu:///embed?root=/some/dir
</pre>
<p>
Broadly speaking the range of functionality is intended to be
on a par with that seen when using the traditional system or
session libvirt connections to QEMU. The features will of course
differ depending on whether the application using the embedded
driver is running privileged or unprivileged. For example PCI
device assignment or TAP based networking are only available
when running privileged. While the embedded mode is still classed
as experimental some features may change their default settings
between releases.
</p>
<p>
By default if the application uses any APIs associated with
secondary drivers, these will result in a connection being
opened to the corresponding driver in libvirtd. For example,
this allows a virtual machine from the embedded QEMU to connect
its NIC to a virtual network or connect its disk to a storage
volume. Some of the secondary drivers will also be able to support
running in embedded mode. Currently this is supported by the
secrets driver, to allow for use of VMs with encrypted disks
</p>
<h4><a id="embedTree">Directory tree</a></h4>
<p>
Under the specified root directory the following locations will
be used
</p>
<pre>
/some/dir
|
+- log
| |
| +- qemu
| +- swtpm
|
+- etc
| |
| +- qemu
| +- pki
| |
| +- qemu
|
+- run
| |
| +- qemu
| +- swtpm
|
+- cache
| |
| +- qemu
|
+- lib
|
+- qemu
+- swtpm
</pre>
<p>
Note that UNIX domain sockets used for QEMU virtual machines had
a maximum filename length of 108 characters. Bear this in mind
when picking a root directory to avoid risk of exhausting the
filename space. The application is responsible for recursively
purging the contents of this directory tree once they no longer
require a connection, though it can also be left intact for reuse
when opening a future connection.
</p>
<h4><a id="embedAPI">API usage with event loop></a></h4>
<p>
To use the QEMU driver in embedded mode the application must
register an event loop with libvirt. Many of the QEMU driver
API calls will rely on the event loop processing data. With this
in mind, applications must <strong>NEVER</strong> invoke API
calls from the event loop thread itself, only other threads.
Not following this rule will lead to deadlocks in the API.
This restriction is intended to be lifted in a future release
of libvirt, once QMP processing moves to a dedicated thread.
</p>
<h2><a id="security">Driver security architecture</a></h2>
<p>

View File

@ -104,7 +104,8 @@ qemuDriverUnlock(virQEMUDriverPtr driver)
#endif
virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged)
virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged,
const char *root)
{
g_autoptr(virQEMUDriverConfig) cfg = NULL;
@ -114,7 +115,11 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged)
if (!(cfg = virObjectNew(virQEMUDriverConfigClass)))
return NULL;
cfg->uri = privileged ? "qemu:///system" : "qemu:///session";
if (root) {
cfg->uri = g_strdup_printf("qemu:///embed?root=%s", root);
} else {
cfg->uri = g_strdup(privileged ? "qemu:///system" : "qemu:///session");
}
if (privileged) {
if (virGetUserID(QEMU_USER, &cfg->user) < 0)
@ -130,7 +135,24 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged)
cfg->cgroupControllers = -1; /* -1 == auto-detect */
if (privileged) {
if (root != NULL) {
cfg->logDir = g_strdup_printf("%s/log/qemu", root);
cfg->swtpmLogDir = g_strdup_printf("%s/log/swtpm", root);
cfg->configBaseDir = g_strdup_printf("%s/etc", root);
cfg->stateDir = g_strdup_printf("%s/run/qemu", root);
cfg->swtpmStateDir = g_strdup_printf("%s/run/swtpm", root);
cfg->cacheDir = g_strdup_printf("%s/cache/qemu", root);
cfg->libDir = g_strdup_printf("%s/lib/qemu", root);
cfg->swtpmStorageDir = g_strdup_printf("%s/lib/swtpm", root);
cfg->saveDir = g_strdup_printf("%s/save", cfg->libDir);
cfg->snapshotDir = g_strdup_printf("%s/snapshot", cfg->libDir);
cfg->checkpointDir = g_strdup_printf("%s/checkpoint", cfg->libDir);
cfg->autoDumpPath = g_strdup_printf("%s/dump", cfg->libDir);
cfg->channelTargetDir = g_strdup_printf("%s/channel/target", cfg->libDir);
cfg->nvramDir = g_strdup_printf("%s/nvram", cfg->libDir);
cfg->memoryBackingDir = g_strdup_printf("%s/ram", cfg->libDir);
} else if (privileged) {
cfg->logDir = g_strdup_printf("%s/log/libvirt/qemu", LOCALSTATEDIR);
cfg->swtpmLogDir = g_strdup_printf("%s/log/swtpm/libvirt/qemu",
@ -189,6 +211,16 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged)
cfg->memoryBackingDir = g_strdup_printf("%s/qemu/ram", cfg->configBaseDir);
cfg->swtpmStorageDir = g_strdup_printf("%s/qemu/swtpm",
cfg->configBaseDir);
}
if (privileged) {
if (!virDoesUserExist("tss") ||
virGetUserID("tss", &cfg->swtpm_user) < 0)
cfg->swtpm_user = 0; /* fall back to root */
if (!virDoesGroupExist("tss") ||
virGetGroupID("tss", &cfg->swtpm_group) < 0)
cfg->swtpm_group = 0; /* fall back to root */
} else {
cfg->swtpm_user = (uid_t)-1;
cfg->swtpm_group = (gid_t)-1;
}
@ -201,7 +233,11 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged)
* This will then be used as a fallback if the service specific
* directory doesn't exist (although we don't check if this exists).
*/
cfg->defaultTLSx509certdir = g_strdup(SYSCONFDIR "/pki/qemu");
if (root == NULL) {
cfg->defaultTLSx509certdir = g_strdup(SYSCONFDIR "pki/qemu");
} else {
cfg->defaultTLSx509certdir = g_strdup_printf("%s/etc/pki/qemu", root);
}
cfg->vncListen = g_strdup(VIR_LOOPBACK_IPV4_ADDR);
cfg->spiceListen = g_strdup(VIR_LOOPBACK_IPV4_ADDR);
@ -264,6 +300,7 @@ static void virQEMUDriverConfigDispose(void *obj)
virBitmapFree(cfg->namespaces);
virStringListFree(cfg->cgroupDeviceACL);
VIR_FREE(cfg->uri);
VIR_FREE(cfg->configBaseDir);
VIR_FREE(cfg->configDir);

View File

@ -75,7 +75,7 @@ typedef virQEMUDriverConfig *virQEMUDriverConfigPtr;
struct _virQEMUDriverConfig {
virObject parent;
const char *uri;
char *uri;
uid_t user;
gid_t group;
@ -240,8 +240,9 @@ struct _virQEMUDriver {
/* Atomic inc/dec only */
unsigned int nactive;
/* Immutable value */
/* Immutable values */
bool privileged;
char *embeddedRoot;
/* Immutable pointers. Caller must provide locking */
virStateInhibitCallback inhibitCallback;
@ -313,7 +314,8 @@ struct _virQEMUDriver {
virHashAtomicPtr migrationErrors;
};
virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged);
virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged,
const char *root);
int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg,
const char *filename,

View File

@ -645,12 +645,6 @@ qemuStateInitialize(bool privileged,
const char *defsecmodel = NULL;
g_autofree virSecurityManagerPtr *sec_managers = NULL;
if (root != NULL) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("Driver does not support embedded mode"));
return -1;
}
if (VIR_ALLOC(qemu_driver) < 0)
return VIR_DRV_STATE_INIT_ERROR;
@ -668,6 +662,8 @@ qemuStateInitialize(bool privileged,
qemu_driver->privileged = privileged;
qemu_driver->hostarch = virArchFromHost();
if (root != NULL)
qemu_driver->embeddedRoot = g_strdup(root);
if (!(qemu_driver->domains = virDomainObjListNew()))
goto error;
@ -681,7 +677,7 @@ qemuStateInitialize(bool privileged,
if (privileged)
qemu_driver->hostsysinfo = virSysinfoRead();
if (!(qemu_driver->config = cfg = virQEMUDriverConfigNew(privileged)))
if (!(qemu_driver->config = cfg = virQEMUDriverConfigNew(privileged, root)))
goto error;
if (!(driverConf = g_strdup_printf("%s/qemu.conf", cfg->configBaseDir)))
@ -1185,10 +1181,30 @@ static virDrvOpenStatus qemuConnectOpen(virConnectPtr conn,
return VIR_DRV_OPEN_ERROR;
}
if (!virConnectValidateURIPath(conn->uri->path,
"qemu",
virQEMUDriverIsPrivileged(qemu_driver)))
return VIR_DRV_OPEN_ERROR;
if (qemu_driver->embeddedRoot) {
const char *root = virURIGetParam(conn->uri, "root");
if (!root)
return VIR_DRV_OPEN_ERROR;
if (STRNEQ(conn->uri->path, "/embed")) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("URI must be qemu:///embed"));
return VIR_DRV_OPEN_ERROR;
}
if (STRNEQ(root, qemu_driver->embeddedRoot)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot open embedded driver at path '%s', "
"already open with path '%s'"),
root, qemu_driver->embeddedRoot);
return VIR_DRV_OPEN_ERROR;
}
} else {
if (!virConnectValidateURIPath(conn->uri->path,
"qemu",
virQEMUDriverIsPrivileged(qemu_driver)))
return VIR_DRV_OPEN_ERROR;
}
if (virConnectOpenEnsureACL(conn) < 0)
return VIR_DRV_OPEN_ERROR;
@ -23416,6 +23432,7 @@ static virHypervisorDriver qemuHypervisorDriver = {
static virConnectDriver qemuConnectDriver = {
.localOnly = true,
.uriSchemes = (const char *[]){ "qemu", NULL },
.embeddable = true,
.hypervisorDriver = &qemuHypervisorDriver,
};

View File

@ -6683,10 +6683,17 @@ qemuProcessLaunch(virConnectPtr conn,
cfg = virQEMUDriverGetConfig(driver);
if ((flags & VIR_QEMU_PROCESS_START_AUTODESTROY) && !conn) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Domain autodestroy requires a connection handle"));
return -1;
if (flags & VIR_QEMU_PROCESS_START_AUTODESTROY) {
if (!conn) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Domain autodestroy requires a connection handle"));
return -1;
}
if (driver->embeddedRoot) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Domain autodestroy not supported for embedded drivers yet"));
return -1;
}
}
hookData.vm = vm;

View File

@ -369,7 +369,7 @@ mymain(void)
#endif
#if WITH_QEMU
virQEMUDriverConfigPtr cfg = virQEMUDriverConfigNew(false);
virQEMUDriverConfigPtr cfg = virQEMUDriverConfigNew(false, "");
if (!cfg)
return EXIT_FAILURE;

View File

@ -380,8 +380,7 @@ int qemuTestDriverInit(virQEMUDriver *driver)
return -1;
driver->hostarch = virArchFromHost();
driver->config = virQEMUDriverConfigNew(false);
driver->config = virQEMUDriverConfigNew(false, "");
if (!driver->config)
goto error;