Currently there are two ways to test the Ganeti (including HTools) code base:
The difference in time between these two is significant:
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.
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.
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.
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:
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).
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.
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:
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.
The RAPI daemon is not privileged and furthermore we only need one per cluster, so it presents no issues.
confd has somewhat the same issues as the node daemon regarding multiple daemons per machine, but the per-address binding still works.
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).
As long as the master node is set to the machine hostname, these should work fine.
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.
With the above investigation results in mind, the only thing we need are: