Nesse artigo o SDK Python do oVirt é apresentado através da análise de sua estrutura e alguns exemplos práticos. Ele não pretende ser uma extensa referência mas sim um tutorial para rapidamente familiarizar o leitor com os diversos componentes suprindo-o com o conhecimento de base para que ele possa continuar seu plano de aprendizado de forma autônoma.

Fernando Granha Jeronimo, Staff Software Engineer, Linux Technology Center - IBM

Fernando Granha JeronimoFernando Granha Jeronimo

Fernando é formado em Engenharia da Computação pela UNICAMP e possui Duplo-Diploma em Engenharia pela Universidade Francesa Télécom ParisTech. Trabalha há um ano no Linux Technology Center da IBM.
Perfil no My developerWorks



Paulo de Rezende Pinatti, Staff Software Engineer, Linux Technology Center - IBM

Paulo de Rezende PinattiPaulo de Rezende Pinatti

Engenheiro de software na IBM com 10 anos de experiência em computação, atualmente trabalha no desenvolvimento de ferramentas para sistemas de virtualização (Openstack, oVirt). Formado em Ciência da Computação, já trabalhou no desenvolvimento de toolkits de instalação e configuração de Linux em máquinas IBM POWER, boot loaders, desenvolvimento para web, administração de sistemas, e outros.
Perfil no My developerWorks



25/Jun/2013

Introdução

oVirt é uma solução completa de Virtualização de datacenter e muitas de suas funcionalidades estão disponíveis com toda a comodidade da interface web. Entretanto, pode ser interessante automatizar tarefas ou construir soluções customizadas que sejam capazes de comunicar-se com o sistema. Para endereçar tais necessidades o oVirt expõe uma API REST e também disponibiliza um SDK Python. Nada melhor do que a flexibilidade de uma linguagem script para acessar todo poder dessa plataforma de gerência de virtualização.

Com o SDK é possível criar, ligar, pausar e remover uma máquina virtual (VM), ou seja, todo o seu ciclo de vida pode ser controlado de forma automatizada. Ademais, esse controle estende-se inclusive até outros elementos, tais como storage, redes e hosts.

Inicialmente, são apresentadas algumas instruções sobre como instalar o SDK. Em seguida, aborda-se sua arquitetura, uma vez que o domínio de seus conceitos simplifica muito sua compreensão como um todo. Por fim, alguns exemplos de utilidade prática são mostrados como a criação de uma máquina virtual completa.


Instalação

Em sistemas Fedora a instalação é simples, basta adicionar o repositório do oVirt e instalar o rpm do SDK.

rpm -ivh http://resources.ovirt.org/releases/ovirt-release-fedora.noarch.rpm
yum -y install ovirt-engine-sdk

Um método alternativo é instalar o SDK a partir do código fonte, disponível no site do projeto em http://www.ovirt.org/.


Arquitetura

O SDK apresenta uma estrutura bem regular. Uma vez entendido certos conceitos, fica fácil estendê-los a novas partes da API. Existem dois grandes grupos de classes: classes de parâmetros e classes "vivas". O primeiro grupo representa classes cujos objetos são incapazes de executar qualquer ação no backend mas que são usadas como parâmetros para outros métodos uma vez que são responsáveis por armazenar dados. Já as classes da categoria "vivas" têm o poder de executar alguma ação no backend como por exemplo listar objetos ou criar novos. É importante dizer que objetos dessa classe podem também armazenar dados.

Dentro da categoria de classes "vivas" existem duas subcategorias: os controladores e os objetos controlados. Cada controlador está associado a um determinado tipo de objeto controlado (controlador de VMs, controlador de hosts, etc.) e dentre suas funcionalidades é possível listar os objetos controlados, fazer uma consulta especificando alguns atributos de busca ou criar novos objetos. Já os objetos controlados são aqueles que em geral estão na base de dados do backend e que podem ser mapeados para algum elemento do datacenter. Máquinas virtuais, storage e hosts são apenas alguns exemplos de objetos que podem ser controlados. Basicamente todo objeto controlado possui os campos nome e id (mais especificamente um uuid) e contam com métodos de remoção (delete()) e atualização (update()).

