Design for virtual clusters support

Introduction

Currently there are two ways to test the Ganeti (including HTools) code base:

  • unittests, which run using mocks as normal user and test small bits of the code
  • QA/burnin/live-test, which require actual hardware (either physical or virtual) and will build an actual cluster, with one machine to one node correspondence

The difference in time between these two is significant:

  • the unittests run in about 1-2 minutes
  • a so-called ‘quick’ QA (without burnin) runs in about an hour, and a full QA could be double that time

On one hand, the unittests have a clear advantage: quick to run, not requiring many machines, but on the other hand QA is actually able to run end-to-end tests (including HTools, for example).

Ideally, we would have an intermediate step between these two extremes: be able to test most, if not all, of Ganeti’s functionality but without requiring actual hardware, full machine ownership or root access.

Current situation

Ganeti

It is possible, given a manually built config.data and _autoconf.py, to run the masterd under the current user as a single-node cluster master. However, the node daemon and related functionality (cluster initialisation, master failover, etc.) are not directly runnable in this model.

Also, masterd only works as a master of a single node cluster, due to our current “hostname” method of identifying nodes, which results in a limit of maximum one node daemon per machine, unless we use multiple name and IP aliases.

HTools

In HTools the situation is better, since it doesn’t have to deal with actual machine management: all tools can use a custom LUXI path, and can even load RAPI data from the filesystem (so the RAPI backend can be tested), and both the ‘text’ backend for hbal/hspace and the input files for hail are text-based, loaded from the file-system.

Proposed changes

The end-goal is to have full support for “virtual clusters”, i.e. be able to run a “big” (hundreds of virtual nodes and towards thousands of virtual instances) on a reasonably powerful, but single machine, under a single user account and without any special privileges.

This would have significant advantages:

  • being able to test end-to-end certain changes, without requiring a complicated setup
  • better able to estimate Ganeti’s behaviour and performance as the cluster size grows; this is something that we haven’t been able to test reliably yet, and as such we still have not yet diagnosed scaling problems
  • easier integration with external tools (and even with HTools)

masterd

As described above, masterd already works reasonably well in a virtual setup, as it won’t execute external programs and it shouldn’t directly read files from the local filesystem (or at least not virtualisation-related, as the master node can be a non-vm_capable node).

noded

The node daemon executes many privileged operations, but they can be split in a few general categories:

Category Description Solution
disk operations Disk creation and removal Use only diskless or file-based instances
disk query Node disk total/free, used in node listing and htools Not supported currently, could use file-based
hypervisor operations Instance start, stop and query Use the fake hypervisor
instance networking Bridge existence query Unprivileged operation, can be used with an existing bridge at system level or use NIC-less instances
instance OS operations OS add, OS rename, export and import Only used with non-diskless instances; could work with custom OS scripts that just dd without mounting filesystems
node networking IP address management (master ip), IP query, etc. Not supported; Ganeti will need to work without a master IP; for the IP query operations the test machine would need externally-configured IPs
node add
SSH command must be adjusted
node setup ssh, /etc/hosts, so on Can already be disabled from the cluster config
master failover start/stop the master daemon Doable (as long as we use a single user), might get tricky w.r.t. paths to executables
file upload Uploading of system files, job queue files and ganeti config The only issue could be with system files, which are not owned by the current user; internal ganeti files should be working fine
node oob Out-of-band commands Since these are user-defined, we can mock them easily
node OS discovery List the existing OSes and their properties No special privileges needed, so works fine as-is
hooks Running hooks for given operations No special privileges needed
iallocator Calling an iallocator script No special privileges needed
export/import Exporting and importing instances When exporting/importing file-based instances, this should work, as the listening ports are dynamically chosen
hypervisor validation The validation of hypervisor parameters As long as the hypervisors don’t call to privileged commands, it should work
node powercycle The ability to power cycle a node remotely Privileged, so not supported, but anyway not very interesting for testing

It seems that much of the functionality works as is, or could work with small adjustments, even in a non-privileged setup. The bigger problem is the actual use of multiple node daemons per machine.

Multiple noded per machine

Currently Ganeti identifies node simply by their hostname. Since changing this method would imply significant changes to tracking the nodes, the proposal is to simply have as many IPs per the (single) machine that is used for tests as nodes, and have each IP correspond to a different name, and thus no changes are needed to the core RPC library. Unfortunately this has the downside of requiring root rights for setting up the extra IPs and hostnames.

An alternative option is to implement per-node IP/port support in Ganeti (especially in the RPC layer), which would eliminate the root rights. We expect that this will get implemented as a second step of this design, but as the port is currently static will require changes in many places.

The only remaining problem is with sharing the localstatedir structure (lib, run, log) amongst the daemons, for which we propose to introduce an environment variable (GANETI_ROOTDIR) acting as a prefix for essentially all paths. An environment variable is easier to transport through several levels of programs (shell scripts, Python, etc.) than a command line parameter. In Python code this prefix will be applied to all paths in constants.py. Every virtual node will get its own root directory. The rationale for this is two-fold:

  • having two or more node daemons writing to the same directory might introduce artificial scenarios not existent in real life; currently noded either owns the entire /var/lib/ganeti directory or shares it with masterd, but never with another noded
  • having separate directories allows cluster verify to check correctly consistency of file upload operations; otherwise, as long as one node daemon wrote a file successfully, the results from all others are “lost”

In case the use of an environment variable turns out to be too difficult a compile-time prefix path could be used. This would then require one Ganeti installation per virtual node, but it might be good enough.

rapi

The RAPI daemon is not privileged and furthermore we only need one per cluster, so it presents no issues.

confd

confd has somewhat the same issues as the node daemon regarding multiple daemons per machine, but the per-address binding still works.

ganeti-watcher

Since the startup of daemons will be customised with per-IP binds, the watcher either has to be modified to not activate the daemons, or the start-stop tool has to take this into account. Due to watcher’s use of the hostname, it’s recommended that the master node is set to the machine hostname (also a requirement for the master daemon).

CLI scripts

As long as the master node is set to the machine hostname, these should work fine.

Cluster initialisation

It could be possible that the cluster initialisation procedure is a bit more involved (this was not tried yet). A script will be used to set up all necessary IP addresses and hostnames, as well as creating the initial directory structure. Building config.data manually should not be necessary.

Needed tools

With the above investigation results in mind, the only thing we need are:

  • a tool to setup per-virtual node tree structure of localstatedir (with the help of ensure-dirs) and setup correctly the extra IP/hostnames
  • changes to the startup daemon tools to launch correctly the daemons per virtual node
  • changes to constants.py to override the localstatedir path
  • documentation for running such a virtual cluster
  • and eventual small fixes to the node daemon backend functionality, to better separate privileged and non-privileged code