Scriptable KVM/QEMU guest agent implemented in Python¶
Welcome to the documentation of negotiator version 0.12.2! The following sections are available:
User documentation¶
The readme is the best place to start reading, it’s targeted at all users and documents the command line interface:
Scriptable KVM/QEMU guest agent implemented in Python¶
The Python packages negotiator-host, negotiator-guest and negotiator-common together implement a scriptable KVM/QEMU guest agent infrastructure in Python. This infrastructure supports realtime bidirectional communication between Linux hosts and guests which allows the hosts and guests to invoke user defined commands on ‘the other side’.
Because the user defines the commands that hosts and guests can execute, the user controls the amount of influence that hosts and guests have over each other (there are several built-in commands, these are all read only).
Status¶
Some points to consider:
- The Negotiator project does what I expect from it: realtime bidirectional communication between Linux based KVM/QEMU hosts and guests.
- The project doesn’t have an automated test suite, although its functionality has been extensively tested during development and is being used in a production environment on more than 100 virtual machines (for non-critical tasks).
- The project has not been peer reviewed with regards to security. My primary use case is KVM/QEMU hosts and guests that trust each other to some extent (think private clouds, not shared hosting :-).
Installation¶
The negotiator packages and their dependencies are compatible with Python 2.7 and newer and are all pure Python. This means you don’t need a compiler toolchain to install the negotiator packages. This is a design decision and so won’t be changed.
On KVM/QEMU hosts¶
Here’s how to install the negotiator-host package on your host(s):
$ sudo pip install negotiator-host
If you prefer you can install the Python package in a virtual environment:
$ sudo apt-get install --yes python-virtualenv
$ virtualenv /tmp/negotiator-host
$ source /tmp/negotiator-host/bin/activate
$ pip install negotiator-host
After installation the negotiator-host
program is available. The usage
message will help you get started, try the --help
option. Now you need to
find a way to run the negotiator-host
command as a daemon. I have good
experiences with supervisord, here’s how to set that up:
$ sudo apt-get install --yes supervisor
$ sudo tee /etc/supervisor/conf.d/negotiator-host.conf >/dev/null << EOF
[program:negotiator-host]
command = /usr/local/bin/negotiator-host --daemon
autostart = True
stdout_logfile = /var/log/negotiator-host.log
stderr_logfile = /var/log/negotiator-host.log
EOF
$ sudo supervisorctl update negotiator-host
On KVM/QEMU guests¶
Install the negotiator-guest package on your guest(s):
$ sudo pip install negotiator-guest
If you prefer you can install the Python package in a virtual environment:
$ sudo apt-get install --yes python-virtualenv
$ virtualenv /tmp/negotiator-guest
$ source /tmp/negotiator-guest/bin/activate
$ pip install negotiator-guest
After installation you need to find a way to run the negotiator-guest
command as a daemon. I have good experiences with supervisord, here’s how
to set that up:
$ sudo apt-get install --yes supervisor
$ sudo tee /etc/supervisor/conf.d/negotiator-guest.conf >/dev/null << EOF
[program:negotiator-guest]
command = /usr/local/bin/negotiator-guest --daemon
autostart = True
stdout_logfile = /var/log/negotiator-guest.log
stderr_logfile = /var/log/negotiator-guest.log
EOF
$ sudo supervisorctl update negotiator-guest
Getting started¶
If the instructions below are not enough to get you started, take a look at the Debugging section below for hints about what to do when things don’t work as expected.
First you have to add two virtual devices to your QEMU guest. You can do so by editing the guest’s XML definition file. On Ubuntu Linux KVM/QEMU hosts these files are found in the directory
/etc/libvirt/qemu
. Open the file in your favorite text editor (Vim? :-) and add the the following XML snippet inside the<devices>
section:<channel type='unix'> <source mode='bind' path='/var/lib/libvirt/qemu/channel/target/GUEST_NAME.negotiator-host-to-guest.0' /> <target type='virtio' name='negotiator-host-to-guest.0' /> </channel> <channel type='unix'> <source mode='bind' path='/var/lib/libvirt/qemu/channel/target/GUEST_NAME.negotiator-guest-to-host.0' /> <target type='virtio' name='negotiator-guest-to-host.0' /> </channel>
Replace
GUEST_NAME
with the name of your guest in both places. If you use libvirt 1.0.6 or newer (you can check withvirsh --version
) you can omit thepath='...'
attribute because libvirt will fill it in automatically when it reloads the guest’s XML definition file (in step 2).After adding the configuration snippet you have to activate it:
$ sudo virsh define /etc/libvirt/qemu/GUEST_NAME.xml
Now you need to shut down the guest and then start it again:
$ sudo virsh shutdown --mode acpi GUEST_NAME $ sudo virsh start GUEST_NAME
Note that just rebooting the guest will not add the new virtual devices, you have to actually stop the guest and then start it again!
Now go and create some scripts in
/usr/lib/negotiator/commands
and try to execute them from the other side! Once you start writing your own commands it’s useful to know that commands on the KVM/QEMU host side have access to some environment variables.
Usage¶
This section documents the command line interfaces of the programs running on hosts and guests. For information on the Python API please refer to the online documentation on Read the Docs.
The negotiator-host program¶
Usage: negotiator-host [OPTIONS] GUEST_NAME
Communicate from a KVM/QEMU host system with running guest systems using a guest agent daemon running inside the guests.
Supported options:
Option | Description |
---|---|
-g , --list-guests |
List the names of the guests that have the appropriate channel. |
-c , --list-commands |
List the commands that the guest exposes to its host. |
-e , --execute=COMMAND |
Execute the given command inside GUEST_NAME. The standard output stream of the command inside the guest is intercepted and copied to the standard output stream on the host. If the command exits with a nonzero status code the negotiator-host program will also exit with a nonzero status code. |
-t , --timeout=SECONDS |
Set the number of seconds before a remote call without a response times out. A value of zero disables the timeout (in this case the command can hang indefinitely). The default is 10 seconds. |
-d , --daemon |
Start the host daemon that answers real time requests from guests. |
-v , --verbose |
Increase logging verbosity (can be repeated). |
-q , --quiet |
Decrease logging verbosity (can be repeated). |
-h , --help |
Show this message and exit. |
The negotiator-guest program¶
Usage: negotiator-guest [OPTIONS]
Communicate from a KVM/QEMU guest system to its host or start the guest daemon to allow the host to execute commands on its guests.
Supported options:
Option | Description |
---|---|
-l , --list-commands |
List the commands that the host exposes to its guests. |
-e , --execute=COMMAND |
Execute the given command on the KVM/QEMU host. The standard output stream of the command on the host is intercepted and copied to the standard output stream on the guest. If the command exits with a nonzero status code the negotiator-guest program will also exit with a nonzero status code. |
-d , --daemon |
Start the guest daemon. When using this command line option the “negotiator-guest” program never returns (unless an unexpected error condition occurs). |
-t , --timeout=SECONDS |
Set the number of seconds before a remote call without a response times out. A value of zero disables the timeout (in this case the command can hang indefinitely). The default is 10 seconds. |
-c , --character-device=PATH |
By default the appropriate character device is automatically selected based on /sys/class/virtio-ports/*/name. If the automatic selection doesn’t work, you can set the absolute pathname of the character device that’s used to communicate with the negotiator-host daemon running on the KVM/QEMU host. |
-v , --verbose |
Increase logging verbosity (can be repeated). |
-q , --quiet |
Decrease logging verbosity (can be repeated). |
-h , --help |
Show this message and exit. |
Debugging¶
This section contains hints about what to do when things don’t work as expected.
Broken channels on KVM/QEMU hosts¶
Whether you want to get the official QEMU guest agent or the Negotiator project running, you will need a working bidirectional channel. I’m testing Negotiator on an Ubuntu 14.04 KVM/QEMU host and I needed several changes to get things working properly:
$ CHANNELS_DIRECTORY=/var/lib/libvirt/qemu/channel/target
$ sudo mkdir -p $CHANNELS_DIRECTORY
$ sudo chown libvirt-qemu:kvm $CHANNELS_DIRECTORY
The above should be done by the KVM/QEMU system packages if you ask me, but anyway. On top of this if you are running Ubuntu with AppArmor enabled (the default) you may need to apply the following patch:
$ diff -u /etc/apparmor.d/abstractions/libvirt-qemu.orig /etc/apparmor.d/abstractions/libvirt-qemu
--- /etc/apparmor.d/abstractions/libvirt-qemu.orig 2015-09-19 12:46:54.316593334 +0200
+++ /etc/apparmor.d/abstractions/libvirt-qemu 2015-09-24 14:43:43.642064576 +0200
@@ -49,6 +49,9 @@
/run/shm/ r,
owner /run/shm/spice.* rw,
+ # Local modification to enable the QEMU guest agent.
+ owner /var/lib/libvirt/qemu/channel/target/* rw,
+
# 'kill' is not required for sound and is a security risk. Do not enable
# unless you absolutely need it.
deny capability kill,
Again this should just be part of the KVM/QEMU system packages, but whatever. The Negotiator project is playing with new-ish functionality so I pretty much know to expect sharp edges :-)
Character device detection fails¶
When the negotiator-guest
program fails to detect the correct character
devices it will complain loudly and point you here. Here are some of things
I’ve run into that can cause this:
- The virtual channel(s) have not been correctly configured or the correct configuration hasn’t been applied yet. Please carefully follow the instructions in the Getting started section above.
- The kernel module
virtio_console
is not loaded because it is not available in your kernel. You can check by using thelsmod
command. If the module is not loaded you’ll need to install and boot to a kernel that does have the module.
Why another guest agent?¶
The QEMU project provides an official guest agent and this agent is very useful to increase integration between QEMU hosts and guests. However the official QEMU guest agent has two notable shortcomings (for me at least):
- Extensibility
- The official QEMU guest agent has some generic mechanisms like being able to write files inside guests, but this is a far cry from a generic, extensible architecture. Ideally given the host and guest’s permission we should be able to transfer arbitrary data and execute user defined logic on both sides.
- Platform support
- Despite considerable effort I haven’t been able to get a recent version of the QEMU guest agent running on older Linux distributions (e.g. Ubuntu Linux 10.04). Older versions of the guest agent can be succesfully compiled for such distributions but don’t support the features I require. By creating my own guest agent I have more control over platform support (given the primitives required for communication).
Note that my project in no way tries to replace the official QEMU guest agent. For example I have no intention of implementing freezing and thawing of file systems because the official agent already does that just fine :-). In other words the two projects share a lot of ideas but have very different goals.
How does it work?¶
The scriptable guest agent infrastructure uses the same mechanism that the official QEMU guest agent does:
- Inside the guest special character devices are created that allow reading and
writing. These character devices are
/dev/vport[0-9]p[0-9]
. - On the host UNIX domain sockets are created that are connected to the
character devices inside the guest. On Ubuntu Linux KVM/QEMU hosts,
these UNIX domain sockets are created in the directory
/var/lib/libvirt/qemu/channel/target
.
Contact¶
The latest version of negotiator is available on PyPI and GitHub. You can find the documentation on Read The Docs. For bug reports please create an issue on GitHub. If you have questions, suggestions, etc. feel free to send me an e-mail at peter@peterodding.com.
API documentation¶
The following API documentation is automatically generated from the source code:
API documentation¶
The following documentation is based on the source code of version 0.12.2 of the negotiator project:
negotiator_host
¶
Channel for communication with guests.
This module implements the GuestChannel
class which provides the
host side of the channel between QEMU hosts and guests. Channel objects can be
used to query and command running guests.
-
class
negotiator_host.
AutomaticGuestChannel
(guest_name, unix_socket)¶ Thin wrapper for
GuestChannel
that puts it in a separate process.Uses
multiprocessing.Process
to isolate guest channels in separate processes.-
__init__
(guest_name, unix_socket)¶ Initialize a
GuestChannel
in a separate process.Parameters: - guest_name – The name of the guest to connect to (a string).
- unix_socket – The absolute pathname of the UNIX socket that we should connect to (a string).
-
run
()¶ Start the main loop of the common negotiator interface.
-
-
class
negotiator_host.
GuestChannel
(guest_name, unix_socket=None)¶ The host side of the channel connecting KVM/QEMU hosts and guests.
See also
AutomaticGuestChannel
which wrapsGuestChannel
and puts it in its own process.-
__init__
(guest_name, unix_socket=None)¶ Initialize a negotiator host agent.
Parameters: - guest_name – The name of the guest to connect to (a string).
- unix_socket – The absolute pathname of the UNIX socket that we should connect to (a string, optional).
-
prepare_environment
()¶ Prepare environment variables for command execution on KVM/QEMU hosts.
The following environment variables are currently exposed to commands:
$NEGOTIATOR_GUEST
- The name of the KVM/QEMU guest that invoked the command.
-
-
exception
negotiator_host.
GuestChannelInitializationError
¶ Exception raised by
GuestChannel
when socket initialization fails.
-
exception
negotiator_host.
GuestDiscoveryError
¶ Exception raised by
find_running_guests()
whenvirsh list
fails.
-
class
negotiator_host.
HostDaemon
¶ The host daemon automatically manages a group of processes that handle “guest to host” calls.
-
__init__
()¶ Initialize the host daemon.
-
cleanup_workers
(running_guests)¶ Cleanup crashed workers and workers for guests that are no longer running.
-
enter_main_loop
()¶ Create and maintain active channels for all running guests.
-
spawn_workers
(running_guests)¶ Spawn new workers on demand (ignoring guests known not to support negotiator).
-
update_workers
()¶ Automatically spawn subprocesses (workers) to maintain connections to all guests.
-
-
negotiator_host.
find_channels_of_guest
(guest_name)¶ Find the pathnames of the channels associated to a guest.
Parameters: guest_name – The name of the guest (a string). Returns: A dictionary with channel names (strings) as keys and pathnames of UNIX socket files (strings) as values. If no channels are detected an empty dictionary will be returned. This function uses
virsh dumpxml
and parses the XML output to determine the pathnames of the channels associated to the guest.
-
negotiator_host.
find_running_guests
()¶ Find the names of the guests running on the current host.
This function parses the output of the
virsh list
command instead of using the libvirt API because of two reasons:- I’m under the impression that the libvirt API is still very much in flux and large changes are still being made, so it’s not the most stable foundation for Negotiator to find running guests.
- The Python libvirt API needs to match the version of the libvirt API on
the host system and there is AFAIK no obvious way to express this in the
setup.py
script of Negotiator.
Returns: A generator of strings. Raises: GuestDiscoveryError
whenvirsh list
fails.
-
negotiator_host.
find_supported_guests
()¶ Find guests supporting the negotiator interface.
Returns: A generator of strings with guest names. This function uses
find_running_guests()
to determine which guests are currently running and then usesfind_channels_of_guest()
to determine which guests support the negotiator interface.
negotiator_host.cli
¶
Usage: negotiator-host [OPTIONS] GUEST_NAME
Communicate from a KVM/QEMU host system with running guest systems using a guest agent daemon running inside the guests.
Supported options:
Option | Description |
---|---|
-g , --list-guests |
List the names of the guests that have the appropriate channel. |
-c , --list-commands |
List the commands that the guest exposes to its host. |
-e , --execute=COMMAND |
Execute the given command inside GUEST_NAME. The standard output stream of the command inside the guest is intercepted and copied to the standard output stream on the host. If the command exits with a nonzero status code the negotiator-host program will also exit with a nonzero status code. |
-t , --timeout=SECONDS |
Set the number of seconds before a remote call without a response times out. A value of zero disables the timeout (in this case the command can hang indefinitely). The default is 10 seconds. |
-d , --daemon |
Start the host daemon that answers real time requests from guests. |
-v , --verbose |
Increase logging verbosity (can be repeated). |
-q , --quiet |
Decrease logging verbosity (can be repeated). |
-h , --help |
Show this message and exit. |
-
class
negotiator_host.cli.
Context
¶ Enables
main()
to inject a custom timeout into partially applied actions.-
__init__
()¶ Initialize a context for executing commands on the host.
-
execute_command
(guest_name, command_line)¶ Execute a command inside the named guest.
-
print_commands
(guest_name)¶ Print the commands supported by the guest.
-
print_guest_names
()¶ Print the names of the guests that Negotiator can connect with.
-
-
negotiator_host.cli.
main
()¶ Command line interface for the
negotiator-host
program.
negotiator_guest
¶
The guest agent daemon and client.
This module implements the guest agent, the Python daemon process that’s always running inside KVM/QEMU guests.
-
class
negotiator_guest.
GuestAgent
(character_device, retry=False)¶ Implementation of the daemon running inside KVM/QEMU guests.
-
__init__
(character_device, retry=False)¶ Initialize a negotiator guest agent.
Parameters:
-
raw_readline
()¶ Read a newline terminated string from the remote side.
This method overrides the
raw_readline()
method of theNegotiatorInterface()
class to implement blocking reads based onos.O_ASYNC
andsignal.SIGIO
(see alsoWaitForRead
).Returns: The data read from the remote side (a string).
-
retry_open
(character_device, mode)¶ Open the character device and retry
EBUSY
errors.
-
-
class
negotiator_guest.
WaitForRead
(group=None, target=None, name=None, args=(), kwargs={})¶ Used by
GuestAgent.raw_readline()
to implement blocking reads.-
run
()¶ Endless loop that waits for one or more
SIGIO
signals to arrive.
-
signal_handler
(signal_number, frame)¶ Signal handler for
SIGIO
signals that immediately exits the process.
-
negotiator_guest.cli
¶
Usage: negotiator-guest [OPTIONS]
Communicate from a KVM/QEMU guest system to its host or start the guest daemon to allow the host to execute commands on its guests.
Supported options:
Option | Description |
---|---|
-l , --list-commands |
List the commands that the host exposes to its guests. |
-e , --execute=COMMAND |
Execute the given command on the KVM/QEMU host. The standard output stream of the command on the host is intercepted and copied to the standard output stream on the guest. If the command exits with a nonzero status code the negotiator-guest program will also exit with a nonzero status code. |
-d , --daemon |
Start the guest daemon. When using this command line option the “negotiator-guest” program never returns (unless an unexpected error condition occurs). |
-t , --timeout=SECONDS |
Set the number of seconds before a remote call without a response times out. A value of zero disables the timeout (in this case the command can hang indefinitely). The default is 10 seconds. |
-c , --character-device=PATH |
By default the appropriate character device is automatically selected based on /sys/class/virtio-ports/*/name. If the automatic selection doesn’t work, you can set the absolute pathname of the character device that’s used to communicate with the negotiator-host daemon running on the KVM/QEMU host. |
-v , --verbose |
Increase logging verbosity (can be repeated). |
-q , --quiet |
Decrease logging verbosity (can be repeated). |
-h , --help |
Show this message and exit. |
-
negotiator_guest.cli.
main
()¶ Command line interface for the
negotiator-guest
program.
negotiator_common
¶
Common shared functionality between the negotiator host and guest.
This Python module contains the functionality that is shared between the negotiator-host and negotiator-guest packages. By moving all of the shared functionality to a separate Python package and using Python package dependencies to pull in the negotiator-common package we stimulate code reuse while avoiding code duplication.
-
class
negotiator_common.
NegotiatorInterface
(handle, label)¶ Common logic shared between the host/guest components.
This class defines the protocol that’s used to communicate between the Python programs running on the hosts and guests.
-
__init__
(handle, label)¶ Initialize a negotiator host or guest agent.
Parameters: - handle – A file like object connected to the other side.
- label – A string describing the file like object (used in logging).
This constructor is intended to be called by sub classes to provide the base class with the context it needs to set up bidirectional communication between the host and guest agents.
-
call_remote_method
(method, *args, **kw)¶ Call a method on the remote object.
Parameters: - method – The name of the method to call (a string).
- args – The positional arguments for the method.
- kw – The keyword arguments for the method.
Returns: The return value of the remote method.
-
enter_main_loop
()¶ Wait for requests from the other side.
The communication protocol for remote procedure calls is as follows:
- Every request is a dictionary containing at least a
command
key with a string value (the name of the method to invoke). - The value of the optional
arguments
key gives a list of positional arguments to pass to the method. - The value of the optional
keyword-arguments
key gives a dictionary of keyword arguments to pass to the method.
Responses are structured as follows:
- Every response is a dictionary containing at least a
success
key with a boolean value. - If
success=True
the keyresult
gives the return value of the method. - If
success=False
the keyerror
gives a string explaining what went wrong.
Raises: ProtocolError
when the remote side violates the defined protocol.- Every request is a dictionary containing at least a
-
execute
(*command, **options)¶ Execute a user defined or built-in command.
Parameters: - command – The command name and any arguments (one or more strings).
- input – The input to feed to the command on its standard input
stream (a string or
None
).
Returns: The output of the command (a string) or
None
if the command exited with a nonzero exit code.
-
list_commands
()¶ Find the names of the user defined commands.
Returns: A list of executable names (strings).
-
prepare_environment
()¶ Prepare environment variables for command execution.
This method can be overridden by sub classes to prepare environment variables for external command execution.
-
raw_read
(num_bytes)¶ Read the given number of bytes from the remote side.
Parameters: num_bytes – The number of bytes to read (an integer). Returns: The data read from the remote side (a string).
-
raw_readline
()¶ Read a newline terminated string from the remote side.
Returns: The data read from the remote side (a string).
-
raw_write
(data)¶ Write a string of data to the remote side.
Parameters: data – The data to write tot the remote side (a string).
-
read
()¶ Wait for a JSON encoded message from the remote side.
The basic communication protocol is really simple:
- First an ASCII encoded integer number is received, terminated by a newline.
- Second the number of bytes given by step 1 is read and interpreted as a JSON encoded value. This step is not terminated by a newline.
That’s it :-).
Returns: The JSON value decoded to a Python value. Raises: ProtocolError
when the remote side violates the defined protocol.
-
write
(value)¶ Send a Python value to the other side.
Parameters: value – Any Python value that can be encoded as JSON.
-
-
exception
negotiator_common.
ProtocolError
¶ Exception that is raised when the communication protocol is violated.
-
exception
negotiator_common.
RemoteMethodFailed
¶ Exception that is raised when a remote method call failed.
negotiator_common.config
¶
Configuration defaults for the negotiator project.
-
negotiator_common.config.
BUILTIN_COMMANDS_DIRECTORY
= '/home/docs/checkouts/readthedocs.org/user_builds/negotiator/checkouts/latest/common/negotiator_common/scripts'¶ The directory with built-in commands (a string).
-
negotiator_common.config.
DEFAULT_TIMEOUT
= 10¶ The number of seconds to wait for a reply from the other side (an integer).
If more time elapses an exception is raised causing the process to exit with a nonzero status code.
-
negotiator_common.config.
GUEST_TO_HOST_CHANNEL_NAME
= 'negotiator-guest-to-host.0'¶ The name of the channel that’s used for communication initiated by the guest (a string).
-
negotiator_common.config.
HOST_TO_GUEST_CHANNEL_NAME
= 'negotiator-host-to-guest.0'¶ The name of the channel that’s used for communication initiated by the host (a string).
-
negotiator_common.config.
SUPPORTED_CHANNEL_NAMES
= ('negotiator-guest-to-host.0', 'negotiator-host-to-guest.0')¶ A tuple of strings with supported channel names (containing
GUEST_TO_HOST_CHANNEL_NAME
andHOST_TO_GUEST_CHANNEL_NAME
).
-
negotiator_common.config.
USER_COMMANDS_DIRECTORY
= '/usr/lib/negotiator/commands'¶ The pathname of the directory containing user defined commands that ‘the other side’ can invoke through negotiator.
negotiator_common.utils
¶
Miscellaneous functionality.
-
class
negotiator_common.utils.
GracefulShutdown
¶ Context manager to enable graceful handling of
SIGTERM
.This context manager translates termination signals (
SIGTERM
) intoTerminationError
exceptions.-
__enter__
()¶ Start intercepting termination signals.
-
__exit__
(exc_type, exc_value, traceback)¶ Stop intercepting termination signals.
-
signal_handler
(signum, frame)¶ Raise
TerminationError
when the timeout elapses.
-
-
exception
negotiator_common.utils.
TerminationError
¶ Exception that is raised when
SIGTERM
is received.
-
class
negotiator_common.utils.
TimeOut
(num_seconds)¶ Context manager that enforces timeouts using UNIX alarm signals.
-
__enter__
()¶ Schedule the timeout.
-
__exit__
(exc_type, exc_value, traceback)¶ Clear the timeout and restore the previous signal handler.
-
__init__
(num_seconds)¶ Initialize the context manager.
Parameters: num_seconds – The number of seconds after which to interrupt the running operation (an integer).
-
signal_handler
(signum, frame)¶ Raise
TimeOutError
when the timeout elapses.
-
-
negotiator_common.utils.
format_call
(function, *args, **kw)¶ Format a Python function call into a human readable string.
Parameters: - function – The name of the function that’s called (a string).
- args – The positional arguments to the function (if any).
- kw – The keyword arguments to the function (if any).
Change log¶
The change log lists notable changes to the project:
Changelog¶
The purpose of this document is to list all of the notable changes to this project. The format was inspired by Keep a Changelog. This project adheres to semantic versioning.
- Release 0.12.2 (2019-12-11)
- Release 0.12.1 (2019-12-09)
- Release 0.12 (2019-12-05)
- Release 0.11 (2019-10-11)
- Release 0.10 (2019-03-03)
- Release 0.9 (2019-03-03)
- Release 0.8.6 (2019-02-25)
- Release 0.8.5 (2019-02-23)
- Release 0.8.4 (2016-04-08)
- Release 0.8.3 (2016-04-08)
- Release 0.8.2 (2015-10-29)
- Release 0.8.1 (2014-12-30)
- Release 0.8 (2014-11-01)
- Release 0.7 (2014-10-24)
- Release 0.6.1 (2014-09-28)
- Release 0.6 (2014-09-26)
- Release 0.5.2 (2014-09-24)
- Release 0.5.1 (2014-09-24)
- Release 0.5 (2014-09-24)
- Release 0.2.1 (2014-09-22)
- Release 0.2 (2014-09-22)
- Release 0.1 (2014-09-22)
Release 0.12.2 (2019-12-11)¶
Bug fix for 2 most recent releases: s/OSError/EnvironmentError/g
This is a follow up to / bug fix for release 0.12 and release 0.12.1
where I accidentally used OSError
when I should have used IOError
or
the generic parent class EnvironmentError
. I’ve verified that
EnvironmentError
is compatible with both Python 2.7 and 3.
The last few releases clearly show the value of having an automated test suite to guard against regressions, unfortunately due to the nature of the Negotiator project I’m not quite sure how I would get a functional test suite up and running on Travis CI.
It’s definitely possible, and on my wish list, but it is one of the higher hanging fruits on that -almost infinite- wish list, so don’t hold your breath 😛.
Release 0.12.1 (2019-12-09)¶
Enable retry in guest CLI.
This is a follow up to release 0.12 because I neglected to add
retry=True
to the negotiator_guest.cli
module in that
release (the new behavior is opt-in in the Python API so as to
improve backwards compatibility and make the API more foolproof).
Release 0.12 (2019-12-05)¶
Retry character device access in guest agent when EBUSY
error is reported.
At my employer we operate 200+ virtual servers that have Negotiator installed and in recent months we’ve started building more and more monitoring on top of Negotiator, resulting in hundreds of invocations per day. This is when I started seeing intermittent errors like the following:
IOError: [Errno 16] Device or resource busy: '/dev/vport2p2'
The new retry on EBUSY
behavior is intended to minimize occurrences of such
race conditions.
Release 0.11 (2019-10-11)¶
- Fix error in error handling in
negotiator-host
This resolves the traceback below when
negotiator-host
is running but ‘crashes’ becausevirsh list
fails because it can’t connect tolibvirtd
. This situation is still an unrecoverable error, but the intention was for it to be an unrecoverable error that did not produce a traceback…Traceback (most recent call last): File ".../negotiator_host/cli.py", line 117, in main action() File ".../negotiator_host/__init__.py", line 46, in __init__ self.enter_main_loop() File ".../negotiator_host/__init__.py", line 53, in enter_main_loop self.update_workers() File ".../negotiator_host/__init__.py", line 62, in update_workers running_guests = set(find_running_guests()) File ".../negotiator_host/__init__.py", line 270, in find_running_guests except ExternalCommandFailed: NameError: global name 'ExternalCommandFailed' is not defined
- Cleaned up flake8 F401 (imported but unused) warning
- As a result of moving to
humanfriendly.compact()
. - Minor changes relating to Python versions
Two minor changes relating to Python version compatibility:
- Unbreak
make docs
(Sphinx insists on Python 3, and rightfully so). - Replace Python 2.6 reference in README with Python 2.7.
Compatibility with Python 3 hasn’t been verified but is more or less expected given a pure Python code base. Maybe a Unicode error slipped in here or there, as I said “to be verified”.
- Unbreak
Release 0.10 (2019-03-03)¶
- Clarify verbosity control
- Update the command line interface usage messages to clarify that the options
--verbose
and--quiet
can be repeated (in response to #1). - No traceback when guest discovery fails
Don’t log a traceback when guest discovery using the
virsh list
command fails, to avoid spamming the logs about a known problem. This change was made to counteract the following interaction:- The negotiator documentation specifically suggests to use a process supervision solution like supervisord to automatically restart the negotiator daemon when it dies.
- When the libvirt daemon is down
virsh list
will fail and the negotiator daemon dies with a rather verbose traceback (before release 0.10). - Because supervisord automatically restarts the negotiator daemon but doesn’t know about the libvirt dependency, several restarts may be required to get the negotiator daemon up and running again.
- This “restart until it stays up” interaction would result in quite a few useless tracebacks being logged which “polluted” the logs and might raise the impression that something is really broken (that can’t be fixed by an automatic restart).
Release 0.9 (2019-03-03)¶
Refactored channel discovery to use virsh list
and virsh dumpxml
:
- The recent addition of Ubuntu 18.04 support proved once again that the old channel discovery strategy was error prone and hard to maintain.
- Since then it had come to my attention that on Ubuntu 18.04 guest names embedded in pathnames of UNIX sockets may be truncated in which case the domain id provides the only way to match a UNIX socket to its guest.
- Despite the previous point, I also wanted to maintain compatibility with libvirt releases that don’t embed the domain id in the pathnames. Doing so based on the old channel discovery strategy would have become messy.
So I decided to take a big step back and opted for a new strategy that will hopefully prove to be more robust and future proof. Thanks to @tarmack for initially suggesting this approach.
Release 0.8.6 (2019-02-25)¶
Follow-up to making channel discovery compatible with Ubuntu 18.04:
- Release 0.8.5 updated
negotiator-host --daemon
. - Release 0.8.6 updates
negotiator-host --list-commands
and similar commands.
Release 0.8.5 (2019-02-23)¶
- Made channel discovery compatible with Ubuntu 18.04 (related to #1).
- Added this changelog, restructured the documentation.
- Embedded CLI usage messages in readme and documentation.
- Updated supervisord configuration examples to use
stderr_logfile
instead ofredirect_stderr
. - Other minor changes not touching the code base.
Release 0.8.4 (2016-04-08)¶
Follow-up to previous commit (Ubuntu 16.04 support).
Release 0.8.3 (2016-04-08)¶
Make channel discovery compatible with Ubuntu 16.04.
Release 0.8.2 (2015-10-29)¶
Make platform support more explicit in the documentation (Linux only, basically :-P).
Release 0.8.1 (2014-12-30)¶
Improve guest channel (re)spawning on hosts (improves robustness).
Release 0.8 (2014-11-01)¶
Proper sub process cleanup, more robust blocking read emulation.
Release 0.7 (2014-10-24)¶
Support for (custom) remote call timeouts with a default of 10s.
Release 0.6.1 (2014-09-28)¶
Bug fix for Python 2.6 compatibility (count()
does not take keyword arguments).
Release 0.6 (2014-09-26)¶
- Implemented blocking reads inside guests (don’t ask me how, please …).
- Improved getting started instructions on adding virtual devices.
- Rebranded
s/generic/scriptable/g
and improved the readme a bit.
Release 0.5.2 (2014-09-24)¶
Add syntax highlighting to the code and configuration samples in the readme and explicitly link to the online documentation available on Read the Docs.
Release 0.5.1 (2014-09-24)¶
- Minor improvements and fixes to the documentation.
- Properly documented the environment variables exposed to host commands.
- Added trove classifiers to the
setup.py
scripts. - Bumped the version to release updated documentation to PyPI.
Release 0.5 (2014-09-24)¶
- Support for proper bidirectional user defined command execution on both sides.
- Improved the
negotiator-guest
usage message (by mentioning character device detection).
Release 0.2.1 (2014-09-22)¶
Fixed a typo in the readme, fixed a bug in the makefile and bumped the version so I could push a new release to PyPI because the readme was missing there (due to the makefile bug).
Release 0.2 (2014-09-22)¶
- Added automatic character device selection.
- Created online documentation on Read the Docs.
Release 0.1 (2014-09-22)¶
The initial commit and release.