= oVirt Engine API Ruby SDK
:reference: http://www.rubydoc.info/gems/ovirt-engine-sdk/OvirtSDK4
== Introduction
This project contains the Ruby SDK for the oVirt Engine API.
== Important
Note that most of the code of this SDK is automatically generated. If you
just installed the gem then you will have everything already, but if you
downloaded the source then you will need to generate it, follow the
instructions in the README.adoc
file of the parent directory.
== Installation
The SDK can be installed in Fedora 24 and CentOS 7 using the RPM packages
provided by the oVirt project. To do so install the oVirt release package:
Then install the SDK package:
dnf install rubygem-ovirt-engine-sdk4
For other operating systems (and also for Fedora and CentOS) you can
install the SDK using the gem
command, which will download the source
from https://rubygems.org[RubyGems], build and install it:
$ gem install ovirt-engine-sdk
The SDK uses http://www.xmlsoft.org[libxml2] for parsing and rendering
XML and https://curl.haxx.se/libcurl[libcurl] for HTTP transfers. The
parts of the SDK that interact with those libraries are written in C.
This means that before building you must make sure you have the C
compiler and the required header and libraries files installed in your
system. For example, if you are using distributions like Fedora, or
CentOS:
dnf -y install \
gcc
libcurl-devel
libxml2-devel
redhat-rpm-config
ruby
ruby-devel
rubygems
rubygems-devel
NOTE: The installation of the ruby
, ruby-devel
, rubygems
and
rubygems-devel
packages is necessary if you are going to use the Ruby
version included in your distribution. If you are using tools like
rbenv
or rvm
then this is not necessary.
If you are using distributions like Debian, or Ubuntu:
apt-get --assume-yes install \
gcc
libcurl4-openssl-dev
libxml2-dev
ruby
ruby-dev
NOTE: The installation of the ruby
and ruby-dev
packages is
necessary if you are going to use the Ruby version included in your
distribution. If you are using tools like rbenv
or rvm
then this is
not necessary.
Some Linux distributions, like Debian and Ubuntu, provide multiple
versions of libcurl
, compiled with support for different TLS
libraries: OpenSSL, NSS and GnuTLS. Currently the SDK only supports
OpenSSL, so make sure that you have that version of libcurl
installed.
For example, in Ubuntu 16.04 if you have the NSS version installed, you
will have to remove it and then install the OpenSSL version:
apt-get --assume-yes remove libcurl4-nss-dev
apt-get --assume-yes install libcurl4-openssl-dev
NOTE: The examples above use the dnf
command, which is the default in
Fedora 24. In CentOS 7 you may need to use the yum
command, as dnf
is optional.
== Usage
=== Modules and classes
All the classes of the SDK are inside the {reference}[OvirtSDK4] module.
Within the module there are several kinds of classes:
Connection::
The {reference}/Connection[Connection] class, as is the mechanism to
connect to the server and to get the reference to the root of the
services tree.
Errors::
The {reference}/Error[Error] class is the base exception class that the
SDK will raise when it needs to report any error.
+
For certain kinds of errors there are specific error classes, extending
the base error class:
+
- {reference}/AuthError[AuthError] - Raised when authentication or
authorization fail.
- {reference}/ConnectionError[ConnectionError] - Raised when the name of
the server can't be resolved, and when the server is down or
unreachable.
- {reference}/NotFoundError[NotFoundError] - Raised when the requested
object doesn't exist.
- {reference}/TimeoutError[TimeoutError] - Raised when an operation times
out.
Types::
These classes implement the types used by the API. For example, the
{reference}/Vm[Vm] Ruby class is the implementation of the virtual
machine type. These classes are just containers of data, they don't
contain any logic.
+
Instances of these classes are used as parameters and return values of
service methods. The conversion to/from the underlying representation
is handled transparently by the SDK.
Services::
These classes implement the services supported by the API. For
example, the {reference}/VmsService[VmsService] Ruby class is the
implementation of the service that manages the collection of virtual
machines of the system.
+
Instances of these classes are automatically created by the SDK when a
service is located. For example, a new instance of the VmsService
class will be automatically created by the SDK when doing the following:
+
[source,ruby]
vms_service = connection.system_service.vms_service
Avoid creating instances of these classes manually, as the parameters of
the constructors, and in general all the methods except the service
locators and service methods (described later) may change in the
future.
There are other classes, like HTTP client classes, readers and writers.
These are used to implement the HTTP communication, and for XML
parsing and rendering. Refrain from using them, as they are internal
implementation details that may change in the future: backwards
compatibility isn't guaranteed.
=== Connecting to the server
To connect to the server require the ovirtsdk4
file. That will give to
the {reference}/Connection[Connection] class. This is the entry point of
the SDK, and gives you access to the root of the tree of services of the
API:
[source,ruby]
Create a connection to the server:
connection = OvirtSDK4::Connection.new(
url: 'https://engine.example.com/ovirt-engine/api',
username: 'admin@internal',
password: '...',
ca_file: 'ca.pem',
)
Get the reference to the root of the tree of services:
system_service = connection.system_service
The connection holds expensive resources, including a pool of HTTP
connections to the server and an authentication token. It is very
important to free these resources when they are no longer in use:
[source,ruby]
Close the connection to the server:
connection.close
The connection and all the services that have been obtained from it
can't be used after the connection has been closed.
The ca.pem
file is required when connecting to a server protected
with TLS. In an usual oVirt installation it will be in
/etc/pki/ovirt-engine/ca.pem
. If you don't specify ca_file
, then
system wide CA certificate store will be used.
If something fails when trying to create the connection (authentication
failure, communication failure, etc) the SDK will raise a
{reference}/Error[Error] exception containing the details.
=== Using types
The type classes are pure data containers, they don't have any logic or
operations. Instances can be created and modified at will.
Creating or modifying one of this instances does not have any effect
in the server side, unless they are explicitly passed to a call to one
of the service methods described below. Changes in the server side are
not automatically reflected in the instances that already exist in
memory.
The constructors of these classes have multiple optional arguments, one
for each attribute of the type. This is intended to simplify creation of
objects using nested calls to multiple constructors. For example, to
create an instance of a virtual machine, with an specification of the
cluster and template that it should use, and the memory it should have:
[source,ruby]
vm = OvirtSDK4::Vm.new(
name: 'myvm',
cluster: OvirtSDK4::Cluster.new(
name: 'mycluster'
),
template: OvirtSDK4::Template.new(
name: 'mytemplate'
),
memory: 1073741824
)
The hashes passed to these constructors are processed recursively. For
example, in the above code instead of explicitly calling the constructor
for the Cluster
and Template
classes it is also possible to use
plain hashes:
[source,ruby]
vm = OvirtSDK4::Vm.new(
name: 'myvm',
cluster: {
name: 'mycluster'
},
template: {
name: 'mytemplate'
},
memory: 1073741824
)
The SDK will internally convert those hashes to the required classes, so
the result will be exactly the same.
Using the constructors in this way is recommended, but not mandatory.
You can also create the instance with no arguments in the call to the
constructor, and then populate the object step by step, using the
setters, or using a mix of both approaches:
[source,ruby]
vm = OvirtSDK4::Vm.new
vm.name = 'myvm'
vm.cluster = OvirtSDK4::Cluster.new(name: 'mycluster')
vm.template = OvirtSDK4::Template.new(name: 'mytemplate')
vm.memory = 1073741824
Attributes that are defined as lists of objects in the specification of
the API are implemented as Ruby arrays. For example, the
custom_properties
attributes of the
http://ovirt.github.io/ovirt-engine-api-model/master/#types/vm[Vm] type
is defined as a list of objects of type CustomProperty
, so when using
it in the SDK it will be a Ruby array:
[source,ruby]
vm = OvirtSDK4::Vm.new(
name: 'myvm',
custom_properties: [
OvirtSDK4::CustomProperty.new(...),
OvirtSDK4::CustomProperty.new(...),
...
]
)
Attributes that are defined as enumerated values in the specification of
the API are implemented as constatns within a module that has the same
name than the enumerated type. For example, the status
attribute of
the Vm
type is defined using the
http://ovirt.github.io/ovirt-engine-api-model/master/#types/vm_status[VmStatus]
enum:
[source,ruby]
case vm.status
when OvirtSDK4::VmStatus::DOWN
...
when OvirtSDK4::VmStatus::IMAGE_LOCKED
...
end
NOTE: In the specification of the API the values of enum types appear in
lower case, because that is what is used in the XML or JSON documents,
but in Ruby it is common practice to use upper case for this kind of
constants, so that is how they are defined in the Ruby SDK: all upper
case.
Reading the attributes of instances of types is done using the
corresponding attribute readers:
[source,ruby]
puts "vm.name: #{vm.name}"
puts "vm.memory: #{vm.memory}"
vm.custom_properties.each do |custom_property|
...
end
=== Using links
Some of the attributes of types are defined as links in the
specification of the API. This is done to indicate that their value
won't usually be populated when retrieving the representation of that
object, only a link will be returned instead. For example, when
retrieving a virtual machine, the XML returned by the server will look
like this:
[source,xml]
myvm
That link is available as vm.disk_attachments
, but it doesn't contain
the actual disk attachments. To get the actual data the
{reference}/Connection[Connection] class provides a
{reference}/Connection#follow_link-instance_method[follow_link] method
that uses the value of the href
XML attribute to retrieve the actual
data. For example, to retrieve the details of the disks of the virtual
machine, you can first follow the link to the disk attachments, and then
follow the link to each of the disks:
[source,ruby]
Retrieve the virtual machine:
vm = vm_service.get
Follow the link to the disk attachments, and then to the disks:
attachments = connection.follow_link(vm.disk_attachments)
attachments.each do |attachment|
disk = connection.follow_link(attachment.disk)
puts "disk.alias: #{disk.alias}"
end
=== Locating services
The API provides a set of services, each associated to a particular
path within the URL space of the server. For example, the service that
manages the collection of virtual machines of the system lives in
/vms
, and the service that manages the virtual machine with identifier
123
lives in /vms/123
.
In the SDK the root of that tree of services is implemented by the
system service. It is obtained calling the
{reference}/Connection#system_service-instance_method[system_service]
method of the connection:
[source,ruby]
system_service = connection.system_service
Once you have the reference to this system service you can use it to get
references to other services, calling the +*_service+
methods (called
service locators) of the previous service. For example, to get a
reference to the service that manages the collection of virtual machines
of the system use the
{reference}/SystemService#vms_service-instance_method[vms_service]
service locator:
[source,ruby]
vms_service = system_service.vms_service
To get a reference to the service that manages the virtual machine with
identifier 123
, use the
{reference}/VmsService#vm_service-instance_method[vm_service] service
locator of the service that manages the collection of virtual machines.
It receives as a parameter the identifier of the virtual machine:
[source,ruby]
vm_service = vms_service.vms_service('123')
IMPORTANT: Calling the service locators doesn't send any request to the
server. The Ruby objects that they return are pure services, they
don't contain any data. For example, the vm_service
Ruby object
obtained in the previous example is not the representation of a
virtual machine. It is the service that can be used to retrieve, update,
delete, start and stop that virtual machine.
=== Using services
Once you have located the service you are interested on, you can start
calling its service methods, the methods that send requests to the
server and do the real work.
The services that manage collections of object usually have the list
and add
methods.
The services that manage a single object usually have the get
,
update
and remove
methods.
Both kinds of services can also have additional action methods, which
perform actions other than retrieving, creating, updating or removing.
Most frequently they are available in services that manage a single
object.
==== Using the get methods
These service methods are used to retrieve the representation of a
single object. For example, to retrieve the representation of the
virtual machine with identifier 123
:
[source,ruby]
Find the service that manages the virtual machine:
vms_service = system_service.vms_service
vm_service = vms_service.vm_service('123')
Retrieve the representation of the virtual machine:
vm = vm_service.get
The result will be an instance of the corresponding type. For example,
in this case, the result will be an instance of the Ruby class
{reference}/Vm[Vm].
The get
methods of some services support additional parameters that
control how to retrieve the representation of the object, or what
representation to retrieve in case there are multiple representations.
For example, for virtual machines you may want to retrieve its current
state, or the state that will be used the next time it is started, as
they may be different. To do so the get
method of the service that
manages a virtual machine supports a
http://ovirt.github.io/ovirt-engine-api-model/master/#services/vm/methods/get/parameters/next_run[next_run]
boolean parameter:
[source,ruby]
Retrieve the representation of the virtual machine, not the
current one, but the one that will be used after the next
boot:
vm = vm_service.get(next_run: true)
Check the {reference}[reference] documentation of the SDK to find out
the details.
If the object can't be retrieved, for whatever reason, the SDK will
raise an {reference}/Error[Error] exception, containing the details of
the failure. This includes the situation when the object doesn't
actually exist. Note that the exception will be raised when calling the
get
service method, the call to the service locator method never
fails, even if the object doesn't exist, because it doesn't send any
request to the server. For example:
[source,ruby]
Find the service that manages a virtual machine that does
not exist. This will succeed.
vm_service = vms_service.vm_service('junk')
Retrieve the virtual machine. This will raise an exception.
vm = vm_service.get
==== Using the list methods
These service methods are used to retrieve the representations of the
objects of the collection. For example, to retrieve the complete
collection of virtual machines of the system:
[source,ruby]
Find the service that manages the collection of virtual
machines:
vms_service = system_service.vms_service
vms = vms_service.list
The result will be a Ruby array containing the instances of the
corresponding types. For example, in this case, the result will be a
list of instances of the Ruby class {reference}/Vm[Vm].
The list
methods of some services support additional parameters. For
example, almost all the top level collections support a search
parameter that can be used ask the server to filter the results, and a
max
parameter that can be used to limit the number of results returned
by the server. For example, to get the list of virtual machines whose
name starts with my
, and to get at most 10 results:
[source,ruby]
vms = vms_service.list(search: 'name=my*', max: 10)
NOTE: Not all the list
methods support these parameters, and some
list
methods may support other additional parameters. Check the
{reference}[reference] documentation of the SDK to find out the details.
If list of results is empty, for whatever reason, the returned value
will be an empty Ruby array, it will never be nil
.
If there is an error while trying to retrieve the result, then the SDK
will raise an {reference}/Error[Error] exception containing the details
of the failure.
==== Using the add methods
These service methods add new elements to collections. They receive an
instance of the relevant type describing the object to add, send the
request to add it, and return an instance of the type describing the
added object.
For example, to add a new virtual machine named myvm
:
[source,ruby]
Add the virtual machine:
vm = vms_service.add(
OvirtSDK4::Vm.new(
name: 'myvm',
cluster: {
name: 'mycluster'
},
template: {
name: 'mytemplate'
}
)
)
If the object can't be created, for whatever reason, the SDK will
raise an {reference}/Error[Error] exception containing the details of
the failure. It will never return nil
.
It is very important to understand that the Ruby object returned by this
add
method is an instance of the relevant type, it isn't a service,
just a container of data. In this particular example the returned object
will be an instance of the {reference}/Vm[Vm] class. If once the
virtual machine is created you need to perform some operation on it,
like retrieving it again, or starting it, you will first need to find
the service that manages it, calling the corresponding service locator:
[source,ruby]
Add the virtual machine:
vm = vms_service.add(
...
)
Find the service that manages the virtual machine:
vm_service = vms_service.vm_service(vm.id)
Perform some other operation on the virtual machine, like
starting it:
vm_service.start
Note that the creation of most objects is an asynchronous task. That
means, for example, that when creating a new virtual machine the add
method will return before the virtual machine is completely created
and ready to be used. It is good practice to poll the status of the
object till it is completely created. For a virtual machine that means
checking till the status is down. So the recommended approach to create
a virtual machine is the following:
[source,ruby]
Add the virtual machine:
vm = vms_service.add(
...
)
Find the service that manages the virtual machine:
vm_service = vms_service.vm_service(vm.id)
Wait till the virtual machine is down, which indicats that all the
disks have been created:
loop do
sleep(5)
vm = vm_service.get
break if vm.status == OvirtSDK4::VmStatus::DOWN
end
In the above loop it is very important to retrieve the object each time,
using the get
method, otherwise the status
attribute won't be
updated.
==== Using the update methods
These service methods update existing objects. They receive an instance
of the relevant type describing the update to perform, send the request
to update it, and return an instance of the type describing the updated
object.
For example, to update the name of a virtual machine from myvm
to
newvm
:
[source,ruby]
Find the virtual machine, and then the service that
manages it:
vm = vms_service.list(search: 'name=myvm').first
vm_service = vms_service.vm_service(vm.id)
Update the name:
updated_vm = vms_service.update(
OvirtSDK4::Vm.new(
name: 'newvm'
)
)
When performing updates, try to avoid sending the complete
representation of the object, send only the attributes that you want to
update. For example, try to avoid this:
[source,ruby]
Retrieve the current representation:
vm = vm_service.get
Update the representation, in memory, no request sent
to the server:
vm.name = 'newvm'
Send the update. Do not do this.
vms_service.update(vm)
The problem with that is double. First you are sending much more
information than what the server needs, thus wasting resources. Second,
and more important, the server will try to update all the attributes of
the object, even those that you didn't need to change. Usually that
isn't a problem, but has caused many unexpected bugs in the server side
in the past.
The update
methods of some services support additional parameters that
control how or what to update. For example, for virtual machines you may
want to update its current state, or the state that will be used the
next time it is started. To do so the update
method of the service
that manages a virtual machine supports a
http://ovirt.github.io/ovirt-engine-api-model/master/#services/vm/methods/update/parameters/next_run[next_run]
boolean parameter:
[source,ruby]
Update the memory of the virtual machine 1 GiB, but not the current
one, the one it will have after the next boot:
vm = vm_service.update(
OvirtSDK4::Vm.new(
memory: 1073741824
),
next_run: true
)
If the update can't be performed, for whatever reason, the SDK will
raise an {reference}/Error[Error] exception containing the details of
the failure. It will never return nil
.
The Ruby object returned by this update
method is an instance of the
relevant type, it isn't a service, just a container of data. In this
particular example the returned object will be an instance of the
{reference}/Vm[Vm] class.
==== Using the remove methods
These service methods remove existing objects. They usually don't
receive any parameters, as they are methods of the services that manage
single objects, therefore the service already knows what object to
remove.
For example, to remove the virtual machine with identifier 123
:
[source,ruby]
Find the service that manages the virtual machine:
vm_service = vms_service.vm_service('123')
Remove the virtual machine:
vms_service.remove
The remove
methods of some services support additional parameters that
control how or what to remove. For example, for virtual machines it is
possible to remove the virtual machine while preserving the disks.
To do so the remove
method of the service that manages a virtual machine supports a
http://ovirt.github.io/ovirt-engine-api-model/master/#services/vm/methods/remove[detach_only]
boolean parameter:
[source,ruby]
Remove the virtual machine, but preserve the disks:
vm_service.remove(detach_only: true)
The remove
methods return nil
if the object is removed successfully.
It does not return the removed object. If the object can't be removed,
for whatever reason, the SDK will raise an {reference}/Vm[Vm]
exception containing the details of the failure.
==== Using action methods
These service methods perform miscellaneous operations. For example, the
service that manages a virtual machine has methods to start and stop it:
[source,ruby]
Start the virtual machine:
vm_service.start
Many of these methods include parameters that modify the operation. For
example, the method that starts a virtual machine supports a
http://ovirt.github.io/ovirt-engine-api-model/master/#services/vm/methods/start/parameters/use_cloud_init[use_cloud_init]
parameter that indicates if you want to start it using
https://cloudinit.readthedocs.io/cloud-init[cloud-init]:
[source,ruby]
Start the virtual machine:
vm_service.start(cloud_init: true)
Most action methods return nil
when they succeed, and raise a
{reference}/Error[Error] when they fail. But a few action methods return
values. For example, the service that manages a storage domains has an
http://ovirt.github.io/ovirt-engine-api-model/master/#services/storage_domain/methods/is_attachedd[is_attached]
action method that checks if the storage domain is already attached to a
data center. That method returns a boolean:
[source,ruby]
Check if the storage domain is attached to a data center:
sds_service = system_service.storage_domains_service
sd_service = sds_service.storage_domain_service('123')
if sd_service.is_attached
...
end
Check the {reference}[reference] documentation of the SDK to see the
action methods supported by each service, the parameters that they
support, and the values that they return.
== More information
The reference documentation of the API is available
http://ovirt.github.io/ovirt-engine-api-model[here].
The reference documentation of the SDK is available {reference}[here].
There is a collection of examples that show how to use the SDK
https://github.com/oVirt/ovirt-engine-sdk-ruby/tree/master/sdk/examples[here].