Quando deseja-se criar um novo objeto, deve-se instanciar um objeto da classe de parâmetros, preenchê-lo com os dados desejados e em seguida passá-lo para o método add() de seu controlador.

Para melhor entender como isso funciona, vamos agora mergulhar no objeto API que é a raiz de acesso à todas as funcionalidades do backend:

# -*- coding: utf8 -*- 
import sys 

# importa o módulo principal da api
from ovirtsdk.api import API 

# cria conexão com o servidor oVirt
try: 
    api = API(url='http://192.168.0.1', username='admin@internal', password='admin') 
except Exception, e: 
    print "Falhou ao conectar ao servidor: %s" % e.message 
    sys.exit(1) 

# lista todos os datacenters no servidor
for dc in api.datacenters.list(): 
    print dc.name

Como você pode ver no exemplo anterior, o objeto ovirtsdk.api.API é o ponto de partida para o acesso às funcionalidades do SDK. Ao criar uma instância da API você deve informar como parâmetros o endereço do servidor oVirt e as credenciais de acesso. O objeto criado retorna um manipulador para acesso às funcionalidades daquele servidor. Note que é importante proteger a chamada ao objeto com um bloco try pois no caso de falha na conexão (endereço incorreto, senha inválida) uma exceção será disparada. Para cada elemento primordial do datacenter existe um atributo na API com seu respectivo controlador. No exemplo foi utilizado o controlador de datacenters para listar o nome de todos os datacenters disponíveis. Seguem alguns exemplos de controladores acessíveis através do manipulador:

  • api.datacenters
  • api.clusters
  • api.storagedomains
  • api.networks
  • api.hosts
  • api.vms

Por serem controladores cada um desses objetos possui ao menos três métodos que detalharemos a seguir:

  • list(): lista todos os objetos controlados pelo controlador em questão
  • get(): permite fazer uma consulta usando atributos do objeto como id e nome retornando no máximo apenas um objeto.
  • add(): recebe um objeto da categoria parâmetro e tenta criá-lo no backend.

Agora exploraremos a natureza hierárquica dos objetos controlados e controladores. Suponha que precisamos verificar a percentagem de utilização de cada disco esparso (formato qcow2) da VM de nome "VM-1". Primeiramente precisamos de uma referência para seu objeto controlado. Para isso devemos fazer uma consulta ao controlador de VMs passando como parâmetro o nome, da seguinte forma:

vm = api.vms.get(name='VM-1')

Se o objeto realmente existir teremos uma referencia válida na variável vm. Agora nos resta descobrir seus discos. De que forma isso pode ser feito? Analogamente ao objeto api que possui controladores, o objeto de máquina virtual contém controladores capazes de gerenciar seus recursos específicos como discos e placas de redes. Com isso, fica claro que os controladores estão sujeitos ao contexto de onde são acessados, que nesse caso é a VM em questão. Como é de costume o SDK usa nomes no plural para controladores, então para os discos da VM temos o controlador disks. Por se tratar de um controlador, os mesmos três métodos mencionados anteriormente estão disponíveis. Sendo assim, é possível listar os discos da seguinte maneira:

disks = vm.disks.list()

Na variável disks, tem-se uma lista de todos os objetos controlados de disco da máquina virtual "VM-1". Por ser um objeto controlado, ele também armazena dados que podem ser manipulados usando métodos do tipo get e set, explicados a seguir:

  • get_campo(): retorna o valor do campo. É importante observar que esse tipo de método retorna, em geral, objetos parâmetro e muitas vezes com menos dados do que é esperado. Se listarmos todos os clusters com api.clusters.list() e para cada um pegarmos o datacenter ao qual está associado, teremos um objeto parâmetro de datacenter que conterá apenas o campo id. Apesar de ser menos do que poderíamos esperar, com o id tem-se informação o suficiente para fazer uma consulta ao seu respectivo controlador e recuperar um objeto controlado com mais informações.
  • set_campo(): modifica o valor do campo. Atente-se ao fato de que essa é uma operação simples que apenas altera a cópia local do objeto. Para fazer essas alterações efetivas no backend, deve-se chamar o método update() do objeto. Há ainda outra sutileza, pois existe um potencial problema de concorrência. Imagine que você tenha feito uma consulta e recuperado um cópia local da VM cujo nome você pretende modificar, depois disso um outro usuário pela interface web resolve adicionar uma descrição dessa VM e quando ele termina, você chama o método update(). Uma vez que sua cópia local de VM não continha nenhuma descrição, sua chamada de update() vai apagar as alterações desse outro usuário.

