Skip to content

Commit

Permalink
(FACT-867) Port xendomains fact
Browse files Browse the repository at this point in the history
Re-implements the xendomains fact in Facter 3.
  • Loading branch information
Michael Smith committed Jun 26, 2015
1 parent 9b1ef72 commit f735036
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 1 deletion.
2 changes: 2 additions & 0 deletions lib/CMakeLists.txt
Expand Up @@ -57,6 +57,7 @@ set(LIBFACTER_COMMON_SOURCES
"src/facts/resolvers/timezone_resolver.cc"
"src/facts/resolvers/uptime_resolver.cc"
"src/facts/resolvers/virtualization_resolver.cc"
"src/facts/resolvers/xen_resolver.cc"
"src/facts/resolvers/zpool_resolver.cc"
"src/facts/resolvers/zone_resolver.cc"
"src/facts/resolvers/zfs_resolver.cc"
Expand Down Expand Up @@ -103,6 +104,7 @@ if (UNIX)
"src/facts/posix/ssh_resolver.cc"
"src/facts/posix/timezone_resolver.cc"
"src/facts/posix/uptime_resolver.cc"
"src/facts/posix/xen_resolver.cc"
"src/ruby/posix/api.cc"
"src/util/posix/dynamic_library.cc"
"src/util/posix/environment.cc"
Expand Down
10 changes: 10 additions & 0 deletions lib/inc/facter/facts/fact.hpp
Expand Up @@ -632,6 +632,16 @@ namespace facter { namespace facts {
* The fact for augeas version.
*/
constexpr static char const* augeasversion = "augeasversion";

/**
* The fact for Xen metadata.
*/
constexpr static char const* xen = "xen";

/**
* The fact for Xen domains.
*/
constexpr static char const* xendomains = "xendomains";
};

}} // namespace facter::facts
24 changes: 24 additions & 0 deletions lib/inc/internal/facts/posix/xen_resolver.hpp
@@ -0,0 +1,24 @@
/**
* @file
* Declares the Xen fact resolver on POSIX systems.
*/
#pragma once

#include <internal/facts/resolvers/xen_resolver.hpp>

namespace facter { namespace facts { namespace posix {

/**
* Responsible for resolving Xen facts.
*/
struct xen_resolver : resolver
{
protected:
/**
* Gets the Xen management command.
* @return Returns the Xen management command.
*/
virtual std::string xen_command();
};

}}} // namespace facter::facts::posix
55 changes: 55 additions & 0 deletions lib/inc/internal/facts/resolvers/xen_resolver.hpp
@@ -0,0 +1,55 @@
/**
* @file
* Declares the Xen fact resolver.
*/
#pragma once

#include <facter/facts/resolver.hpp>
#include <string>
#include <vector>

namespace facter { namespace facts { namespace resolvers {

/**
* Responsible for resolving Xen facts.
*/
struct xen_resolver : resolver
{
/**
* Constructs the xen_resolver.
*/
xen_resolver();

/**
* Called to resolve all facts the resolver is responsible for.
* @param facts The fact collection that is resolving facts.
*/
virtual void resolve(collection& facts) override;

protected:
/**
* Gets the Xen management command.
* @return Returns the Xen management command.
*/
virtual std::string xen_command() = 0;

/**
* Represents the resolver's data.
*/
struct data
{
/**
* Stores the Xen domains.
*/
std::vector<std::string> domains;
};

/**
* Collects the resolver data.
* @param facts The fact collection that is resolving facts.
* @return Returns the resolver data.
*/
virtual data collect_data(collection& facts);
};

}}} // namespace facter::facts::resolvers
21 changes: 21 additions & 0 deletions lib/schema/facter.yaml
Expand Up @@ -1715,6 +1715,27 @@ virtual:
Solaris: use the `zonename` utility to retrieve virtual machine name.
Windows: use WMI to retrieve virtual machine name.
xen:
type: map
description: Return metadata for the Xen hypervisor.
resolution: |
POSIX platforms: use `/usr/lib/xen-common/bin/xen-toolstack` to locate xen admin commands if available, otherwise fallback to `/usr/sbin/xl` or `/usr/sbin/xm`. Use the found command to execute the `list` query.
caveats: |
POSIX platforms: confined to Xen privileged virtual machines
elements:
domains:
type: array
description: list of strings identifying active Xen domains

