domifaddr: Implement the API for qemu

By querying the qemu guest agent with the QMP command
"guest-network-get-interfaces" and converting the received JSON
output to structured objects.

Although "ifconfig" is deprecated, IP aliases created by "ifconfig"
are supported by this API. The legacy syntax of an IP alias is:
"<ifname>:<alias-name>". Since we want all aliases to be clubbed
under parent interface, simply stripping ":<alias-name>" suffices.
Note that IP aliases formed by "ip" aren't visible to "ifconfig",
and aliases created by "ip" do not have any specific name. But
we are lucky, as qemu guest agent detects aliases created by both.

src/qemu/qemu_agent.h:
  * Define qemuAgentGetInterfaces

src/qemu/qemu_agent.c:
  * Implement qemuAgentGetInterface

src/qemu/qemu_driver.c:
  * New function qemuGetDHCPInterfaces
  * New function qemuDomainInterfaceAddresses

src/remote_protocol-sructs:
  * Define new structs

tests/qemuagenttest.c:
  * Add new test: testQemuAgentGetInterfaces
    Test cases for IP aliases, 0 or multiple ipv4/ipv6 address(es)

Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com>
This commit is contained in:
Nehal J Wani 2015-01-26 00:08:48 +05:30 committed by Daniel P. Berrange
parent 71546d1798
commit 0977b8aa07
4 changed files with 570 additions and 0 deletions

View File

@ -1953,3 +1953,206 @@ qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info,
virJSONValueFree(reply);
return ret;
}
/*
* qemuAgentGetInterfaces:
* @mon: Agent monitor
* @ifaces: pointer to an array of pointers pointing to interface objects
*
* Issue guest-network-get-interfaces to guest agent, which returns a
* list of interfaces of a running domain along with their IP and MAC
* addresses.
*
* Returns: number of interfaces on success, -1 on error.
*/
int
qemuAgentGetInterfaces(qemuAgentPtr mon,
virDomainInterfacePtr **ifaces)
{
int ret = -1;
size_t i, j;
int size = -1;
virJSONValuePtr cmd = NULL;
virJSONValuePtr reply = NULL;
virJSONValuePtr ret_array = NULL;
size_t ifaces_count = 0;
size_t addrs_count = 0;
virDomainInterfacePtr *ifaces_ret = NULL;
virHashTablePtr ifaces_store = NULL;
char **ifname = NULL;
/* Hash table to handle the interface alias */
if (!(ifaces_store = virHashCreate(ifaces_count, NULL))) {
virHashFree(ifaces_store);
return -1;
}
if (!(cmd = qemuAgentMakeCommand("guest-network-get-interfaces", NULL)))
goto cleanup;
if (qemuAgentCommand(mon, cmd, &reply, false, VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0 ||
qemuAgentCheckError(cmd, reply) < 0) {
goto cleanup;
}
if (!(ret_array = virJSONValueObjectGet(reply, "return"))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("qemu agent didn't provide 'return' field"));
goto cleanup;
}
if ((size = virJSONValueArraySize(ret_array)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("qemu agent didn't return an array of interfaces"));
goto cleanup;
}
for (i = 0; i < size; i++) {
virJSONValuePtr tmp_iface = virJSONValueArrayGet(ret_array, i);
virJSONValuePtr ip_addr_arr = NULL;
const char *hwaddr, *ifname_s, *name = NULL;
int ip_addr_arr_size;
virDomainInterfacePtr iface = NULL;
/* Shouldn't happen but doesn't hurt to check neither */
if (!tmp_iface) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("qemu agent reply missing interface entry in array"));
goto error;
}
/* interface name is required to be presented */
name = virJSONValueObjectGetString(tmp_iface, "name");
if (!name) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("qemu agent didn't provide 'name' field"));
goto error;
}
/* Handle interface alias (<ifname>:<alias>) */
ifname = virStringSplit(name, ":", 2);
ifname_s = ifname[0];
iface = virHashLookup(ifaces_store, ifname_s);
/* If the hash table doesn't contain this iface, add it */
if (!iface) {
if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0)
goto error;
if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0)
goto error;
if (virHashAddEntry(ifaces_store, ifname_s,
ifaces_ret[ifaces_count - 1]) < 0)
goto error;
iface = ifaces_ret[ifaces_count - 1];
iface->naddrs = 0;
if (VIR_STRDUP(iface->name, ifname_s) < 0)
goto error;
hwaddr = virJSONValueObjectGetString(tmp_iface, "hardware-address");
if (!hwaddr) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("qemu agent didn't provide"
" 'hardware-address' field"));
goto error;
}
if (VIR_STRDUP(iface->hwaddr, hwaddr) < 0)
goto error;
}
/* Has to be freed for each interface. */
virStringFreeList(ifname);
/* as well as IP address which - moreover -
* can be presented multiple times */
ip_addr_arr = virJSONValueObjectGet(tmp_iface, "ip-addresses");
if (!ip_addr_arr)
continue;
if ((ip_addr_arr_size = virJSONValueArraySize(ip_addr_arr)) < 0)
/* Mmm, empty 'ip-address'? */
goto error;
/* If current iface already exists, continue with the count */
addrs_count = iface->naddrs;
for (j = 0; j < ip_addr_arr_size; j++) {
const char *type, *addr;
virJSONValuePtr ip_addr_obj = virJSONValueArrayGet(ip_addr_arr, j);
virDomainIPAddressPtr ip_addr;
if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0)
goto error;
ip_addr = &iface->addrs[addrs_count - 1];
/* Shouldn't happen but doesn't hurt to check neither */
if (!ip_addr_obj) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("qemu agent reply missing IP addr in array"));
goto error;
}
type = virJSONValueObjectGetString(ip_addr_obj, "ip-address-type");
if (!type) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("qemu agent didn't provide 'ip-address-type'"
" field for interface '%s'"), name);
goto error;
} else if (STREQ(type, "ipv4")) {
ip_addr->type = VIR_IP_ADDR_TYPE_IPV4;
} else if (STREQ(type, "ipv6")) {
ip_addr->type = VIR_IP_ADDR_TYPE_IPV6;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown ip address type '%s'"),
type);
goto error;
}
addr = virJSONValueObjectGetString(ip_addr_obj, "ip-address");
if (!addr) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("qemu agent didn't provide 'ip-address'"
" field for interface '%s'"), name);
goto error;
}
if (VIR_STRDUP(ip_addr->addr, addr) < 0)
goto error;
if (virJSONValueObjectGetNumberUint(ip_addr_obj, "prefix",
&ip_addr->prefix) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("malformed 'prefix' field"));
goto error;
}
}
iface->naddrs = addrs_count;
}
*ifaces = ifaces_ret;
ifaces_ret = NULL;
ret = ifaces_count;
cleanup:
virJSONValueFree(cmd);
virJSONValueFree(reply);
virHashFree(ifaces_store);
return ret;
error:
if (ifaces_ret) {
for (i = 0; i < ifaces_count; i++)
virDomainInterfaceFree(ifaces_ret[i]);
}
VIR_FREE(ifaces_ret);
virStringFreeList(ifname);
goto cleanup;
}

View File

@ -108,4 +108,8 @@ int qemuAgentSetTime(qemuAgentPtr mon,
long long seconds,
unsigned int nseconds,
bool sync);
int qemuAgentGetInterfaces(qemuAgentPtr mon,
virDomainInterfacePtr **ifaces);
#endif /* __QEMU_AGENT_H__ */

View File

