[Libguestfs] [PATCH 1/4] p2v: Pass host CPU details to virt-v2v.

Richard W.M. Jones rjones at redhat.com
Thu Mar 16 18:57:12 UTC 2017


In the fake <domain type='physical'> libvirt XML that we create to
describe the physical host, we did not accurately pass any information
about the host CPU except the number of cores (<vcpu/>).

This commit extracts detailed information about the vendor, model and
topology of the host CPU and adds that to the libvirt XML for
virt-v2v.  Conveniently we can use libvirt capabilities to get this
information without needing to parse /proc/cpuinfo or similar
techniques.

The libvirt XML looks like this:

  <domain type="physical">
  ...
    <cpu match="minimum">
      <vendor>Intel</vendor>
      <model fallback="allow">Broadwell</model>
      <topology sockets="1" cores="2" threads="2"/>
    </cpu>
  ...
    <features>
      <acpi/>
      <apic/>
      <pae/>
    </features>
---
 p2v/Makefile.am     |  12 ++-
 p2v/config.c        |  18 +++-
 p2v/conversion.c    |  35 ++++++-
 p2v/cpuid.c         | 294 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 p2v/dependencies.m4 |   5 +
 p2v/main.c          |  56 +---------
 p2v/p2v.h           |  20 +++-
 7 files changed, 373 insertions(+), 67 deletions(-)
 create mode 100644 p2v/cpuid.c

diff --git a/p2v/Makefile.am b/p2v/Makefile.am
index 0152dc1..9751a07 100644
--- a/p2v/Makefile.am
+++ b/p2v/Makefile.am
@@ -74,6 +74,7 @@ virt_p2v_SOURCES = \
 	about-license.c \
 	config.c \
 	conversion.c \
+	cpuid.c \
 	gui.c \
 	gui-gtk2-compat.h \
 	gui-gtk3-compat.h \
@@ -97,6 +98,7 @@ virt_p2v_CPPFLAGS = \
 virt_p2v_CFLAGS = \
 	-pthread \
 	$(WARN_CFLAGS) $(WERROR_CFLAGS) \
+	$(LIBVIRT_CFLAGS) \
 	$(PCRE_CFLAGS) \
 	$(LIBXML2_CFLAGS) \
 	$(GTK_CFLAGS) \
@@ -105,6 +107,7 @@ virt_p2v_CFLAGS = \
 virt_p2v_LDADD = \
 	$(top_builddir)/common/utils/libutils.la \
 	$(top_builddir)/common/miniexpect/libminiexpect.la \
+	$(LIBVIRT_LIBS) \
 	$(PCRE_LIBS) \
 	$(LIBXML2_LIBS) \
 	$(GTK_LIBS) \
@@ -120,9 +123,16 @@ dependencies_files = \
 	dependencies.redhat \
 	dependencies.suse
 
+if HAVE_LIBVIRT
+dependencies_have_libvirt = -DHAVE_LIBVIRT=1
+endif
+
 $(dependencies_files): dependencies.m4
 	define=`echo $@ | $(SED) 's/dependencies.//;y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`; \
-	m4 -D$$define=1 -DGTK_VERSION=$(GTK_VERSION) $< > $@-t
+	m4 -D$$define=1 \
+	   -DGTK_VERSION=$(GTK_VERSION) \
+	   $(dependencies_have_libvirt) \
+	   $< > $@-t
 	mv $@-t $@
 
 # Support files needed by the virt-p2v-make-* scripts.
diff --git a/p2v/config.c b/p2v/config.c
index 054b07c..f9f610c 100644
--- a/p2v/config.c
+++ b/p2v/config.c
@@ -95,6 +95,8 @@ free_config (struct config *c)
   free (c->identity_url);
   free (c->identity_file);
   free (c->guestname);
+  free (c->cpu.vendor);
+  free (c->cpu.model);
   guestfs_int_free_string_list (c->disks);
   guestfs_int_free_string_list (c->removable);
   guestfs_int_free_string_list (c->interfaces);
@@ -132,10 +134,20 @@ print_config (struct config *config, FILE *fp)
            config->guestname ? config->guestname : "none");
   fprintf (fp, "vcpus  . . . . .   %d\n", config->vcpus);
   fprintf (fp, "memory . . . . .   %" PRIu64 "\n", config->memory);
