Support for <channel> in domain and QEmu backend

allows the following to be specified in a domain:
<channel type='pipe'>
  <source path='/tmp/guestfwd'/>
  <target type='guestfwd' address='10.0.2.1' port='4600'/>
</channel>

* proxy/Makefile.am: add network.c as dep of domain_conf.c
* docs/schemas/domain.rng src/conf/domain_conf.[ch]: extend the domain
  schemas and the parsing/serialization side for the new construct

QEmu support will add the following on the qemu command line:
 -chardev pipe,id=channel0,path=/tmp/guestfwd
 -net user,guestfwd=tcp:10.0.2.1:4600-chardev:channel0

* src/qemu/qemu_conf.c: Add argument output for channel
* tests/qemuxml2(argv|xml)test.c: Add test for <channel> domain syntax
This commit is contained in:
Matthew Booth 2009-11-05 15:31:03 +01:00 committed by Daniel Veillard
parent 7400396801
commit af249ea468
7 changed files with 375 additions and 50 deletions

View File

@ -930,6 +930,19 @@
definition doesn't fully specify the constraints on this node.
-->
<define name="qemucdev">
<ref name="qemucdevSrcType"/>
<interleave>
<ref name="qemucdevSrcDef"/>
<optional>
<element name="target">
<optional>
<attribute name="port"/>
</optional>
</element>
</optional>
</interleave>
</define>
<define name="qemucdevSrcType">
<attribute name="type">
<choice>
<value>dev</value>
@ -944,43 +957,36 @@
<value>pty</value>
</choice>
</attribute>
<interleave>
<optional>
<oneOrMore>
<element name="source">
<optional>
<attribute name="mode"/>
</optional>
<optional>
<attribute name="path"/>
</optional>
<optional>
<attribute name="host"/>
</optional>
<optional>
<attribute name="service"/>
</optional>
<optional>
<attribute name="wiremode"/>
</optional>
</element>
</oneOrMore>
</optional>
<optional>
<element name="protocol">
</define>
<define name="qemucdevSrcDef">
<optional>
<oneOrMore>
<element name="source">
<optional>
<attribute name="type"/>
<attribute name="mode"/>
</optional>
<optional>
<attribute name="path"/>
</optional>
<optional>
<attribute name="host"/>
</optional>
<optional>
<attribute name="service"/>
</optional>
<optional>
<attribute name="wiremode"/>
</optional>
</element>
</optional>
<optional>
<element name="target">
<optional>
<attribute name="port"/>
</optional>
</element>
</optional>
</interleave>
</oneOrMore>
</optional>
<optional>
<element name="protocol">
<optional>
<attribute name="type"/>
</optional>
</element>
</optional>
</define>
<!--
The description for a console
@ -1044,6 +1050,24 @@
<ref name="qemucdev"/>
</element>
</define>
<define name="guestfwdTarget">
<element name="target">
<attribute name="type">
<value>guestfwd</value>
</attribute>
<attribute name="address"/>
<attribute name="port"/>
</element>
</define>
<define name="channel">
<element name="channel">
<ref name="qemucdevSrcType"/>
<interleave>
<ref name="qemucdevSrcDef"/>
<ref name="guestfwdTarget"/>
</interleave>
</element>
</define>
<define name="input">
<element name="input">
<attribute name="type">
@ -1158,6 +1182,7 @@
<ref name="console"/>
<ref name="parallel"/>
<ref name="serial"/>
<ref name="channel"/>
</choice>
</zeroOrMore>
<optional>

View File

@ -17,6 +17,7 @@ libvirt_proxy_SOURCES = libvirt_proxy.c \
@top_srcdir@/src/util/buf.c \
@top_srcdir@/src/util/logging.c \
@top_srcdir@/src/util/memory.c \
@top_srcdir@/src/util/network.c \
@top_srcdir@/src/util/threads.c \
@top_srcdir@/src/util/util.c \
@top_srcdir@/src/util/uuid.c \

View File

