BACKGROUND
----------
With Ovirt we have developed a very good system for managing large
numbers of virtual machines, generic hardware nodes, and storage systems.
However, the system is currently completely driven through a web UI (WUI).
It is our intention to expand that to include a general purpose API to
control all aspects of the configuration and task management supplied
by the WUI.
Through much discussion it was decided that we would leverage the rails
models created for use in the WUI, and create an alternative way to
access these models, start tasks etc. This will be integrated with the
current task management system (taskomatic) to produce a complete package.
The idea is to create a set of classes available via QMF as objects.
These classes would then be backed by either a mid-level API that
encompasses the models, or by active record models directly. Any changes
made to these records outside of the API (eg by the WUI) would require
notification of changes to taskomatic.
The API is a work in progress and is missing some aspects such as hardware
pools etc., but this will give you a good idea of how it looks.
The API is documented below using the QMF XML schema format.
Enjoy! :)
API
---
<schema package="org.ovirt.ovirt">
<!-- This is an object model of the Ovirt API for QMF.
access: Access mode: RC = Read/Create, RO = read only (settable only by
agent implementing class), RW = read/write.
dir: Direction of argument: I = in, O = out, IO = in/out.
-->
<class name="Ovirt">
<property name="version" type="sstr"
access="RC" desc="Ovirt version string"/>
<method name="create_vm_def" desc="Define a new virtual
machine definition.">
<arg name="description" dir="I"
type="sstr" desc="Description of new VM definition."/>
<!-- What is the use case for being able to set the UUID? -->
<arg name="num_cpus" dir="I"
type="uint32" desc="Number of virtual CPUs to
allocate."/>
<arg name="memory" dir="I" type="uint64"
desc="Amount of memory to allocate in KB."/>
<arg name="UUID" dir="I" type="sstr"
desc="UUID of VM. Will be assigned if left empty."/>
<arg name="vnic_mac" dir="I" type="sstr"
desc="MAC address of virtual NIC. Will be assigned if left
empty."/>
<arg name="vm" dir="O" type="objId"
references="VmDef" desc="Newly created domain object"/>
</method>
<method name="create_nfs_pool_def" desc="Define new nfs
storage pool definition">
<arg name="name" type="sstr"
desc="Description/name of the pool."/>
<arg name="server_ip" type="sstr" desc="IP
Address of NFS server."/>
<arg name="export_path" type="lstr" desc="Path
of export on NFS server."/>
<arg name="pool" dir="O" type="objId"
references="PoolDef" desc="Newly created pool object"/>
</method>
<method name="create_iscsi_pool_def" desc="Define new
iscsi storage pool definition">
<arg name="name" type="sstr"
desc="Description/name of the pool."/>
<arg name="server_ip" type="sstr" desc="IP
Address of NFS server."/>
<arg name="server_port" type="uint32"
desc="iSCSI server port."/>
<arg name="target" type="lstr" desc="iSCSI
target."/>
<arg name="pool" dir="O" type="objId"
references="PoolDef" desc="Newly created pool object"/>
</method>
</class>
<class name="VmDef">
<property name="description" type="sstr"
access="RW" desc="VM description/name"/>
<property name="num_vcpus_allocated" type="uint32"
access="RW" desc="Number of virtual CPUs to allocate to
VM"/>
<property name="memory_allocated" type="uint64"
access="RW" desc="Amount of memory to allocate for VM in
KB."/>
<property name="mac" type="sstr"
access="RC" desc="VM virtual network card MAC Address"/>
<property name="uuid" type="sstr"
access="RC" desc="VM UUID"/>
<property name="provisioning" type="lstr"
access="RW" desc="Cobbler profile or image to use on boot, or
empty."/>
<property name="needs_restart" type="bool"
access="R" desc="Flag specifies if changes to object properties
require that this VM be restarted for changes to take effect."/>
<property name="state" type="sstr"
access="R" desc="Current state of the VM instance, or can be
queried directly from instance."/>
<property name="node" type="objId"
references="NodeDef" access="R" desc="Object reference
pointing to host this VM is running on, if it is running."/>
<property name="instance" type="objId"
references="Domain" access="R" desc="Object reference
pointing to the libvirt 'domain' object."/>
<method name="delete" desc="Delete this VM
definition.">
</method>
<method name="migrate" desc="Queue a migration event for
this VMs instance"/>
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
</method>
<method name="start" desc="Queue a start event for this
VMs instance"/>
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
</method>
<method name="shutdown" desc="Queue a shutdown event for
this VMs instance"/>
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
</method>
<method name="poweroff" desc="Queue a poweroff event for
this VMs instance"/>
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
</method>
<method name="suspend" desc="Queue a suspend event for
this VMs instance"/>
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
</method>
<method name="resume" desc="Queue a resume event for this
VMs instance"/>
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
</method>
</class>
<!-- Should this be a Host or Node def? Should get our terms straight.
-->
<class name="NodeDef">
<property name="name" type="sstr"
access="RC" desc="Host name"/>
<property name="uuid" type="sstr"
access="RC" desc="UUID of this node definition"/>
<property name="cpus" type="uint32"
access="RC" desc="Number of CPUs on this node."/>
<property name="cpu_speed" type="sstr"
access="RC" desc="Speed of CPUs on this node."/>
<property name="architecture" type="sstr"
access="RC" desc="Architecture of this node."/>
<property name="memory" type="uint64"
access="RC" desc="Amount of RAM on this machine in KB."/>
<property name="available" type="bool"
access="R" desc="See if node is currently available for
use."/>
<property name="enabled" type="bool"
access="RW" desc="Set enabled/disabled for this host."/>
<property name="instance" type="objId"
references="Node" access="R" desc="Object reference
pointing to the libvirt 'Node' object."/>
<method name="delete" desc="Delete this host from the
records.">
</method>
</class>
<class name="PoolDef">
<!-- FIXME: 'name' is not part of the model schema at this time.
-->
<property name="name" type="sstr"
access="RC" desc="Name of pool."/>
<!-- FIXME: 'uuid' is not part of the model schema at this time.
However, I actually think both of these should go in the database
if that will work as it allows you to track libvirt pools to pool
definitions. -->
<property name="uuid" type="sstr"
access="RC" desc="Pool UUID."/>
<property name="impl" type="objId"
access="RC" desc="References an NFSPoolImpl or
ISCSIPoolImpl."/>
<property name="type" type="sstr"
access="RC" desc="The type of storage pool, currently
'nfs' or 'iscsi'."/>
<property name="state" type="sstr"
access="R" desc="State of storage pool."/>
<method name="rescan" desc="Rescan the pool using a valid
node to determine volume information.">
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
</method>
<!-- FIXME: This is different from the WUI in that it just deletes the
info from the database. However I think it is
correct to have a task to do this. -->
<method name="delete" desc="Delete this pool. A new task
is issued to ensure there are no references to this Pool in use.">
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
</method>
</class>
<class name="NFSPoolImpl">
<property name="server_ip" type="sstr"
access="RC" desc="IP Address of NFS server."/>
<property name="export_path" type="lstr"
access="RC" desc="Path of export on NFS server."/>
<property name="pool" references='PoolDev'
type="objId" access="RC" desc="References parent
PoolDef object."/>
<method name="create_volume" desc="Create a new NFS
volume.">
<arg name="filename" type="lstr" dir="I"
desc="File name to back volume."/>
<arg name="size" type="uint64" dir="I"
desc="Size of new volume in KB."/>
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
<arg name="volume" dir="O" type="objId"
references="VolumeDef" desc="New Volume object. Note that the
state will be pending_setup on creation and the task must complete before it is
ready for use."/>
</method>
</class>
<class name="ISCSIPoolImpl">
<property name="server_ip" type="sstr"
access="RC" desc="IP Address of iSCSI server."/>
<property name="server_port" type="uint32"
access="RC" desc="Port on iSCSI server."/>
<property name="target" type="lstr"
access="RC" desc="iSCSI target."/>
<property name="pool" references='PoolDev'
type="objId" access="RC" desc="References parent
PoolDef object."/>
<method name="create_volume" desc="Create a new iSCSI
volume.">
<arg name="name" type="lstr" dir="I"
desc="Name of volume."/>
<arg name="size" type="uint64" dir="I"
desc="Size of new volume in KB."/>
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
<arg name="volume" dir="O" type="objId"
references="VolumeDef" desc="New Volume object. Note that the
state will be pending_setup on creation and the task must complete before it is
ready for use."/>
</method>
</class>
<class name="VolumeDef">
<!-- FIXME: name and key are also not in the WUI model but should be.
-->
<property name="name" type="lstr"
access="RC" desc="The name of this volume, unique to this
pool."/>
<property name="key" type="lstr"
access="RC" desc="The unique identifier of this
volume."/>
<property name="impl" type="objId"
access="RC" desc="References an NFSVolumeImpl or
ISCSIVolumeImpl."/>
<property name="type" type="sstr"
access="RC" desc="The type of volume, currently 'nfs' or
'iscsi'."/>
<property name="vm" type="objId"
references="VmDef" access="RW" desc="Attach this volume
to a specific VM."/>
<method name="delete" desc="Delete this volume. A new
task is issued to ensure there are no references to this volume in
use.">
<arg name="task" dir="O" type="objId"
references="Task" desc="New Task object representing this
task."/>
</method>
</class>
<class name="NFSVolumeImpl">
<property name="filename" type="lstr"
access="RC" desc="File name on NFS mount"/>
<property name="path" type="lstr"
access="RC" desc="Full path of NFS file."/>
<property name="state" type="sstr"
access="R" desc="State of NFS volume."/>
<property name="volume" type="objId"
references="VolumeDef" access="RC" desc="The volume
this implementation belongs to."/>
<property name="pool" type="objId"
references="PoolDef" access="RC" desc="The pool this
volume implementation belongs to."/>
</class>
<class name="ISCSIVolumeImpl">
<!-- FIXME Have to figure these out better -->
<property name="name" type="lstr"
access="RC" desc="iSCSI volume name."/>
<property name="volume" type="objId"
references="VolumeDef" access="RC" desc="The volume
this implementation belongs to."/>
<property name="pool" type="objId"
references="PoolDef" access="RC" desc="The pool this
volume implementation belongs to."/>
</class>
<class name="Task">
<property name="task_id" type="uint64"
access="RC" desc="ID of this task"/>
<property name="description" type="sstr"
access="RC" desc="The type of task this is
implementing"/>
<property name="state" type="sstr"
access="R" desc="The state of this task, 'queued',
'running', 'completed', 'failed'."/>
<property name="completed" type="bool"
access="R" desc="Convenient way to check if task is
completed."/>
<property name="error" type="bool"
access="R" desc="Convenient way to check if there was an
error."/>
<property name="message" type="lstr"
access="R" desc="Information about task processing; failure
information etc."/>
<property name="impl" type="objId"
access="RC" desc="References the task specific implementation
dependent on type."/>
<method name="cancel" desc="Cancel this task."/>
<method name="wait_for_completion" desc="Method that just
sits and waits for this task to complete. Can also be used with an async call
to recieve an event when it is completed."/>
</class>
<class name="VmTaskImpl">
<property name="task" type="objId"
references="Task" access="RC" desc="Reference to the
task this implementation is for."/>
<property name="vm_def" type="objId"
references="VmDef" access="RC" desc="Reference to VM
this action is being performed for."/>
</class>
<class name="PoolTaskImpl">
<property name="task" type="objId"
references="Task" access="RC" desc="Reference to the
task this implementation is for."/>
<property name="pool_def" type="objId"
references="PoolDef" access="RC" desc="Reference to
pool definition this action is being performed for."/>
</class>
<class name="VolumeTaskImpl">
<property name="task" type="objId"
references="Task" access="RC" desc="Reference to the
task this implementation is for."/>
<property name="volume_def" type="objId"
access="R" desc="Reference to volume definition this action is
being performed for."/>
</class>
</schema>
EXAMPLES
--------
I realize this could be confusing to some so I'll show you some examples
of how this might be used. These examples all use ruby but any other
language supported by QMF can be used, including C++, java, python and
ruby. It's still a little bit bulky.. we may see about adding some
support to the QMF API to make it easier to get objects from IDs etc.
s = Qpid::Qmf::Session.new()
b = s.add_broker(server_hostname, :mechanism => 'GSSAPI')
# Now that we are hooked up to the AMQP broker, we can use the
# QMF session to perform lookups of various objects. We'll assume
# for now however that there is no configuration in place.
ovirt = s.object(:class => 'ovirt')
# We now have the main 'ovirt' object. Now we can use that to
# create configuration objects.
# Create a storage pool
result = ovirt.create_nfs_pool_def('192.168.50.2',
'/home/exports')
raise "Error creating new pool: #{result.text}" if result.status == 0
pool = s.object(:object_id => result.pool)
# We created our new pool, now we start a 'scan' task to make sure it
# can come up ok.
result = pool.rescan
raise "Error starting pool rescan task: #{result.text}" if
result.status == 0
task = s.object(:object_id => result.task)
# This is just a convenience for linear setup.. obviously you don't want
# to do this all the time.
task.wait_for_completion
raise "Error configuring storage volume: #{task.message}" if
task.error
# Get all the nodes that are configured and running. We're going to use
# that to see how many VMs we should start.
nodes = s.objects(:class => 'NodeDef', 'available' =>
true, 'enabled' => true)
nodes.each do |node|
puts "node #{node.hostname} is up and ready."
end
# We just did that so we will make 2 VMs for each node.
num_vms = nodes.length * 2
# This is just queuing them all up and disregarding the task results.
# In real usage you'd probably either want to use a thread for each or
# use the qmf async call with a callback to know when the task completed
# but I'm going to ignore that for now.. maybe I'll add it later
while num_vms > 0
result = ovirt.create_vm_def('test_vm_#{num_vms}', 1, 1024,
'', '', '')
raise "Error creating new VM object: #{result.text}" if
result.status == 0
vm = s.object(:object_id => result.vm)
puts "vm description: #{vm.description}"
puts "vm uuid is: #{vm.uuid}"
result = pool.create_volume('disk#{num_vms}', 10000)
raise "Error starting create volume task: #{result.text}" if
result.status == 0
volume = s.object(:object_id => result.volume)
# Link this volume to our VM.
volume.vm = vm
result = vm.start
raise "Error queuing vm start: #{result.text}" if result.status == 0
task = s.object(:object_id => result.task)
num_vms -= 1
end
# So now we have a pile of tasks queued to create volumes and start a VM
# on each one. You can view uncompleted tasks by doing eg:
tasks = s.objects(:class => 'Task', 'completed' => false)
tasks.each do |task|
puts "task state is #{task.state}"
puts "task description is #{task.description}"
end
# Look up completed tasks.
tasks = s.objects(:class => 'Task', 'completed' => true)
tasks.each do |task|
puts "task #{task.description} completed"
puts "Error message: #{task.message}" if task.error
end