+  if (config->cpu.vendor)
+    fprintf (fp, "cpu vendor . . .   %s\n", config->cpu.vendor);
+  if (config->cpu.model)
+    fprintf (fp, "cpu model  . . .   %s\n", config->cpu.model);
+  if (config->cpu.sockets > 0)
+    fprintf (fp, "cpu sockets  . .   %u\n", config->cpu.sockets);
+  if (config->cpu.cores > 0)
+    fprintf (fp, "cpu cores  . . .   %u\n", config->cpu.cores);
+  if (config->cpu.threads > 0)
+    fprintf (fp, "cpu threads  . .   %u\n", config->cpu.threads);
   fprintf (fp, "flags  . . . . .  %s%s%s\n",
-           config->flags & FLAG_ACPI ? " acpi" : "",
-           config->flags & FLAG_APIC ? " apic" : "",
-           config->flags & FLAG_PAE  ? " pae"  : "");
+           config->cpu.acpi ? " acpi" : "",
+           config->cpu.apic ? " apic" : "",
+           config->cpu.pae  ? " pae"  : "");
   fprintf (fp, "disks  . . . . .  ");
   if (config->disks != NULL) {
     for (i = 0; config->disks[i] != NULL; ++i)
diff --git a/p2v/conversion.c b/p2v/conversion.c
index 0c17ef2..55fbfb1 100644
--- a/p2v/conversion.c
+++ b/p2v/conversion.c
@@ -612,6 +612,35 @@ generate_libvirt_xml (struct config *config, struct data_conn *data_conns,
       string_format ("%d", config->vcpus);
     } end_element ();
 
+    if (config->cpu.vendor || config->cpu.model ||
+        config->cpu.sockets || config->cpu.cores || config->cpu.threads) {
+      /* https://libvirt.org/formatdomain.html#elementsCPU */
+      start_element ("cpu") {
+        attribute ("match", "minimum");
+        if (config->cpu.vendor) {
+          start_element ("vendor") {
+            string (config->cpu.vendor);
+          } end_element ();
+        }
+        if (config->cpu.model) {
+          start_element ("model") {
+            attribute ("fallback", "allow");
+            string (config->cpu.model);
+          } end_element ();
+        }
+        if (config->cpu.sockets || config->cpu.cores || config->cpu.threads) {
+          start_element ("topology") {
+            if (config->cpu.sockets)
+              attribute_format ("sockets", "%u", config->cpu.sockets);
+            if (config->cpu.cores)
+              attribute_format ("cores", "%u", config->cpu.cores);
+            if (config->cpu.threads)
+              attribute_format ("threads", "%u", config->cpu.threads);
+          } end_element ();
+        }
+      } end_element ();
+    }
+
     start_element ("os") {
       start_element ("type") {
         attribute ("arch", host_cpu);
@@ -620,9 +649,9 @@ generate_libvirt_xml (struct config *config, struct data_conn *data_conns,
     } end_element ();
 
     start_element ("features") {
-      if (config->flags & FLAG_ACPI) empty_element ("acpi");
-      if (config->flags & FLAG_APIC) empty_element ("apic");
-      if (config->flags & FLAG_PAE)  empty_element ("pae");
+      if (config->cpu.acpi) empty_element ("acpi");
+      if (config->cpu.apic) empty_element ("apic");
+      if (config->cpu.pae)  empty_element ("pae");
     } end_element ();
 
     start_element ("devices") {
diff --git a/p2v/cpuid.c b/p2v/cpuid.c
new file mode 100644
index 0000000..b7d4a4d
--- /dev/null
+++ b/p2v/cpuid.c
@@ -0,0 +1,294 @@
+/* virt-p2v
+ * Copyright (C) 2009-2017 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * Process CPU capabilities into libvirt-compatible C<E<lt>cpuE<gt>> data.
+ *
+ * If libvirt is available at compile time then this is quite
+ * simple - libvirt API C<virConnectGetCapabilities> provides
+ * a C<E<lt>hostE<ge>> element which has mostly what we need.
+ *
+ * Flags C<acpi>, C<apic>, C<pae> still have to be parsed out of
+ * F</proc/cpuinfo> because these will not necessarily be present in
+ * the libvirt capabilities directly (they are implied by the
+ * processor model, requiring a complex lookup in the CPU map).
+ *
+ * Note that #vCPUs and amount of RAM is handled by F<main.c>.
+ *
+ * See: L<https://libvirt.org/formatdomain.html#elementsCPU>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <libintl.h>
+
+#ifdef HAVE_LIBVIRT
+#include <libvirt/libvirt.h>
+#include <libvirt/virterror.h>
+#endif
+
+#include <libxml/xpath.h>
+
+#include "getprogname.h"
+#include "ignore-value.h"
+
+#include "p2v.h"
+
+static void
+free_cpu_config (struct cpu_config *cpu)
+{
+  if (cpu->vendor)
+    free (cpu->vendor);
+  if (cpu->model)
+    free (cpu->model);
+  memset (cpu, 0, sizeof *cpu);
+}
+
+/**
+ * Read flags from F</proc/cpuinfo>.
+ */
+static void
+cpuinfo_flags (struct cpu_config *cpu)
+{
+  const char *cmd;
+  CLEANUP_PCLOSE FILE *fp = NULL;
+  CLEANUP_FREE char *flag = NULL;
+  ssize_t len;
+  size_t buflen = 0;
+
+  /* Get the flags, one per line. */
+  cmd = "< /proc/cpuinfo "
+#if defined(__arm__)
+    "grep ^Features"
+#else
+    "grep ^flags"
+#endif
+    " | awk '{ for (i = 3; i <= NF; ++i) { print $i }; exit }'";
+
+  fp = popen (cmd, "re");
+  if (fp == NULL) {
+    perror ("/proc/cpuinfo");
+    return;
+  }
+
+  while (errno = 0, (len = getline (&flag, &buflen, fp)) != -1) {
+    if (len > 0 && flag[len-1] == '\n')
+      flag[len-1] = '\0';
+
+    if (STREQ (flag, "acpi"))
+      cpu->acpi = 1;
+    else if (STREQ (flag, "apic"))
+      cpu->apic = 1;
+    else if (STREQ (flag, "pae"))
+      cpu->pae = 1;
+  }
+
+  if (errno) {
+    perror ("getline");
+    return;
+  }
+}
+
+#ifdef HAVE_LIBVIRT
+
+static void
+ignore_errors (void *ignore, virErrorPtr ignore2)
+{
+  /* empty */
+}
+
+static void libvirt_error (const char *fs, ...) __attribute__((format (printf,1,2)));
+
+static void
+libvirt_error (const char *fs, ...)
+{
+  va_list args;
+  CLEANUP_FREE char *msg = NULL;
+  int len;
+  virErrorPtr err;
+
+  va_start (args, fs);
+  len = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (len < 0) goto fallback;
+
+  /* In all recent libvirt, this retrieves the thread-local error. */
+  err = virGetLastError ();
+  if (err)
+    fprintf (stderr,
+             "%s: %s: %s [code=%d int1=%d]\n",
+             getprogname (), msg, err->message, err->code, err->int1);
+  else
+  fallback:
+    fprintf (stderr, "%s: %s\n", getprogname (), msg);
+}
+
+/**
+ * Read the capabilities from libvirt and parse out the fields
+ * we care about.
+ */
+static void
+libvirt_capabilities (struct cpu_config *cpu)
+{
+  virConnectPtr conn;
+  CLEANUP_FREE char *capabilities_xml = NULL;
+  CLEANUP_XMLFREEDOC xmlDocPtr doc = NULL;
+  CLEANUP_XMLXPATHFREECONTEXT xmlXPathContextPtr xpathCtx = NULL;
+  CLEANUP_XMLXPATHFREEOBJECT xmlXPathObjectPtr xpathObj = NULL;
+  xmlNodeSetPtr nodes;
+  size_t nr_nodes, i;
+  xmlNodePtr node;
+
+  /* Connect to libvirt and get the capabilities XML. */
+  conn = virConnectOpenReadOnly (NULL);
+  if (!conn) {
+    libvirt_error (_("could not connect to libvirt"));
+    return;
+  }
+
+  /* Suppress default behaviour of printing errors to stderr.  Note
+   * you can't set this to NULL to ignore errors; setting it to NULL
+   * restores the default error handler ...
+   */
+  virConnSetErrorFunc (conn, NULL, ignore_errors);
+
+  capabilities_xml = virConnectGetCapabilities (conn);
+  if (!capabilities_xml) {
+    libvirt_error (_("could not get libvirt capabilities"));
+    virConnectClose (conn);
+    return;
+  }
+
+  /* Parse the capabilities XML with libxml2. */
+  doc = xmlReadMemory (capabilities_xml, strlen (capabilities_xml),
+                       NULL, NULL, XML_PARSE_NONET);
+  if (doc == NULL) {
+    fprintf (stderr,
+             _("%s: unable to parse capabilities XML returned by libvirt\n"),
+             getprogname ());
+    return;
+  }
+
+  xpathCtx = xmlXPathNewContext (doc);
+  if (xpathCtx == NULL) {
+    fprintf (stderr, _("%s: unable to create new XPath context\n"),
+             getprogname ());
+    return;
+  }
+
+  /* Get the CPU vendor. */
+  xpathObj =
+    xmlXPathEvalExpression (BAD_CAST "/capabilities/host/cpu/vendor/text()",
+                            xpathCtx);
+  if (xpathObj == NULL) {
+    fprintf (stderr, _("%s: %s: %d: unable to evaluate xpath expression\n"),
+             getprogname (), __FILE__, __LINE__);
+    return;
+  }
+  nodes = xpathObj->nodesetval;
+  nr_nodes = nodes->nodeNr;
+  if (nr_nodes > 0) {
+    node = nodes->nodeTab[0];
+    cpu->vendor = (char *) xmlNodeGetContent (node);
+  }
+
+  /* Get the CPU model. */
+  xmlXPathFreeObject (xpathObj);
+  xpathObj =
+    xmlXPathEvalExpression (BAD_CAST "/capabilities/host/cpu/model/text()",
+                            xpathCtx);
+  if (xpathObj == NULL) {
+    fprintf (stderr, _("%s: %s: %d: unable to evaluate xpath expression\n"),
+             getprogname (), __FILE__, __LINE__);
+    return;
+  }
+  nodes = xpathObj->nodesetval;
+  nr_nodes = nodes->nodeNr;
+  if (nr_nodes > 0) {
+    node = nodes->nodeTab[0];
+    cpu->model = (char *) xmlNodeGetContent (node);
+  }
+
+  /* Get the topology.  Note the XPath expression returns all
+   * attributes of the <topology> node.
+   */
+  xmlXPathFreeObject (xpathObj);
+  xpathObj =
+    xmlXPathEvalExpression (BAD_CAST "/capabilities/host/cpu/topology/@*",
+                            xpathCtx);
+  if (xpathObj == NULL) {
+    fprintf (stderr, _("%s: %s: %d: unable to evaluate xpath expression\n"),
+             getprogname (), __FILE__, __LINE__);
+    return;
+  }
+  nodes = xpathObj->nodesetval;
+  nr_nodes = nodes->nodeNr;
+  /* Iterate over the attributes of the <topology> node. */
+  for (i = 0; i < nr_nodes; ++i) {
+    node = nodes->nodeTab[i];
+
+    if (node->type == XML_ATTRIBUTE_NODE) {
+      xmlAttrPtr attr = (xmlAttrPtr) node;
+      CLEANUP_FREE char *content = NULL;
+      unsigned *up;
+
+      if (STREQ ((const char *) attr->name, "sockets")) {
+        up = &cpu->sockets;
+      parse_attr:
+        *up = 0;
+        content = (char *) xmlNodeListGetString (doc, attr->children, 1);
+        if (content)
+          ignore_value (sscanf (content, "%u", up));
+      }
+      else if (STREQ ((const char *) attr->name, "cores")) {
+        up = &cpu->cores;
+        goto parse_attr;
+      }
+      else if (STREQ ((const char *) attr->name, "threads")) {
+        up = &cpu->threads;
+        goto parse_attr;
+      }
+    }
+  }
+}
+
+#else /* !HAVE_LIBVIRT */
+
+static void
+libvirt_capabilities (struct cpu_config *cpu)
+{
+  fprintf (stderr,
+           _("%s: program was compiled without libvirt support\n"),
+           getprogname ());
+}
+
+#endif /* !HAVE_LIBVIRT */
+
+void
+get_cpu_config (struct cpu_config *cpu)
+{
+  free_cpu_config (cpu);
+  libvirt_capabilities (cpu);
+  cpuinfo_flags (cpu);
+}
diff --git a/p2v/dependencies.m4 b/p2v/dependencies.m4
index 21541b4..adbac26 100644
--- a/p2v/dependencies.m4
+++ b/p2v/dependencies.m4
@@ -25,6 +25,8 @@ ifelse(REDHAT,1,
   libxml2
   gtk`'GTK_VERSION
   dbus-libs
+  dnl libvirt is optional, used just to parse the host CPU capabilities.
+  ifdef(`HAVE_LIBVIRT', `libvirt-libs')
 
   dnl Run as external programs by the p2v binary.
   /usr/bin/ssh
@@ -79,6 +81,7 @@ ifelse(DEBIAN,1,
   libxml2
   libgtk`'GTK_VERSION`'.0-0
   libdbus-1-3
+  ifdef(`HAVE_LIBVIRT', `libvirt0')
   openssh-client
   qemu-utils
   gawk
@@ -112,6 +115,7 @@ ifelse(ARCHLINUX,1,
   libxml2
   gtk`'GTK_VERSION
   dbus
+  ifdef(`HAVE_LIBVIRT', `libvirt')
   openssh
   qemu
   gawk
@@ -146,6 +150,7 @@ ifelse(SUSE,1,
   libxml2
   gtk`'GTK_VERSION
   libdbus-1-3
+  ifdef(`HAVE_LIBVIRT', `libvirt-libs')
   qemu-tools
   openssh
   gawk
diff --git a/p2v/main.c b/p2v/main.c
index af14240..e1a7550 100644
--- a/p2v/main.c
+++ b/p2v/main.c
@@ -65,7 +65,6 @@ static void udevadm_settle (void);
 static void set_config_defaults (struct config *config);
 static void find_all_disks (void);
 static void find_all_interfaces (void);
-static int cpuinfo_flags (void);
 
 enum { HELP_OPTION = CHAR_MAX + 1 };
 static const char options[] = "Vv";
@@ -276,7 +275,6 @@ set_config_defaults (struct config *config)
 {
   long i;
   char hostname[257];
-  int flags;
 
   /* Default guest name is derived from the source hostname.  If we
    * assume that the p2v ISO gets its IP address and hostname from
@@ -340,11 +338,7 @@ set_config_defaults (struct config *config)
   config->memory |= config->memory >> 32;
   config->memory++;
 
-  flags = cpuinfo_flags ();
-  if (flags >= 0)
-    config->flags = flags;
-  else
-    config->flags = 0;
+  get_cpu_config (&config->cpu);
 
   /* Find all block devices in the system. */
   if (!test_disk)
@@ -585,51 +579,3 @@ find_all_interfaces (void)
   if (all_interfaces)
     qsort (all_interfaces, nr_interfaces, sizeof (char *), compare);
 }
-
-/**
- * Read the list of flags from F</proc/cpuinfo>.
- */
-static int
-cpuinfo_flags (void)
-{
-  const char *cmd;
-  CLEANUP_PCLOSE FILE *fp = NULL;
-  CLEANUP_FREE char *flag = NULL;
-  ssize_t len;
-  size_t buflen = 0;
-  int ret = 0;
-
-  /* Get the flags, one per line. */
-  cmd = "< /proc/cpuinfo "
-#if defined(__arm__)
-    "grep ^Features"
-#else
-    "grep ^flags"
-#endif
-    " | awk '{ for (i = 3; i <= NF; ++i) { print $i }; exit }'";
-
-  fp = popen (cmd, "re");
-  if (fp == NULL) {
-    perror ("/proc/cpuinfo");
-    return -1;
-  }
-
-  while (errno = 0, (len = getline (&flag, &buflen, fp)) != -1) {
-    if (len > 0 && flag[len-1] == '\n')
-      flag[len-1] = '\0';
-
-    if (STREQ (flag, "acpi"))
-      ret |= FLAG_ACPI;
-    else if (STREQ (flag, "apic"))
-      ret |= FLAG_APIC;
-    else if (STREQ (flag, "pae"))
-      ret |= FLAG_PAE;
-  }
-
-  if (errno) {
-    perror ("getline");
-    return -1;
-  }
-
-  return ret;
-}
diff --git a/p2v/p2v.h b/p2v/p2v.h
index 5223aa2..69ed35c 100644
--- a/p2v/p2v.h
+++ b/p2v/p2v.h
@@ -59,6 +59,17 @@ extern int feature_colours_option;
 extern int force_colour;
 
 /* config.c */
+struct cpu_config {
+  char *vendor;                 /* eg. "Intel" */
+  char *model;                  /* eg. "Broadwell" */
+  unsigned sockets;             /* number of sockets */
+  unsigned cores;               /* number of cores per socket */
+  unsigned threads;             /* number of hyperthreads per core */
+  int acpi;
+  int apic;
+  int pae;
+};
+
 struct config {
   char *server;
   int port;
@@ -71,7 +82,7 @@ struct config {
   char *guestname;
   int vcpus;
   uint64_t memory;
-  int flags;
+  struct cpu_config cpu;
   char **disks;
   char **removable;
   char **interfaces;
@@ -83,10 +94,6 @@ struct config {
   char *output_storage;
 };
 
-#define FLAG_ACPI 1
-#define FLAG_APIC 2
-#define FLAG_PAE  4
-
 #define OUTPUT_ALLOCATION_NONE         0
 #define OUTPUT_ALLOCATION_SPARSE       1
 #define OUTPUT_ALLOCATION_PREALLOCATED 2
@@ -96,6 +103,9 @@ extern struct config *copy_config (struct config *);
 extern void free_config (struct config *);
 extern void print_config (struct config *, FILE *);
 
+/* cpuid.c */
+extern void get_cpu_config (struct cpu_config *);
+
 /* kernel-cmdline.c */
 extern char **parse_cmdline_string (const char *cmdline);
 extern char **parse_proc_cmdline (void);
-- 
2.10.2




More information about the Libguestfs mailing list