Erlang implementation of the libvirtd remote protocol.
For an Erlang binding to the C libvirt interface, see:
<https://github.com/msantos/erlang-libvirt>
## WARNING
remote\_protocol.x contains this warning:
(1) The protocol is internal and may change at any time, without
notice. Do not use it. Instead link to libvirt and use the remote
driver.
<http://libvirt.org/git/?p=libvirt.git;a=blob_plain;f=src/remote/remote_protocol.x;hb=HEAD>
However, see the section _GENERATING THE REMOTE PROTOCOL MODULE_ below for
instructions on recompiling the XDR protocol spec if any changes occur.
The RPC protocol is documented here:
<http://libvirt.org/internals/rpc.html>
For the remote support documentation:
<http://libvirt.org/remote.html>
The version of remote\_protocol.x used was taken from libvirt master
at 1.3.1 (commit 8fd68675e2b5eed5b2aae636544a0a80f9fc70e9).
## HOW TO BUILD IT
make
See _GENERATING THE REMOTE PROTOCOL MODULE_ to rebuild the XDR protocol
parser.
## TESTING EVERYTHING WORKS
To quickly test everything works, the libvirtd test driver can be used
with `bin/verx`, an escript that provides a simple command line interface
to the verx library.
You'll have to set up the ERL\_LIBS environment variable first, e.g.,
if verx is checked out in ~/src:
export ERL_LIBS=$ERL_LIBS:~/src
Then run:
bin/verx
To list the test VMs:
bin/verx list --uri test:///default
To retrieve the test virtual machine configuration:
bin/verx dumpxml test --uri test:///default
To create the example VM (a no-op with the test driver):
bin/verx create priv/example.xml --uri test:///default
To see all the VMs (if you have TLS set up):
bin/verx list --all --transport verx_client_tls --uri test:///default
To screenshot the test VM:
bin/verx screenshot test --uri test:///default
The test driver will return an error if console access is requested. To
connect to an actual VM's console using the Unix transport:
bin/verx console localvm # control-C to exit
## HOW TO USE IT
## libvirt documentation
See <http://libvirt.org/html/libvirt-libvirt.html>
### DATA TYPES
verx_transport()
Reference to the underlying transport and transport handler.
unix_socket() = string() | binary()
Path to Unix socket.
### verx
verx:Call(Ref) -> ok | {ok, Payload} | {error, Error}
verx:Call(Ref, Arg) -> ok | {ok, Payload} | {error, Error}
Types Call = [connect_open, connect_close, connect_list_domain, ...]
Ref = verx_transport()
Arg = [remote_protocol_args()]
Payload = [remote_protocol_ret()]
Error = [ posix() | libvirt() ]
verx has a large number of functions (283). See verx.erl or the
exports in verx:module_info() for a list.
Understanding the arguments for a remote protocol call takes some
work. For example, for verx:domain_define_xml/2, here are some
places to look at:
* check verx.erl for the arity
* check remote_protocol_xdr.erl for the argument format. The
parsing function is prefaced with "enc_remote_" and ends with
"_args":
enc_remote_domain_define_xml_args/1
* check the XDR protocol file, remote_protocol.x:
struct remote_domain_define_xml_args {
remote_nonnull_string xml;
};
* look at the libvirt documentation. Generally the libvirt
counterpart is camelcased and prefaced with "vir":
virDomainDefineXML
Similarly, for the call return values, search for the
suffix "_ret", e.g., dec_remote_domain_define_xml_ret and
remote_domain_define_xml_ret.
### verx\_client
verx_client:start(Opt) -> {ok, Ref} | {error, posix()}
Types Opt = [ Options ]
Options = {transport, Transport}
% Unix socket
| {path, unix_socket()}
% TCP and TLS
| {host, ip_address()}
| {port, uint16()}
% TLS
| {cacert, path()}
| {cert, path()}
| {key, path()}
| {depth, integer()}
| {password, string()}
| {ciphers, ciphers()}
Transport = verx_client_unix
| verx_client_tcp
| verx_client_tls
RPC transport layer, supports Unix sockets, TCP and TLS (IPv4
and IPV6).
Options depend on the underlying transport mechanism.
verx_client:stop(Ref) -> ok
Closes the transport socket.
verx_client:recv(Ref) -> {ok, Buf} | {error, posix()}
Types Ref = verx_transport()
Buf = [binary()]
Returns streamed data. The stream must first be prepared
by making the appropriate remote protocol call, e.g.,
verx:domain_snapshot/2.
### verx\_client\_unix
### verx\_client\_tcp
### verx\_client\_tls
### verx\_rpc
## EXAMPLES
### OPEN A CONNECTION TO LIBVIRTD
% Connect to the libvirtd socket
{ok, Ref} = verx_client:start(),
% libvirt remote procotol open message
% by default to qemu:///system
ok = verx:connect_open(Ref),
% send a close message
ok = verx:connect_close(Ref),
% send a remote protocol open message
% connecting to lxc containers
ok = verx:connect_open(Ref, ["lxc:///", 0]),
% close and stop the transport
ok = verx:connect_close(Ref),
ok = verx_client:stop(Ref).
% open a TLS connection on the default port
CACert = "/tmp/cert/cacert.pem",
Cert = "/tmp/cert/clientcert.pem",
Key = "/tmp/cert/clientkey.pem",
{ok, Ref} = verx_client:start([
{transport, verx_client_tls},
{cacert, CACert},
{cert, Cert},
{key, Key}
]).
### CREATING A DOMAIN
-module(crvm).
-export([file/0]).
file() ->
file("priv/example.xml").
file(Path) ->
% Connect to the libvirtd socket
{ok, Ref} = verx_client:start(),
% libvirt remote procotol open message
ok = verx:connect_open(Ref),
{ok, XML} = file:read_file(Path),
% Domain is defined but not running
{ok, [Domain]} = verx:domain_define_xml(Ref, [XML]),
% Start the VM
ok = verx:domain_create(Ref, [Domain]),
{ok, [Active] = verx:connect_num_of_domains(Ref),
io:format("Active Domains: ~p~n", [Active]),
% Send a protocol close message
ok = verx:connect_close(R),
% Close the socket
ok = verx_client:stop(R),
{ok, Domain}.
To list the VMs:
-module(lsvm).
-export([ls/0]).
ls() ->
{ok, Ref} = verx_client:start(),
ok = verx:connect_open(Ref),
{ok, [NumDef]} = verx:connect_num_of_defined_domains(Ref),
{ok, [NumRun]} = verx:connect_num_of_domains(Ref),
{ok, [Shutoff]} = verx:connect_list_defined_domains(Ref, [NumDef]),
{ok, [Running]} = verx:connect_list_domains(Ref, [NumRun]),
{ok, [{running, info(Ref, Running)},
{shutoff, info(Ref, Shutoff)}]}.
info(Ref, Domains) ->
[ begin
{ok, [{Name, UUID, Id}]} = verx:domain_lookup_by_id(Ref, [N]),
{Name, [{uuid, UUID}, {id, Id}]}
end || N <- Domains ].
To shutdown the VM:
% Get the domain resource
lookup(Ref, Id) when is_integer(Id) ->
{ok, [Domain]} = verx:domain_lookup_by_id(Ref, [Id]),
{ok, Domain};
lookup(Ref, Name) when is_binary(Name) ->
{ok, [Domain]} = verx:domain_lookup_by_name(Ref, [Name]),
{ok, Domain}.
halt(Ref, Domain) ->
% shutdown only works if acpid is installed in the VM
ok = verx:domain_shutdown(R, [Domain]),
verx:domain_destroy(Ref, [Domain]).
To remove the VM, undefine it:
verx:domain_undefine(Ref, [Domain])
### SUSPENDING AND RESUMING A DOMAIN
This example provides the Erlang equivalent of a Python script to
manipulate a running domain. The example was taken from:
<http://www.ibm.com/developerworks/linux/library/l-libvirt/>
-module(ex6).
-export([start/0, states/2]).
start() ->
{ok, Ref} = verx_client:start(),
ok = verx:connect_open(Ref),
{ok, [Num]} = verx:connect_num_of_domains(Ref),
{ok, [Ids]} = verx:connect_list_domains(Ref, [Num]),
[ states(Ref, Id) || Id <- Ids ],
ok.
states(Ref, Id) ->
{ok, [Domain]} = verx:domain_lookup_by_id(Ref, [Id]),
% return value of domain_get_info from remote_protocol.x:
%
% struct remote_domain_get_info_ret {
% unsigned char state;
% unsigned hyper maxMem;
% unsigned hyper memory;
% unsigned short nrVirtCpu;
% unsigned hyper cpuTime;
% };
io:format("running: ~p~n", [verx:domain_get_info(Ref, [Domain])]),
ok = verx:domain_suspend(Ref, [Domain]),
io:format("suspended: ~p~n", [verx:domain_get_info(Ref, [Domain])]),
ok = verx:domain_resume(Ref, [Domain]),
io:format("resumed: ~p~n", [verx:domain_get_info(Ref, [Domain])]),
ok = verx:domain_shutdown(Ref, [Domain]),
io:format("shutdown: ~p~n", [verx:domain_get_info(Ref, [Domain])]),
ok = verx:domain_destroy(Ref, [Domain]),
io:format("destroyed: ~p~n", [verx:domain_get_info(Ref, [Domain])]).
### RETRIEVING HYPERVISOR INFORMATION
Here is some code to retrieve information about the hypervisor,
similar to the example in the Ruby libvirt documentation
(<http://libvirt.org/ruby/examples/node_info.rb>):
-module(node_info).
-export([start/0]).
start() ->
{ok, Ref} = verx_client:start(),
ok = verx:connect_open(Ref),
[ begin
Reply = case Proc of
{Call, Arg} -> verx:Call(Ref, Arg);
Call -> verx:Call(Ref)
end,
result(Proc, Reply)
end || Proc <- [
node_get_info,
{node_get_cells_free_memory, [0, 100]},
connect_get_version,
connect_get_lib_version,
connect_get_hostname,
connect_get_uri,
node_get_free_memory,
node_get_security_model,
connect_is_secure,
connect_get_capabilities
] ],
ok = verx:connect_close(Ref),
verx_client:stop(Ref).
result(Call, {ok, N}) ->
error_logger:info_report([{call, Call}] ++ N);
result(Call, {error, _Error} = N) ->
error_logger:error_report([{call, Call}] ++ N).
### SYSTEM CONSOLE
The VM system console can be accessed using any of the transports.
% Connect to libvirtd using the Unix socket
1> {ok, Ref} = verx_client:start().
{ok,<0.43.0>}
% Open a remote protocol session to the Linux containers hypervisor
2> verx:connect_open(Ref, ["lxc:///", 0]).
ok
% Get a domain reference
3> {ok, [Domain]} = verx:domain_lookup_by_name(Ref, [<<"lxc-1">>]).
{ok,[{<<"lxc-1">>,
<<150,162,91,134,54,66,203,130,29,224,244,242,121,45,5,118>>,
19586}]}
% Open the console. The arguments are:
% Domain
% Device name : string() or void (NULL)
% Flags : integer()
4> verx:domain_open_console(Ref, [Domain, void, 0]).
% Send a message to the console, check the results with
% flush()
% Start up Erlang ...
5> verx_client:send(Ref, [<<"erl\n">>]).
6> verx_client:send(Ref, [<<"spawn(fun() -> io:format(\"Erlang process in an Erlang VM in a Linux VM in an Erlang process!\") end).\n">>]).
% Receive the message back from the console
8> verx_client:recv(Ref).
{ok,<<"Erlang process in an Erlang VM in a Linux VM in an Erlang process!">>}
### TAKING A SCREENSHOT
An example of using the libvirt stream interface to capture an image of
the VM console:
-module(ss).
-export([host/1]).
host(Name) when is_list(Name) ->
host(list_to_binary(Name));
host(Name) when is_binary(Name) ->
{ok, Ref} = verx_client:start(),
ok = verx:connect_open(Ref),
{ok, [Domain]} = verx:domain_lookup_by_name(Ref, [Name]),
Screen = 0,
Flags = 0,
{ok, [Mime]} = verx:domain_screenshot(Ref, [Domain, Screen, Flags]),
Ext = case Mime of
<<"image/x-portable-pixmap">> -> <<".ppm">>;
_ -> <<".screen">>
end,
{ok, Buf} = verx_client:recvall(Ref),
File = <<Name/binary, Ext/binary>>,
ok = file:write_file(File, Buf),
{ok, Mime, File}.
## CREATING LINUX CONTAINERS
This example will generate many Linux containers (LXC) attached to a
bridge (br0).
-module(clxc).
-export([start/2, start/3, create/2, template/2]).
start(Prefix, Num) ->
{ok, Ref} = verx_client:start(),
ok = verx:connect_open(Ref, ["lxc:///", 0]),
start(Ref, Prefix, Num).
start(_Ref, _Prefix, 0) ->
ok;
start(Ref, Prefix, Num) ->
Name = Prefix ++ integer_to_list(Num),
<<Bytes:3/bytes, _/binary>> = erlang:md5(Name),
Macaddr = "52:54:00:" ++ string:join([ httpd_util:integer_to_hexlist(N)
|| <<N:8>> <= Bytes ], ":"),
XML = template(Name, Macaddr),
ok = create(Ref, XML),
start(Ref, Prefix, Num-1).
create(Ref, XML) ->
{ok, [Domain]} = verx:domain_define_xml(Ref, [XML]),
verx:domain_create(Ref, [Domain]).
template(Name, Macaddr) ->
"<domain type='lxc'>
<name>" ++ Name ++ "</name>
<memory>102400</memory>
<os>
<type>exe</type>
<init>/bin/sh</init>
</os>
<devices>
<console type='pty'/>
<interface type='bridge'>
<mac address='" ++ Macaddr ++ "'/>
<source bridge='br0'/>
</interface>
</devices>
</domain>".
## GENERATING THE REMOTE PROTOCOL MODULE
To create the remote\_protocol\_xdr.erl from a remote\_protocol.x file:
1. Copy remote\_protocol.x to priv
2. Run:
make clean; make
If there are any errors, read through `bin/mk_remote_protocol.escript`.
## VERX CLIENT
`verx` is a simple command line client similar to `virsh`. To use
`verx`, the ERL\_LIBS environment variable must point to the directory
_containing_ the verx repository:
export ERL_LIBS=$ERL_LIBS:~/src
export PATH=$PATH:~/src/verx/bin
Running `verx` without any options will return the list of commands.
All `verx` commands can take some options:
--uri : URI supported by libvirt (default: qemu:///system)
--transport : (default: verx_client_unix)
verx_client_unix
verx_client_tcp
verx_client_tls
For TCP and TLS transports:
--host : hostname
--port : port
For the TLS transport:
--cacert : path to CA cert (default: /etc/pki/CA/cacert.pem)
--cert : path to client cert (default: /etc/pki/libvirt/clientcert.pem)
--depth : cert validation depth (default: 1)
Examples:
# List all defined Qemu/KVM instances through the libvirtd Unix socket
verx list --all
# List running LXC instances
verx list --uri=lxc:///
# Dump the configuration of a KVM using the TLS transport over IPv6
verx dumpxml myvm --transport verx_client_tls --host ::1
# Access the console of a container over TLS/IPv6
# Use ctl-C to exit
verx console mylxc --uri lxc:/// --transport verx_client_tls --host ::1
## TODO
* verx\_client\_tls
* single byte received before packet (works if thrown away)