O diagrama de classes abaixo sumariza a visão lógica do SDK apresentada até agora:

Com uma lista de discos em mãos, precisamos descobrir agora os métodos get do objeto disco que retornam o que precisamos. Para isso podemos usar o interpretador Python e ir explorando os objetos e controladores com os built-ins help() e dir(). Lembre-se de utilizar o objeto API, já que ele é a porta de entrada para o backend oVirt:

$ python 
Python 2.7.3 (default, Jul 24 2012, 10:05:38) 
[GCC 4.7.0 20120507 (Red Hat 4.7.0-5)] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> from ovirtsdk.api import API 
>>> api = API(url='http://192.168.0.1', username='admin@internal', password='admin') 
>>> help(api) 
Help on instance of API in module ovirtsdk.api: 

class API 
 |  Methods defined here: 
...
>>> dir(api)
['__doc__', '__init__', '__module__', 'capabilities', 'clusters', 'datacenters', 
'disconnect', 'disks', 'domains', 'events', 'get_product_info', 'get_special_objects', 
'get_summary', 'get_time', 'groups', 'hosts', 'networks', 'roles', 'set_filter', 
'storagedomains', 'tags', 'templates', 'test', 'users', 'vmpools', 'vms'] 
>>>

Note que por questões de clareza, parte da saída de help(api) foi suprimida. Com a função dir() conseguimos ver os controladores que havíamos mencionado tais como: datacenters, hosts e vms. Agora que identificamos sua presença, que tal explorá-lo com alguns dos métodos conhecidos:

>>> api.vms.list() 
[<ovirtsdk.infrastructure.brokers.VM  object  at 0x10530d0>,
<ovirtsdk.infrastructure.brokers.VM object at 0x10ff410>] 
>>>

No sistema em que estamos trabalhando sabemos de antemão pela interface web que foi criada uma VM de nome 'VM-1'. Podemos verificar sua existência fazendo uma consulta no controlador:

>>> vm = api.vms.get(name="VM-1") 
>>> vm 
<ovirtsdk.infrastructure.brokers.VM object at 0x10e1e90> 
>>> vm.get_name() 
'VM-1' 
>>> help(vm)
Help on VM in module ovirtsdk.infrastructure.brokers object: 

class VM(ovirtsdk.xml.params.VM, ovirtsdk.infrastructure.common.Base) 
 |  Method resolution order: 
...

Vamos mais uma vez inspecionar campos com a função dir(), mas agora com um objeto controlado de VM. Com isso, constatamos a existência do controlador disks:

>>> dir(vm) 
['Tag_strip_pattern_', '__class__', '__delattr__', '__dict__', '__doc__', 
'__eq__', '__format__', '__getattr__', '__getattribute__', '__hash__', '__init__',
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_getProxy',
'add_link', 'build', 'buildAttributes', 'buildChildren', 'cancelmigration', 'cdroms', 
'delete', 'detach', 'disks','export', 'exportAttributes', 'exportChildren', 
'exportLiteral', 'exportLiteralAttributes',
'exportLiteralChildren', 'factory', 'gds_build_any', 'gds_format_boolean', ... ,
'update'] 
>>>

Com o controlador de discos em mãos, vamos listar os discos da VM e inspecionar o primeiro:

>>> disks= vm.disks.list() 
>>> first_disk = disks[0] 
>>> dir(first_disk) 
['Tag_strip_pattern_', ... , 'get_actual_size', 'get_bootable', 'get_class_obj_',
'get_creation_status', 'get_description', 'get_extensiontype_', 'get_format', 'get_href',
'get_id', 'get_image_id', 'get_interface', 'get_link', 'get_lunStorage', 'get_name',
'get_path_', 'get_path_list_', 'get_propagate_errors', 'get_provisioned_size', 
'get_quota', 'get_shareable', 'get_size', 'get_sparse', ... , 'update'] 
>>>

