Cobbler Provisioning via CloudFORMS 2.0

Hooking Cobbler and CloudFORMS 2.0 together is actually quite simple. Lets first understand the use case.

We want to deploy virtual machines using PXE boot from Cobbler.

Cobbler stores each vm’s boot information as a “System Record”, there is a nice API that you can launch that will create these system records, you just need to pass the right parameters. The data required to drive Cobbler to create a system record are;

  • Name
  • MAC Address
  • Hostname
  • IP Address
  • NetMask
  • Gateway
  • VLAN
  • KickStart Profile

All this information is held within CloudFORM and easily accesiable. The choice is when….!

The CloudFORMS provisioning process goes through a number of statemachine gates including approval, quotas, customize, provision, post provision etc…

Its during the provision state machine that the vm is actually created by CloudFORMS in either RHEV or vCENTER. So this is the first time that the MAC Address will be available. The other detail was actually captured in the provisioning dialog shown here;

cust_prov

So just to recap, we have a dialog that will collect some of the information, other variable required is the mac address that is only available after the VM has been created by CloudFORMS. Other considerations are that CloudFORMS automatically powers the VM on after it is cloned, this can cause a failure if Cobbler has not been updated in time. Therefore we need a stop of the VM incase its already started, a delay and a start VM again to continue on. And lastly the only exit point for us to do this is one that runs many many times throughout the provisioning life cycle. So we need to ensure that the message to Cobbler is only sent once. So what does the finished article look like?

We have out RUBY method code for / Factory / VM / check_provisioned

check_prov

Take note that we write a lock file for the mac address to keep things unique, we use this to ensure we only run once this code chunk, also we don’t want to run to early so a pre check on mac address and destination id (the id of the VM to be created)

$evm.log("info", "===============CHECKED PROVISIONED==================")
request = $evm.root['miq_provision']
$evm.log("info", "Destination ID - #{request.destination_id}")
if request.destination_id != nil
  vm = $evm.vmdb('vm', request.destination_id )
  if vm.mac_addresses != nil
    if File.exist?("/tmp/#{vm.mac_addresses[0].gsub(/W/,'')}")
      $evm.log("info", "============BEEN HERE BEFORE==========")
    else
      vm.stop
      f = File.open("/tmp/#{vm.mac_addresses[0].gsub(/W/,'')}", 'w')
      cmd = "nohup python /root/cobbler.py "
      cmd += " --server=x.x.x.x"
      cmd += " --name=#{vm.name}"
      cmd += " --hostname=#{request.get_option(:hostname)}"
      cmd += " --ip-address=#{request.get_option(:ip_addr)}"
      cmd += " --subnet=#{request.get_option(:subnet_mask)}"
      cmd += " --mac-address=#{vm.mac_addresses[0]}"
      cmd += " --gateway=#{request.get_option(:gateway)}"
      cmd += " --ksmeta="'networkName'='dvPortGroup VLAN www nonlive-product x.x.x.x/yy' rqid=6""
      cmd += " --profile=RHEL-6-x86-64"
      cmd += " &"
      f.puts "#{cmd}"
      f.flush
      f.close
      $evm.log("info", "Running: #{cmd}")
      results = system(cmd)
      sleep(10)
      vm.start
    end
  else
    $evm.log("info", "==================BLANK VM MAC ADDRESS=====================")
  end
else
  $evm.log("info", "==================BLANK DESTINATION ID=====================")
end
$evm.log("info", "=================CHECKED PROVISIONED=====================")

Notice that the code here is simply taking the inputs from the dialog (MIQ_PROVISION) object, also finding the MAC ADDRESS for the newly created VM and passing them to a python script that will perform the api call to Cobbler. Here is the python script code. Download from here https://github.com/jonnyfiveiq/CloudFORMSNOW/blob/master/Integrations/Provisioning/Cobbler/cobbler.py

#!/usr/bin/env python

import xmlrpclib

import getopt

import string

import sys

SATELLITE_USER=""

SATELLITE_PASSWORD=""

def usage():

print """

cobbler.py [options]

Options:

-h, --help brief help message

-S, --server= FQDN of the Cobbler Server

-n, --name= Cobbler System Record name

-H, --hostname= Hostname of the System

-i, --ip-address= IP Address

-s, --subnet= Subnetmask

-m, --mac-address= MAC Address

-g, --gateway= Gateway

-p, --profile= Kickstartprofile to use

-k, --ksmeta= Kickstart Metainformation

'foo=bar bar=foo key=value'

[Connect to cobbler over XMLRPC and create a cobbler system record]

"""

# -I, --interface= Network Interface e.g. eth0

try:

opts, args = getopt.getopt(sys.argv[1:],

"hS:n:H:I:i:s:m:g:p:k:",

["help",

"server=",

"name=",

"hostname=",

"interface=",

"ip-address=",

"subnet=",

"mac-address=",

"gateway=",

"profile=",

"ksmeta="])

