Skip to content

Instantly share code, notes, and snippets.

@gladiatr72
Created August 29, 2014 20:44
Show Gist options
  • Save gladiatr72/20fcdb91f3cd380f7efe to your computer and use it in GitHub Desktop.
Save gladiatr72/20fcdb91f3cd380f7efe to your computer and use it in GitHub Desktop.
Example of ovirt-sdk creating a guest using cloud-init features (authorized_keys and root password update)
#!/usr/bin/python python
import threading
from ovirtsdk.api import API
from ovirtsdk.xml import params
from ovirtsdk.infrastructure.errors import (NoCertificatesError,
ImmutableError,
RequestError,
ConnectionError,
MissingParametersError)
import re
from time import sleep, strftime, localtime
debug=0
class authPersist(threading.Thread):
"""
arguments:
ovauth: connected oversdk api object
This is meant to run in a thread which will ping the api every 10 minutes to keep
the connection alive. This is in response the abjectly loathesome response time
ovirt manager provides authenticating from an IPA system that has more than two
groups.
"""
def __init__(self, ovauth, interval=600, name=None):
''' ovirt api will be pinged every _interval_ seconds '''
threading.Thread.__init__(self)
self.daemon = True
self._api = ovauth.connection
self.interval = interval
def run(self):
while 1:
self.api.vms.list(max=1)
self._lastping = localtime()
sleep(900)
@property
def api(self):
return self._api
@property
def lastping(self):
return strftime('%c', self._lastping)
class ovirtAuth(object):
"""
arguments:
url: ovirt manager url
username
password
ca_file
optional:
connection_name (symbolic name of connection)
"""
_connections={}
def __init__(self,
username=None, password=None,
url=None, ca_file=None, connection_name=None):
self._connection_name=None
self._illegals=[]
self._id = id(self)
for arg in [ username, password, url, ca_file ]:
if not arg:
self._illegals.append(arg)
if self._illegals:
raise MissingParametersError('username, password, url, and ca_file '
+ 'location are required')
# something to tell us something about the connection when interacting
# with the class interactively
if connection_name:
self._connection_name = connection_name
else:
self._connection_name = re.sub(r'^https?://(.*)/?', r'\1', url)
# if an API connection handle already exists for the requested host, use it.
if self._connection_name in ovirtAuth._connections:
self._connection = ovirtAuth._connections[self.connection_name]
if debug:
print "connection exists. reusing " + str(
ovirtAuth._connections[self.connection_name])
else:
try:
self._connection = API(url=url,
username=username,
password=password,
ca_file=ca_file,
renew_session = True,
session_timeout=100000)
ovirtAuth._connections[self._connection_name] = self._connection
self._ping = authPersist(self, name=self._id)
self._ping.start()
except ( ConnectionError, NoCertificatesError, ImmutableError,
RequestError ), e:
print 'wheeeee! {}: '.format(e)
@classmethod
def connections(self):
conn = ovirtAuth._connections
return { k: conn[k] for k in conn }
@classmethod
def get_connection(self,name):
if name in ovirtAuth._connections:
return ovirtAuth._connections[name]
else:
return None
@property
def connection(self):
return self._connection
@property
def connection_name(self):
return self._connection_name
if __name__ == '__main__':
try:
api=ovirtAuth()
except:
print 'pass'
#!/usr/bin/env python2
from ovirtlay.connect import ovirtAuth
from ovirtsdk import api, xml
from ovirtsdk.xml import params
from collections import namedtuple
import re, sys, time
MB = 1024 ** 2
GB = 1024 ** 3
start=time.localtime()
print('Starting at {}'.format(time.strftime('%c', start)))
disk_ = namedtuple('disk', 'storage_domain name size interface format sparse bootable'.split())
vm_ = namedtuple('vm', 'name memory cluster template type_ os'.split())
LAW=ovirtAuth(url='https://vman-01.law.caltesting.org', username='saltmaster@caltesting.org',
ca_file='/etc/pki/tls/certs/vman-01', password='***')
api = LAW.connection
DC = [ dc for dc in api.datacenters.list() if dc.get_name() == 'CAL_LAWRENCE' ][0]
DC_VER = ( DC.get_version().get_major(), DC.get_version().get_minor() )
CAPS = [ cap for cap in api.capabilities.list() if (cap.get_major(), cap.get_minor()) == DC_VER ][0]
BOOT_DEVS = {}
for el in CAPS.get_boot_devices().boot_device:
BOOT_DEVS[el] = params.Boot(dev=el)
p_ = { 'boot': {
'net': params.OperatingSystem(boot=[ BOOT_DEVS['network'], BOOT_DEVS['hd'] ]),
'hd': params.OperatingSystem(boot=[ BOOT_DEVS['hd'] ]), },
'guestOS': {
t: params.OperatingSystem(type_=t) for t in CAPS.get_os_types().os_type },
'clusters': { t.name: t for t in api.clusters.list() },
'networks': { t.name: t for t in api.networks.list() },
'storage_domains': { t.name: t
for t in api.storagedomains.list()
if t.get_type() == 'data' },
}
vm_def = {
'count': 10,
'disks': ( 8 ),
'memory': 2 * GB,
'prefix': '00_kill',
'cluster': 'cluster_01_2014',
'nic_init': 'net_lab_ito',
'nic_perm': 'office_ito',
}
vm_storage_domain = params.StorageDomains(storage_domain=[ api.storagedomains.get('ibm3512_law') ])
p_async=params.Action(async=True)
p_vm1 = params.VM(
name = '00_kill',
memory=2 * GB,
cluster=api.clusters.get('cluster_01_2014'),
template=api.templates.get('Blank'),
type_ = 'server',
os = p_['guestOS']['rhel_6x64'],
)
p_vm_disk1 = params.Disk(
storage_domains=vm_storage_domain,
name='killit-01',
size=8 * GB,
status=None,
interface='virtio',
format='raw',
sparse=False,
bootable=True)
p_vm_disk2 = params.Disk(
storage_domains=vm_storage_domain,
name='killit-02',
size=4 * GB,
status=None,
interface='virtio',
format='raw',
sparse=False,
bootable=False)
p_vm_nic1 = params.NIC(name='eth0', network=p_['networks']['provisioning'])
try:
vm1 = api.vms.add(p_vm1)
vm1.disks.add(p_vm_disk1)
vm1.disks.add(p_vm_disk2)
vm1.nics.add(p_vm_nic1)
vm1.os = p_['boot']['net']
vm_nic1 = [ t for t in vm1.nics.list() if t.name == 'eth0' ][0]
vm_nic1.set_network(p_['networks']['net_lab_ito'])
vm_nic1.update()
vm1.update()
# note: kickstart occurs at this stage
while 1:
print('Starting vm1')
try:
vm1_start_action = vm1.start(p_async)
break
except:
print('Sleeping for 10 seconds while devices settle...')
time.sleep(10)
# wait until the system shuts down at the end of the kickstart run
print('Waiting for kickstart to complete')
while api.vms.get(name='00_kill').status.state != 'down':
time.sleep(10)
print('Done')
except:
vm1 = api.vms.get(name='00_kill')
vm_nic1 = [ t for t in vm1.nics.list() if t.name == 'eth0' ][0]
target_net = p_['networks']['office_ito']
vm_nic1.set_network(target_net)
vm_nic1.update()
vm1.os = p_['boot']['hd']
vm1.update()
# cloudinit things
print('Assembling data for the cloud-init run')
# ssh public keys must be added to this container
p_ssh_keys = params.AuthorizedKeys()
# user definitions must be added to this container
p_users = params.Users(active=True)
p_user_root = params.User(user_name='root', password='something')
p_user_me = params.User(user_name='sdspence', password='something')
p_users.add_user(p_user_root)
p_users.add_user(p_user_me)
with open('authorized_keys') as fh_ak:
for el in fh_ak:
if not re.match(r'^\s*$', el):
p_ssh_keys.add_authorized_key(params.AuthorizedKey(key=el, user=p_user_root))
# this is mostly a parameter class that is used for dealing with hypervisor
# hosts, but apparently the ovirt guys decided to double-book it for setting
# a guest's host name
p_hostname = params.Host(address='00_kill.law.caltesting.org')
p_hostname2 = params.Host(address='not_00_kill')
p_domain = params.Domain(name='law.caltesting.org')
p_cloudinit = params.CloudInit(authorized_keys=p_ssh_keys, users=p_users, host=p_hostname)
# NOTE: I'm not altogether certain why there is a root_password attribute for
# the params.Initialization class, but it appears to be useless. Setting it
# accomplishes nothing.
p_init = params.Initialization(
cloud_init=p_cloudinit,
root_password='useless',
host_name=p_hostname2,
domain=p_domain)
# parameters are nested within the params.CloudInit class and then within
# the params.Initialization class. The former gets packaged into a params.VM
# class which then is (deep breath) wrapped by an Action which can *then* be
# fed to the vm object's start method.
p_vm1_init = params.Action(vm=params.VM(initialization=p_init))
print('Starting cloud-init run')
vm1.start(p_vm1_init)
while not api.vms.get(id=vm1.id).status.state == 'up':
print('Waiting for vm to finish booting')
time.sleep(10)
while not api.vms.get(name='00_kill').get_guest_info().fqdn.startswith('00_kill'):
print('Waiting for cloud-init run to complete')
time.sleep(5)
vm1.stop(p_async)
while api.vms.get(id=vm1.id).status.state != 'down':
time.sleep(5)
print('Starting vm with changes applied')
vm1.start(p_async)
end=time.localtime()
print('Ending at {}'.format(time.strftime('%c', end)))
print('Elapsed time: approximately {} minutes'.format((int(time.mktime(end)) - int(time.mktime(start))) // 60))
@gladiatr72
Copy link
Author

A few things that took a bit for me to figure out

Breaking a piece off the ovirt API leaves you with a bit of an half-dead object. An example is a vm object

vm1 = api.vms.get(name='some-guest')
vm1_nic_eth0 = [ t for t in api.vms.get(name='some-guest').nics.list() if t.name == 'eth0' ].pop()

I found it easy to forget that I was interacting with an SDK that is a couple layers off of a REST API, so I experienced some frustration when dealing with actions such as a requesting the status of a vm, disk or nic object.

If you want live data, ask the api.

# this gives you the state of the object when the *object* was created
vm1.status.state
# this gives you a freshly instantiated object (with the expected, correct state) via the api
api.vms.get(name='some-guest').status.state

The half of the object that is living is via the update() method. Objects can be modified by way of their same-class properties (vmobj.disks, nicobj.mac, etc) or their set_ methods. Follow such updates with Nobj.update() and, assuming you've successfully constructed the xml.params.class bits correctly, the changes will be applied to the referred object on the ovirt management system.

Watch the API directly

I have to admit that I found wading into this API from the python sdk to be somewhat daunting. It helped quite a lot to point a browser at the https://vman-01.law/api/vms/*vm.id* to actually see what pieces (from the XML structure perspective) were being fed to the ovirt system.

Beyond the pre-development note page (http://bit.ly/1zTmYbd) very little documentation exists regarding payloads.

<payloads>
    <payload type="cdrom">
        <files>
            <file>
                <name>openstack/latest/meta_data.json</name>
                <content>
                    {
                      "launch_index" : "0",
                      "availability_zone" : "nova",
                      "name" : "00_kill",
                      "hostname" : "00_kill",
                      "uuid" : "7b8160a8-7e89-4a3b-9874-3b64e67ba7e9",
                      "public_keys" : [
                      "ssh-rsa [...] dummy key one", "ssh-rsa [...] dummy key two", "ssh-rsa [...] dummy key three" ],
                      "meta" : {
                        "essential" : "false",
                        "role" : "server",
                        "dsmode" : "local" }
                    }
                </content>
            </file>
            <file>
                <name>openstack/latest/user_data</name>
                <content>
                    #cloud-config
                    ssh_pwauth: true
                    disable_root: 0
                    output:
                      all: '>> /var/log/cloud-init-output.log'
                    user: root
                    password: something
                    chpasswd:
                      expire: false
                    runcmd:
                    - 'sed -i ''/^datasource_list: /d'' /etc/cloud/cloud.cfg; echo ''datasource_list:
                      ["NoCloud", "ConfigDrive"]'' >> /etc/cloud/cloud.cfg'
                </content>
            </file>
        </files>
        <volume_id>config-2</volume_id>
    </payload>
</payloads>

Huh. Well, how about that! So, the cloud-init pieces within the ovirt-sdk are yet another level abstraction that creates a virtual cdrom with data points for cloud-init to consume and execute. Interesting possibilities abound.

Enjoy :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment