Created
April 3, 2012 10:55
-
-
Save seungjin/2291013 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
# Xapi Base Module | |
# | |
# Description:: Setup the Session and auth for xapi | |
# Author:: Jesse Nelson <[email protected]> | |
# | |
# Copyright:: Copyright (c) 2012, Jesse Nelson | |
# | |
# License:: Apache License, Version 2.0 | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
XAPI_TEMP_REGEX ||= /^CentOS 5.*\(64-bit\)/ | |
require 'chef/knife' | |
require 'units/standard' | |
class Chef::Knife | |
module XapiBase | |
def self.included(includer) | |
includer.class_eval do | |
deps do | |
require 'xenapi' | |
require 'highline' | |
require 'highline/import' | |
require 'readline' | |
end | |
option :host, | |
:short => "-h SERVER_URL", | |
:long => "--host SERVER_URL", | |
:description => "The url to the xenserver, http://somehost.local.lan/", | |
:proc => Proc.new { |host| Chef::Config[:knife][:xenserver_host] = host } | |
option :xenserver_password, | |
:short => "-K PASSWORD", | |
:long => "--xenserver-password PASSWORD", | |
:description => "Your xenserver password", | |
:proc => Proc.new { |key| Chef::Config[:knife][:xenserver_password] = key } | |
option :xenserver_username, | |
:short => "-A USERNAME", | |
:long => "--xenserver-username USERNAME", | |
:description => "Your xenserver username", | |
:proc => Proc.new { |username| Chef::Config[:knife][:xenserver_username] = username } | |
end | |
end | |
# highline setup | |
def h | |
@highline ||= ui.highline | |
end | |
# setup and return an authed xen api instance | |
def xapi | |
@xapi ||= begin | |
session = XenApi::Client.new( Chef::Config[:knife][:xenserver_host] ) | |
# get the password from the user | |
password = Chef::Config[:knife][:xenserver_password] || nil | |
username = Chef::Config[:knife][:xenserver_username] || "root" | |
if password.nil? or password.empty? | |
password = ask("Enter password for user #{username}: " ) { |input| input.echo = "*" } | |
end | |
session.login_with_password(username, password) | |
session | |
end | |
end | |
# get template by name_label | |
def get_template(template) | |
xapi.VM.get_by_name_label(template).first | |
end | |
# | |
# find a template matching what the user provided | |
# | |
# returns a ref to the vm or nil if nothing found | |
# | |
def find_template(template=XAPI_TEMP_REGEX) | |
# if we got a string then try to find that template exact | |
# if no exact template matches, search | |
if template.is_a?(String) | |
found = get_template(template) | |
return found if found | |
end | |
# make sure our nil template gets set to default | |
if template.nil? | |
template = XAPI_TEMP_REGEX | |
end | |
Chef::Log.debug "Name: #{template.class}" | |
# quick and dirty string to regex | |
unless template.is_a?(Regexp) | |
template = /#{template}/ | |
end | |
# loop over all vm's and find the template | |
# Wish there was a better API method for this, and there might be | |
# but i couldn't find it | |
Chef::Log.debug "Using regex: #{template}" | |
xapi.VM.get_all_records().each_value do |vm| | |
if vm["is_a_template"] and vm["name_label"] =~ template | |
Chef::Log.debug "Matched: #{h.color(vm["name_label"], :yellow )}" | |
found = vm # we're gonna go with the last found | |
end | |
end | |
# ensure return values | |
if found | |
puts "Using Template: #{h.color(found["name_label"], :cyan)}" | |
return get_template(found["name_label"]) # get the ref to this one | |
end | |
return nil | |
end | |
# generate a random mac address | |
def generate_mac | |
("%02x"%(rand(64)*4|2))+(0..4).inject(""){|s,x|s+":%02x"%rand(256)} | |
end | |
# add a new vif | |
def add_vif_by_name(vm_ref, dev_num, net_name) | |
puts "Looking up vif for: #{h.color(net_name, :cyan)}" | |
network_ref = xapi.network.get_by_name_label(net_name).first | |
if network_ref.nil? | |
ui.warn "#{h.color(net_name,:red)} not found, moving on" | |
return | |
end | |
mac = generate_mac | |
puts "Provisioning: #{h.color(net_name, :cyan)}, #{h.color(mac,:green)}, #{h.color(network_ref, :yellow)}" | |
vif = { | |
'device' => dev_num.to_s, | |
'network' => network_ref, | |
'VM' => vm_ref, | |
'MAC' => generate_mac, | |
'MTU' => "1500", | |
"other_config" => {}, | |
"qos_algorithm_type" => "", | |
"qos_algorithm_params" => {} | |
} | |
vif_ref = xapi.VIF.create(vif) | |
vif_ref | |
end | |
# remove all vifs on a record | |
def clear_vm_vifs(record) | |
record["VIFs"].each do |vif| | |
Chef::Log.debug "Removing vif: #{h.color(vif, :red, :bold)}" | |
xapi.VIF.destroy(vif) | |
end | |
end | |
# returns sr_ref to the default sr on pool | |
def find_default_sr() | |
xapi.pool.get_default_SR( xapi.pool.get_all()[0] ) | |
end | |
# return an SR record from the name_label | |
def get_sr_by_name(name) | |
sr_ref = xapi.SR.get_by_name_label(name) | |
if sr_ref.empty? or sr_ref.nil? | |
ui.error "SR name #{h.color( name ) } not found" | |
return nil | |
end | |
sr = xapi.SR.get_record( sr_ref ) | |
end | |
# convert 1g/1m/1t to bytes | |
def input_to_bytes(size) | |
case size | |
when /g|gb/i | |
size.to_i.gb.to_bytes.to_i | |
when /m|mb/i | |
size.to_i.mb.to_bytes.to_i | |
when /t|tb/i | |
size.to_i.tb.to_bytes.to_i | |
else | |
size.to_i.gb.to_bytes.to_i | |
end | |
end | |
# create a vdi return ref | |
def create_vdi(name, sr_ref, size) | |
vdi_record = { | |
"name_label" => "#{name}", | |
"name_description" => "Root disk for #{name} created by knfie xapi", | |
"SR" => sr_ref, | |
"virtual_size" => input_to_bytes(size).to_s, | |
"type" => "system", | |
"sharable" => false, | |
"read_only" => false, | |
"other_config" => {}, | |
} | |
vdi_ref = xapi.VDI.create( vdi_record ) | |
ui.msg "VDI: #{h.color( name, :cyan )} #{h.color( vdi_ref, :cyan )} created" | |
vdi_ref | |
end | |
# create vbd and return a ref | |
def create_vbd(vm_ref, vdi_ref, position) | |
vbd_record = { | |
"VM" => vm_ref, | |
"VDI" => vdi_ref, | |
"empty" => false, | |
"other_config" => {"owner"=>""}, | |
"userdevice" => position.to_s, | |
"bootable" => true, | |
"mode" => "RW", | |
"qos_algorithm_type" => "", | |
"qos_algorithm_params" => {}, | |
"qos_supported_algorithms" => [], | |
"type" => "Disk" | |
} | |
vbd_ref = xapi.VBD.create(vbd_record) | |
ui.msg "VBD: #{h.color vbd_ref, :cyan } created" | |
vbd_ref | |
end | |
end | |
end |
This file contains hidden or 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
# | |
# Author:: Jesse Nelson (<[email protected]>) | |
# | |
# Based on xapi plugin written by | |
# Author:: Adam Jacob (<[email protected]>) | |
# | |
# Copyright:: Copyright (c) 2012 Jesse Nelson | |
# | |
# License:: Apache License, Version 2.0 | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
# | |
require 'chef/knife/xapi_base' | |
class Chef | |
class Knife | |
class XapiGuestCreate < Knife | |
include Chef::Knife::XapiBase | |
banner "knife xapi guest create NAME [NETWORKS] (options)" | |
option :bootstrap, | |
:short => "-B BOOTSTRAP", | |
:long => "--xapi-bootstrap BOOTSTRAP", | |
:description => "bootstrap template to push to the server", | |
:proc => Proc.new { |bootstrap| Chef::Config[:knife][:xapi_bootstrap] = bootstrap } | |
option :vm_template, | |
:short => "-T Template Name Label", | |
:long => "--xapi-vm-template", | |
:description => "xapi template name to create from. accepts an string or regex", | |
:proc => Proc.new { |template| Chef::Config[:knife][:xapi_vm_template] = template } | |
option :install_repo, | |
:short => "-R If you're using a builtin template you will need to specify a repo url", | |
:long => "--xapi-install-repo", | |
:description => "Install repo for this template (if needed)", | |
:proc => Proc.new { |repo| Chef::Config[:knife][:xapi_install_repo] = repo } | |
option :storage_repo, | |
:short => "-S Storage repo to provision VM from", | |
:long => "--xapi-sr", | |
:description => "The Xen SR to use, If blank will use pool/hypervisor default", | |
:proc => Proc.new { |sr| Chef::Config[:knife][:xapi_sr] = sr } | |
option :kernel_params, | |
:short => "-B Set of kernel boot params to pass to the vm", | |
:long => "--xapi-kernel-params", | |
:description => "You can add more boot options to the vm e.g.: \"ks='http://foo.local/ks'\"", | |
:proc => Proc.new {|kernel| Chef::Config[:knife][:xapi_kernel_params] = kernel } | |
option :disk_size, | |
:short => "-D Size of disk. 1g 512m etc", | |
:long => "--xapi-disk-size", | |
:description => "The size of the root disk, use 'm' 'g' 't' if no unit specified assumes g", | |
:proc => Proc.new {|disk| Chef::Config[:knife][:xapi_disk_size] = disk } | |
option :cpus, | |
:short => "-C Number of VCPUs to provision", | |
:long => "--xapi-cpus", | |
:description => "Number of VCPUS this vm should have 1 4 8 etc", | |
:proc => Proc.new {|cpu| Chef::Config[:knife][:xapi_cpus] = cpu } | |
option :mem, | |
:short => "-M Ammount of memory to provision", | |
:long => "--xapi-mem", | |
:description => "Ammount of memory the VM should have specify with m g etc 512m, 2g if no unit spcified it assumes gigabytes", | |
:proc => Proc.new {|mem| Chef::Config[:knife][:xapi_mem] = mem } | |
# destroy/remove VM refs and exit | |
def cleanup(vm_ref) | |
ui.warn "Clenaing up work and exiting" | |
xapi.VM.destroy(vm_ref) | |
exit 1 | |
end | |
def run | |
server_name = @name_args[0] | |
$stdout.sync = true | |
# get the template vm we are going to build from | |
template_ref = find_template( Chef::Config[:knife][:xapi_vm_template] ) | |
ui.msg "Cloning Guest from Template: #{h.color(template_ref, :bold, :cyan )}" | |
vm_ref = xapi.VM.clone(template_ref, server_name) | |
begin | |
xapi.VM.set_name_description(vm_ref, "VM built by knife-xapi as #{server_name}") | |
# configure the install repo | |
repo = Chef::Config[:knife][:xapi_install_repo] || "http://isoredirect.centos.org/centos/5/os/x86_64/" | |
ui.msg "Setting Install Repo: #{h.color(repo,:bold, :cyan)}" | |
xapi.VM.set_other_config(vm_ref, { "install-repository" => repo } ) | |
cpus = Chef::Config[:knife][:xapi_cpus] || 2 | |
ui.msg "Setting up CPUS #{ h.color cpus, :cyan }" | |
xapi.VM.set_VCPUs_max( vm_ref, cpus.to_s ) | |
xapi.VM.set_VCPUs_at_startup( vm_ref, cpus.to_s ) | |
memory_size = input_to_bytes( Chef::Config[:knife][:xapi_mem] || "1g" ).to_s | |
ui.msg "Mem size: #{ h.color( memory_size, :cyan)}" | |
# static-min <= dynamic-min = dynamic-max = static-max | |
xapi.VM.set_memory_limits(vm_ref, memory_size, memory_size, memory_size, memory_size) | |
# setup the Boot args | |
args = Chef::Config[:knife][:xapi_kernel_params] || "graphical utf8" | |
ui.msg "Setting Boot Args: #{h.color args, :cyan}" | |
xapi.VM.set_PV_args( vm_ref, args ) | |
# TODO: validate that the vm gets a network here | |
networks = @name_args[1..-1] | |
# if the user has provided networks | |
if networks.length >= 1 | |
clear_vm_vifs( xapi.VM.get_record( vm_ref ) ) | |
networks.each_with_index do |net, index| | |
add_vif_by_name(vm_ref, index, net) | |
end | |
end | |
sr_ref = find_default_sr | |
if Chef::Config[:knife][:xapi_sr] | |
sr_ref = get_sr_by_name( Chef::Config[:knife][:xapi_sr] ) | |
end | |
if sr_ref.nil? | |
ui.error "SR specified not found or can't be used Aborting" | |
cleanup(vm_ref) | |
end | |
ui.msg "SR: #{h.color sr_ref, :cyan} created" | |
# Create the VDI | |
disk_size = Chef::Config[:knife][:xapi_disk_size] || "8g" | |
vdi_ref = create_vdi("#{server_name}-root", sr_ref, disk_size ) | |
# Attach the VDI to the VM | |
vbd_ref = create_vbd(vm_ref, vdi_ref, 0) | |
ui.msg "Provisioning new Guest: #{h.color(vm_ref, :bold, :cyan )}" | |
provisioned = xapi.VM.provision(vm_ref) | |
ui.msg "Starting new Guest: #{h.color( provisioned, :cyan)} " | |
xapi.Async.VM.start(vm_ref, false, true) | |
rescue Exception => e | |
ui.msg "#{h.color 'ERROR:'} #{h.color( e.message, :red )}" | |
ui.error "#{h.color( e.backtrace, :yellow)}" | |
cleanup(vm_ref) | |
end | |
# TODO: bootstrap | |
end | |
end | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment