[libvirt] [PATCH] Add virsh commands for virInterface* functions.

I think this is complete (can't really try it out until the test driver is running), but have a few questions about it: 1) Does the command set look complete? 2) Do you agree with the naming (using "if-", and following the libvirt convention for if-create/if-destroy, rather than the fedora convention (if-up/if-down)? 3) I'm all for eliminating unnecessary copy/paste in code, but the way that cmdNetworkEdit() and cmdPoolEdit() are created (by awking/seding virsh.c to create a modified version of the cmdEdit function that lives in a separate .c which is #included by virsh.c) seems just a bit convoluted to me. I've made it one step worse for cmdInterfaceEdit because virInterfaceDefineXML has an extra argument (flags) that virDomainDefineXML doesn't. What are your opinions? Should I continue on in the tradition for consistency's sake (as I've done in this patch), or manually put cmdInterfaceEdit() directly into virsh.c? --- src/Makefile.am | 18 +++- src/virsh.c | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 414 insertions(+), 2 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index d4f7ea1..7601611 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -585,7 +585,7 @@ virsh_LDADD = \ ../gnulib/lib/libgnu.la \ $(VIRSH_LIBS) virsh_CFLAGS = $(COVERAGE_CFLAGS) $(READLINE_CFLAGS) $(NUMACTL_CFLAGS) -BUILT_SOURCES = virsh-net-edit.c virsh-pool-edit.c libvirt.syms +BUILT_SOURCES = virsh-net-edit.c virsh-if-edit.c virsh-pool-edit.c libvirt.syms virsh-net-edit.c: virsh.c Makefile.am rm -f $@-tmp @@ -602,6 +602,22 @@ virsh-net-edit.c: virsh.c Makefile.am rm -f $@ mv $@-tmp $@ +virsh-if-edit.c: virsh.c Makefile.am + rm -f $@-tmp + echo '/* Automatically generated from: $^ */' > $@-tmp + echo 'static int' >> $@-tmp + awk '/^cmdEdit/, /^}/' $< \ + | sed -e 's/domain/interface/g' \ + -e 's/Domain/Interface/g' \ + -e 's/cmdEdit/cmdInterfaceEdit/g' \ + -e 's/dom/iface/g' \ + -e 's/int flags.*/int flags = 0;/g' \ + -e 's/DefineXML\(.*\))/DefineXML\1, 0)/' \ + >> $@-tmp + chmod a-w $@-tmp + rm -f $@ + mv $@-tmp $@ + virsh-pool-edit.c: virsh.c Makefile.am rm -f $@-tmp echo '/* Automatically generated from: $^ */' > $@-tmp diff --git a/src/virsh.c b/src/virsh.c index f5248d9..6fd090c 100644 --- a/src/virsh.c +++ b/src/virsh.c @@ -228,6 +228,7 @@ static int vshCommandOptBool(const vshCmd *cmd, const char *name); #define VSH_BYID (1 << 1) #define VSH_BYUUID (1 << 2) #define VSH_BYNAME (1 << 3) +#define VSH_BYMAC (1 << 4) static virDomainPtr vshCommandOptDomainBy(vshControl *ctl, const vshCmd *cmd, char **name, int flag); @@ -244,6 +245,14 @@ static virNetworkPtr vshCommandOptNetworkBy(vshControl *ctl, const vshCmd *cmd, vshCommandOptNetworkBy(_ctl, _cmd, _name, \ VSH_BYUUID|VSH_BYNAME) +static virInterfacePtr vshCommandOptInterfaceBy(vshControl *ctl, const vshCmd *cmd, + char **name, int flag); + +/* default is lookup by Name and MAC */ +#define vshCommandOptInterface(_ctl, _cmd, _name) \ + vshCommandOptInterfaceBy(_ctl, _cmd, _name, \ + VSH_BYMAC|VSH_BYNAME) + static virStoragePoolPtr vshCommandOptPoolBy(vshControl *ctl, const vshCmd *cmd, const char *optname, char **name, int flag); @@ -2955,6 +2964,325 @@ cmdNetworkUuid(vshControl *ctl, const vshCmd *cmd) } +/**************************************************************************/ +/* + * "if-list" command + */ +static const vshCmdInfo info_interface_list[] = { + {"help", gettext_noop("list physical host interfaces")}, + {"desc", gettext_noop("Returns list of physical host interfaces.")}, + {NULL, NULL} +}; + +static int +cmdInterfaceList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED) +{ + int ifCount = 0, i; + char **ifNames = NULL; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + ifCount = virConnectNumOfInterfaces(ctl->conn); + if (ifCount < 0) { + vshError(ctl, FALSE, "%s", _("Failed to list interfaces")); + return FALSE; + } + if (ifCount) { + ifNames = vshMalloc(ctl, sizeof(char *) * ifCount); + + if ((ifCount = virConnectListInterfaces(ctl->conn, + ifNames, ifCount)) < 0) { + vshError(ctl, FALSE, "%s", _("Failed to list interfaces")); + free(ifNames); + return FALSE; + } + + qsort(&ifNames[0], ifCount, sizeof(char *), namesorter); + } + + vshPrintExtra(ctl, "%-20s %-30s\n", _("Name"), _("MAC Address")); + vshPrintExtra(ctl, "--------------------------------------------------\n"); + + for (i = 0; i < ifCount; i++) { + virInterfacePtr iface = virInterfaceLookupByName(ctl->conn, ifNames[i]); + + /* this kind of work with interfaces is not atomic operation */ + if (!iface) { + free(ifNames[i]); + continue; + } + + vshPrint(ctl, "%-20s %-30s\n", + virInterfaceGetName(iface), + virInterfaceGetMACString(iface)); + virInterfaceFree(iface); + free(ifNames[i]); + } + free(ifNames); + return TRUE; +} + +/* + * "if-name" command + */ +static const vshCmdInfo info_interface_name[] = { + {"help", gettext_noop("convert an interface MAC address to interface name")}, + {"desc", ""}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_name[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface mac")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceName(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, + VSH_BYMAC))) + return FALSE; + + vshPrint(ctl, "%s\n", virInterfaceGetName(iface)); + virInterfaceFree(iface); + return TRUE; +} + +/* + * "if-mac" command + */ +static const vshCmdInfo info_interface_mac[] = { + {"help", gettext_noop("convert an interface name to interface MAC address")}, + {"desc", ""}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_mac[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceMAC(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, + VSH_BYNAME))) + return FALSE; + + vshPrint(ctl, "%s\n", virInterfaceGetMACString(iface)); + virInterfaceFree(iface); + return TRUE; +} + +/* + * "if-dumpxml" command + */ +static const vshCmdInfo info_interface_dumpxml[] = { + {"help", gettext_noop("interface information in XML")}, + {"desc", gettext_noop("Output the physical host interface information as an XML dump to stdout.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_dumpxml[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceDumpXML(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + int ret = TRUE; + char *dump; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(iface = vshCommandOptInterface(ctl, cmd, NULL))) + return FALSE; + + dump = virInterfaceGetXMLDesc(iface, 0); + if (dump != NULL) { + printf("%s", dump); + free(dump); + } else { + ret = FALSE; + } + + virInterfaceFree(iface); + return ret; +} + +/* + * "if-define" command + */ +static const vshCmdInfo info_interface_define[] = { + {"help", gettext_noop("define (but don't start) a physical host interface from an XML file")}, + {"desc", gettext_noop("Define a physical host interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_define[] = { + {"file", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("file containing an XML interface description")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceDefine(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr interface; + char *from; + int found; + int ret = TRUE; + char *buffer; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + from = vshCommandOptString(cmd, "file", &found); + if (!found) + return FALSE; + + if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) + return FALSE; + + interface = virInterfaceDefineXML(ctl->conn, buffer, 0); + free (buffer); + + if (interface != NULL) { + vshPrint(ctl, _("Interface %s defined from %s\n"), + virInterfaceGetName(interface), from); + } else { + vshError(ctl, FALSE, _("Failed to define interface from %s"), from); + ret = FALSE; + } + return ret; +} + +/* + * "if-undefine" command + */ +static const vshCmdInfo info_interface_undefine[] = { + {"help", gettext_noop("undefine a physical host interface (remove it from configuration)")}, + {"desc", gettext_noop("undefine an interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_undefine[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceUndefine(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + int ret = TRUE; + char *name; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(iface = vshCommandOptInterface(ctl, cmd, &name))) + return FALSE; + + if (virInterfaceUndefine(iface) == 0) { + vshPrint(ctl, _("Interface %s undefined\n"), name); + } else { + vshError(ctl, FALSE, _("Failed to undefine interface %s"), name); + ret = FALSE; + } + + virInterfaceFree(iface); + return ret; +} + +/* + * "if-create" command + */ +static const vshCmdInfo info_interface_create[] = { + {"help", gettext_noop("create a physical host interface (enable it / \"if-up\")")}, + {"desc", gettext_noop("create a physical host interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_create[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceCreate(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + int ret = TRUE; + char *name; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(iface = vshCommandOptInterface(ctl, cmd, &name))) + return FALSE; + + if (virInterfaceCreate(iface, 0) == 0) { + vshPrint(ctl, _("Interface %s created\n"), name); + } else { + vshError(ctl, FALSE, _("Failed to create interface %s"), name); + ret = FALSE; + } + + virInterfaceFree(iface); + return ret; +} + +/* + * "if-destroy" command + */ +static const vshCmdInfo info_interface_destroy[] = { + {"help", gettext_noop("destroy a physical host interface (disable it / \"if-down\")")}, + {"desc", gettext_noop("destroy a physical host interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_destroy[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceDestroy(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + int ret = TRUE; + char *name; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(iface = vshCommandOptInterface(ctl, cmd, &name))) + return FALSE; + + if (virInterfaceDestroy(iface, 0) == 0) { + vshPrint(ctl, _("Interface %s destroyed\n"), name); + } else { + vshError(ctl, FALSE, _("Failed to destroy interface %s"), name); + ret = FALSE; + } + + virInterfaceFree(iface); + return ret; +} + +/**************************************************************************/ /* * "pool-autostart" command */ @@ -6059,7 +6387,7 @@ static const vshCmdOptDef opts_edit[] = { }; /* This function also acts as a template to generate cmdNetworkEdit - * and cmdPoolEdit functions (below) using a sed script in the Makefile. + * cmdInterfaceEdit and cmdPoolEdit functions (below) using a sed script in the Makefile. */ static int cmdEdit (vshControl *ctl, const vshCmd *cmd) @@ -6165,6 +6493,23 @@ static const vshCmdOptDef opts_network_edit[] = { #include "virsh-net-edit.c" /* + * "if-edit" command + */ +static const vshCmdInfo info_interface_edit[] = { + {"help", gettext_noop("edit XML configuration for a physical host interface")}, + {"desc", gettext_noop("Edit the XML configuration for a physical host interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_edit[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +/* This is generated from this file by a sed script in the Makefile. */ +#include "virsh-if-edit.c" + +/* * "pool-edit" command */ static const vshCmdInfo info_pool_edit[] = { @@ -6247,6 +6592,17 @@ static const vshCmdDef commands[] = { {"net-start", cmdNetworkStart, opts_network_start, info_network_start}, {"net-undefine", cmdNetworkUndefine, opts_network_undefine, info_network_undefine}, {"net-uuid", cmdNetworkUuid, opts_network_uuid, info_network_uuid}, + + {"if-list", cmdInterfaceList, NULL, info_interface_list}, + {"if-name", cmdInterfaceName, opts_interface_name, info_interface_name}, + {"if-mac", cmdInterfaceMAC, opts_interface_mac, info_interface_mac}, + {"if-dumpxml", cmdInterfaceDumpXML, opts_interface_dumpxml, info_interface_dumpxml}, + {"if-define", cmdInterfaceDefine, opts_interface_define, info_interface_define}, + {"if-undefine", cmdInterfaceUndefine, opts_interface_undefine, info_interface_undefine}, + {"if-edit", cmdInterfaceEdit, opts_interface_edit, info_interface_edit}, + {"if-create", cmdInterfaceCreate, opts_interface_create, info_interface_create}, + {"if-destroy", cmdInterfaceDestroy, opts_interface_destroy, info_interface_destroy}, + {"nodeinfo", cmdNodeinfo, NULL, info_nodeinfo}, {"nodedev-list", cmdNodeListDevices, opts_node_list_devices, info_node_list_devices}, @@ -6698,6 +7054,46 @@ vshCommandOptNetworkBy(vshControl *ctl, const vshCmd *cmd, return network; } +static virInterfacePtr +vshCommandOptInterfaceBy(vshControl *ctl, const vshCmd *cmd, + char **name, int flag) +{ + virInterfacePtr iface = NULL; + char *n; + const char *optname = "interface"; + if (!cmd_has_option (ctl, cmd, optname)) + return NULL; + + if (!(n = vshCommandOptString(cmd, optname, NULL))) { + vshError(ctl, FALSE, "%s", _("undefined interface identifier")); + return NULL; + } + + vshDebug(ctl, 5, "%s: found option <%s>: %s\n", + cmd->def->name, optname, n); + + if (name) + *name = n; + + /* try it by MAC */ + if ((iface == NULL) && (flag & VSH_BYMAC)) { + vshDebug(ctl, 5, "%s: <%s> trying as interface MAC\n", + cmd->def->name, optname); + iface = virInterfaceLookupByMACString(ctl->conn, n); + } + /* try it by NAME */ + if ((iface == NULL) && (flag & VSH_BYNAME)) { + vshDebug(ctl, 5, "%s: <%s> trying as interface NAME\n", + cmd->def->name, optname); + iface = virInterfaceLookupByName(ctl->conn, n); + } + + if (!iface) + vshError(ctl, FALSE, _("failed to get interface '%s'"), n); + + return iface; +} + static virStoragePoolPtr vshCommandOptPoolBy(vshControl *ctl, const vshCmd *cmd, const char *optname, char **name, int flag) -- 1.6.0.6

On Fri, Jun 12, 2009 at 10:25:19AM -0400, Laine Stump wrote:
I think this is complete (can't really try it out until the test driver is running), but have a few questions about it:
1) Does the command set look complete?
2) Do you agree with the naming (using "if-", and following the libvirt convention for if-create/if-destroy, rather than the fedora convention (if-up/if-down)?
Sigh. I wish we hadn't called our original commands 'net-XXX' as it is a rather misleading name. I think perhaps I'd go slightly longer and suggest 'iface-XXX' as the prefix.
3) I'm all for eliminating unnecessary copy/paste in code, but the way that cmdNetworkEdit() and cmdPoolEdit() are created (by awking/seding virsh.c to create a modified version of the cmdEdit function that lives in a separate .c which is #included by virsh.c) seems just a bit convoluted to me. I've made it one step worse for cmdInterfaceEdit because virInterfaceDefineXML has an extra argument (flags) that virDomainDefineXML doesn't. What are your opinions? Should I continue on in the tradition for consistency's sake (as I've done in this patch), or manually put cmdInterfaceEdit() directly into virsh.c?
Personally I think this auto-generation of network/pool edit commands is overkill, and has actually caused bugs in the past.
--- src/Makefile.am | 18 +++- src/virsh.c | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 414 insertions(+), 2 deletions(-)
diff --git a/src/Makefile.am b/src/Makefile.am index d4f7ea1..7601611 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -585,7 +585,7 @@ virsh_LDADD = \ ../gnulib/lib/libgnu.la \ $(VIRSH_LIBS) virsh_CFLAGS = $(COVERAGE_CFLAGS) $(READLINE_CFLAGS) $(NUMACTL_CFLAGS) -BUILT_SOURCES = virsh-net-edit.c virsh-pool-edit.c libvirt.syms +BUILT_SOURCES = virsh-net-edit.c virsh-if-edit.c virsh-pool-edit.c libvirt.syms
virsh-net-edit.c: virsh.c Makefile.am rm -f $@-tmp @@ -602,6 +602,22 @@ virsh-net-edit.c: virsh.c Makefile.am rm -f $@ mv $@-tmp $@
+virsh-if-edit.c: virsh.c Makefile.am + rm -f $@-tmp + echo '/* Automatically generated from: $^ */' > $@-tmp + echo 'static int' >> $@-tmp + awk '/^cmdEdit/, /^}/' $< \ + | sed -e 's/domain/interface/g' \ + -e 's/Domain/Interface/g' \ + -e 's/cmdEdit/cmdInterfaceEdit/g' \ + -e 's/dom/iface/g' \ + -e 's/int flags.*/int flags = 0;/g' \ + -e 's/DefineXML\(.*\))/DefineXML\1, 0)/' \ + >> $@-tmp + chmod a-w $@-tmp + rm -f $@ + mv $@-tmp $@ + virsh-pool-edit.c: virsh.c Makefile.am rm -f $@-tmp echo '/* Automatically generated from: $^ */' > $@-tmp diff --git a/src/virsh.c b/src/virsh.c index f5248d9..6fd090c 100644 --- a/src/virsh.c +++ b/src/virsh.c @@ -228,6 +228,7 @@ static int vshCommandOptBool(const vshCmd *cmd, const char *name); #define VSH_BYID (1 << 1) #define VSH_BYUUID (1 << 2) #define VSH_BYNAME (1 << 3) +#define VSH_BYMAC (1 << 4)
static virDomainPtr vshCommandOptDomainBy(vshControl *ctl, const vshCmd *cmd, char **name, int flag); @@ -244,6 +245,14 @@ static virNetworkPtr vshCommandOptNetworkBy(vshControl *ctl, const vshCmd *cmd, vshCommandOptNetworkBy(_ctl, _cmd, _name, \ VSH_BYUUID|VSH_BYNAME)
+static virInterfacePtr vshCommandOptInterfaceBy(vshControl *ctl, const vshCmd *cmd, + char **name, int flag); + +/* default is lookup by Name and MAC */ +#define vshCommandOptInterface(_ctl, _cmd, _name) \ + vshCommandOptInterfaceBy(_ctl, _cmd, _name, \ + VSH_BYMAC|VSH_BYNAME) + static virStoragePoolPtr vshCommandOptPoolBy(vshControl *ctl, const vshCmd *cmd, const char *optname, char **name, int flag);
@@ -2955,6 +2964,325 @@ cmdNetworkUuid(vshControl *ctl, const vshCmd *cmd) }
+/**************************************************************************/ +/* + * "if-list" command + */ +static const vshCmdInfo info_interface_list[] = { + {"help", gettext_noop("list physical host interfaces")}, + {"desc", gettext_noop("Returns list of physical host interfaces.")}, + {NULL, NULL} +}; + +static int +cmdInterfaceList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED) +{ + int ifCount = 0, i; + char **ifNames = NULL; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + ifCount = virConnectNumOfInterfaces(ctl->conn); + if (ifCount < 0) { + vshError(ctl, FALSE, "%s", _("Failed to list interfaces")); + return FALSE; + } + if (ifCount) { + ifNames = vshMalloc(ctl, sizeof(char *) * ifCount); + + if ((ifCount = virConnectListInterfaces(ctl->conn, + ifNames, ifCount)) < 0) { + vshError(ctl, FALSE, "%s", _("Failed to list interfaces")); + free(ifNames); + return FALSE; + } + + qsort(&ifNames[0], ifCount, sizeof(char *), namesorter); + } + + vshPrintExtra(ctl, "%-20s %-30s\n", _("Name"), _("MAC Address")); + vshPrintExtra(ctl, "--------------------------------------------------\n"); + + for (i = 0; i < ifCount; i++) { + virInterfacePtr iface = virInterfaceLookupByName(ctl->conn, ifNames[i]); + + /* this kind of work with interfaces is not atomic operation */ + if (!iface) { + free(ifNames[i]); + continue; + } + + vshPrint(ctl, "%-20s %-30s\n", + virInterfaceGetName(iface), + virInterfaceGetMACString(iface)); + virInterfaceFree(iface); + free(ifNames[i]); + } + free(ifNames); + return TRUE; +}
This one should also take the flags --inactive / --all to allow selection of offline NICs vs online NICs. Which reminds me that the current public APIs are missing the equivalent for listing inactive interfaces. eg, virConnectNumOfDefinedInterfaces and virConnectListDefinedInterfaces
+ +/* + * "if-name" command + */ +static const vshCmdInfo info_interface_name[] = { + {"help", gettext_noop("convert an interface MAC address to interface name")}, + {"desc", ""}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_name[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface mac")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceName(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, + VSH_BYMAC))) + return FALSE; + + vshPrint(ctl, "%s\n", virInterfaceGetName(iface)); + virInterfaceFree(iface); + return TRUE; +} + +/* + * "if-mac" command + */ +static const vshCmdInfo info_interface_mac[] = { + {"help", gettext_noop("convert an interface name to interface MAC address")}, + {"desc", ""}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_mac[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceMAC(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, + VSH_BYNAME))) + return FALSE; + + vshPrint(ctl, "%s\n", virInterfaceGetMACString(iface)); + virInterfaceFree(iface); + return TRUE; +} + +/* + * "if-dumpxml" command + */ +static const vshCmdInfo info_interface_dumpxml[] = { + {"help", gettext_noop("interface information in XML")}, + {"desc", gettext_noop("Output the physical host interface information as an XML dump to stdout.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_dumpxml[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceDumpXML(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + int ret = TRUE; + char *dump; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(iface = vshCommandOptInterface(ctl, cmd, NULL))) + return FALSE; + + dump = virInterfaceGetXMLDesc(iface, 0); + if (dump != NULL) { + printf("%s", dump); + free(dump); + } else { + ret = FALSE; + } + + virInterfaceFree(iface); + return ret; +} + +/* + * "if-define" command + */ +static const vshCmdInfo info_interface_define[] = { + {"help", gettext_noop("define (but don't start) a physical host interface from an XML file")}, + {"desc", gettext_noop("Define a physical host interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_define[] = { + {"file", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("file containing an XML interface description")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceDefine(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr interface; + char *from; + int found; + int ret = TRUE; + char *buffer; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + from = vshCommandOptString(cmd, "file", &found); + if (!found) + return FALSE; + + if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) + return FALSE; + + interface = virInterfaceDefineXML(ctl->conn, buffer, 0); + free (buffer); + + if (interface != NULL) { + vshPrint(ctl, _("Interface %s defined from %s\n"), + virInterfaceGetName(interface), from); + } else { + vshError(ctl, FALSE, _("Failed to define interface from %s"), from); + ret = FALSE; + } + return ret; +} + +/* + * "if-undefine" command + */ +static const vshCmdInfo info_interface_undefine[] = { + {"help", gettext_noop("undefine a physical host interface (remove it from configuration)")}, + {"desc", gettext_noop("undefine an interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_undefine[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceUndefine(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + int ret = TRUE; + char *name; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(iface = vshCommandOptInterface(ctl, cmd, &name))) + return FALSE; + + if (virInterfaceUndefine(iface) == 0) { + vshPrint(ctl, _("Interface %s undefined\n"), name); + } else { + vshError(ctl, FALSE, _("Failed to undefine interface %s"), name); + ret = FALSE; + } + + virInterfaceFree(iface); + return ret; +} + +/* + * "if-create" command + */ +static const vshCmdInfo info_interface_create[] = { + {"help", gettext_noop("create a physical host interface (enable it / \"if-up\")")}, + {"desc", gettext_noop("create a physical host interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_create[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceCreate(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + int ret = TRUE; + char *name; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(iface = vshCommandOptInterface(ctl, cmd, &name))) + return FALSE; + + if (virInterfaceCreate(iface, 0) == 0) { + vshPrint(ctl, _("Interface %s created\n"), name); + } else { + vshError(ctl, FALSE, _("Failed to create interface %s"), name); + ret = FALSE; + } + + virInterfaceFree(iface); + return ret; +}
This command should be called 'start' for consistency with other commands. 'create' is the command name we use for creating a transient object, rather than starting a persistent object. Regards, Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On 06/15/2009 03:26 PM, Daniel P. Berrange wrote:
On Fri, Jun 12, 2009 at 10:25:19AM -0400, Laine Stump wrote:
I think this is complete (can't really try it out until the test driver is running), but have a few questions about it:
1) Does the command set look complete?
2) Do you agree with the naming (using "if-", and following the libvirt convention for if-create/if-destroy, rather than the fedora convention (if-up/if-down)?
Sigh. I wish we hadn't called our original commands 'net-XXX' as it is a rather misleading name.
I think perhaps I'd go slightly longer and suggest 'iface-XXX' as the prefix.
Okay.
3) I'm all for eliminating unnecessary copy/paste in code, but the way that cmdNetworkEdit() and cmdPoolEdit() are created (by awking/seding virsh.c to create a modified version of the cmdEdit function that lives in a separate .c which is #included by virsh.c) seems just a bit convoluted to me. I've made it one step worse for cmdInterfaceEdit because virInterfaceDefineXML has an extra argument (flags) that virDomainDefineXML doesn't. What are your opinions? Should I continue on in the tradition for consistency's sake (as I've done in this patch), or manually put cmdInterfaceEdit() directly into virsh.c?
Personally I think this auto-generation of network/pool edit commands is overkill, and has actually caused bugs in the past.
So it's not just me. I'll pull the generated code into virsh.c then.
--- src/Makefile.am | 18 +++- src/virsh.c | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 414 insertions(+), 2 deletions(-)
diff --git a/src/Makefile.am b/src/Makefile.am index d4f7ea1..7601611 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -585,7 +585,7 @@ virsh_LDADD = \ ../gnulib/lib/libgnu.la \ $(VIRSH_LIBS) virsh_CFLAGS = $(COVERAGE_CFLAGS) $(READLINE_CFLAGS) $(NUMACTL_CFLAGS) -BUILT_SOURCES = virsh-net-edit.c virsh-pool-edit.c libvirt.syms +BUILT_SOURCES = virsh-net-edit.c virsh-if-edit.c virsh-pool-edit.c libvirt.syms
virsh-net-edit.c: virsh.c Makefile.am rm -f $@-tmp @@ -602,6 +602,22 @@ virsh-net-edit.c: virsh.c Makefile.am rm -f $@ mv $@-tmp $@
+virsh-if-edit.c: virsh.c Makefile.am + rm -f $@-tmp + echo '/* Automatically generated from: $^ */' > $@-tmp + echo 'static int' >> $@-tmp + awk '/^cmdEdit/, /^}/' $< \ + | sed -e 's/domain/interface/g' \ + -e 's/Domain/Interface/g' \ + -e 's/cmdEdit/cmdInterfaceEdit/g' \ + -e 's/dom/iface/g' \ + -e 's/int flags.*/int flags = 0;/g' \ + -e 's/DefineXML\(.*\))/DefineXML\1, 0)/' \ + >> $@-tmp + chmod a-w $@-tmp + rm -f $@ + mv $@-tmp $@ + virsh-pool-edit.c: virsh.c Makefile.am rm -f $@-tmp echo '/* Automatically generated from: $^ */' > $@-tmp diff --git a/src/virsh.c b/src/virsh.c index f5248d9..6fd090c 100644 --- a/src/virsh.c +++ b/src/virsh.c @@ -228,6 +228,7 @@ static int vshCommandOptBool(const vshCmd *cmd, const char *name); #define VSH_BYID (1 << 1) #define VSH_BYUUID (1 << 2) #define VSH_BYNAME (1 << 3) +#define VSH_BYMAC (1 << 4)
static virDomainPtr vshCommandOptDomainBy(vshControl *ctl, const vshCmd *cmd, char **name, int flag); @@ -244,6 +245,14 @@ static virNetworkPtr vshCommandOptNetworkBy(vshControl *ctl, const vshCmd *cmd, vshCommandOptNetworkBy(_ctl, _cmd, _name, \ VSH_BYUUID|VSH_BYNAME)
+static virInterfacePtr vshCommandOptInterfaceBy(vshControl *ctl, const vshCmd *cmd, + char **name, int flag); + +/* default is lookup by Name and MAC */ +#define vshCommandOptInterface(_ctl, _cmd, _name) \ + vshCommandOptInterfaceBy(_ctl, _cmd, _name, \ + VSH_BYMAC|VSH_BYNAME) + static virStoragePoolPtr vshCommandOptPoolBy(vshControl *ctl, const vshCmd *cmd, const char *optname, char **name, int flag);
@@ -2955,6 +2964,325 @@ cmdNetworkUuid(vshControl *ctl, const vshCmd *cmd) }
+/**************************************************************************/ +/* + * "if-list" command + */ +static const vshCmdInfo info_interface_list[] = { + {"help", gettext_noop("list physical host interfaces")}, + {"desc", gettext_noop("Returns list of physical host interfaces.")}, + {NULL, NULL} +}; + +static int +cmdInterfaceList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED) +{ + int ifCount = 0, i; + char **ifNames = NULL; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + ifCount = virConnectNumOfInterfaces(ctl->conn); + if (ifCount < 0) { + vshError(ctl, FALSE, "%s", _("Failed to list interfaces")); + return FALSE; + } + if (ifCount) { + ifNames = vshMalloc(ctl, sizeof(char *) * ifCount); + + if ((ifCount = virConnectListInterfaces(ctl->conn, + ifNames, ifCount)) < 0) { + vshError(ctl, FALSE, "%s", _("Failed to list interfaces")); + free(ifNames); + return FALSE; + } + + qsort(&ifNames[0], ifCount, sizeof(char *), namesorter); + } + + vshPrintExtra(ctl, "%-20s %-30s\n", _("Name"), _("MAC Address")); + vshPrintExtra(ctl, "--------------------------------------------------\n"); + + for (i = 0; i < ifCount; i++) { + virInterfacePtr iface = virInterfaceLookupByName(ctl->conn, ifNames[i]); + + /* this kind of work with interfaces is not atomic operation */ + if (!iface) { + free(ifNames[i]); + continue; + } + + vshPrint(ctl, "%-20s %-30s\n", + virInterfaceGetName(iface), + virInterfaceGetMACString(iface)); + virInterfaceFree(iface); + free(ifNames[i]); + } + free(ifNames); + return TRUE; +}
This one should also take the flags --inactive / --all to allow selection of offline NICs vs online NICs. Which reminds me that the current public APIs are missing the equivalent for listing inactive interfaces. eg, virConnectNumOfDefinedInterfaces and virConnectListDefinedInterfaces
They're missing it because netcf is missing it, and I've so far just been implementing a conduit to channel netcf functionality through libvirt. The problem is that netcf currently interacts with the host OS in only two ways: 1) modifying the ifcfg-XXX config files, and 2) exec'ing the if-up and if-down scripts, and has nothing to get current state of the interface. I talked to lutter about this and he agrees that this would be useful functionality to add to netcf, it just needs a person to do it (I'll even volunteer, just not this week!). Possibly we could add a flag now to the virConnectListInterfaces() API function in libvirt, in anticipation of a similar flag being added to netcf's ncf_list_interfaces() function. Is it too late to modify the API? (it is in a minor release, but nobody has used it yet. I guess the problem would be if someone happened to run libvirt 0.6.4 with an application that *did* use the new functions.). (Now I'm wondering why, when we thought of adding flags to all those other API functions, we didn't think to add flags to this one :-( ) Alternately (or maybe additionally) we can/should add a new function to return current state/statistics for interfaces. Along with throughput/packet counts, that API could return flags for the interface (all the flags shown in the output of ifconfig) as well as the current IP address(es). The virsh command could then get the list of interfaces, and iterate through looking at the state of each, eliminating the interfaces that weren't UP (or DOWN).
+ +/* + * "if-name" command + */ +static const vshCmdInfo info_interface_name[] = { + {"help", gettext_noop("convert an interface MAC address to interface name")}, + {"desc", ""}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_name[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface mac")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceName(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, + VSH_BYMAC))) + return FALSE; + + vshPrint(ctl, "%s\n", virInterfaceGetName(iface)); + virInterfaceFree(iface); + return TRUE; +} + +/* + * "if-mac" command + */ +static const vshCmdInfo info_interface_mac[] = { + {"help", gettext_noop("convert an interface name to interface MAC address")}, + {"desc", ""}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_mac[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceMAC(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, + VSH_BYNAME))) + return FALSE; + + vshPrint(ctl, "%s\n", virInterfaceGetMACString(iface)); + virInterfaceFree(iface); + return TRUE; +} + +/* + * "if-dumpxml" command + */ +static const vshCmdInfo info_interface_dumpxml[] = { + {"help", gettext_noop("interface information in XML")}, + {"desc", gettext_noop("Output the physical host interface information as an XML dump to stdout.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_dumpxml[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceDumpXML(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + int ret = TRUE; + char *dump; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(iface = vshCommandOptInterface(ctl, cmd, NULL))) + return FALSE; + + dump = virInterfaceGetXMLDesc(iface, 0); + if (dump != NULL) { + printf("%s", dump); + free(dump); + } else { + ret = FALSE; + } + + virInterfaceFree(iface); + return ret; +} + +/* + * "if-define" command + */ +static const vshCmdInfo info_interface_define[] = { + {"help", gettext_noop("define (but don't start) a physical host interface from an XML file")}, + {"desc", gettext_noop("Define a physical host interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_define[] = { + {"file", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("file containing an XML interface description")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceDefine(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr interface; + char *from; + int found; + int ret = TRUE; + char *buffer; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + from = vshCommandOptString(cmd, "file", &found); + if (!found) + return FALSE; + + if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) + return FALSE; + + interface = virInterfaceDefineXML(ctl->conn, buffer, 0); + free (buffer); + + if (interface != NULL) { + vshPrint(ctl, _("Interface %s defined from %s\n"), + virInterfaceGetName(interface), from); + } else { + vshError(ctl, FALSE, _("Failed to define interface from %s"), from); + ret = FALSE; + } + return ret; +} + +/* + * "if-undefine" command + */ +static const vshCmdInfo info_interface_undefine[] = { + {"help", gettext_noop("undefine a physical host interface (remove it from configuration)")}, + {"desc", gettext_noop("undefine an interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_undefine[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceUndefine(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + int ret = TRUE; + char *name; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(iface = vshCommandOptInterface(ctl, cmd, &name))) + return FALSE; + + if (virInterfaceUndefine(iface) == 0) { + vshPrint(ctl, _("Interface %s undefined\n"), name); + } else { + vshError(ctl, FALSE, _("Failed to undefine interface %s"), name); + ret = FALSE; + } + + virInterfaceFree(iface); + return ret; +} + +/* + * "if-create" command + */ +static const vshCmdInfo info_interface_create[] = { + {"help", gettext_noop("create a physical host interface (enable it / \"if-up\")")}, + {"desc", gettext_noop("create a physical host interface.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_interface_create[] = { + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("interface name or MAC address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdInterfaceCreate(vshControl *ctl, const vshCmd *cmd) +{ + virInterfacePtr iface; + int ret = TRUE; + char *name; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(iface = vshCommandOptInterface(ctl, cmd, &name))) + return FALSE; + + if (virInterfaceCreate(iface, 0) == 0) { + vshPrint(ctl, _("Interface %s created\n"), name); + } else { + vshError(ctl, FALSE, _("Failed to create interface %s"), name); + ret = FALSE; + } + + virInterfaceFree(iface); + return ret; +}
This command should be called 'start' for consistency with other commands.
'create' is the command name we use for creating a transient object, rather than starting a persistent object.
I see what you mean - "net-start" hooks up to virNetworkCreate(), so virInterfaceCreate() should be connected to "iface-start". It's a bit confusing, though, that the virsh commands don't match the libvirt API names. (I guess at least they should *consistently* mismatch! ;-)

On Mon, Jun 15, 2009 at 10:56:41PM -0400, Laine Stump wrote:
+ vshPrintExtra(ctl, "%-20s %-30s\n", _("Name"), _("MAC Address")); + vshPrintExtra(ctl, "--------------------------------------------------\n"); + + for (i = 0; i < ifCount; i++) { + virInterfacePtr iface = virInterfaceLookupByName(ctl->conn, ifNames[i]); + + /* this kind of work with interfaces is not atomic operation */ + if (!iface) { + free(ifNames[i]); + continue; + } + + vshPrint(ctl, "%-20s %-30s\n", + virInterfaceGetName(iface), + virInterfaceGetMACString(iface)); + virInterfaceFree(iface); + free(ifNames[i]); + } + free(ifNames); + return TRUE; +}
This one should also take the flags --inactive / --all to allow selection of offline NICs vs online NICs. Which reminds me that the current public APIs are missing the equivalent for listing inactive interfaces. eg, virConnectNumOfDefinedInterfaces and virConnectListDefinedInterfaces
They're missing it because netcf is missing it, and I've so far just been implementing a conduit to channel netcf functionality through libvirt. The problem is that netcf currently interacts with the host OS in only two ways: 1) modifying the ifcfg-XXX config files, and 2) exec'ing the if-up and if-down scripts, and has nothing to get current state of the interface.
If netcf can't do it, it is really very trivial to do in libvirt. All you need todo is use the SIOCIFFLAGS ioctl() and check for either the interface not existing at all, or the IFF_UP bit not being set. We're already doing this in bridge.c for the virNetwork code and also for QEMU tap devices.
I talked to lutter about this and he agrees that this would be useful functionality to add to netcf, it just needs a person to do it (I'll even volunteer, just not this week!). Possibly we could add a flag now to the virConnectListInterfaces() API function in libvirt, in anticipation of a similar flag being added to netcf's ncf_list_interfaces() function. Is it too late to modify the API? (it is in a minor release, but nobody has used it yet. I guess the problem would be if someone happened to run libvirt 0.6.4 with an application that *did* use the new functions.). (Now I'm wondering why, when we thought of adding flags to all those other API functions, we didn't think to add flags to this one :-( )
There is no need to modify the API, just follow the model used by all the other APIs, where we have separate APIs for active vs inactive objects There two APIs only list active interfaces virConnectListInterfaces() virConnectNumOfInterfaces() While these two APIs should list inative interfaces virConnectListDefinedInterfaces() virConnectNumOfDefinedInterfaces()
Alternately (or maybe additionally) we can/should add a new function to return current state/statistics for interfaces. Along with throughput/packet counts, that API could return flags for the interface (all the flags shown in the output of ifconfig) as well as the current IP address(es). The virsh command could then get the list of interfaces, and iterate through looking at the state of each, eliminating the interfaces that weren't UP (or DOWN).
This command should be called 'start' for consistency with other commands.
'create' is the command name we use for creating a transient object, rather than starting a persistent object.
I see what you mean - "net-start" hooks up to virNetworkCreate(), so virInterfaceCreate() should be connected to "iface-start". It's a bit confusing, though, that the virsh commands don't match the libvirt API names. (I guess at least they should *consistently* mismatch! ;-)
Consistency is my number 1 priority with API & virsh command naming, such that once users know the libvirt naming scheme, it can be predictably followed for all APIs. Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|
participants (2)
-
Daniel P. Berrange
-
Laine Stump