lib/vagrant-vcloud/action/build_vapp.rb
require 'securerandom'
require 'etc'
require 'netaddr'
module VagrantPlugins
module VCloud
module Action
class BuildVApp
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new('vagrant_vcloud::action::build_vapp')
end
def call(env)
# FIXME: we need to find a way to clean things up when a SIGINT get
# called... see env[:interrupted] in the vagrant code
cfg = env[:machine].provider_config
cnx = cfg.vcloud_cnx.driver
vm_name = cfg.name ? cfg.name.to_sym : env[:machine].name
if !cfg.ip_subnet.nil?
if cfg.ip_dns.nil?
dns_address1 = '8.8.8.8'
dns_address2 = '8.8.4.4'
else
dns_address1 = cfg.ip_dns.shift
dns_address2 = cfg.ip_dns.shift
end
@logger.debug("Input address: #{cfg.ip_subnet}")
begin
cidr = NetAddr::CIDR.create(cfg.ip_subnet)
rescue NetAddr::ValidationError
raise Errors::InvalidSubnet, :message => cfg.ip_subnet
end
if cidr.bits > 30
@logger.debug('Subnet too small!')
raise Errors::SubnetTooSmall, :message => cfg.ip_subnet
end
range_addresses = cidr.range(0)
@logger.debug("Range: #{range_addresses}")
# Delete the "network" address from the range.
range_addresses.shift
# Retrieve the first usable IP, to be used as a gateway.
gateway_ip = range_addresses.shift
# Reverse the array in place.
range_addresses.reverse!
# Delete the "broadcast" address from the range.
range_addresses.shift
# Reverse back the array.
range_addresses.reverse!
dhcp_enabled = 'false'
if !cfg.dhcp_enabled.nil? and cfg.dhcp_enabled == true
dhcp_enabled = 'true'
range_addresses = cfg.pool_range
dhcp_start = cfg.dhcp_range.shift
dhcp_end = cfg.dhcp_range.shift
end
@logger.debug("Gateway IP: #{gateway_ip.to_s}")
@logger.debug("Netmask: #{cidr.wildcard_mask}")
@logger.debug(
"IP Pool: #{range_addresses.first}-#{range_addresses.last}"
)
@logger.debug("DNS1: #{dns_address1} DNS2: #{dns_address2}")
network_options = {
:name => 'Vagrant-vApp-Net',
:gateway => gateway_ip.to_s,
:netmask => cidr.wildcard_mask,
:start_address => range_addresses.first,
:end_address => range_addresses.last,
:fence_mode => 'natRouted',
:ip_allocation_mode => 'POOL',
:parent_network => cfg.vdc_network_id,
:enable_firewall => 'false',
:dhcp_enabled => dhcp_enabled,
:dhcp_start => dhcp_start,
:dhcp_end => dhcp_end,
:dns1 => dns_address1,
:dns2 => dns_address2
}
elsif !cfg.network_bridge.nil?
# Bridged mode, avoid deploying a vShield Edge altogether.
network_options = {
:name => 'Vagrant-vApp-Net',
:fence_mode => 'bridged',
:ip_allocation_mode => 'POOL',
:parent_network => cfg.vdc_network_id
}
env[:bridged_network] = true
elsif !cfg.networks.nil?
# Advanced network definition
env[:advanced_network] = true
network_options = []
if cfg.networks[:org]
cfg.networks[:org].each do |net|
net_id = cfg.org[:networks][net]
if !net_id
# TEMP FIX: permissions issues at the Org Level for vApp authors
# to "view" Org vDC Networks but they can see them at the
# Organization vDC level (tsugliani)
net_id = cfg.vdc[:networks][net]
if !net_id
raise 'vCloud User credentials has insufficient privileges'
end
end
network_options.push({
:name => net,
:fence_mode => 'bridged',
:ip_allocation_mode => 'POOL',
:parent_network => net_id
})
end
end
if cfg.networks[:vapp]
cfg.networks[:vapp].each_with_index do |net, i|
if net[:ip_dns].nil?
dns_address1 = '8.8.8.8'
dns_address2 = '8.8.4.4'
else
dns_address1 = net[:ip_dns].shift
dns_address2 = net[:ip_dns].shift
end
@logger.debug("Input address[#{i}]: #{net[:ip_subnet]}")
begin
cidr = NetAddr::CIDR.create(net[:ip_subnet])
rescue NetAddr::ValidationError
raise Errors::InvalidSubnet, :message => net[:ip_subnet]
end
if cidr.bits > 30
@logger.debug('Subnet too small!')
raise Errors::SubnetTooSmall, :message => net[:ip_subnet]
end
range_addresses = cidr.range(0)
@logger.debug("Range[#{i}]: #{range_addresses}")
# Delete the "network" address from the range.
range_addresses.shift
# Retrieve the first usable IP, to be used as a gateway.
if net[:gateway]
gateway_ip = net[:gateway]
else
gateway_ip = range_addresses.shift
end
# Reverse the array in place.
range_addresses.reverse!
# Delete the "broadcast" address from the range.
range_addresses.shift
# Reverse back the array.
range_addresses.reverse!
dhcp_enabled = 'false'
dhcp_start = nil
dhcp_end = nil
if !net[:dhcp_enabled].nil? and net[:dhcp_enabled] == true
dhcp_enabled = 'true'
range_addresses = net[:pool_range]
dhcp_start = net[:dhcp_range].shift
dhcp_end = net[:dhcp_range].shift
end
@logger.debug("Gateway IP[#{i}]: #{gateway_ip.to_s}")
@logger.debug("Netmask[#{i}]: #{cidr.wildcard_mask}")
@logger.debug("IP Pool[#{i}]: #{range_addresses.first}-#{range_addresses.last}")
@logger.debug("DNS1[#{i}]: #{dns_address1} DNS2[#{i}]: #{dns_address2}")
n = {
:name => net[:name],
:gateway => gateway_ip.to_s,
:netmask => cidr.wildcard_mask,
:start_address => range_addresses.first,
:end_address => range_addresses.last,
:fence_mode => 'natRouted',
:ip_allocation_mode => 'POOL',
:enable_firewall => 'false',
:dhcp_enabled => dhcp_enabled,
:dhcp_start => dhcp_start,
:dhcp_end => dhcp_end,
:dns1 => dns_address1,
:dns2 => dns_address2
}
if net[:vdc_network_name]
net_id = cfg.org[:networks][net[:vdc_network_name]]
if !net_id
# TEMP FIX: permissions issues at the Org Level for vApp authors
# to "view" Org vDC Networks but they can see them at the
# Organization vDC level (tsugliani)
net_id = cfg.vdc[:networks][net[:vdc_network_name]]
if !net_id
raise 'vCloud User credentials has insufficient privileges'
end
end
n[:parent_network] = net_id
net[:parent_network] = net_id
end
network_options.push(n)
end
end
else
if cfg.ip_dns.nil?
dns_address1 = '8.8.8.8'
dns_address2 = '8.8.4.4'
else
dns_address1 = cfg.ip_dns.shift
dns_address2 = cfg.ip_dns.shift
end
@logger.debug("DNS1: #{dns_address1} DNS2: #{dns_address2}")
# No IP subnet specified, reverting to defaults
network_options = {
:name => 'Vagrant-vApp-Net',
:gateway => '10.1.1.1',
:netmask => '255.255.255.0',
:start_address => '10.1.1.2',
:end_address => '10.1.1.254',
:fence_mode => 'natRouted',
:ip_allocation_mode => 'POOL',
:parent_network => cfg.vdc_network_id,
:enable_firewall => 'false',
:dns1 => dns_address1,
:dns2 => dns_address2
}
end
network_options = [network_options] if !network_options.kind_of?(Array)
if env[:machine].get_vapp_id.nil?
env[:ui].info('Building vApp...')
if cfg.vapp_name
vapp_name = cfg.vapp_name
else
vapp_prefix = cfg.vapp_prefix
vapp_prefix = 'Vagrant' if vapp_prefix.nil?
vapp_name = "#{vapp_prefix}-#{Etc.getlogin}-#{Socket.gethostname.downcase}-" +
"#{SecureRandom.hex(4)}"
end
compose = cnx.compose_vapp_from_vm(
cfg.vdc_id,
vapp_name,
"vApp created by #{Etc.getlogin} running on " +
"#{Socket.gethostname.downcase} using vagrant-vcloud on " +
"#{Time.now.strftime("%B %d, %Y")}",
{
vm_name => cfg.catalog_item[:vms_hash].first.last[:id]
},
network_options,
cfg
)
@logger.debug('Launch Compose vApp...')
# Wait for the task to finish.
wait = cnx.wait_task_completion(compose[:task_id])
unless wait[:errormsg].nil?
fail Errors::ComposeVAppError, :message => wait[:errormsg]
end
# Fetch thenewly created vApp ID
vapp_id = compose[:vapp_id]
# putting the vApp Id in a globally reachable var and file.
env[:machine].vappid = vapp_id
# Fetching new vApp object to check stuff.
new_vapp = cnx.get_vapp(vapp_id)
# FIXME: Add a lot of error handling for each step here !
if new_vapp
env[:ui].success("vApp #{new_vapp[:name]} successfully created.")
# add vapp metadata
if !cfg.metadata_vapp.nil?
env[:ui].info('Setting vApp metadata...')
set_metadata_vapp = cnx.set_vapp_metadata vapp_id, cfg.metadata_vapp
cnx.wait_task_completion(set_metadata_vapp)
end
else
env[:ui].error("vApp #{new_vapp[:name]} creation failed!")
raise # FIXME: error handling missing.
end
else
env[:ui].info('Adding VM to existing vApp...')
recompose = cnx.recompose_vapp_from_vm(
env[:machine].get_vapp_id,
{
vm_name => cfg.catalog_item[:vms_hash].first.last[:id]
},
network_options,
cfg
)
@logger.info('Waiting for the recompose task to complete ...')
# Wait for the task to finish.
cnx.wait_task_completion(recompose[:task_id])
new_vapp = cnx.get_vapp(env[:machine].get_vapp_id)
# FIXME: Add a lot of error handling for each step here !
if !new_vapp
env[:ui].error("VM #{vm_name} add to #{new_vapp[:name]} failed!")
raise
end
end
# Add the vm id as machine.id
new_vm_properties = new_vapp[:vms_hash].fetch(vm_name)
env[:machine].id = new_vm_properties[:id]
@app.call env
end
end
end
end
end