Interessante, encontramos três métodos que parecem retornar o que precisamos: get_actual_size(), get_size e get_sparse. Vamos verificar:

>>> first_disk.get_size()
21474836480
>>> first_disk.get_actual_size()
18441695232
>>> first_disk.get_sparse() 
True 
>>>

Parece consistente com o que esperávamos. Finalmente, basta combinar esses elementos para gerar uma lista de tuplas com o nome do disco e percentagem de utilização:

>>> [ (disk.get_name(), (100.0*disk.get_actual_size())/disk.get_size()) 
for disk in disks if disk.get_sparse() ] 
[('Fedora_17_Disk1', 85.87583541870117)]
>>>

Espera-se que agora o leitor tenha uma ideia clara da estrutura do SDK e consiga navegar com tranquilidade. Havendo dúvida, é sempre válido recorrer à documentação (o comando help() retorna bastante conteúdo) ou até mesmo ao código fonte.


Exemplos

Abaixo o leitor pode encontrar três exemplos práticos de utilização do SDK Python do oVirt.

Criação de uma VM com um disco e uma placa de rede:

# -*- coding: utf8 -*- 

import sys
# importa o módulo de parâmetros 
from ovirtsdk.xml import params 
# importa o módulo principal da api 
from ovirtsdk.api import API 

# cria conexão com o servidor oVirt 
try: 
    api = API(url='http://192.168.0.1', username='admin@internal', password='admin') 
except Exception, e: 
    print "Falhou ao conectar ao servidor: %s" % e.message 
    sys.exit(1) 

# Dados para criação da vm 
# nome da vm 
name = 'vm-test' 
# memoria RAM (2 GB) 
memory = 2 * pow(2, 30) 
# cluster onde a vm será criada 
cluster = api.clusters.get(name='Cluster_NFS') 
# utiliza template vazio 
template = api.templates.get('Blank') 
# tipo da vm (server ou desktop) 
vmType = 'server' 
# associa dois CPU cores a vm 
cpuTopology = params.CpuTopology(cores=2, sockets=1) 
cpu = params.CPU(topology=cpuTopology) 

# cria o objeto parâmetro especificando os dados 
ovirtVMParam = params.VM(name=name, 
                     type_=vmType, 
                     memory=memory, 
                     cluster=cluster, 
                     cpu=cpu, 
                     template=template) 
 
# cria a vm no datacenter 
newVm = api.vms.add(ovirtVMParam) 

# Dados para criação da placa de rede 
# nome da placa 
nicName = 'nic-1' 
# especifica o endereço MAC 
mac = params.MAC(address='00:16:3e:0d:1c:6a') 
# rede a ser utilizada 
network = api.networks.get(name='ovirtmgmt') 
# modelo da placa (e1000, virtio, rtl8139)
nicInterface = 'virtio' 

# cria o objeto parâmetro especificando os dados 
nic = params.NIC(name=nicName, 
                 interface=nicInterface, 
                 mac=mac, 
                 network=network) 

# cria a interface de rede na vm 
newNic = newVm.nics.add(nic) 

# Dados para criação do disco 
# storage onde disco será alocado 
storage = api.storagedomains.get(name='Data_NFS') 
storageDomain = params.StorageDomains(storage_domain=[storage]) 
# tamanho (10 GB) 
size = 10 * pow(2, 30) 
# tipo do disco (system ou data) 
diskType = 'system' 
# formato (cow ou raw) 
diskFormat = 'cow' 
# interface do disco (ide, virtio) 
diskInterface = 'virtio' 
# disco esparso (consome apenas espaço efetivamente em uso) 
sparse = True 
# disco deve ser bootável 
bootable = True 

# cria o objeto parâmetro especificando os dados 
disk = params.Disk(storage_domains=storageDomain, 
                   size=size, 
                   type_=diskType, 
                   interface=diskInterface, 
                   format=diskFormat, 
                   sparse=sparse, 
                   bootable=bootable) 

