Created
August 29, 2014 20:44
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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='[email protected]', | |
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)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
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.
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.
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