xendomains:
type: string
hidden: true
description: Return a list of comma-separated active Xen domain names
resolution: |
POSIX platforms: see the `xen` structured fact
caveats: |
POSIX platforms: confined to Xen privileged virtual machines
zfs_featurenumbers:
type: string
description: Return the comma-delimited feature numbers for ZFS.
Expand Down
1 change: 1 addition & 0 deletions lib/src/facts/linux/collection.cc
Expand Up @@ -35,6 +35,7 @@ namespace facter { namespace facts {
add(make_shared<linux::filesystem_resolver>());
add(make_shared<linux::memory_resolver>());
add(make_shared<glib::load_average_resolver>());
add(make_shared<posix::xen_resolver>());
}

}} // namespace facter::facts
45 changes: 45 additions & 0 deletions lib/src/facts/posix/xen_resolver.cc
@@ -0,0 +1,45 @@
#include <internal/facts/posix/xen_resolver.hpp>
#include <facter/execution/execution.hpp>
#include <leatherman/logging/logging.hpp>
#include <boost/filesystem.hpp>

using namespace std;
using namespace facter::facts;
using namespace facter::execution;
using namespace boost::filesystem;
namespace bs = boost::system;

namespace facter { namespace facts { namespace posix {

string xen_resolver::xen_command()
{
constexpr char const* xen_toolstack = "/usr/lib/xen-common/bin/xen-toolstack";

bs::error_code ec;
if (exists(xen_toolstack, ec) && !ec) {
bool success;
string output, error;
tie(success, output, error) = execute(xen_toolstack);
if (success) {
return output;
} else {
LOG_DEBUG("failure executing %1%: %2%", xen_toolstack, error);
return {};
}
} else {
LOG_TRACE("xen toolstack command not found: %1%", ec.message());

static vector<string> xen_commands{"/usr/sbin/xl", "/usr/sbin/xm"};
for (auto const& cmd : xen_commands) {
auto cmd_path = execution::which(cmd);
if (!cmd_path.empty()) {
return cmd_path;
}
}

LOG_TRACE("no xen commands found");
return {};
}
}

}}} // namespace facter::facts::posix
81 changes: 81 additions & 0 deletions lib/src/facts/resolvers/xen_resolver.cc
@@ -0,0 +1,81 @@
#include <internal/facts/resolvers/xen_resolver.hpp>
#include <internal/util/regex.hpp>
#include <leatherman/logging/logging.hpp>
#include <facter/facts/collection.hpp>
#include <facter/facts/fact.hpp>
#include <facter/facts/vm.hpp>
#include <facter/facts/scalar_value.hpp>
#include <facter/facts/array_value.hpp>
#include <facter/facts/map_value.hpp>
#include <facter/execution/execution.hpp>
#include <boost/algorithm/string.hpp>

using namespace std;
using namespace facter::facts;

namespace facter { namespace facts { namespace resolvers {

xen_resolver::xen_resolver() :
resolver(
"Xen",
{
fact::xen,
fact::xendomains
})
{
}

void xen_resolver::resolve(collection& facts)
{
// Confine to fact virtual == xen0
auto virt = facts.get<string_value>(fact::virtualization);
if (!virt || virt->value() != vm::xen_privileged) {
return;
}

auto data = collect_data(facts);

if (!data.domains.empty()) {
auto xendomains = boost::algorithm::join(data.domains, ",");
facts.add(fact::xendomains, make_value<string_value>(move(xendomains), true));
}

auto domains = make_value<array_value>();
for (auto& domain : data.domains) {
domains->add(make_value<string_value>(move(domain)));
}

auto xen = make_value<map_value>();
if (!domains->empty()) {
xen->add("domains", move(domains));
}

if (!xen->empty()) {
facts.add(fact::xen, move(xen));
}
}

xen_resolver::data xen_resolver::collect_data(collection& facts)
{
data result;

auto command = xen_command();
if (!command.empty()) {
static boost::regex domain_header("^(Name|Domain-0)");
static boost::regex domain_entry("^([^\\s]*)\\s");
execution::each_line(command, {"list"}, [&](string& line) {
string domain;
if (!boost::regex_match(line, domain_header) && util::re_search(line, domain_entry, &domain)) {
result.domains.emplace_back(move(domain));
}
return true;
}, [&](string& line) {
LOG_DEBUG("output on stderr running %1% list: %2%", command, line);
return true;
});
}

return result;
}

}}} // namespace facter::facts::resolvers
1 change: 1 addition & 0 deletions lib/src/facts/solaris/collection.cc
Expand Up @@ -37,6 +37,7 @@ namespace facter { namespace facts {
add(make_shared<solaris::virtualization_resolver>());
add(make_shared<solaris::memory_resolver>());
add(make_shared<glib::load_average_resolver>());
add(make_shared<posix::xen_resolver>());

// solaris specific
add(make_shared<solaris::zpool_resolver>());
Expand Down
1 change: 1 addition & 0 deletions lib/tests/CMakeLists.txt
Expand Up @@ -29,6 +29,7 @@ set(LIBFACTER_TESTS_COMMON_SOURCES
"facts/resolvers/timezone_resolver.cc"
"facts/resolvers/uptime_resolver.cc"
"facts/resolvers/virtualization_resolver.cc"
"facts/resolvers/xen_resolver.cc"
"facts/resolvers/zfs_resolver.cc"
"facts/resolvers/zone_resolver.cc"
"facts/resolvers/zpool_resolver.cc"
Expand Down
100 changes: 100 additions & 0 deletions lib/tests/facts/resolvers/xen_resolver.cc
@@ -0,0 +1,100 @@
#include <catch.hpp>
#include <internal/facts/resolvers/xen_resolver.hpp>
#include <facter/facts/collection.hpp>
#include <facter/facts/fact.hpp>
#include <facter/facts/vm.hpp>
#include <facter/facts/scalar_value.hpp>
#include <facter/facts/map_value.hpp>
#include <facter/facts/array_value.hpp>
#include "../../collection_fixture.hpp"

using namespace std;
using namespace facter::facts;
using namespace facter::facts::resolvers;
using namespace facter::testing;

struct empty_xen_resolver : xen_resolver
{
protected:
virtual string xen_command()
{
return "";
}

virtual data collect_data(collection& facts) override
{
data result;
return result;
}
};

struct test_xen_resolver : xen_resolver
{
protected:
virtual string xen_command()
{
return "";
}

virtual data collect_data(collection& facts) override
{
data result;
result.domains = { "domain1", "domain2" };
return result;
}
};

// CATCH doesn't behave well with constexpr, so create memory for
// the string here before using it in the test.
static string xen_privileged = vm::xen_privileged;

SCENARIO("using the Xen resolver on a privileged VM") {
collection_fixture facts;
facts.add(fact::virtualization, make_value<string_value>(xen_privileged));
WHEN("data is not present") {
facts.add(make_shared<empty_xen_resolver>());
THEN("facts should not be added") {
REQUIRE(facts.size() == 1u);
}
}
WHEN("data is present") {
facts.add(make_shared<test_xen_resolver>());
THEN("flat facts are added") {
REQUIRE(facts.size() == 3u);
auto value = facts.get<string_value>(fact::xendomains);
REQUIRE(value);
REQUIRE(value->value() == "domain1,domain2");
}

THEN("structured facts are added") {
REQUIRE(facts.size() == 3u);
auto xen = facts.get<map_value>(fact::xen);
REQUIRE(xen);
REQUIRE(xen->size() == 1u);
auto domains = xen->get<array_value>("domains");
REQUIRE(domains);
REQUIRE(domains->size() == 2u);
for (size_t i = 0; i < 2; ++i) {
auto domain = domains->get<string_value>(i);
REQUIRE(domain);
REQUIRE(domain->value() == "domain" + to_string(i+1));
}
}
}
}

SCENARIO("using the Xen resolver on an unprivileged machine") {
collection_fixture facts;
WHEN("data is not present") {
facts.add(make_shared<empty_xen_resolver>());
THEN("facts should not be added") {
REQUIRE(facts.size() == 0u);
}
}
WHEN("data is present") {
facts.add(make_shared<test_xen_resolver>());
THEN("facts should not be added") {
REQUIRE(facts.size() == 0u);
}
}
}

0 comments on commit f735036

Please sign in to comment.