# cria o disco na vm
ovirtDisk = newVm.disks.add(disk)

Adicionar um host ao Engine:

# -*- coding: utf8 -*- 
from ovirtsdk.api import API 
from ovirtsdk.xml import params 
from time import sleep 

URL = 'http://192.168.0.1' 
USERNAME = 'admin@internal' 
PASSWORD = 'admin' 

api = API(url=URL, username=USERNAME, password=PASSWORD) 

CLUSTER_NAME = 'Cluster_NFS' 
HOST_NAME = 'host-test' 
HOST_ADDRESS = '192.168.0.15' 
ROOT_PASSWORD = 'senha' 


try: 
    # cluster a ser adicionado o host 
    cluster = api.clusters.get(CLUSTER_NAME) 
    # objeto parâmetro com os dados 
    hostParam = params.Host(name=HOST_NAME, address=HOST_ADDRESS, 
                            root_password=ROOT_PASSWORD, cluster=cluster) 
    # adiciona o host ao cluster 
    if api.hosts.add(hostParam): 
        print 'Host adicionado com sucesso' 

        # busca objeto controlado do host 
        host = api.hosts.get(HOST_NAME) 

        grace_period = params.GracePeriod(0) 
        # objeto parametro referente a ação de aprovação do host 
        action = params.Action(cluster=cluster, async=True, 
                               grace_period=grace_period) 
 
        print 'Aguardando para aprovar host' 
        while api.hosts.get(HOST_NAME).status.state != 'pending_approval': 
            print 'Aguardando pelo estado de aprovação pendente' 
            sleep(1) 
            
        # aprova o host no cluster 
        host.approve(action=action) 

        print 'Aguardando o host atingir o estado ativo' 
        while api.hosts.get(HOST_NAME).status.state != 'up': 
            print api.hosts.get(HOST_NAME).status.state 
            sleep(1) 
        print "Host ativo" 
except Exception as e: 
    print 'Falhou ao adicionar host: %s' % str(e)

Função que realiza boot via rede de uma VM:

# -*- coding: utf8 -*- 

from ovirtsdk.api import API 
from ovirtsdk.xml import params 

def netBoot(vm, 
            kernel=None, 
            initrd=None, 
            cmdline=None, 
            host=None): 
    """ 
    Boot via rede 

    @type vm: ovirtsdk.infrastructure.brokers.VM 
    @param vm: máquina virtual a ser inciada 

    @type kernel: str 
    @param kernel: caminho para o arquivo de kernel 

    @type initrd: str 
    @param initrd: caminho para o arquivo de initrd 

    @type cmdline: str 
    @param cmdline: linha de comando do kernel 

    @type host: Host 
    @param host: host onde a vm rodará 

    @rtype: NoneType 
    @returns: None 
    """ 
    boot = params.Boot(dev='ovirtmgmt') 
    os = params.OperatingSystem(boot=[ boot ], 
                                kernel=kernel, 
                                initrd=initrd, 
                                cmdline=cmdline) 
    vmParam = params.VM(os=os) 

    action = params.Action(host=host, vm=vmParam) 

    vm.start(action=action) 
# netBoot() 

# cria conexão com o servidor oVirt 
try: 
    api = API(url='http://192.168.0.1', username='admin@internal', password='admin') 
except Exception, e: 
    print "Falhou ao conectar ao servidor: %s" % e.message 
    sys.exit(1) 

vm = api.vms.get(name='vm-test') 
netBoot(vm)

Conclusão

O SDK do oVirt possibilita a criação de funcionalidades novas assim como a automação de muitas tarefas. No caso do SDK Python há ainda o conforto de uma linguagem script que em poucas linhas é capaz de realizar muitas atividades. Existe também um SDK disponível em Java para os interessados em desenvolver para essa plataforma.

Por ter uma estrutura bem regular é relativamente simples aprender o funcionamento geral do SDK e com isso ter acesso a todo poder da estrutura virtualizada do oVirt.


Referências

http://www.ovirt.org/SDK

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux
ArticleID=934865
ArticleTitle=oVirt Python SDK
publish-date=06252013