@ -171,6 +171,9 @@ static int qemuOpenFileAs(uid_t fallback_uid, gid_t fallback_gid,
const char *path, int oflags,
bool *needUnlink, bool *bypassSecurityDriver);
static int qemuGetDHCPInterfaces(virDomainPtr dom,
virDomainObjPtr vm,
virDomainInterfacePtr **ifaces);
virQEMUDriverPtr qemu_driver = NULL;
@ -19571,6 +19574,177 @@ qemuDomainGetFSInfo(virDomainPtr dom,
return ret;
}
static int
qemuDomainInterfaceAddresses(virDomainPtr dom,
virDomainInterfacePtr **ifaces,
unsigned int source,
unsigned int flags)
{
virQEMUDriverPtr driver = dom->conn->privateData;
qemuDomainObjPrivatePtr priv = NULL;
virDomainObjPtr vm = NULL;
int ret = -1;
virCheckFlags(0, -1);
if (!(vm = qemuDomObjFromDomain(dom)))
goto cleanup;
priv = vm->privateData;
if (virDomainInterfaceAddressesEnsureACL(dom->conn, vm->def) < 0)
goto cleanup;
if (!virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
_("domain is not running"));
goto cleanup;
}
switch (source) {
case VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE:
ret = qemuGetDHCPInterfaces(dom, vm, ifaces);
break;
case VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT:
if (priv->agentError) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("QEMU guest agent is not "
"available due to an error"));
goto cleanup;
}
if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0)
goto cleanup;
if (!virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
_("domain is not running"));
goto endjob;
}
if (!priv->agent) {
virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
_("QEMU guest agent is not configured"));
goto endjob;
}
qemuDomainObjEnterAgent(vm);
ret = qemuAgentGetInterfaces(priv->agent, ifaces);
qemuDomainObjExitAgent(vm);
endjob:
qemuDomainObjEndJob(driver, vm);
break;
default:
virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED,
_("Unknown IP address data source %d"),
source);
break;
}
cleanup:
if (vm)
virObjectUnlock(vm);
return ret;
}
static int
qemuGetDHCPInterfaces(virDomainPtr dom,
virDomainObjPtr vm,
virDomainInterfacePtr **ifaces)
{
int rv = -1;
int n_leases = 0;
size_t i, j;
size_t ifaces_count = 0;
virNetworkPtr network;
char macaddr[VIR_MAC_STRING_BUFLEN];
virDomainInterfacePtr iface = NULL;
virNetworkDHCPLeasePtr *leases = NULL;
virDomainInterfacePtr *ifaces_ret = NULL;
if (!dom->conn->networkDriver ||
!dom->conn->networkDriver->networkGetDHCPLeases) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Network driver does not support DHCP lease query"));
return -1;
}
for (i = 0; i < vm->def->nnets; i++) {
if (vm->def->nets[i]->type != VIR_DOMAIN_NET_TYPE_NETWORK)
continue;
virMacAddrFormat(&(vm->def->nets[i]->mac), macaddr);
network = virNetworkLookupByName(dom->conn,
vm->def->nets[i]->data.network.name);
if ((n_leases = virNetworkGetDHCPLeases(network, macaddr,
&leases, 0)) < 0)
goto error;
if (n_leases) {
if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0)
goto error;
if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0)
goto error;
iface = ifaces_ret[ifaces_count - 1];
/* Assuming each lease corresponds to a separate IP */
iface->naddrs = n_leases;
if (VIR_ALLOC_N(iface->addrs, iface->naddrs) < 0)
goto error;
if (VIR_STRDUP(iface->name, vm->def->nets[i]->ifname) < 0)
goto cleanup;
if (VIR_STRDUP(iface->hwaddr, macaddr) < 0)
goto cleanup;
}
for (j = 0; j < n_leases; j++) {
virNetworkDHCPLeasePtr lease = leases[j];
virDomainIPAddressPtr ip_addr = &iface->addrs[j];
if (VIR_STRDUP(ip_addr->addr, lease->ipaddr) < 0)
goto cleanup;
ip_addr->type = lease->type;
ip_addr->prefix = lease->prefix;
}
for (j = 0; j < n_leases; j++)
virNetworkDHCPLeaseFree(leases[j]);
VIR_FREE(leases);
}
*ifaces = ifaces_ret;
ifaces_ret = NULL;
rv = ifaces_count;
cleanup:
if (leases) {
for (i = 0; i < n_leases; i++)
virNetworkDHCPLeaseFree(leases[i]);
}
VIR_FREE(leases);
return rv;
error:
if (ifaces_ret) {
for (i = 0; i < ifaces_count; i++)
virDomainInterfaceFree(ifaces_ret[i]);
}
VIR_FREE(ifaces_ret);
goto cleanup;
}
static virHypervisorDriver qemuHypervisorDriver = {
.name = QEMU_DRIVER_NAME,
@ -19775,6 +19949,7 @@ static virHypervisorDriver qemuHypervisorDriver = {
.connectGetAllDomainStats = qemuConnectGetAllDomainStats, /* 1.2.8 */
.nodeAllocPages = qemuNodeAllocPages, /* 1.2.9 */
.domainGetFSInfo = qemuDomainGetFSInfo, /* 1.2.11 */
.domainInterfaceAddresses = qemuDomainInterfaceAddresses, /* 1.2.14 */
};

View File

@ -723,6 +723,193 @@ testQemuAgentTimeout(const void *data)
return ret;
}
static const char testQemuAgentGetInterfacesResponse[] =
"{\"return\": "
" ["
" {\"name\":\"eth2\","
" \"hardware-address\":\"52:54:00:36:2a:e5\""
" },"
" {\"name\":\"eth1:0\","
" \"ip-addresses\":"
" ["
" {\"ip-address-type\":\"ipv4\","
" \"ip-address\":\"192.168.10.91\","
" \"prefix\":24"
" },"
" {\"ip-address-type\":\"ipv6\","
" \"ip-address\":\"fe80::fc54:ff:fefe:4c4f\","
" \"prefix\":64"
" }"
" ],"
" \"hardware-address\":\"52:54:00:d3:39:ee\""
" },"
" {\"name\":\"eth0\","
" \"ip-addresses\":"
" ["
" {\"ip-address-type\":\"ipv6\","
" \"ip-address\":\"fe80::5054:ff:fe89:ad35\","
" \"prefix\":64"
" },"
" {\"ip-address-type\":\"ipv4\","
" \"ip-address\":\"192.168.102.142\","
" \"prefix\":24"
" },"
" {\"ip-address-type\":\"ipv4\","
" \"ip-address\":\"192.168.234.152\","
" \"prefix\":16"
" },"
" {\"ip-address-type\":\"ipv6\","
" \"ip-address\":\"fe80::5054:ff:fec3:68bb\","
" \"prefix\":64"
" }"
" ],"
" \"hardware-address\":\"52:54:00:89:ad:35\""
" },"
" {\"name\":\"eth1\","
" \"ip-addresses\":"
" ["
" {\"ip-address-type\":\"ipv4\","
" \"ip-address\":\"192.168.103.83\","
" \"prefix\":32"
" },"
" {\"ip-address-type\":\"ipv6\","
" \"ip-address\":\"fe80::5054:ff:fed3:39ee\","
" \"prefix\":64"
" }"
" ],"
" \"hardware-address\":\"52:54:00:d3:39:ee\""
" },"
" {\"name\":\"lo\","
" \"ip-addresses\":"
" ["
" {\"ip-address-type\":\"ipv4\","
" \"ip-address\":\"127.0.0.1\","
" \"prefix\":8"
" },"
" {\"ip-address-type\":\"ipv6\","
" \"ip-address\":\"::1\","
" \"prefix\":128"
" }"
" ],"
" \"hardware-address\":\"00:00:00:00:00:00\""
" }"
" ]"
"}";
static int
testQemuAgentGetInterfaces(const void *data)
{
virDomainXMLOptionPtr xmlopt = (virDomainXMLOptionPtr)data;
qemuMonitorTestPtr test = qemuMonitorTestNewAgent(xmlopt);
size_t i;
int ret = -1;
int ifaces_count = 0;
virDomainInterfacePtr *ifaces = NULL;
if (!test)
return -1;
if (qemuMonitorTestAddAgentSyncResponse(test) < 0)
goto cleanup;
if (qemuMonitorTestAddItem(test, "guest-network-get-interfaces",
testQemuAgentGetInterfacesResponse) < 0)
goto cleanup;
if ((ifaces_count = qemuAgentGetInterfaces(qemuMonitorTestGetAgent(test),
&ifaces)) < 0)
goto cleanup;
if (ifaces_count != 4) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"expected 4 interfaces, got %d", ret);
goto cleanup;
}
if (STRNEQ(ifaces[0]->name, "eth2") ||
STRNEQ(ifaces[1]->name, "eth1") ||
STRNEQ(ifaces[2]->name, "eth0") ||
STRNEQ(ifaces[3]->name, "lo")) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
"unexpected return values for interface names");
goto cleanup;
}
if (STRNEQ(ifaces[0]->hwaddr, "52:54:00:36:2a:e5") ||
STRNEQ(ifaces[1]->hwaddr, "52:54:00:d3:39:ee") ||
STRNEQ(ifaces[2]->hwaddr, "52:54:00:89:ad:35") ||
STRNEQ(ifaces[3]->hwaddr, "00:00:00:00:00:00")) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
"unexpected return values for MAC addresses");
goto cleanup;
}
if (ifaces[0]->naddrs != 0 ||
ifaces[1]->naddrs != 4 ||
ifaces[2]->naddrs != 4 ||
ifaces[3]->naddrs != 2) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
"unexpected return values for number of IP addresses");
goto cleanup;
}
if (ifaces[1]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV4 ||
ifaces[1]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV6 ||
ifaces[1]->addrs[2].type != VIR_IP_ADDR_TYPE_IPV4 ||
ifaces[1]->addrs[3].type != VIR_IP_ADDR_TYPE_IPV6 ||
ifaces[2]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV6 ||
ifaces[2]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV4 ||
ifaces[2]->addrs[2].type != VIR_IP_ADDR_TYPE_IPV4 ||
ifaces[2]->addrs[3].type != VIR_IP_ADDR_TYPE_IPV6 ||
ifaces[3]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV4 ||
ifaces[3]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV6) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
"unexpected return values for IP address types");
goto cleanup;
}
if (ifaces[1]->addrs[0].prefix != 24 ||
ifaces[1]->addrs[1].prefix != 64 ||
ifaces[1]->addrs[2].prefix != 32 ||
ifaces[1]->addrs[3].prefix != 64 ||
ifaces[2]->addrs[0].prefix != 64 ||
ifaces[2]->addrs[1].prefix != 24 ||
ifaces[2]->addrs[2].prefix != 16 ||
ifaces[2]->addrs[3].prefix != 64 ||
ifaces[3]->addrs[0].prefix != 8 ||
ifaces[3]->addrs[1].prefix != 128) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
"unexpected return values for IP address prefix");
goto cleanup;
}
if (STRNEQ(ifaces[1]->addrs[0].addr, "192.168.10.91") ||
STRNEQ(ifaces[1]->addrs[1].addr, "fe80::fc54:ff:fefe:4c4f") ||
STRNEQ(ifaces[1]->addrs[2].addr, "192.168.103.83") ||
STRNEQ(ifaces[1]->addrs[3].addr, "fe80::5054:ff:fed3:39ee") ||
STRNEQ(ifaces[2]->addrs[0].addr, "fe80::5054:ff:fe89:ad35") ||
STRNEQ(ifaces[2]->addrs[1].addr, "192.168.102.142") ||
STRNEQ(ifaces[2]->addrs[2].addr, "192.168.234.152") ||
STRNEQ(ifaces[2]->addrs[3].addr, "fe80::5054:ff:fec3:68bb") ||
STRNEQ(ifaces[3]->addrs[0].addr, "127.0.0.1") ||
STRNEQ(ifaces[3]->addrs[1].addr, "::1")) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
"unexpected return values for IP address values");
goto cleanup;
}
ret = 0;
cleanup:
qemuMonitorTestFree(test);
if (ifaces) {
for (i = 0; i < ifaces_count; i++)
virDomainInterfaceFree(ifaces[i]);
}
VIR_FREE(ifaces);
return ret;
}
static int
mymain(void)
@ -753,6 +940,7 @@ mymain(void)
DO_TEST(Shutdown);
DO_TEST(CPU);
DO_TEST(ArbitraryCommand);
DO_TEST(GetInterfaces);
DO_TEST(Timeout); /* Timeout should always be called last */