@ -40,6 +40,7 @@
#include "buf.h"
#include "c-ctype.h"
#include "logging.h"
#include "network.h"
#define VIR_FROM_THIS VIR_FROM_DOMAIN
@ -132,7 +133,8 @@ VIR_ENUM_IMPL(virDomainChrTarget, VIR_DOMAIN_CHR_TARGET_TYPE_LAST,
"monitor",
"parallel",
"serial",
"console")
"console",
"guestfwd")
VIR_ENUM_IMPL(virDomainChr, VIR_DOMAIN_CHR_TYPE_LAST,
"null",
@ -412,6 +414,12 @@ void virDomainChrDefFree(virDomainChrDefPtr def)
if (!def)
return;
switch (def->targetType) {
case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD:
VIR_FREE(def->target.addr);
break;
}
switch (def->type) {
case VIR_DOMAIN_CHR_TYPE_PTY:
case VIR_DOMAIN_CHR_TYPE_DEV:
@ -541,6 +549,7 @@ void virDomainDefFree(virDomainDefPtr def)
for (i = 0 ; i < def->nnets ; i++)
virDomainNetDefFree(def->nets[i]);
VIR_FREE(def->nets);
for (i = 0 ; i < def->nserials ; i++)
virDomainChrDefFree(def->serials[i]);
VIR_FREE(def->serials);
@ -549,6 +558,10 @@ void virDomainDefFree(virDomainDefPtr def)
virDomainChrDefFree(def->parallels[i]);
VIR_FREE(def->parallels);
for (i = 0 ; i < def->nchannels ; i++)
virDomainChrDefFree(def->channels[i]);
VIR_FREE(def->channels);
virDomainChrDefFree(def->console);
for (i = 0 ; i < def->nsounds ; i++)
@ -1332,7 +1345,10 @@ virDomainChrDefParseXML(virConnectPtr conn,
char *path = NULL;
char *mode = NULL;
char *protocol = NULL;
const char *nodeName;
const char *targetType = NULL;
const char *addrStr = NULL;
const char *portStr = NULL;
virDomainChrDefPtr def;
if (VIR_ALLOC(def) < 0) {
@ -1346,17 +1362,15 @@ virDomainChrDefParseXML(virConnectPtr conn,
else if ((def->type = virDomainChrTypeFromString(type)) < 0)
def->type = VIR_DOMAIN_CHR_TYPE_NULL;
targetType = (const char *) node->name;
if (targetType == NULL) {
/* Shouldn't be possible */
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s",
_("node->name is NULL in virDomainChrDefParseXML()"));
return NULL;
}
if ((def->targetType = virDomainChrTargetTypeFromString(targetType)) < 0) {
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
_("unknown target type for character device: %s"),
targetType);
nodeName = (const char *) node->name;
if ((def->targetType = virDomainChrTargetTypeFromString(nodeName)) < 0) {
/* channel is handled below */
if(STRNEQ(nodeName, "channel")) {
virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN,
_("unknown target type for character device: %s"),
nodeName);
return NULL;
}
def->targetType = VIR_DOMAIN_CHR_TARGET_TYPE_NULL;
}
@ -1405,6 +1419,89 @@ virDomainChrDefParseXML(virConnectPtr conn,
} else if (xmlStrEqual(cur->name, BAD_CAST "protocol")) {
if (protocol == NULL)
protocol = virXMLPropString(cur, "type");
} else if (xmlStrEqual(cur->name, BAD_CAST "target")) {
/* If target type isn't set yet, expect it to be set here */
if(def->targetType == VIR_DOMAIN_CHR_TARGET_TYPE_NULL) {
targetType = virXMLPropString(cur, "type");
if(targetType == NULL) {
virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, "%s",
_("character device target does "
"not define a type"));
goto error;
}
if ((def->targetType =
virDomainChrTargetTypeFromString(targetType)) < 0)
{
virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN,
_("unknown target type for "
"character device: %s"),
targetType);
goto error;
}
}
unsigned int port;
switch (def->targetType) {
case VIR_DOMAIN_CHR_TARGET_TYPE_PARALLEL:
case VIR_DOMAIN_CHR_TARGET_TYPE_SERIAL:
case VIR_DOMAIN_CHR_TARGET_TYPE_CONSOLE:
portStr = virXMLPropString(cur, "port");
if(portStr == NULL) {
/* Not required. It will be assigned automatically
* later */
break;
}
if(virStrToLong_ui(portStr, NULL, 10, &port) < 0) {
virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN,
_("Invalid port number: %s"),
portStr);
goto error;
}
break;
case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD:
addrStr = virXMLPropString(cur, "address");
portStr = virXMLPropString(cur, "port");
if(addrStr == NULL) {
virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, "%s",
_("guestfwd channel does not "
"define a target address"));
goto error;
}
if(VIR_ALLOC(def->target.addr) < 0) {
virReportOOMError(conn);
goto error;
}
if(virSocketParseAddr(addrStr, def->target.addr, 0) < 0)
{
virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN,
_("%s is not a valid address"),
addrStr);
goto error;
}
if(portStr == NULL) {
virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, "%s",
_("guestfwd channel does "
"not define a target port"));
goto error;
}
if(virStrToLong_ui(portStr, NULL, 10, &port) < 0) {
virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN,
_("Invalid port number: %s"),
portStr);
goto error;
}
virSocketSetPort(def->target.addr, port);
break;
default:
virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN,
_("unexpected target type type %u"),
def->targetType);
}
}
}
cur = cur->next;
@ -1534,6 +1631,9 @@ cleanup:
VIR_FREE(connectHost);
VIR_FREE(connectService);
VIR_FREE(path);
VIR_FREE(targetType);
VIR_FREE(addrStr);
VIR_FREE(portStr);
return def;
@ -3006,6 +3106,25 @@ static virDomainDefPtr virDomainDefParseXML(virConnectPtr conn,
}
}
if ((n = virXPathNodeSet(conn, "./devices/channel", ctxt, &nodes)) < 0) {
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
"%s", _("cannot extract channel devices"));
goto error;
}
if (n && VIR_ALLOC_N(def->channels, n) < 0)
goto no_memory;
for (i = 0 ; i < n ; i++) {
virDomainChrDefPtr chr = virDomainChrDefParseXML(conn,
nodes[i],
flags);
if (!chr)
goto error;
def->channels[def->nchannels++] = chr;
}
VIR_FREE(nodes);
/* analysis of the input devices */
if ((n = virXPathNodeSet(conn, "./devices/input", ctxt, &nodes)) < 0) {
@ -3991,13 +4110,26 @@ virDomainChrDefFormat(virConnectPtr conn,
{
const char *type = virDomainChrTypeToString(def->type);
const char *targetName = virDomainChrTargetTypeToString(def->targetType);
const char *elementName;
const char *elementName = targetName; /* Currently always the same */
const char *addr = NULL;
int ret = 0;
switch (def->targetType) {
/* channel types are in a common channel element */
case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD:
elementName = "channel";
break;
default:
elementName = targetName;
}
if (!type) {
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
_("unexpected char type %d"), def->type);
return -1;
ret = -1;
goto cleanup;
}
/* Compat with legacy <console tty='/dev/pts/5'/> syntax */
@ -4079,6 +4211,25 @@ virDomainChrDefFormat(virConnectPtr conn,
}
switch (def->targetType) {
case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD:
addr = virSocketFormatAddr(def->target.addr);
if (addr == NULL) {
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to format guestfwd address"));
ret = -1;
goto cleanup;
}
int port = virSocketGetPort(def->target.addr);
if (port < 0) {
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to format guestfwd port"));
ret = -1;
goto cleanup;
}
virBufferVSprintf(buf, " <target type='guestfwd' address='%s' port='%d'/>\n",
addr, port);
break;
case VIR_DOMAIN_CHR_TARGET_TYPE_PARALLEL:
case VIR_DOMAIN_CHR_TARGET_TYPE_SERIAL:
case VIR_DOMAIN_CHR_TARGET_TYPE_CONSOLE:
@ -4096,7 +4247,10 @@ virDomainChrDefFormat(virConnectPtr conn,
virBufferVSprintf(buf, " </%s>\n",
elementName);
return 0;
cleanup:
VIR_FREE(addr);
return ret;
}
static int
@ -4562,6 +4716,10 @@ char *virDomainDefFormat(virConnectPtr conn,
goto cleanup;
}
for (n = 0 ; n < def->nchannels ; n++)
if (virDomainChrDefFormat(conn, &buf, def->channels[n], flags) < 0)
goto cleanup;
for (n = 0 ; n < def->ninputs ; n++)
if (def->inputs[n]->bus == VIR_DOMAIN_INPUT_BUS_USB &&
virDomainInputDefFormat(conn, &buf, def->inputs[n]) < 0)

View File

@ -34,6 +34,7 @@
#include "util.h"
#include "threads.h"
#include "hash.h"
#include "network.h"
/* Private component of virDomainXMLFlags */
typedef enum {
@ -217,6 +218,7 @@ enum virDomainChrTargetType {
VIR_DOMAIN_CHR_TARGET_TYPE_PARALLEL,
VIR_DOMAIN_CHR_TARGET_TYPE_SERIAL,
VIR_DOMAIN_CHR_TARGET_TYPE_CONSOLE,
VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD,
VIR_DOMAIN_CHR_TARGET_TYPE_LAST
};
@ -249,6 +251,7 @@ struct _virDomainChrDef {
int targetType;
union {
int port; /* parallel, serial, console */
virSocketAddrPtr addr; /* guestfwd */
} target;
int type;
@ -623,6 +626,9 @@ struct _virDomainDef {
int nparallels;
virDomainChrDefPtr *parallels;
int nchannels;
virDomainChrDefPtr *channels;
/* Only 1 */
virDomainChrDefPtr console;
virSecurityLabelDef seclabel;

View File

@ -51,6 +51,7 @@
#include "xml.h"
#include "nodeinfo.h"
#include "logging.h"
#include "network.h"
#define VIR_FROM_THIS VIR_FROM_QEMU
@ -1411,6 +1412,97 @@ qemuBuildHostNetStr(virConnectPtr conn,
return 0;
}
/* This function outputs a -chardev command line option which describes only the
* host side of the character device */
static void qemudBuildCommandLineChrDevChardevStr(virDomainChrDefPtr dev,
const char *const id,
virBufferPtr buf)
{
bool telnet;
switch(dev->type) {
case VIR_DOMAIN_CHR_TYPE_NULL:
virBufferVSprintf(buf, "null,id=%s", id);
break;
case VIR_DOMAIN_CHR_TYPE_VC:
virBufferVSprintf(buf, "vc,id=%s", id);
break;
case VIR_DOMAIN_CHR_TYPE_PTY:
virBufferVSprintf(buf, "pty,id=%s", id);
break;
case VIR_DOMAIN_CHR_TYPE_DEV:
virBufferVSprintf(buf, "tty,id=%s,path=%s", id, dev->data.file.path);
break;
case VIR_DOMAIN_CHR_TYPE_FILE:
virBufferVSprintf(buf, "file,id=%s,path=%s", id, dev->data.file.path);
break;
case VIR_DOMAIN_CHR_TYPE_PIPE:
virBufferVSprintf(buf, "pipe,id=%s,path=%s", id, dev->data.file.path);
break;
case VIR_DOMAIN_CHR_TYPE_STDIO:
virBufferVSprintf(buf, "stdio,id=%s", id);
break;
case VIR_DOMAIN_CHR_TYPE_UDP:
virBufferVSprintf(buf,
"udp,id=%s,host=%s,port=%s,localaddr=%s,localport=%s",
id,
dev->data.udp.connectHost,
dev->data.udp.connectService,
dev->data.udp.bindHost,
dev->data.udp.bindService);
break;
case VIR_DOMAIN_CHR_TYPE_TCP:
telnet = dev->data.tcp.protocol == VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET;
virBufferVSprintf(buf,
"socket,id=%s,host=%s,port=%s%s%s",
id,
dev->data.tcp.host,
dev->data.tcp.service,
telnet ? ",telnet" : "",
dev->data.tcp.listen ? ",server,nowait" : "");
break;
case VIR_DOMAIN_CHR_TYPE_UNIX:
virBufferVSprintf(buf,
"socket,id=%s,path=%s%s",
id,
dev->data.nix.path,
dev->data.nix.listen ? ",server,nowait" : "");
break;
}
}
static int qemudBuildCommandLineChrDevTargetStr(virDomainChrDefPtr dev,
const char *const id,
virBufferPtr buf)
{
int ret = 0;
const char *addr = NULL;
int port;
switch (dev->targetType) {
case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD:
addr = virSocketFormatAddr(dev->target.addr);
port = virSocketGetPort(dev->target.addr);
virBufferVSprintf(buf, "user,guestfwd=tcp:%s:%i-chardev:%s",
addr, port, id);
VIR_FREE(addr);
break;
}
return ret;
}
/* This function outputs an all-in-one character device command line option */
static int qemudBuildCommandLineChrDevStr(virDomainChrDefPtr dev,
char *buf,
int buflen)
@ -2088,6 +2180,46 @@ int qemudBuildCommandLine(virConnectPtr conn,
}
}
for (i = 0 ; i < def->nchannels ; i++) {
virBuffer buf = VIR_BUFFER_INITIALIZER;
const char *argStr;
char id[16];
virDomainChrDefPtr channel = def->channels[i];
if (snprintf(id, sizeof(id), "channel%i", i) > sizeof(id))
goto error;
switch(channel->targetType) {
case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD:
if (!(qemuCmdFlags & QEMUD_CMD_FLAG_CHARDEV)) {
qemudReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT,
"%s", _("guestfwd requires QEMU to support -chardev"));
goto error;
}
qemudBuildCommandLineChrDevChardevStr(channel, id, &buf);
argStr = virBufferContentAndReset(&buf);
if (argStr == NULL)
goto error;
ADD_ARG_LIT("-chardev");
ADD_ARG_LIT(argStr);
VIR_FREE(argStr);
qemudBuildCommandLineChrDevTargetStr(channel, id, &buf);
argStr = virBufferContentAndReset(&buf);
if (argStr == NULL)
goto error;
ADD_ARG_LIT("-net");
ADD_ARG_LIT(argStr);
VIR_FREE(argStr);
}
}
ADD_ARG_LIT("-usb");
for (i = 0 ; i < def->ninputs ; i++) {
virDomainInputDefPtr input = def->inputs[i];

View File

@ -268,11 +268,13 @@ mymain(int argc, char **argv)
DO_TEST("serial-many", 0);
DO_TEST("parallel-tcp", 0);
DO_TEST("console-compat", 0);
DO_TEST("channel-guestfwd", QEMUD_CMD_FLAG_CHARDEV);
DO_TEST("sound", 0);
DO_TEST("hostdev-usb-product", 0);
DO_TEST("hostdev-usb-address", 0);
DO_TEST("hostdev-pci-address", QEMUD_CMD_FLAG_PCIDEVICE);
DO_TEST_FULL("restore-v1", QEMUD_CMD_FLAG_MIGRATE_KVM_STDIO, "stdio");

View File

@ -129,6 +129,7 @@ mymain(int argc, char **argv)
DO_TEST("serial-many");
DO_TEST("parallel-tcp");
DO_TEST("console-compat");
DO_TEST("channel-guestfwd");
DO_TEST("hostdev-usb-product");
DO_TEST("hostdev-usb-address");