From nobody Thu May 2 22:52:57 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) client-ip=209.132.183.28; envelope-from=libvir-list-bounces@redhat.com; helo=mx1.redhat.com; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1567442329; cv=none; d=zoho.com; s=zohoarc; b=EPDetQX07MVY6DrOKN+aF6peWDXhNJobY1mlxJ20yt/XozdhFPII1c95whM5P4bsBrY2l49X6wxN/FNRO1b/NmAluh8DuyvVEa7JCLiwrrw1f726ZQhD/v6Wk+xwZyl4BOfsKBUI3OzlbYOL5lYaE6Fm/p8pgSyHDnXb97LyuNA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1567442329; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=V+XbGbCHnpssyC03u8TUnECGlNEU/n9uSMKE4SqWMEM=; b=mo5cIog6TcwL83IN4180gK0nEGbq5jCqOrM5ZYMV5iGFXkJAnLI1YTJEAiKhFQR72J1mdGPyvhXtTYx9bRikpiqPlg0ecF9qGzUONZKajmvl44C37wINwTBqYlAOUfRTDRimSQAtOnqe3z35gzrYENT5KV1OapMm80Xillourt8= ARC-Authentication-Results: i=1; mx.zoho.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 15674423297801004.4747235898934; Mon, 2 Sep 2019 09:38:49 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.23]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 82E1783BDA; Mon, 2 Sep 2019 16:38:48 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 5B4B8196B2; Mon, 2 Sep 2019 16:38:48 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 1CE481806B00; Mon, 2 Sep 2019 16:38:48 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x82GcWcA004847 for ; Mon, 2 Sep 2019 12:38:32 -0400 Received: by smtp.corp.redhat.com (Postfix) id 9E05A1001B02; Mon, 2 Sep 2019 16:38:32 +0000 (UTC) Received: from domokun.gsslab.fab.redhat.com (dhcp-94.gsslab.fab.redhat.com [10.33.9.94]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7817E1001B01; Mon, 2 Sep 2019 16:38:31 +0000 (UTC) From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= To: libvir-list@redhat.com Date: Mon, 2 Sep 2019 17:38:26 +0100 Message-Id: <20190902163826.19200-2-berrange@redhat.com> In-Reply-To: <20190902163826.19200-1-berrange@redhat.com> References: <20190902163826.19200-1-berrange@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-loop: libvir-list@redhat.com Subject: [libvirt] [PATCH 1/1] tools: rewrite virt-host-validate in Go to be data driven X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.84 on 10.5.11.23 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.6.2 (mx1.redhat.com [10.5.110.71]); Mon, 02 Sep 2019 16:38:48 +0000 (UTC) The current virt-host-validate command has a bunch of checks defined in the source code which are thus only extensible by the upstream project, or downstream code modification. The checks are implemented by a fairly simple set of rules, mostly matching the contents of files, or output from commands, against some expected state or regex. This lends itself very well to having the checks defined as metadata rules, which can be processed by a generic engine. This patch thus converts the virt-host-validate command to be data driven, with the desired checks defined by a set of XML files. Parsing XML from C is incredibly tedious, which has long put me off doing this conversion to XML defined checks. The size of the parsing code would be too high to justify the benefits of the rewrite. This new impl is thus written in Go to take advantage of its XML encoding feature that lets you simply annotate struct fields to define an XML parser. The new impl also has a few new features in its CLI interface. It is possible display a list of all facts that are set, instead of just the subset which have reports associated with them. For example, by default $ virt-host-validate Checking cgroup memory controller present...PASS Checking cgroup memory controller mounted...PASS Checking cgroup cpu controller present...PASS Checking cgroup cpu controller mounted...PASS Checking cgroup cpuacct controller present...PASS Checking cgroup cpuacct controller mounted...PASS ...snip... But it can be run with $ virt-host-validate -q -f Set fact 'libvirt.driver.qemu' =3D 'true' Set fact 'libvirt.driver.lxc' =3D 'true' Set fact 'libvirt.driver.parallels' =3D 'true' Set fact 'cpu.arch' =3D 'x86_64' Set fact 'os.kernel' =3D 'Linux' Set fact 'os.release' =3D '5.1.16-300.fc30.x86_64' Set fact 'os.version' =3D '#1 SMP Wed Jul 3 15:06:51 UTC 2019' Set fact 'os.cgroup.controller.cpuset' =3D 'true' Set fact 'os.cgroup.controller.cpu' =3D 'true' Set fact 'os.cgroup.controller.cpuacct' =3D 'true' ...snip... This is quite useful for generating reports to include on bug reports / support requests. The main thing lost with this new impl is support for translation, which covers two sources: - Reports associated with the facts defined in the XML. It is unclear how we would best deal with this. Merge translations from the .po file into the .xml files, and then ensure we pick the appropriate one to display - Error messages when things go wrong in the code itself. This would need to use some gettext like translation system for golang. I have not investigated the options here yet Signed-off-by: Daniel P. Berrang=C3=A9 --- configure.ac | 1 + libvirt.spec.in | 23 + m4/virt-golang.m4 | 46 ++ m4/virt-host-validate.m4 | 8 +- po/POTFILES | 5 - tools/Makefile.am | 59 +- tools/host-validate/go.mod | 8 + tools/host-validate/go.sum | 4 + tools/host-validate/main.go | 98 +++ tools/host-validate/pkg/engine.go | 469 +++++++++++ tools/host-validate/pkg/facts.go | 784 +++++++++++++++++++ tools/host-validate/pkg/facts_test.go | 67 ++ tools/host-validate/pkg/xml_utils.go | 287 +++++++ tools/host-validate/rules/acpi.xml | 29 + tools/host-validate/rules/builtin.xml | 33 + tools/host-validate/rules/cgroups.xml | 403 ++++++++++ tools/host-validate/rules/cpu.xml | 148 ++++ tools/host-validate/rules/devices.xml | 54 ++ tools/host-validate/rules/freebsd-kernel.xml | 14 + tools/host-validate/rules/iommu.xml | 93 +++ tools/host-validate/rules/namespaces.xml | 94 +++ tools/host-validate/rules/pci.xml | 9 + tools/virt-host-validate-bhyve.c | 77 -- tools/virt-host-validate-bhyve.h | 24 - tools/virt-host-validate-common.c | 419 ---------- tools/virt-host-validate-common.h | 85 -- tools/virt-host-validate-lxc.c | 87 -- tools/virt-host-validate-lxc.h | 24 - tools/virt-host-validate-qemu.c | 116 --- tools/virt-host-validate-qemu.h | 24 - tools/virt-host-validate.c | 152 ---- tools/virt-host-validate.pod | 12 +- 32 files changed, 2693 insertions(+), 1063 deletions(-) create mode 100644 m4/virt-golang.m4 create mode 100644 tools/host-validate/go.mod create mode 100644 tools/host-validate/go.sum create mode 100644 tools/host-validate/main.go create mode 100644 tools/host-validate/pkg/engine.go create mode 100644 tools/host-validate/pkg/facts.go create mode 100644 tools/host-validate/pkg/facts_test.go create mode 100644 tools/host-validate/pkg/xml_utils.go create mode 100644 tools/host-validate/rules/acpi.xml create mode 100644 tools/host-validate/rules/builtin.xml create mode 100644 tools/host-validate/rules/cgroups.xml create mode 100644 tools/host-validate/rules/cpu.xml create mode 100644 tools/host-validate/rules/devices.xml create mode 100644 tools/host-validate/rules/freebsd-kernel.xml create mode 100644 tools/host-validate/rules/iommu.xml create mode 100644 tools/host-validate/rules/namespaces.xml create mode 100644 tools/host-validate/rules/pci.xml delete mode 100644 tools/virt-host-validate-bhyve.c delete mode 100644 tools/virt-host-validate-bhyve.h delete mode 100644 tools/virt-host-validate-common.c delete mode 100644 tools/virt-host-validate-common.h delete mode 100644 tools/virt-host-validate-lxc.c delete mode 100644 tools/virt-host-validate-lxc.h delete mode 100644 tools/virt-host-validate-qemu.c delete mode 100644 tools/virt-host-validate-qemu.h delete mode 100644 tools/virt-host-validate.c diff --git a/configure.ac b/configure.ac index 7c76a9c9ec..c217bc7d77 100644 --- a/configure.ac +++ b/configure.ac @@ -513,6 +513,7 @@ LIBVIRT_ARG_TLS_PRIORITY LIBVIRT_ARG_SYSCTL_CONFIG =20 =20 +LIBVIRT_CHECK_GOLANG LIBVIRT_CHECK_DEBUG LIBVIRT_CHECK_DTRACE LIBVIRT_CHECK_NUMAD diff --git a/libvirt.spec.in b/libvirt.spec.in index 41c4a142d6..5ac38366bb 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -402,6 +402,10 @@ BuildRequires: libtirpc-devel BuildRequires: firewalld-filesystem %endif =20 +BuildRequires: golang >=3D 1.12 +BuildRequires: golang-ipath(github.com/spf13/pflag) +BuildRequires: golang-ipath(golang.org/x/sys) + Provides: bundled(gnulib) =20 %description @@ -1155,6 +1159,15 @@ export SOURCE_DATE_EPOCH=3D$(stat --printf=3D'%Y' %{= _specdir}/%{name}.spec) autoreconf -if %endif =20 +# We're building without go modules enabled, so +# must make a local Go root with the old style +# dir naming scheme/hierarchy +mkdir -p gocode/src/libvirt.org +ln -s `pwd`/tools/host-validate `pwd`/gocode/src/libvirt.org/host-validate + +export GO111MODULE=3Doff +export GOPATH=3D/usr/share/gocode:`pwd`/gocode + rm -f po/stamp-po %configure --with-runstatedir=3D%{_rundir} \ %{?arg_qemu} \ @@ -1886,6 +1899,16 @@ exit 0 %if %{with_qemu} %{_datadir}/systemtap/tapset/libvirt_qemu_probes*.stp %endif +%dir %{_datadir}/libvirt/host-validate +%{_datadir}/libvirt/host-validate/acpi.xml +%{_datadir}/libvirt/host-validate/builtin.xml +%{_datadir}/libvirt/host-validate/cgroups.xml +%{_datadir}/libvirt/host-validate/cpu.xml +%{_datadir}/libvirt/host-validate/devices.xml +%{_datadir}/libvirt/host-validate/freebsd-kernel.xml +%{_datadir}/libvirt/host-validate/iommu.xml +%{_datadir}/libvirt/host-validate/namespaces.xml +%{_datadir}/libvirt/host-validate/pci.xml =20 %if %{with_bash_completion} %{_datadir}/bash-completion/completions/virsh diff --git a/m4/virt-golang.m4 b/m4/virt-golang.m4 new file mode 100644 index 0000000000..c2638cad39 --- /dev/null +++ b/m4/virt-golang.m4 @@ -0,0 +1,46 @@ +dnl Golang checks +dnl +dnl Copyright (C) 2019 Red Hat, Inc. +dnl +dnl This library is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Lesser General Public +dnl License as published by the Free Software Foundation; either +dnl version 2.1 of the License, or (at your option) any later version. +dnl +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Lesser General Public License for more details. +dnl +dnl You should have received a copy of the GNU Lesser General Public +dnl License along with this library. If not, see +dnl . +dnl + +AC_DEFUN([LIBVIRT_CHECK_GOLANG], [ + AC_PATH_PROGS([GO], [go], [no]) + if test "x$ac_cv_path_GO" !=3D "xno" + then + with_go=3Dyes + else + with_go=3Dno + fi + AM_CONDITIONAL([HAVE_GOLANG], [test "$with_go" !=3D "no"]) + + if test "$with_go" !=3D "no" + then + GOVERSION=3D`$ac_cv_path_GO version | awk '{print \$ 3}' | sed -e 's/g= o//' -e 's/rc.*//'` + GOMAJOR=3D`echo $GOVERSION | awk -F . '{print \$ 1}'` + GOMINOR=3D`echo $GOVERSION | awk -F . '{print \$ 2}'` + GOMICRO=3D`echo $GOVERSION | awk -F . '{print \$ 3}'` + + AC_MSG_CHECKING([for Go version >=3D 1.11]) + if test "$GOMAJOR" !=3D "1" || test "$GOMINOR" -lt "11" + then + with_go=3Dno + AC_MSG_RESULT([no]) + else + AC_MSG_RESULT([yes]) + fi + fi +]) diff --git a/m4/virt-host-validate.m4 b/m4/virt-host-validate.m4 index 643cd8f06e..b03b2e9dc4 100644 --- a/m4/virt-host-validate.m4 +++ b/m4/virt-host-validate.m4 @@ -21,14 +21,14 @@ AC_DEFUN([LIBVIRT_ARG_HOST_VALIDATE], [ =20 AC_DEFUN([LIBVIRT_CHECK_HOST_VALIDATE], [ if test "x$with_host_validate" !=3D "xno"; then - if test "x$with_win" =3D "xyes"; then + if test "$with_go" =3D "no"; then if test "x$with_host_validate" =3D "xyes"; then - AC_MSG_ERROR([virt-host-validate is not supported on Windows]) + AC_MSG_ERROR([Cannot build virt-host-validate without Go toolchain= ]) else - with_host_validate=3Dno; + with_host_validate=3Dno fi else - with_host_validate=3Dyes; + with_host_validate=3Dyes fi fi =20 diff --git a/po/POTFILES b/po/POTFILES index e466e1bc55..98b6f39398 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -313,11 +313,6 @@ tools/virsh-util.c tools/virsh-volume.c tools/virsh.c tools/virt-admin.c -tools/virt-host-validate-bhyve.c -tools/virt-host-validate-common.c -tools/virt-host-validate-lxc.c -tools/virt-host-validate-qemu.c -tools/virt-host-validate.c tools/virt-login-shell.c tools/vsh.c tools/vsh.h diff --git a/tools/Makefile.am b/tools/Makefile.am index 29fdbfe846..0c4d306347 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -157,50 +157,25 @@ libvirt_shell_la_SOURCES =3D \ vsh-table.c vsh-table.h =20 virt_host_validate_SOURCES =3D \ - virt-host-validate.c \ - virt-host-validate-common.c virt-host-validate-common.h - -VIRT_HOST_VALIDATE_QEMU =3D \ - virt-host-validate-qemu.c \ - virt-host-validate-qemu.h -VIRT_HOST_VALIDATE_LXC =3D \ - virt-host-validate-lxc.c \ - virt-host-validate-lxc.h -VIRT_HOST_VALIDATE_BHYVE =3D \ - virt-host-validate-bhyve.c \ - virt-host-validate-bhyve.h -if WITH_QEMU -virt_host_validate_SOURCES +=3D $(VIRT_HOST_VALIDATE_QEMU) -else ! WITH_QEMU -EXTRA_DIST +=3D $(VIRT_HOST_VALIDATE_QEMU) -endif ! WITH_QEMU - -if WITH_LXC -virt_host_validate_SOURCES +=3D $(VIRT_HOST_VALIDATE_LXC) -else ! WITH_LXC -EXTRA_DIST +=3D $(VIRT_HOST_VALIDATE_LXC) -endif ! WITH_LXC - -if WITH_BHYVE -virt_host_validate_SOURCES +=3D $(VIRT_HOST_VALIDATE_BHYVE) -else ! WITH_BHYVE -EXTRA_DIST +=3D $(VIRT_HOST_VALIDATE_BHYVE) -endif ! WITH_BHYVE - -virt_host_validate_LDFLAGS =3D \ - $(AM_LDFLAGS) \ - $(PIE_LDFLAGS) \ - $(COVERAGE_LDFLAGS) \ - $(NULL) + $(srcdir)/host-validate/go.mod \ + $(srcdir)/host-validate/go.sum \ + $(srcdir)/host-validate/main.go \ + $(srcdir)/host-validate/pkg/facts.go \ + $(srcdir)/host-validate/pkg/facts_test.go \ + $(srcdir)/host-validate/pkg/xml_utils.go \ + $(srcdir)/host-validate/pkg/engine.go \ + $(NULL) =20 -virt_host_validate_LDADD =3D \ - ../src/libvirt.la \ - ../gnulib/lib/libgnu.la \ - $(NULL) +virt_host_validate_rulesdir =3D $(pkgdatadir)/host-validate +virt_host_validate_rules_DATA =3D $(wildcard $(srcdir)/host-validate/rules= /*.xml) =20 -virt_host_validate_CFLAGS =3D \ - $(AM_CFLAGS) \ - $(NULL) +EXTRA_DIST +=3D $(virt_host_validate_rules_DATA) $(virt_host_validate_SOUR= CES) + +virt-host-validate$(EXEEXT): $(virt_host_validate_SOURCES) + $(AM_V_CC) cd $(srcdir)/host-validate && $(GO) build $(GOBUILDFLAGS) -o $= (abs_builddir)/$@ main.go + +check-local: + cd $(srcdir)/host-validate && $(GO) test $(GOTESTFLAGS) ./... =20 # virt-login-shell will be setuid, and must not link to anything # except glibc. It wil scrub the environment and then invoke the diff --git a/tools/host-validate/go.mod b/tools/host-validate/go.mod new file mode 100644 index 0000000000..6baf16dd95 --- /dev/null +++ b/tools/host-validate/go.mod @@ -0,0 +1,8 @@ +module libvirt.org/host-validate + +go 1.11 + +require ( + github.com/spf13/pflag v1.0.3 + golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a +) diff --git a/tools/host-validate/go.sum b/tools/host-validate/go.sum new file mode 100644 index 0000000000..44c5d39045 --- /dev/null +++ b/tools/host-validate/go.sum @@ -0,0 +1,4 @@ +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazD= kg=3D +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqq= NjLnInEg4=3D +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80a= ffTUvO9UxmJRx8K0gsfABByQ=3D +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLG= QEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=3D diff --git a/tools/host-validate/main.go b/tools/host-validate/main.go new file mode 100644 index 0000000000..c46d5a4843 --- /dev/null +++ b/tools/host-validate/main.go @@ -0,0 +1,98 @@ +/* + * This file is part of the libvirt project + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Copyright (C) 2019 Red Hat, Inc. + * + */ + +package main + +import ( + "flag" + "fmt" + "github.com/spf13/pflag" + "io/ioutil" + vl "libvirt.org/host-validate/pkg" + "os" + "path/filepath" + "strings" +) + +func main() { + var showfacts bool + var quiet bool + var rulesdir string + + pflag.BoolVarP(&showfacts, "show-facts", "f", false, "Show raw fact names= and values") + pflag.BoolVarP(&quiet, "quiet", "q", false, "Don't report on fact checks") + pflag.StringVarP(&rulesdir, "rules-dir", "r", "/usr/share/libvirt/host-va= lidate", "Directory to load validation rules from") + + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + // Convince glog that we really have parsed CLI + flag.CommandLine.Parse([]string{}) + + if len(pflag.Args()) > 1 { + fmt.Printf("syntax: %s [OPTIONS] [DRIVER]\n", os.Args[0]) + os.Exit(1) + } + + driver :=3D "" + if len(pflag.Args()) =3D=3D 1 { + driver =3D pflag.Args()[0] + } + + files, err :=3D ioutil.ReadDir(rulesdir) + if err !=3D nil { + fmt.Printf("Unable to load rules from '%s': %s\n", rulesdir, err) + os.Exit(1) + } + var lists []vl.FactList + for _, file :=3D range files { + path :=3D filepath.Join(rulesdir, file.Name()) + if !strings.HasSuffix(path, ".xml") { + continue + } + facts, err :=3D vl.NewFactList(path) + if err !=3D nil { + fmt.Printf("Unable to load facts '%s': %s\n", path, err) + os.Exit(1) + } + lists =3D append(lists, *facts) + } + + var output vl.EngineOutput + if !quiet { + output |=3D vl.ENGINE_OUTPUT_REPORTS + } + if showfacts { + output |=3D vl.ENGINE_OUTPUT_FACTS + } + + engine :=3D vl.NewEngine(output, driver) + + failed, err :=3D engine.Validate(vl.MergeFactLists(lists)) + if err !=3D nil { + fmt.Printf("Unable to validate facts: %s\n", err) + os.Exit(1) + } + if failed !=3D 0 { + os.Exit(2) + } + + os.Exit(0) +} diff --git a/tools/host-validate/pkg/engine.go b/tools/host-validate/pkg/en= gine.go new file mode 100644 index 0000000000..79f3077038 --- /dev/null +++ b/tools/host-validate/pkg/engine.go @@ -0,0 +1,469 @@ +/* + * This file is part of the libvirt project + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Copyright (C) 2019 Red Hat, Inc. + * + */ + +package pkg + +import ( + "fmt" + "golang.org/x/sys/unix" + "io/ioutil" + "os" + "os/exec" + "regexp" + "strings" +) + +type Engine struct { + Facts map[string]string + Errors uint + Output EngineOutput + Driver string +} + +type EngineOutput int + +const ( + // Print the raw key, value pairs for each fact set + ENGINE_OUTPUT_FACTS =3D EngineOutput(1 << 0) + + // Print the human targetted reports for facts set + ENGINE_OUTPUT_REPORTS =3D EngineOutput(1 << 1) +) + +// Create an engine able to process a list of facts +func NewEngine(output EngineOutput, driver string) *Engine { + engine :=3D &Engine{} + + engine.Output =3D output + engine.Facts =3D make(map[string]string) + engine.Driver =3D driver + + return engine +} + +// Set the value associated with a fact +func (engine *Engine) SetFact(name, value string) { + engine.Facts[name] =3D value + if (engine.Output & ENGINE_OUTPUT_FACTS) !=3D 0 { + fmt.Printf("Set fact '%s' =3D '%s'\n", name, value) + } +} + +func (engine *Engine) EvalExpression(expr *Expression) (bool, error) { + if expr.Any !=3D nil { + for _, subexpr :=3D range expr.Any.Expressions { + ok, err :=3D engine.EvalExpression(&subexpr) + if err !=3D nil { + return false, err + } + if ok { + return true, nil + } + } + return false, nil + } else if expr.All !=3D nil { + for _, subexpr :=3D range expr.All.Expressions { + ok, err :=3D engine.EvalExpression(&subexpr) + if err !=3D nil { + return false, err + } + if !ok { + return false, nil + } + } + return true, nil + } else if expr.Fact !=3D nil { + val, ok :=3D engine.Facts[expr.Fact.Name] + if !ok { + return false, nil + } + if expr.Fact.Match =3D=3D "regex" { + match, err :=3D regexp.Match(expr.Fact.Value, []byte(val)) + if err !=3D nil { + return false, err + } + return match, nil + } else if expr.Fact.Match =3D=3D "exists" { + return true, nil + } else { + return val =3D=3D expr.Fact.Value, nil + } + } else { + return false, fmt.Errorf("Expected expression any or all or fact") + } +} + +// Report a fact that failed to have the desired value +func (engine *Engine) Fail(fact *Fact) { + engine.Errors++ + if fact.Report =3D=3D nil { + return + } + if (engine.Output & ENGINE_OUTPUT_REPORTS) !=3D 0 { + hint :=3D "" + if fact.Hint !=3D nil { + hint =3D " (" + fact.Hint.Message + ")" + } + if fact.Report.Level =3D=3D "note" { + fmt.Printf("\033[34mNOTE\033[0m%s\n", hint) + } else if fact.Report.Level =3D=3D "warn" { + fmt.Printf("\033[33mWARN\033[0m%s\n", hint) + } else { + fmt.Printf("\033[31mFAIL\033[0m%s\n", hint) + } + } +} + +// Report a fact that has the desired value +func (engine *Engine) Pass(fact *Fact) { + if fact.Report =3D=3D nil { + return + } + if (engine.Output & ENGINE_OUTPUT_REPORTS) !=3D 0 { + fmt.Printf("\033[32mPASS\033[0m\n") + } +} + +func utsString(v [65]byte) string { + n :=3D 0 + for i, _ :=3D range v { + if v[i] =3D=3D 0 { + break + } + n++ + } + return string(v[0:n]) +} + +// Populate the engine with values for a built-in fact +func (engine *Engine) SetValueBuiltIn(fact *Fact) error { + var uts unix.Utsname + err :=3D unix.Uname(&uts) + if err !=3D nil { + return err + } + + if fact.Name =3D=3D "os.kernel" { + engine.SetFact(fact.Name, utsString(uts.Sysname)) + } else if fact.Name =3D=3D "os.release" { + engine.SetFact(fact.Name, utsString(uts.Release)) + } else if fact.Name =3D=3D "os.version" { + engine.SetFact(fact.Name, utsString(uts.Version)) + } else if fact.Name =3D=3D "cpu.arch" { + engine.SetFact(fact.Name, utsString(uts.Machine)) + } else if fact.Name =3D=3D "libvirt.driver" { + if engine.Driver !=3D "" { + engine.SetFact(fact.Name+"."+engine.Driver, "true") + } else { + if utsString(uts.Sysname) =3D=3D "Linux" { + engine.SetFact(fact.Name+".qemu", "true") + engine.SetFact(fact.Name+".lxc", "true") + engine.SetFact(fact.Name+".parallels", "true") + } else if utsString(uts.Sysname) =3D=3D "FreeBSD" { + engine.SetFact(fact.Name+".bhyve", "true") + } + } + } else { + return fmt.Errorf("Unknown built-in fact '%s'", fact.Name) + } + + return nil +} + +func (engine *Engine) SetValueBool(fact *Fact) error { + ok, err :=3D engine.EvalExpression(fact.Value.Bool) + if err !=3D nil { + return err + } + if ok { + engine.SetFact(fact.Name, "true") + engine.Pass(fact) + } else { + engine.SetFact(fact.Name, "false") + engine.Fail(fact) + } + return nil +} + +func unescape(val string) (string, error) { + escapes :=3D map[rune]string{ + 'a': "\x07", + 'b': "\x08", + 'e': "\x1b", + 'f': "\x0c", + 'n': "\x0a", + 'r': "\x0d", + 't': "\x09", + 'v': "\x0b", + '\\': "\x5c", + '0': "\x00", + } + var ret string + escape :=3D false + for _, c :=3D range val { + if c =3D=3D '\\' { + escape =3D true + } else if escape { + unesc, ok :=3D escapes[c] + if !ok { + return "", fmt.Errorf("Unknown escape '\\%c'", c) + } + ret +=3D string(unesc) + escape =3D false + } else { + ret +=3D string(c) + } + } + return ret, nil +} + +func (engine *Engine) SetValueParse(fact *Fact, parse *Parse, context stri= ng, val string) error { + if parse =3D=3D nil { + engine.SetFact(context, val) + return nil + } + if parse.Whitespace =3D=3D "trim" { + val =3D strings.TrimSpace(val) + } + if parse.Scalar !=3D nil { + if parse.Scalar.Regex !=3D "" { + re, err :=3D regexp.Compile(parse.Scalar.Regex) + if err !=3D nil { + return err + } + matches :=3D re.FindStringSubmatch(val) + if parse.Scalar.Match >=3D uint(len(matches)) { + return fmt.Errorf("No match %d for '%s' against '%s'", + parse.Scalar.Match, parse.Scalar.Regex, val) + } + val =3D matches[parse.Scalar.Match] + } + engine.SetFact(context, val) + } else if parse.List !=3D nil { + if val =3D=3D "" { + return nil + } + sep, err :=3D unescape(parse.List.Separator) + if err !=3D nil { + return err + } + bits :=3D strings.Split(val, sep) + count :=3D uint(0) + for i, bit :=3D range bits { + if i < int(parse.List.SkipHead) { + continue + } + if i >=3D (len(bits) - int(parse.List.SkipTail)) { + continue + } + subcontext :=3D fmt.Sprintf("%s.%d", context, i) + err :=3D engine.SetValueParse(fact, parse.List.Parse, subcontext, bit) + if err !=3D nil { + return err + } + count++ + if count >=3D parse.List.Limit { + break + } + } + } else if parse.Set !=3D nil { + if val =3D=3D "" { + return nil + } + sep, err :=3D unescape(parse.Set.Separator) + if err !=3D nil { + return err + } + bits :=3D strings.Split(val, sep) + for i, bit :=3D range bits { + if i < int(parse.Set.SkipHead) { + continue + } + if i >=3D (len(bits) - int(parse.Set.SkipTail)) { + continue + } + if parse.Set.Regex !=3D "" { + re, err :=3D regexp.Compile(parse.Set.Regex) + if err !=3D nil { + return err + } + matches :=3D re.FindStringSubmatch(bit) + if parse.Set.Match >=3D uint(len(matches)) { + return fmt.Errorf("No match %d for '%s' against '%s'", + parse.Set.Match, parse.Set.Regex, bit) + } + bit =3D matches[parse.Set.Match] + } + subcontext :=3D fmt.Sprintf("%s.%s", context, bit) + engine.SetFact(subcontext, "true") + } + } else if parse.Dict !=3D nil { + sep, err :=3D unescape(parse.Dict.Separator) + if err !=3D nil { + return err + } + dlm, err :=3D unescape(parse.Dict.Delimiter) + if err !=3D nil { + return err + } + bits :=3D strings.Split(val, sep) + for _, bit :=3D range bits { + pair :=3D strings.SplitN(bit, dlm, 2) + if len(pair) !=3D 2 { + //return fmt.Errorf("Cannot split %s value '%s' on '%s'", fact.Name, p= air, parse.Dict.Delimiter) + continue + } + key :=3D strings.TrimSpace(pair[0]) + subcontext :=3D fmt.Sprintf("%s.%s", context, key) + err :=3D engine.SetValueParse(fact, parse.Dict.Parse, subcontext, pair[= 1]) + if err !=3D nil { + return err + } + } + } else { + return fmt.Errorf("Expecting scalar or list or dict to parse") + } + + return nil +} + +func (engine *Engine) SetValueString(fact *Fact) error { + val, ok :=3D engine.Facts[fact.Value.String.Fact] + if !ok { + return fmt.Errorf("Fact %s not present", fact.Value.String.Fact) + } + + return engine.SetValueParse(fact, fact.Value.String.Parse, fact.Name, str= ing(val)) +} + +func (engine *Engine) SetValueFile(fact *Fact) error { + data, err :=3D ioutil.ReadFile(fact.Value.File.Path) + if err !=3D nil { + if os.IsNotExist(err) && fact.Value.File.IgnoreMissing { + return nil + } + return err + } + + return engine.SetValueParse(fact, fact.Value.File.Parse, fact.Name, strin= g(data)) +} + +func (engine *Engine) SetValueDirEnt(fact *Fact) error { + files, err :=3D ioutil.ReadDir(fact.Value.DirEnt.Path) + if err !=3D nil { + if os.IsNotExist(err) && fact.Value.DirEnt.IgnoreMissing { + return nil + } + return err + } + for _, file :=3D range files { + engine.SetFact(fmt.Sprintf("%s.%s", fact.Name, file.Name()), "true") + } + return nil +} + +func (engine *Engine) SetValueCommand(fact *Fact) error { + var args []string + for _, arg :=3D range fact.Value.Command.Args { + args =3D append(args, arg.Val) + } + cmd :=3D exec.Command(fact.Value.Command.Name, args...) + out, err :=3D cmd.Output() + if err !=3D nil { + return err + } + + return engine.SetValueParse(fact, fact.Value.Command.Parse, fact.Name, st= ring(out)) +} + +func (engine *Engine) SetValueAccess(fact *Fact) error { + var flags uint32 + if fact.Value.Access.Check =3D=3D "exists" { + flags =3D 0 + } else if fact.Value.Access.Check =3D=3D "readable" { + flags =3D unix.R_OK + } else if fact.Value.Access.Check =3D=3D "writable" { + flags =3D unix.W_OK + } else if fact.Value.Access.Check =3D=3D "executable" { + flags =3D unix.X_OK + } else { + return fmt.Errorf("No access check type specified for %s", + fact.Value.Access.Path) + } + err :=3D unix.Access(fact.Value.Access.Path, flags) + if err !=3D nil { + engine.SetFact(fact.Name, "false") + engine.Fail(fact) + } else { + engine.SetFact(fact.Name, "true") + engine.Pass(fact) + } + return nil +} + +func (engine *Engine) ValidateFact(fact *Fact) error { + if fact.Filter !=3D nil { + ok, err :=3D engine.EvalExpression(fact.Filter) + if err !=3D nil { + return err + } + if !ok { + return nil + } + } + if fact.Report !=3D nil && (engine.Output&ENGINE_OUTPUT_REPORTS) !=3D 0 { + fmt.Printf("Checking %s...", fact.Report.Message) + } + + if fact.Value.BuiltIn !=3D nil { + return engine.SetValueBuiltIn(fact) + } else if fact.Value.Bool !=3D nil { + return engine.SetValueBool(fact) + } else if fact.Value.String !=3D nil { + return engine.SetValueString(fact) + } else if fact.Value.File !=3D nil { + return engine.SetValueFile(fact) + } else if fact.Value.DirEnt !=3D nil { + return engine.SetValueDirEnt(fact) + } else if fact.Value.Command !=3D nil { + return engine.SetValueCommand(fact) + } else if fact.Value.Access !=3D nil { + return engine.SetValueAccess(fact) + } else { + return fmt.Errorf("No information provided for value in fact %s", fact.N= ame) + } +} + +// Validate all facts in the list, returning a count of +// any non-fatal errors encountered. +func (engine *Engine) Validate(facts FactList) (uint, error) { + err :=3D facts.Sort() + if err !=3D nil { + return 0, err + } + for _, fact :=3D range facts.Facts { + err =3D engine.ValidateFact(fact) + if err !=3D nil { + return 0, err + } + } + return engine.Errors, nil +} diff --git a/tools/host-validate/pkg/facts.go b/tools/host-validate/pkg/fac= ts.go new file mode 100644 index 0000000000..810ffff280 --- /dev/null +++ b/tools/host-validate/pkg/facts.go @@ -0,0 +1,784 @@ +/* + * This file is part of the libvirt project + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Copyright (C) 2019 Red Hat, Inc. + * + */ + +package pkg + +import ( + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "strings" +) + +// A list of all the facts we are going to validate +type FactList struct { + XMLName xml.Name `xml:"https://libvirt.org/xml/virt-host-validate/facts/1= .0 factlist"` + + Facts []*Fact `xml:"fact"` +} + +// A fact is a description of a single piece of information +// we wish to check. Conceptually a fact is simply a plain +// key, value pair where both parts are strings. +// +// Every fact has a name which is a dot separated list of +// strings, eg 'cpu.family.arm'. By convention the dots +// are forming an explicit hierarchy, so a common prefix +// on names is used to group related facts. +// +// Optionally a report can be associated with a fact +// This is a freeform string intended to be read by +// humans, eg 'hardware virt possible' +// +// If a report is given, there can also be an optional +// hint given, which is used when a fact fails to match +// some desired condition. This is another freeform string +// intended to be read by humans, eg +// 'enable cpuset cgroup controller in Kconfig' +// +// The optional filter is an expression that can be used +// to skip the processing of this fact when certain +// conditions are not met. eg, a filter might skip +// the checking of cgroups when the os kernel is not "linux" +// +// Finally there is a mandatory value. This defines how +// to extract the value for setting the fact. +// +// +// +type Fact struct { + Name string `xml:"name,attr"` + Report *Report `xml:"report"` + Hint *Report `xml:"hint"` + Filter *Expression `xml:"filter"` + Value Value `xml:"value"` +} + +// A report is a message intended to be targetted at humans +// +// The message can be an arbitrary string, informing them +// of some relevant information +// +// The level is one of 'warn' or 'note' or 'error', with +// 'error' being assumed if no value is given +type Report struct { + Message string `xml:"message,attr"` + Level string `xml:"level,attr,omitempty"` +} + +// An expression is used to evaluate some complex conditions +// +// Expressions can be simple, comparing a single fact to +// some desired match. +// +// Expressions can be compound, requiring any or all of a +// list of sub-expressions to evaluate to true. +type Expression struct { + Any *ExpressionCompound `xml:"any"` + All *ExpressionCompound `xml:"all"` + Fact *ExpressionFact `xml:"fact"` +} + +// A compound expression is simply a list of expressions +// to be evaluated +type ExpressionCompound struct { + Expressions []Expression `xml:"-"` +} + +// A fact expression defines a rule that compares the +// value associated with the fact, to some desired +// match. +// +// The name gives the name of the fact to check +// +// The semantics of value vary according to the match +// type +// +// If the match type is 'regex', then the value must +// match against the declared regular expression. +// +// If the match type is 'exists', the value is ignored +// and the fact must simply exist. +// +// If the match type is not set, then a plain string +// equality comparison is done +type ExpressionFact struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr,omitempty"` + Match string `xml:"match,attr,omitempty"` +} + +// A value defines the various data sources for +// setting a fact's value. Only one of the data +// sources is permitted to be non-nil for each +// fact +// +// A builtin value is one of the standard facts +// defined in code. +// +// A bool value is one set to 'true' or 'false' +// depending on the results of evaluating an +// expression. It is user error to construct +// an expression which is self-referential +// +// A string value is one set by parsing the +// the value of another fact. A typical use +// case would be to split a string based on +// a whitespace separator +// +// A file value is one set by parsing the +// contents of a file on disk +// +// A dirent value results in creation of +// many facts, one for each directory entry +// seen +// +// An access value is one that sets a value +// to 'true' or 'false' depending on the +// permissions of a file +// +// A command value is one set by parsing +// the output of a command's stdout +type Value struct { + BuiltIn *ValueBuiltIn `xml:"builtin"` + Bool *Expression `xml:"bool"` + String *ValueString `xml:"string"` + File *ValueFile `xml:"file"` + DirEnt *ValueDirEnt `xml:"dirent"` + Access *ValueAccess `xml:"access"` + Command *ValueCommand `xml:"command"` +} + +// Valid built-in fact names are +// - os.{kernel,release,version} and cpu.arch, +// set from uname() syscall results +// - libvirt.driver set from a command line arg +type ValueBuiltIn struct { +} + +// Sets a value from a command. +// +// The name is the binary command name, either +// unqualified and resolved against $PATH, or +// or fully qualified +// +// A command can be given an arbitray set of +// arguments when run +// +// By default the entire contents of stdout +// will be set as the fact value. +// +// It is possible to instead parse the stdout +// data to extract interesting pieces of information +// from it +type ValueCommand struct { + Name string `xml:"name,attr"` + Args []ValueCommandArg `xml:"arg"` + Parse *Parse `xml:"parse"` +} + +type ValueCommandArg struct { + Val string `xml:"val,attr"` +} + +// Sets a value from a file contents +// +// The path is the fully qualified filename path +// +// By default the entire contents of the file +// will be set as the fact value. +// +// It is possible to instead parse the file +// data to extract interesting pieces of information +// from it +type ValueFile struct { + Path string `xml:"path,attr"` + Parse *Parse `xml:"parse"` + IgnoreMissing bool `xml:"ignoreMissing,attr,omitempty"` +} + +// Sets a value from another fact +// +// The fact is the name of the other fact +// +// By default the entire contents of the other fact +// will be set as the fact value. +// +// More usually though the other fact value will be +// parsed to extract interesting pieces of information +// from it +type ValueString struct { + Fact string `xml:"fact,attr"` + Parse *Parse `xml:"parse"` +} + +// Sets a value from a list of directory entries +// +// The path is the fully qualified path of the directory +// +// By default an error will be raised if the directory +// does not exist. Typically a filter rule would be +// desired to skip processing of the fact in cases +// where it is known the directory may not exist. +// +// If filters are not practical though, missing directory +// can be made non-fatal +type ValueDirEnt struct { + Path string `xml:"path,attr"` + IgnoreMissing bool `xml:"ignoreMissing,attr,omitempty"` +} + +// Sets a value from the access permissions of a file +// +// The path is the fully qualified path of the file +// +// The check can be one of the strings 'readable', +// 'writable' or 'executable'. +type ValueAccess struct { + Path string `xml:"path,attr"` + Check string `xml:"check,attr"` +} + +// The parse object defines a set of rules for +// parsing strings to extract interesting +// pieces of data +// +// The optional whitespace attribute can be set to +// 'trim' to cause leading & trailing whitespace to +// be removed before further processing +// +// To extract a single data item from the string, +// the scalar parsing rule can be used +// +// To extract an ordered list of data items from +// the string, the list parsing rule can be used +// +// To extract an unordered list of data items, +// with duplicates excluded, the set parsing rule +// can be used +// +// To extract a list of key, value pairs from the +// string, the dict parsing rule can be used +type Parse struct { + Whitespace string `xml:"whitespace,attr,omitempty"` + Scalar *ParseScalar `xml:"-"` + List *ParseList `xml:"-"` + Set *ParseSet `xml:"-"` + Dict *ParseDict `xml:"-"` +} + +// Parsing a string to extract a single data item +// using a regular expression. +// +// The regular expression should contain at least +// one capturing group. The match attribute indicates +// which capturing group will be used to set the +// fact value. +type ParseScalar struct { + Regex string `xml:"regex,attr,omitempty"` + Match uint `xml:"match,attr,omitempty"` +} + +// Parsing a string to extract an ordered list of +// data items +// +// The separator declares the boundary on which +// the string will be split +// +// The skip head attribute should be non-zero if +// any leading elements in the list are to be +// discarded. This is typically useful if the +// list contains a header/label as the first +// entry +// +// The skip tail attribute should be non-zero if +// any trailing elements in the list are to be +// discarded +// +// The limit attribute sets an upper bound on +// the number of elements that will be kept in +// the list. It is applied after discarding any +// leading or trailing elements. +// +// Each element in the list is then itself parsed +type ParseList struct { + Separator string `xml:"separator,attr"` + SkipHead uint `xml:"skiphead,attr,omitempty"` + SkipTail uint `xml:"skiptail,attr,omitempty"` + Limit uint `xml:"limit,attr,omitempty"` + Parse *Parse `xml:"parse"` +} + +// Parsing a string to extract an unordered list of +// data items with duplicates removed +// +// The separator declares the boundary on which +// the string will be split +// +// The skip head attribute should be non-zero if +// any leading elements in the list are to be +// discarded. This is typically useful if the +// list contains a header/label as the first +// entry +// +// The skip tail attribute should be non-zero if +// any trailing elements in the list are to be +// discarded +// +// Each element is then parsed using a regular +// expression +// +// The regular expression should contain at least +// one capturing group. The match attribute indicates +// which capturing group will be used to set the +// fact value. +type ParseSet struct { + Separator string `xml:"separator,attr"` + SkipHead uint `xml:"skiphead,attr,omitempty"` + SkipTail uint `xml:"skiptail,attr,omitempty"` + Regex string `xml:"regex,attr,omitempty"` + Match uint `xml:"match,attr,omitempty"` +} + +// Parsing a string to extract an unordered list of +// data items with duplicates removed +// +// The separator declares the boundary on which +// the string will be split to acquire the list +// of pairs +// +// The delimiter declares the boundary to separate +// the key from the value +// +// The value is then further parsed with the declared +// rules +type ParseDict struct { + Separator string `xml:"separator,attr"` + Delimiter string `xml:"delimiter,attr"` + Parse *Parse `xml:"parse"` +} + +func getAttr(attrs []xml.Attr, name string) (string, bool) { + for _, attr :=3D range attrs { + if attr.Name.Local =3D=3D name { + return attr.Value, true + } + } + return "", false +} + +type parse Parse + +type parseScalar struct { + ParseScalar + parse +} + +type parseList struct { + ParseList + parse +} + +type parseSet struct { + ParseSet + parse +} + +type parseDict struct { + ParseDict + parse +} + +// Custom XML generator which ensures that only one of +// the parse rules will be output +func (p *Parse) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name.Local =3D "parse" + if p.List !=3D nil { + start.Attr =3D append(start.Attr, xml.Attr{ + xml.Name{Local: "format"}, "list", + }) + pl :=3D parseList{} + pl.parse =3D parse(*p) + pl.ParseList =3D *p.List + return e.EncodeElement(pl, start) + } else if p.Set !=3D nil { + start.Attr =3D append(start.Attr, xml.Attr{ + xml.Name{Local: "format"}, "set", + }) + ps :=3D parseSet{} + ps.parse =3D parse(*p) + ps.ParseSet =3D *p.Set + return e.EncodeElement(ps, start) + } else if p.Dict !=3D nil { + start.Attr =3D append(start.Attr, xml.Attr{ + xml.Name{Local: "format"}, "dict", + }) + pd :=3D parseDict{} + pd.parse =3D parse(*p) + pd.ParseDict =3D *p.Dict + return e.EncodeElement(pd, start) + } else if p.Scalar !=3D nil { + start.Attr =3D append(start.Attr, xml.Attr{ + xml.Name{Local: "format"}, "scalar", + }) + ps :=3D parseScalar{} + ps.parse =3D parse(*p) + ps.ParseScalar =3D *p.Scalar + return e.EncodeElement(ps, start) + } else { + return fmt.Errorf("Either ParseList or ParseDict must be non-nil") + } +} + +// Custom XML parser which ensures that only one of +// the parse rules will be filled in +func (p *Parse) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error= { + format, ok :=3D getAttr(start.Attr, "format") + if !ok { + return fmt.Errorf("Missing format attribute on ") + } + if format =3D=3D "list" { + pl :=3D parseList{} + err :=3D d.DecodeElement(&pl, &start) + if err !=3D nil { + return err + } + *p =3D Parse(pl.parse) + p.List =3D &pl.ParseList + return nil + } else if format =3D=3D "set" { + ps :=3D parseSet{} + err :=3D d.DecodeElement(&ps, &start) + if err !=3D nil { + return err + } + *p =3D Parse(ps.parse) + p.Set =3D &ps.ParseSet + return nil + } else if format =3D=3D "dict" { + pd :=3D parseDict{} + err :=3D d.DecodeElement(&pd, &start) + if err !=3D nil { + return err + } + *p =3D Parse(pd.parse) + p.Dict =3D &pd.ParseDict + return nil + } else if format =3D=3D "scalar" { + ps :=3D parseScalar{} + err :=3D d.DecodeElement(&ps, &start) + if err !=3D nil { + return err + } + *p =3D Parse(ps.parse) + p.Scalar =3D &ps.ParseScalar + return nil + } else { + return fmt.Errorf("Unknown format '%s' attribute on ", format) + } +} + +// Custom XML generator which ensures that only one of +// the expression rules will be output +func (m *ExpressionCompound) MarshalXML(e *xml.Encoder, start xml.StartEle= ment) error { + e.EncodeToken(start) + for _, match :=3D range m.Expressions { + if match.Any !=3D nil { + el :=3D xml.StartElement{ + Name: xml.Name{Local: "any"}, + } + e.EncodeElement(match.Any, el) + } else if match.All !=3D nil { + el :=3D xml.StartElement{ + Name: xml.Name{Local: "all"}, + } + e.EncodeElement(match.All, el) + } else if match.Fact !=3D nil { + el :=3D xml.StartElement{ + Name: xml.Name{Local: "fact"}, + } + e.EncodeElement(match.Fact, el) + } else { + return fmt.Errorf("Expected Any or All or Fact to be set") + } + } + e.EncodeToken(start.End()) + return nil +} + +// Custom XML parser which ensures that only one of +// the expression rules will be filled in +func (m *ExpressionCompound) UnmarshalXML(d *xml.Decoder, start xml.StartE= lement) error { + for { + tok, err :=3D d.Token() + if err =3D=3D io.EOF { + break + } + if err !=3D nil { + return err + } + + switch tok :=3D tok.(type) { + case xml.StartElement: + el :=3D Expression{} + if tok.Name.Local =3D=3D "any" { + el.Any =3D &ExpressionCompound{} + err =3D d.DecodeElement(el.Any, &tok) + if err !=3D nil { + return err + } + } else if tok.Name.Local =3D=3D "all" { + el.All =3D &ExpressionCompound{} + err =3D d.DecodeElement(el.All, &tok) + if err !=3D nil { + return err + } + } else if tok.Name.Local =3D=3D "fact" { + el.Fact =3D &ExpressionFact{} + err =3D d.DecodeElement(el.Fact, &tok) + if err !=3D nil { + return err + } + } + m.Expressions =3D append(m.Expressions, el) + } + } + return nil +} + +// Helper for parsing a string containing an XML +// doc defining a list of facts +func (f *FactList) Unmarshal(doc string) error { + return xml.Unmarshal([]byte(doc), f) +} + +// Helper for formatting a string to contain an +// XML doc for the list of facts +func (f *FactList) Marshal() (string, error) { + doc, err :=3D xml.MarshalIndent(f, "", " ") + if err !=3D nil { + return "", err + } + return string(doc), nil +} + +// Create a new fact list, loading from the +// specified plain file +func NewFactList(filename string) (*FactList, error) { + xmlstr, err :=3D ioutil.ReadFile(filename) + if err !=3D nil { + return nil, err + } + + facts :=3D &FactList{} + err =3D facts.Unmarshal(string(xmlstr)) + if err !=3D nil { + return nil, err + } + + return facts, nil +} + +// Used to ensure that no fact has a name which is a sub-string of +// another fact. +func validateNames(names map[string]*Fact) error { + for name, _ :=3D range names { + bits :=3D strings.Split(name, ".") + subname :=3D "" + for _, bit :=3D range bits[0 : len(bits)-1] { + if subname =3D=3D "" { + subname =3D bit + } else { + subname =3D subname + "." + bit + } + _, ok :=3D names[subname] + + if ok { + return fmt.Errorf("Fact name '%s' has fact '%s' as a substring", + name, subname) + } + } + } + + return nil +} + +// Identify the name of the corresponding fact that +// is referenced, by chopping off suffixes until a +// match is found +func findFactReference(names map[string]*Fact, name string) (string, error= ) { + bits :=3D strings.Split(name, ".") + subname :=3D "" + for _, bit :=3D range bits { + if subname =3D=3D "" { + subname =3D bit + } else { + subname =3D subname + "." + bit + } + _, ok :=3D names[subname] + if ok { + return subname, nil + } + } + + return "", fmt.Errorf("Cannot find fact providing %s", name) +} + +// Build up a list of dependant facts referenced by an expression +func addDepsExpr(deps *map[string][]string, names map[string]*Fact, fact *= Fact, expr *Expression) error { + if expr.Any !=3D nil { + for _, sub :=3D range expr.Any.Expressions { + err :=3D addDepsExpr(deps, names, fact, &sub) + if err !=3D nil { + return err + } + } + } else if expr.All !=3D nil { + for _, sub :=3D range expr.All.Expressions { + err :=3D addDepsExpr(deps, names, fact, &sub) + if err !=3D nil { + return err + } + } + } else if expr.Fact !=3D nil { + ref, err :=3D findFactReference(names, expr.Fact.Name) + if err !=3D nil { + return err + } + entries, _ :=3D (*deps)[fact.Name] + entries =3D append(entries, ref) + (*deps)[fact.Name] =3D entries + } + return nil +} + +// Build up a list of dependancies between facts +func addDeps(deps *map[string][]string, names map[string]*Fact, fact *Fact= ) error { + if fact.Filter !=3D nil { + err :=3D addDepsExpr(deps, names, fact, fact.Filter) + if err !=3D nil { + return err + } + } + if fact.Value.Bool !=3D nil { + err :=3D addDepsExpr(deps, names, fact, fact.Value.Bool) + if err !=3D nil { + return err + } + } + if fact.Value.String !=3D nil { + ref, err :=3D findFactReference(names, fact.Value.String.Fact) + if err !=3D nil { + return err + } + entries, _ :=3D (*deps)[fact.Name] + entries =3D append(entries, ref) + (*deps)[fact.Name] =3D entries + } + return nil +} + +// Perform a topological sort on facts so that they can be +// processed in the order required to satisfy dependancies +// between facts +func (facts *FactList) Sort() error { + deps :=3D make(map[string][]string) + names :=3D make(map[string]*Fact) + + var remaining []string + for _, fact :=3D range facts.Facts { + deps[fact.Name] =3D []string{} + names[fact.Name] =3D fact + remaining =3D append(remaining, fact.Name) + } + + err :=3D validateNames(names) + if err !=3D nil { + return err + } + + for _, fact :=3D range facts.Facts { + err =3D addDeps(&deps, names, fact) + if err !=3D nil { + return err + } + } + + var sorted []string + done :=3D make(map[string]bool) + for len(remaining) > 0 { + prev_done :=3D len(done) + var skipped []string + for _, fact :=3D range remaining { + using, ok :=3D deps[fact] + if !ok { + done[fact] =3D true + sorted =3D append(sorted, fact) + } else { + unsolved :=3D false + for _, entry :=3D range using { + _, ok :=3D done[entry] + if !ok { + unsolved =3D true + break + } + } + if !unsolved { + sorted =3D append(sorted, fact) + done[fact] =3D true + } else { + skipped =3D append(skipped, fact) + } + } + } + + if len(done) =3D=3D prev_done { + return fmt.Errorf("Cycle detected in facts") + } + + remaining =3D skipped + } + + var newfacts []*Fact + for _, name :=3D range sorted { + newfacts =3D append(newfacts, names[name]) + } + + facts.Facts =3D newfacts + + return nil +} + +// Create a new fact list that contains all the facts +// from the passed in list of fact lists +func MergeFactLists(lists []FactList) FactList { + var allfacts []*Fact + for _, list :=3D range lists { + for _, fact :=3D range list.Facts { + allfacts =3D append(allfacts, fact) + } + } + + facts :=3D FactList{} + facts.Facts =3D allfacts + return facts +} diff --git a/tools/host-validate/pkg/facts_test.go b/tools/host-validate/pk= g/facts_test.go new file mode 100644 index 0000000000..53ed2842c3 --- /dev/null +++ b/tools/host-validate/pkg/facts_test.go @@ -0,0 +1,67 @@ +/* + * This file is part of the libvirt project + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Copyright (C) 2019 Red Hat, Inc. + * + */ + +package pkg + +import ( + "fmt" + "io/ioutil" + "path" + "strings" + "testing" +) + +func testXMLFile(t *testing.T, filename string) { + xml, err :=3D ioutil.ReadFile(filename) + if err !=3D nil { + t.Fatal(err) + } + + doc :=3D &FactList{} + + err =3D doc.Unmarshal(string(xml)) + if err !=3D nil { + t.Fatal(fmt.Errorf("Cannot parse %s: %s", filename, err)) + } + + newxml, err :=3D doc.Marshal() + if err !=3D nil { + t.Fatal(fmt.Errorf("Cannot format %s: %s", filename, err)) + } + + err =3D testCompareXML(filename, string(xml), newxml, nil, nil) + if err !=3D nil { + t.Fatal(fmt.Errorf("Cannot roundtrip %s: %s", filename, err)) + } +} + +func TestRoundTrip(t *testing.T) { + dir :=3D path.Join("..", "rules") + files, err :=3D ioutil.ReadDir(dir) + if err !=3D nil { + t.Fatal(fmt.Errorf("Cannot read %s: %s", dir, err)) + } + for _, file :=3D range files { + if strings.HasSuffix(file.Name(), ".xml") { + testXMLFile(t, path.Join(dir, file.Name())) + } + } +} diff --git a/tools/host-validate/pkg/xml_utils.go b/tools/host-validate/pkg= /xml_utils.go new file mode 100644 index 0000000000..b779e753f3 --- /dev/null +++ b/tools/host-validate/pkg/xml_utils.go @@ -0,0 +1,287 @@ +/* + * This file is part of the libvirt project + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Copyright (C) 2019 Red Hat, Inc. + * + */ + +package pkg + +import ( + "encoding/xml" + "fmt" + "strconv" + "strings" +) + +type element struct { + XMLNS string + Name string + Attrs map[string]string + Content string + Children []*element +} + +type elementstack []*element + +func (s *elementstack) push(v *element) { + *s =3D append(*s, v) +} + +func (s *elementstack) pop() *element { + res :=3D (*s)[len(*s)-1] + *s =3D (*s)[:len(*s)-1] + return res +} + +func getNamespaceURI(xmlnsMap map[string]string, xmlns string, name xml.Na= me) string { + if name.Space !=3D "" { + uri, ok :=3D xmlnsMap[name.Space] + if !ok { + return "undefined://" + name.Space + } else { + return uri + } + } else { + return xmlns + } +} + +func xmlName(xmlns string, name xml.Name) string { + if xmlns =3D=3D "" { + return name.Local + } + return name.Local + "(" + xmlns + ")" +} + +func loadXML(xmlstr string, ignoreNSDecl bool) (*element, error) { + xmlnsMap :=3D make(map[string]string) + xmlr :=3D strings.NewReader(xmlstr) + + d :=3D xml.NewDecoder(xmlr) + var root *element + stack :=3D elementstack{} + for { + t, err :=3D d.RawToken() + if err !=3D nil { + return nil, err + } + + var parent *element + if root !=3D nil { + if len(stack) =3D=3D 0 { + return nil, fmt.Errorf("Unexpectedly empty stack") + } + parent =3D stack[len(stack)-1] + } + + switch t :=3D t.(type) { + case xml.StartElement: + xmlns :=3D "" + if parent !=3D nil { + xmlns =3D parent.XMLNS + } + for _, a :=3D range t.Attr { + if a.Name.Space =3D=3D "xmlns" { + xmlnsMap[a.Name.Local] =3D a.Value + } else if a.Name.Space =3D=3D "" && a.Name.Local =3D=3D "xmlns" { + xmlns =3D a.Value + } + } + xmlns =3D getNamespaceURI(xmlnsMap, xmlns, t.Name) + child :=3D &element{ + XMLNS: xmlns, + Name: xmlName(xmlns, t.Name), + Attrs: make(map[string]string), + } + + for _, a :=3D range t.Attr { + if a.Name.Space =3D=3D "xmlns" { + continue + } + if a.Name.Space =3D=3D "" && a.Name.Local =3D=3D "xmlns" { + continue + } + attrNS :=3D getNamespaceURI(xmlnsMap, "", a.Name) + child.Attrs[xmlName(attrNS, a.Name)] =3D a.Value + } + stack.push(child) + if root =3D=3D nil { + root =3D child + } else { + parent.Children =3D append(parent.Children, child) + parent.Content =3D "" + } + case xml.EndElement: + stack.pop() + case xml.CharData: + if parent !=3D nil && len(parent.Children) =3D=3D 0 { + val :=3D string(t) + if strings.TrimSpace(val) !=3D "" { + parent.Content =3D val + } + } + } + + if root !=3D nil && len(stack) =3D=3D 0 { + break + } + } + + return root, nil +} + +func testCompareValue(filename, path, key, expected, actual string) error { + if expected =3D=3D actual { + return nil + } + + i1, err1 :=3D strconv.ParseInt(expected, 0, 64) + i2, err2 :=3D strconv.ParseInt(actual, 0, 64) + if err1 =3D=3D nil && err2 =3D=3D nil && i1 =3D=3D i2 { + return nil + } + path =3D path + "/@" + key + return fmt.Errorf("%s: %s: attribute actual value '%s' does not match exp= ected value '%s'", + filename, path, actual, expected) +} + +func testCompareElement(filename, expectPath, actualPath string, expect, a= ctual *element, extraExpectNodes, extraActualNodes map[string]bool) error { + if expect.Name !=3D actual.Name { + return fmt.Errorf("%s: name '%s' doesn't match '%s'", + expectPath, expect.Name, actual.Name) + } + + expectAttr :=3D expect.Attrs + for key, val :=3D range actual.Attrs { + expectval, ok :=3D expectAttr[key] + if !ok { + attrPath :=3D actualPath + "/@" + key + if _, ok :=3D extraActualNodes[attrPath]; ok { + continue + } + return fmt.Errorf("%s: %s: attribute in actual XML missing in expected = XML", + filename, attrPath) + } + err :=3D testCompareValue(filename, actualPath, key, expectval, val) + if err !=3D nil { + return err + } + delete(expectAttr, key) + } + for key, _ :=3D range expectAttr { + attrPath :=3D expectPath + "/@" + key + if _, ok :=3D extraExpectNodes[attrPath]; ok { + continue + } + return fmt.Errorf("%s: %s: attribute '%s' in expected XML missing in ac= tual XML", + filename, attrPath, expectAttr[key]) + } + + if expect.Content !=3D actual.Content { + return fmt.Errorf("%s: %s: actual content '%s' does not match expected '= %s'", + filename, actualPath, actual.Content, expect.Content) + } + + used :=3D make([]bool, len(actual.Children)) + expectChildIndexes :=3D make(map[string]uint) + actualChildIndexes :=3D make(map[string]uint) + for _, expectChild :=3D range expect.Children { + expectIndex, _ :=3D expectChildIndexes[expectChild.Name] + expectChildIndexes[expectChild.Name] =3D expectIndex + 1 + subExpectPath :=3D fmt.Sprintf("%s/%s[%d]", expectPath, expectChild.Name= , expectIndex) + + var actualChild *element =3D nil + for i :=3D 0; i < len(used); i++ { + if !used[i] && actual.Children[i].Name =3D=3D expectChild.Name { + actualChild =3D actual.Children[i] + used[i] =3D true + break + } + } + if actualChild =3D=3D nil { + if _, ok :=3D extraExpectNodes[subExpectPath]; ok { + continue + } + return fmt.Errorf("%s: %s: element in expected XML missing in actual XM= L", + filename, subExpectPath) + } + + actualIndex, _ :=3D actualChildIndexes[actualChild.Name] + actualChildIndexes[actualChild.Name] =3D actualIndex + 1 + subActualPath :=3D fmt.Sprintf("%s/%s[%d]", actualPath, actualChild.Name= , actualIndex) + + err :=3D testCompareElement(filename, subExpectPath, subActualPath, expe= ctChild, actualChild, extraExpectNodes, extraActualNodes) + if err !=3D nil { + return err + } + } + + actualChildIndexes =3D make(map[string]uint) + for i, actualChild :=3D range actual.Children { + actualIndex, _ :=3D actualChildIndexes[actualChild.Name] + actualChildIndexes[actualChild.Name] =3D actualIndex + 1 + if used[i] { + continue + } + subActualPath :=3D fmt.Sprintf("%s/%s[%d]", actualPath, actualChild.Name= , actualIndex) + + if _, ok :=3D extraActualNodes[subActualPath]; ok { + continue + } + return fmt.Errorf("%s: %s: element in actual XML missing in expected XML= ", + filename, subActualPath) + } + + return nil +} + +func makeExtraNodeMap(nodes []string) map[string]bool { + ret :=3D make(map[string]bool) + for _, node :=3D range nodes { + ret[node] =3D true + } + return ret +} + +func testCompareXML(filename, expectStr, actualStr string, extraExpectNode= s, extraActualNodes []string) error { + extraExpectNodeMap :=3D makeExtraNodeMap(extraExpectNodes) + extraActualNodeMap :=3D makeExtraNodeMap(extraActualNodes) + + //fmt.Printf("%s\n", expectedstr) + expectRoot, err :=3D loadXML(expectStr, true) + if err !=3D nil { + return err + } + //fmt.Printf("%s\n", actualstr) + actualRoot, err :=3D loadXML(actualStr, true) + if err !=3D nil { + return err + } + + if expectRoot.Name !=3D actualRoot.Name { + return fmt.Errorf("%s: /: expected root element '%s' does not match actu= al '%s'", + filename, expectRoot.Name, actualRoot.Name) + } + + err =3D testCompareElement(filename, "/"+expectRoot.Name+"[0]", "/"+actua= lRoot.Name+"[0]", expectRoot, actualRoot, extraExpectNodeMap, extraActualNo= deMap) + if err !=3D nil { + return err + } + + return nil +} diff --git a/tools/host-validate/rules/acpi.xml b/tools/host-validate/rules= /acpi.xml new file mode 100644 index 0000000000..c02863954f --- /dev/null +++ b/tools/host-validate/rules/acpi.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/host-validate/rules/builtin.xml b/tools/host-validate/ru= les/builtin.xml new file mode 100644 index 0000000000..4e3cdeb61e --- /dev/null +++ b/tools/host-validate/rules/builtin.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/host-validate/rules/cgroups.xml b/tools/host-validate/ru= les/cgroups.xml new file mode 100644 index 0000000000..f69a13f5a1 --- /dev/null +++ b/tools/host-validate/rules/cgroups.xml @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/host-validate/rules/cpu.xml b/tools/host-validate/rules/= cpu.xml new file mode 100644 index 0000000000..024cdae20c --- /dev/null +++ b/tools/host-validate/rules/cpu.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/host-validate/rules/devices.xml b/tools/host-validate/ru= les/devices.xml new file mode 100644 index 0000000000..8e105dc661 --- /dev/null +++ b/tools/host-validate/rules/devices.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/host-validate/rules/freebsd-kernel.xml b/tools/host-vali= date/rules/freebsd-kernel.xml new file mode 100644 index 0000000000..513a394266 --- /dev/null +++ b/tools/host-validate/rules/freebsd-kernel.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/tools/host-validate/rules/iommu.xml b/tools/host-validate/rule= s/iommu.xml new file mode 100644 index 0000000000..8d33509b53 --- /dev/null +++ b/tools/host-validate/rules/iommu.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/host-validate/rules/namespaces.xml b/tools/host-validate= /rules/namespaces.xml new file mode 100644 index 0000000000..ea1ceb6674 --- /dev/null +++ b/tools/host-validate/rules/namespaces.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/host-validate/rules/pci.xml b/tools/host-validate/rules/= pci.xml new file mode 100644 index 0000000000..2ed2e295eb --- /dev/null +++ b/tools/host-validate/rules/pci.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tools/virt-host-validate-bhyve.c b/tools/virt-host-validate-bh= yve.c deleted file mode 100644 index 2f0ec1e36c..0000000000 --- a/tools/virt-host-validate-bhyve.c +++ /dev/null @@ -1,77 +0,0 @@ -/* - * virt-host-validate-bhyve.c: Sanity check a bhyve hypervisor host - * - * Copyright (C) 2017 Roman Bogorodskiy - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - */ - -#include - -#include -#include - -#include "virt-host-validate-bhyve.h" -#include "virt-host-validate-common.h" - -#define MODULE_STATUS(mod, err_msg, err_code) \ - virHostMsgCheck("BHYVE", _("for %s module"), #mod); \ - if (mod ## _loaded) { \ - virHostMsgPass(); \ - } else { \ - virHostMsgFail(err_code, \ - _("%s module is not loaded, " err_msg), \ - #mod); \ - ret =3D -1; \ - } - -#define MODULE_STATUS_FAIL(mod, err_msg) \ - MODULE_STATUS(mod, err_msg, VIR_HOST_VALIDATE_FAIL) - -#define MODULE_STATUS_WARN(mod, err_msg) \ - MODULE_STATUS(mod, err_msg, VIR_HOST_VALIDATE_WARN) - - -int virHostValidateBhyve(void) -{ - int ret =3D 0; - int fileid =3D 0; - struct kld_file_stat stat; - bool vmm_loaded =3D false, if_tap_loaded =3D false; - bool if_bridge_loaded =3D false, nmdm_loaded =3D false; - - for (fileid =3D kldnext(0); fileid > 0; fileid =3D kldnext(fileid)) { - stat.version =3D sizeof(struct kld_file_stat); - if (kldstat(fileid, &stat) < 0) - continue; - - if (STREQ(stat.name, "vmm.ko")) - vmm_loaded =3D true; - else if (STREQ(stat.name, "if_tap.ko")) - if_tap_loaded =3D true; - else if (STREQ(stat.name, "if_bridge.ko")) - if_bridge_loaded =3D true; - else if (STREQ(stat.name, "nmdm.ko")) - nmdm_loaded =3D true; - } - - MODULE_STATUS_FAIL(vmm, "will not be able to start VMs"); - MODULE_STATUS_WARN(if_tap, "networking will not work"); - MODULE_STATUS_WARN(if_bridge, "bridged networking will not work"); - MODULE_STATUS_WARN(nmdm, "nmdm console will not work"); - - return ret; -} diff --git a/tools/virt-host-validate-bhyve.h b/tools/virt-host-validate-bh= yve.h deleted file mode 100644 index a5fd22c871..0000000000 --- a/tools/virt-host-validate-bhyve.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * virt-host-validate-bhyve.h: Sanity check a bhyve hypervisor host - * - * Copyright (C) 2017 Roman Bogorodskiy - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - */ - -#pragma once - -int virHostValidateBhyve(void); diff --git a/tools/virt-host-validate-common.c b/tools/virt-host-validate-c= ommon.c deleted file mode 100644 index 804c0adc2d..0000000000 --- a/tools/virt-host-validate-common.c +++ /dev/null @@ -1,419 +0,0 @@ -/* - * virt-host-validate-common.c: Sanity check helper APIs - * - * Copyright (C) 2012, 2014 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - */ - -#include - -#include -#include -#include -#include - -#include "viralloc.h" -#include "vircgroup.h" -#include "virfile.h" -#include "virt-host-validate-common.h" -#include "virstring.h" -#include "virarch.h" - -#define VIR_FROM_THIS VIR_FROM_NONE - -VIR_ENUM_IMPL(virHostValidateCPUFlag, - VIR_HOST_VALIDATE_CPU_FLAG_LAST, - "vmx", - "svm", - "sie"); - -static bool quiet; - -void virHostMsgSetQuiet(bool quietFlag) -{ - quiet =3D quietFlag; -} - -void virHostMsgCheck(const char *prefix, - const char *format, - ...) -{ - va_list args; - char *msg; - - if (quiet) - return; - - va_start(args, format); - if (virVasprintf(&msg, format, args) < 0) { - perror("malloc"); - abort(); - } - va_end(args); - - fprintf(stdout, _("%6s: Checking %-60s: "), prefix, msg); - VIR_FREE(msg); -} - -static bool virHostMsgWantEscape(void) -{ - static bool detectTty =3D true; - static bool wantEscape; - if (detectTty) { - if (isatty(STDOUT_FILENO)) - wantEscape =3D true; - detectTty =3D false; - } - return wantEscape; -} - -void virHostMsgPass(void) -{ - if (quiet) - return; - - if (virHostMsgWantEscape()) - fprintf(stdout, "\033[32m%s\033[0m\n", _("PASS")); - else - fprintf(stdout, "%s\n", _("PASS")); -} - - -static const char * failMessages[] =3D { - N_("FAIL"), - N_("WARN"), - N_("NOTE"), -}; - -verify(ARRAY_CARDINALITY(failMessages) =3D=3D VIR_HOST_VALIDATE_LAST); - -static const char *failEscapeCodes[] =3D { - "\033[31m", - "\033[33m", - "\033[34m", -}; - -verify(ARRAY_CARDINALITY(failEscapeCodes) =3D=3D VIR_HOST_VALIDATE_LAST); - -void virHostMsgFail(virHostValidateLevel level, - const char *format, - ...) -{ - va_list args; - char *msg; - - if (quiet) - return; - - va_start(args, format); - if (virVasprintf(&msg, format, args) < 0) { - perror("malloc"); - abort(); - } - va_end(args); - - if (virHostMsgWantEscape()) - fprintf(stdout, "%s%s\033[0m (%s)\n", - failEscapeCodes[level], _(failMessages[level]), msg); - else - fprintf(stdout, "%s (%s)\n", - _(failMessages[level]), msg); - VIR_FREE(msg); -} - - -int virHostValidateDeviceExists(const char *hvname, - const char *dev_name, - virHostValidateLevel level, - const char *hint) -{ - virHostMsgCheck(hvname, "if device %s exists", dev_name); - - if (access(dev_name, F_OK) < 0) { - virHostMsgFail(level, "%s", hint); - return -1; - } - - virHostMsgPass(); - return 0; -} - - -int virHostValidateDeviceAccessible(const char *hvname, - const char *dev_name, - virHostValidateLevel level, - const char *hint) -{ - virHostMsgCheck(hvname, "if device %s is accessible", dev_name); - - if (access(dev_name, R_OK|W_OK) < 0) { - virHostMsgFail(level, "%s", hint); - return -1; - } - - virHostMsgPass(); - return 0; -} - - -int virHostValidateNamespace(const char *hvname, - const char *ns_name, - virHostValidateLevel level, - const char *hint) -{ - virHostMsgCheck(hvname, "for namespace %s", ns_name); - char nspath[100]; - - snprintf(nspath, sizeof(nspath), "/proc/self/ns/%s", ns_name); - - if (access(nspath, F_OK) < 0) { - virHostMsgFail(level, "%s", hint); - return -1; - } - - virHostMsgPass(); - return 0; -} - - -virBitmapPtr virHostValidateGetCPUFlags(void) -{ - FILE *fp; - virBitmapPtr flags =3D NULL; - - if (!(fp =3D fopen("/proc/cpuinfo", "r"))) - return NULL; - - if (!(flags =3D virBitmapNewQuiet(VIR_HOST_VALIDATE_CPU_FLAG_LAST))) - goto cleanup; - - do { - char line[1024]; - char *start; - char **tokens; - size_t ntokens; - size_t i; - - if (!fgets(line, sizeof(line), fp)) - break; - - /* The line we're interested in is marked differently depending - * on the architecture, so check possible prefixes */ - if (!STRPREFIX(line, "flags") && - !STRPREFIX(line, "Features") && - !STRPREFIX(line, "features")) - continue; - - /* fgets() includes the trailing newline in the output buffer, - * so we need to clean that up ourselves. We can safely access - * line[strlen(line) - 1] because the checks above would cause - * us to skip empty strings */ - line[strlen(line) - 1] =3D '\0'; - - /* Skip to the separator */ - if (!(start =3D strchr(line, ':'))) - continue; - - /* Split the line using " " as a delimiter. The first token - * will always be ":", but that's okay */ - if (!(tokens =3D virStringSplitCount(start, " ", 0, &ntokens))) - continue; - - /* Go through all flags and check whether one of those we - * might want to check for later on is present; if that's - * the case, set the relevant bit in the bitmap */ - for (i =3D 0; i < ntokens; i++) { - int value; - - if ((value =3D virHostValidateCPUFlagTypeFromString(tokens[i])= ) >=3D 0) - ignore_value(virBitmapSetBit(flags, value)); - } - - virStringListFreeCount(tokens, ntokens); - } while (1); - - cleanup: - VIR_FORCE_FCLOSE(fp); - - return flags; -} - - -int virHostValidateLinuxKernel(const char *hvname, - int version, - virHostValidateLevel level, - const char *hint) -{ - struct utsname uts; - unsigned long thisversion; - - uname(&uts); - - virHostMsgCheck(hvname, _("for Linux >=3D %d.%d.%d"), - ((version >> 16) & 0xff), - ((version >> 8) & 0xff), - (version & 0xff)); - - if (STRNEQ(uts.sysname, "Linux")) { - virHostMsgFail(level, "%s", hint); - return -1; - } - - if (virParseVersionString(uts.release, &thisversion, true) < 0) { - virHostMsgFail(level, "%s", hint); - return -1; - } - - if (thisversion < version) { - virHostMsgFail(level, "%s", hint); - return -1; - } else { - virHostMsgPass(); - return 0; - } -} - -#ifdef __linux__ -int virHostValidateCGroupControllers(const char *hvname, - int controllers, - virHostValidateLevel level) -{ - virCgroupPtr group =3D NULL; - int ret =3D 0; - size_t i; - - if (virCgroupNewSelf(&group) < 0) - return -1; - - for (i =3D 0; i < VIR_CGROUP_CONTROLLER_LAST; i++) { - int flag =3D 1 << i; - const char *cg_name =3D virCgroupControllerTypeToString(i); - - if (!(controllers & flag)) - continue; - - virHostMsgCheck(hvname, "for cgroup '%s' controller support", cg_n= ame); - - if (!virCgroupHasController(group, i)) { - ret =3D -1; - virHostMsgFail(level, "Enable '%s' in kernel Kconfig file or " - "mount/enable cgroup controller in your system", - cg_name); - } else { - virHostMsgPass(); - } - } - - virCgroupFree(&group); - - return ret; -} -#else /* !__linux__ */ -int virHostValidateCGroupControllers(const char *hvname ATTRIBUTE_UNUSED, - int controllers ATTRIBUTE_UNUSED, - virHostValidateLevel level) -{ - virHostMsgFail(level, "%s", "This platform does not support cgroups"); - return -1; -} -#endif /* !__linux__ */ - -int virHostValidateIOMMU(const char *hvname, - virHostValidateLevel level) -{ - virBitmapPtr flags; - struct stat sb; - const char *bootarg =3D NULL; - bool isAMD =3D false, isIntel =3D false; - virArch arch =3D virArchFromHost(); - struct dirent *dent; - DIR *dir; - int rc; - - flags =3D virHostValidateGetCPUFlags(); - - if (flags && virBitmapIsBitSet(flags, VIR_HOST_VALIDATE_CPU_FLAG_VMX)) - isIntel =3D true; - else if (flags && virBitmapIsBitSet(flags, VIR_HOST_VALIDATE_CPU_FLAG_= SVM)) - isAMD =3D true; - - virBitmapFree(flags); - - if (isIntel) { - virHostMsgCheck(hvname, "%s", _("for device assignment IOMMU suppo= rt")); - if (access("/sys/firmware/acpi/tables/DMAR", F_OK) =3D=3D 0) { - virHostMsgPass(); - bootarg =3D "intel_iommu=3Don"; - } else { - virHostMsgFail(level, - "No ACPI DMAR table found, IOMMU either " - "disabled in BIOS or not supported by this " - "hardware platform"); - return -1; - } - } else if (isAMD) { - virHostMsgCheck(hvname, "%s", _("for device assignment IOMMU suppo= rt")); - if (access("/sys/firmware/acpi/tables/IVRS", F_OK) =3D=3D 0) { - virHostMsgPass(); - bootarg =3D "iommu=3Dpt iommu=3D1"; - } else { - virHostMsgFail(level, - "No ACPI IVRS table found, IOMMU either " - "disabled in BIOS or not supported by this " - "hardware platform"); - return -1; - } - } else if (ARCH_IS_PPC64(arch)) { - /* Empty Block */ - } else if (ARCH_IS_S390(arch)) { - /* On s390x, we skip the IOMMU check if there are no PCI - * devices (which is quite usual on s390x). If there are - * no PCI devices the directory is still there but is - * empty. */ - if (!virDirOpen(&dir, "/sys/bus/pci/devices")) - return 0; - rc =3D virDirRead(dir, &dent, NULL); - VIR_DIR_CLOSE(dir); - if (rc <=3D 0) - return 0; - } else { - virHostMsgFail(level, - "Unknown if this platform has IOMMU support"); - return -1; - } - - - /* We can only check on newer kernels with iommu groups & vfio */ - if (stat("/sys/kernel/iommu_groups", &sb) < 0) - return 0; - - if (!S_ISDIR(sb.st_mode)) - return 0; - - virHostMsgCheck(hvname, "%s", _("if IOMMU is enabled by kernel")); - if (sb.st_nlink <=3D 2) { - if (bootarg) - virHostMsgFail(level, - "IOMMU appears to be disabled in kernel. " - "Add %s to kernel cmdline arguments", bootarg); - else - virHostMsgFail(level, "IOMMU capability not compiled into kern= el."); - return -1; - } - virHostMsgPass(); - return 0; -} diff --git a/tools/virt-host-validate-common.h b/tools/virt-host-validate-c= ommon.h deleted file mode 100644 index c4e4fa2175..0000000000 --- a/tools/virt-host-validate-common.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * virt-host-validate-common.h: Sanity check helper APIs - * - * Copyright (C) 2012 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - */ - -#pragma once - -#include "internal.h" -#include "virutil.h" -#include "virbitmap.h" -#include "virenum.h" - -typedef enum { - VIR_HOST_VALIDATE_FAIL, - VIR_HOST_VALIDATE_WARN, - VIR_HOST_VALIDATE_NOTE, - - VIR_HOST_VALIDATE_LAST, -} virHostValidateLevel; - -typedef enum { - VIR_HOST_VALIDATE_CPU_FLAG_VMX =3D 0, - VIR_HOST_VALIDATE_CPU_FLAG_SVM, - VIR_HOST_VALIDATE_CPU_FLAG_SIE, - - VIR_HOST_VALIDATE_CPU_FLAG_LAST, -} virHostValidateCPUFlag; - -VIR_ENUM_DECL(virHostValidateCPUFlag); - -void virHostMsgSetQuiet(bool quietFlag); - -void virHostMsgCheck(const char *prefix, - const char *format, - ...) ATTRIBUTE_FMT_PRINTF(2, 3); - -void virHostMsgPass(void); -void virHostMsgFail(virHostValidateLevel level, - const char *format, - ...) ATTRIBUTE_FMT_PRINTF(2, 3); - -int virHostValidateDeviceExists(const char *hvname, - const char *dev_name, - virHostValidateLevel level, - const char *hint); - -int virHostValidateDeviceAccessible(const char *hvname, - const char *dev_name, - virHostValidateLevel level, - const char *hint); - -virBitmapPtr virHostValidateGetCPUFlags(void); - -int virHostValidateLinuxKernel(const char *hvname, - int version, - virHostValidateLevel level, - const char *hint); - -int virHostValidateNamespace(const char *hvname, - const char *ns_name, - virHostValidateLevel level, - const char *hint); - -int virHostValidateCGroupControllers(const char *hvname, - int controllers, - virHostValidateLevel level); - -int virHostValidateIOMMU(const char *hvname, - virHostValidateLevel level); diff --git a/tools/virt-host-validate-lxc.c b/tools/virt-host-validate-lxc.c deleted file mode 100644 index 8613f37cc7..0000000000 --- a/tools/virt-host-validate-lxc.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * virt-host-validate-lxc.c: Sanity check a LXC hypervisor host - * - * Copyright (C) 2012 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - */ - -#include - -#include "virt-host-validate-lxc.h" -#include "virt-host-validate-common.h" -#include "vircgroup.h" - -int virHostValidateLXC(void) -{ - int ret =3D 0; - - if (virHostValidateLinuxKernel("LXC", (2 << 16) | (6 << 8) | 26, - VIR_HOST_VALIDATE_FAIL, - _("Upgrade to a kernel supporting names= paces")) < 0) - ret =3D -1; - - if (virHostValidateNamespace("LXC", "ipc", - VIR_HOST_VALIDATE_FAIL, - _("IPC namespace support is required")) <= 0) - ret =3D -1; - - if (virHostValidateNamespace("LXC", "mnt", - VIR_HOST_VALIDATE_FAIL, - _("Mount namespace support is required"))= < 0) - ret =3D -1; - - if (virHostValidateNamespace("LXC", "pid", - VIR_HOST_VALIDATE_FAIL, - _("PID namespace support is required")) <= 0) - ret =3D -1; - - if (virHostValidateNamespace("LXC", "uts", - VIR_HOST_VALIDATE_FAIL, - _("UTS namespace support is required")) <= 0) - ret =3D -1; - - if (virHostValidateNamespace("LXC", "net", - VIR_HOST_VALIDATE_WARN, - _("Network namespace support is recommend= ed")) < 0) - ret =3D -1; - - if (virHostValidateNamespace("LXC", "user", - VIR_HOST_VALIDATE_WARN, - _("User namespace support is recommended"= )) < 0) - ret =3D -1; - - if (virHostValidateCGroupControllers("LXC", - (1 << VIR_CGROUP_CONTROLLER_MEMOR= Y) | - (1 << VIR_CGROUP_CONTROLLER_CPU) | - (1 << VIR_CGROUP_CONTROLLER_CPUAC= CT) | - (1 << VIR_CGROUP_CONTROLLER_CPUSE= T) | - (1 << VIR_CGROUP_CONTROLLER_DEVIC= ES) | - (1 << VIR_CGROUP_CONTROLLER_FREEZ= ER) | - (1 << VIR_CGROUP_CONTROLLER_BLKIO= ), - VIR_HOST_VALIDATE_FAIL) < 0) { - ret =3D -1; - } - -#if WITH_FUSE - if (virHostValidateDeviceExists("LXC", "/sys/fs/fuse/connections", - VIR_HOST_VALIDATE_FAIL, - _("Load the 'fuse' module to enable /p= roc/ overrides")) < 0) - ret =3D -1; -#endif - - return ret; -} diff --git a/tools/virt-host-validate-lxc.h b/tools/virt-host-validate-lxc.h deleted file mode 100644 index fefab17552..0000000000 --- a/tools/virt-host-validate-lxc.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * virt-host-validate-lxc.h: Sanity check a LXC hypervisor host - * - * Copyright (C) 2012 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - */ - -#pragma once - -int virHostValidateLXC(void); diff --git a/tools/virt-host-validate-qemu.c b/tools/virt-host-validate-qem= u.c deleted file mode 100644 index ff3c1f0231..0000000000 --- a/tools/virt-host-validate-qemu.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * virt-host-validate-qemu.c: Sanity check a QEMU hypervisor host - * - * Copyright (C) 2012 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - */ - -#include -#include - -#include "virt-host-validate-qemu.h" -#include "virt-host-validate-common.h" -#include "virarch.h" -#include "virbitmap.h" -#include "vircgroup.h" - -int virHostValidateQEMU(void) -{ - virBitmapPtr flags; - int ret =3D 0; - bool hasHwVirt =3D false; - bool hasVirtFlag =3D false; - virArch arch =3D virArchFromHost(); - const char *kvmhint =3D _("Check that CPU and firmware supports virtua= lization " - "and kvm module is loaded"); - - if (!(flags =3D virHostValidateGetCPUFlags())) - return -1; - - switch ((int)arch) { - case VIR_ARCH_I686: - case VIR_ARCH_X86_64: - hasVirtFlag =3D true; - kvmhint =3D _("Check that the 'kvm-intel' or 'kvm-amd' modules are= " - "loaded & the BIOS has enabled virtualization"); - if (virBitmapIsBitSet(flags, VIR_HOST_VALIDATE_CPU_FLAG_SVM) || - virBitmapIsBitSet(flags, VIR_HOST_VALIDATE_CPU_FLAG_VMX)) - hasHwVirt =3D true; - break; - case VIR_ARCH_S390: - case VIR_ARCH_S390X: - hasVirtFlag =3D true; - if (virBitmapIsBitSet(flags, VIR_HOST_VALIDATE_CPU_FLAG_SIE)) - hasHwVirt =3D true; - break; - default: - hasHwVirt =3D false; - } - - if (hasVirtFlag) { - virHostMsgCheck("QEMU", "%s", _("for hardware virtualization")); - if (hasHwVirt) { - virHostMsgPass(); - } else { - virHostMsgFail(VIR_HOST_VALIDATE_FAIL, - _("Only emulated CPUs are available, performanc= e will be significantly limited")); - ret =3D -1; - } - } - - if (hasHwVirt || !hasVirtFlag) { - if (virHostValidateDeviceExists("QEMU", "/dev/kvm", - VIR_HOST_VALIDATE_FAIL, - kvmhint) <0) - ret =3D -1; - else if (virHostValidateDeviceAccessible("QEMU", "/dev/kvm", - VIR_HOST_VALIDATE_FAIL, - _("Check /dev/kvm is worl= d writable or you are in " - "a group that is allowe= d to access it")) < 0) - ret =3D -1; - } - - virBitmapFree(flags); - - if (virHostValidateDeviceExists("QEMU", "/dev/vhost-net", - VIR_HOST_VALIDATE_WARN, - _("Load the 'vhost_net' module to impr= ove performance " - "of virtio networking")) < 0) - ret =3D -1; - - if (virHostValidateDeviceExists("QEMU", "/dev/net/tun", - VIR_HOST_VALIDATE_FAIL, - _("Load the 'tun' module to enable net= working for QEMU guests")) < 0) - ret =3D -1; - - if (virHostValidateCGroupControllers("QEMU", - (1 << VIR_CGROUP_CONTROLLER_MEMOR= Y) | - (1 << VIR_CGROUP_CONTROLLER_CPU) | - (1 << VIR_CGROUP_CONTROLLER_CPUAC= CT) | - (1 << VIR_CGROUP_CONTROLLER_CPUSE= T) | - (1 << VIR_CGROUP_CONTROLLER_DEVIC= ES) | - (1 << VIR_CGROUP_CONTROLLER_BLKIO= ), - VIR_HOST_VALIDATE_WARN) < 0) { - ret =3D -1; - } - - if (virHostValidateIOMMU("QEMU", - VIR_HOST_VALIDATE_WARN) < 0) - ret =3D -1; - - return ret; -} diff --git a/tools/virt-host-validate-qemu.h b/tools/virt-host-validate-qem= u.h deleted file mode 100644 index ddb86aa52c..0000000000 --- a/tools/virt-host-validate-qemu.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * virt-host-validate-qemu.h: Sanity check a QEMU hypervisor host - * - * Copyright (C) 2012 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - */ - -#pragma once - -int virHostValidateQEMU(void); diff --git a/tools/virt-host-validate.c b/tools/virt-host-validate.c deleted file mode 100644 index e797e63475..0000000000 --- a/tools/virt-host-validate.c +++ /dev/null @@ -1,152 +0,0 @@ -/* - * virt-host-validate.c: Sanity check a hypervisor host - * - * Copyright (C) 2012 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - */ - -#include - -#ifdef HAVE_LIBINTL_H -# include -#endif /* HAVE_LIBINTL_H */ -#include - -#include "internal.h" -#include "virgettext.h" - -#include "virt-host-validate-common.h" -#if WITH_QEMU -# include "virt-host-validate-qemu.h" -#endif -#if WITH_LXC -# include "virt-host-validate-lxc.h" -#endif -#if WITH_BHYVE -# include "virt-host-validate-bhyve.h" -#endif - -static void -show_help(FILE *out, const char *argv0) -{ - fprintf(out, - _("\n" - "syntax: %s [OPTIONS] [HVTYPE]\n" - "\n" - " Hypervisor types:\n" - "\n" - " - qemu\n" - " - lxc\n" - " - bhyve\n" - "\n" - " Options:\n" - " -h, --help Display command line help\n" - " -v, --version Display command version\n" - " -q, --quiet Don't display progress information\n" - "\n"), - argv0); -} - -static void -show_version(FILE *out, const char *argv0) -{ - fprintf(out, "version: %s %s\n", argv0, VERSION); -} - -static const struct option argOptions[] =3D { - { "help", 0, NULL, 'h', }, - { "version", 0, NULL, 'v', }, - { "quiet", 0, NULL, 'q', }, - { NULL, 0, NULL, '\0', } -}; - -int -main(int argc, char **argv) -{ - const char *hvname =3D NULL; - int c; - int ret =3D EXIT_SUCCESS; - bool quiet =3D false; - bool usedHvname =3D false; - - if (virGettextInitialize() < 0) - return EXIT_FAILURE; - - while ((c =3D getopt_long(argc, argv, "hvq", argOptions, NULL)) !=3D -= 1) { - switch (c) { - case 'v': - show_version(stdout, argv[0]); - return EXIT_SUCCESS; - - case 'h': - show_help(stdout, argv[0]); - return EXIT_SUCCESS; - - case 'q': - quiet =3D true; - break; - - case '?': - default: - show_help(stderr, argv[0]); - return EXIT_FAILURE; - } - } - - if ((argc-optind) > 2) { - fprintf(stderr, _("%s: too many command line arguments\n"), argv[0= ]); - show_help(stderr, argv[0]); - return EXIT_FAILURE; - } - - if (argc > 1) - hvname =3D argv[optind]; - - virHostMsgSetQuiet(quiet); - -#if WITH_QEMU - if (!hvname || STREQ(hvname, "qemu")) { - usedHvname =3D true; - if (virHostValidateQEMU() < 0) - ret =3D EXIT_FAILURE; - } -#endif - -#if WITH_LXC - if (!hvname || STREQ(hvname, "lxc")) { - usedHvname =3D true; - if (virHostValidateLXC() < 0) - ret =3D EXIT_FAILURE; - } -#endif - -#if WITH_BHYVE - if (!hvname || STREQ(hvname, "bhyve")) { - usedHvname =3D true; - if (virHostValidateBhyve() < 0) - ret =3D EXIT_FAILURE; - } -#endif - - if (hvname && !usedHvname) { - fprintf(stderr, _("%s: unsupported hypervisor name %s\n"), - argv[0], hvname); - return EXIT_FAILURE; - } - - return ret; -} diff --git a/tools/virt-host-validate.pod b/tools/virt-host-validate.pod index 121bb7ed7a..df10530916 100644 --- a/tools/virt-host-validate.pod +++ b/tools/virt-host-validate.pod @@ -19,9 +19,13 @@ to those relevant for that virtualization technology =20 =3Dover 4 =20 -=3Ditem C<-v>, C<--version> +=3Ditem C<-f>, C<--facts> =20 -Display the command version +Display all the key, value pairs set for facts + +=3Ditem C<-r>, C<--rules-dir> + +Override the default location of the XML rule files =20 =3Ditem C<-h>, C<--help> =20 @@ -52,11 +56,11 @@ Alternatively report bugs to your software distributor = / vendor. =20 =3Dhead1 COPYRIGHT =20 -Copyright (C) 2012 by Red Hat, Inc. +Copyright (C) 2019 by Red Hat, Inc. =20 =3Dhead1 LICENSE =20 -virt-host-validate is distributed under the terms of the GNU GPL v2+. +virt-host-validate is distributed under the terms of the GNU LGPL v2.1+. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE --=20 2.21.0 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list