except getopt.GetoptError, err:

print "n" + str(err) + "n"

usage()

sys.exit(1)

opt_server = None

opt_name = None

opt_hostname = None

opt_interface = None

opt_ip_address = None

opt_subnet = None

opt_mac_address= None

opt_gateway = None

opt_profile = None

opt_ksmeta= None

for o, a in opts:

if o in ("-h", "--help"):

usage()

sys.exit()

elif o in ("-S", "--server"):

opt_server= a

elif o in ("-n", "--name"):

opt_name = a

elif o in ("-H", "--hostname"):

opt_hostname = a

#elif o in ("-I", "--interface"):

# opt_interface = a

elif o in ("-i", "--ip-address"):

opt_ip_address = a

elif o in ("-s", "--subnet"):

opt_subnet = a

elif o in ("-m", "--mac-address"):

opt_mac_address = a

elif o in ("-g", "--gateway"):

opt_gateway = a

elif o in ("-p", "--profile"):

opt_profile = a

elif o in ("-k", "--ksmeta"):

opt_ksmeta = a

else:

assert False, "unhandled option " + o

server = xmlrpclib.Server("http://"+opt_server+"/cobbler_api")

token = server.login(SATELLITE_USER,SATELLITE_PASSWORD)

system_id = server.new_system(token)

server.modify_system(system_id,"name", opt_name,token)

server.modify_system(system_id,"hostname", opt_hostname,token)

server.modify_system(system_id,'modify_interface', {

"macaddress-eth0" : opt_mac_address,

"ipaddress-eth0" : opt_ip_address,

"subnet-eth0" : opt_subnet,

"static-eth0" : "true",

}, token)

server.modify_system(system_id,"gateway", opt_gateway, token)

server.modify_system(system_id,"profile", opt_profile, token)

server.modify_system(system_id,"ks_meta", opt_ksmeta, token)

server.save_system(system_id, token)

server.sync(token)

There is one more important thing that needs to be done here, and thats the call back URL to CloudFORMS. Explanation – CloudFORMS needs to be notified that the VM has completed the initial installation of the operating system, this is so that it can continue the provisioning statemachine, that by default include steps to power back on the VM, mark the provisioning request as completed, you may have added steps to register to a CMDB or call a software distribution system to install further software post operating system install. So this call back is important and based on the VM being provisioned, therefore notice in the ruby code a parameter is being passed to the python script the defines the RequestID for the provision. This is used in the call back URL to identify to CloudFORMS what request has completed OS install and to continue on with the statemachine. The call back URL looks something like;


https://CloudFORMS.SERVER/vm/provisioned?guid=

So you need a way for the KickStart that is being issued by Cobbler to call back to the CloudFORMS server to say everything is complete and power off. The power off is simple, in the kickstart profile within Cobbler UI select POWER OFF. The call back routine is added as a trigger. You do this by adding the following python script to (don’t forget to chmod+x!) Download from https://github.com/jonnyfiveiq/CloudFORMSNOW/blob/master/Integrations/Provisioning/Cobbler/cloudforms_trigger.py

/var/lib/cobbler/triggers/install/post/


#!/usr/bin/python

import sys

import json

import urllib

import pycurl

system=sys.argv[2]

cloudforms="https:///vm/provisioned?guid="

json_data=open('/var/lib/cobbler/config/systems.d/' + system + '.json')

data = json.load(json_data)

json_data.close()

try:

rqid = urllib.quote(data["ks_meta"]["rqid"])

rqid = int(rqid)

except KeyError:

rqid = "dummy"

finally:

if type(rqid) == int:

url = cloudforms + str(rqid)

c = pycurl.Curl()

c.setopt(c.URL, url)

c.setopt(pycurl.SSL_VERIFYPEER, 0)

c.setopt(pycurl.SSL_VERIFYHOST, 0)

c.perform()

The trigger script will execute automatically when place in the fore mentioned directory. You should check /var/log/cobbler/cobbler.log to ensure it executed correctly and that the REQUESTID was picked up and inserted ok.

Summary

We let CloudFORMS deliver a PXE configured VM, in fact we select a PXE Image and Kickstart, though these are complete dummy instances and not used at all. CloudFORMS is used to collect the input from the user for the provision, some of this is passed through to a python script that performs the API calls to the Cobbler server to create the system record for the awaiting VM. Lastly Cobbler provides sufficient information to the new VM to close the automation loop with its request ID from CloudFORMS.

Download all the bits from CloudFORMSNOW GIThub

Conclusion – CloudFORMS defined Virtual Machines delivered by Cobbler

Credits

Christian Bolz for an excellent job in integrating this altogether.

Benjamin Kruell for providing the python scripts completely blind to CloudFORMS.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s