class Beaker::OpenStack
Please file any issues/concerns at github.com/puppetlabs/beaker/issues<br>This code is EXPERIMENTAL!
Beaker support for OpenStack
def cleanup
def cleanup @logger.notify "Cleaning up OpenStack" @vms.each do |vm| cleanup_storage(vm) @logger.debug "Release floating IPs for OpenStack host #{vm.name}" floating_ips = vm.all_addresses # fetch and release its floating IPs floating_ips.each do |address| @compute_client.disassociate_address(vm.id, address['ip']) @compute_client.release_address(address['id']) end @logger.debug "Destroying OpenStack host #{vm.name}" vm.destroy if @options[:openstack_keyname].nil? @logger.debug "Deleting random keypair" @compute_client.delete_key_pair vm.name end end end
def cleanup_storage vm
-
vm
(Fog::Compute::OpenStack::Server
) -- the server to detach from
def cleanup_storage vm vm.volumes.each do |vol| @logger.debug "Deleting volume #{vol.name} for OpenStack host #{vm.name}" vm.detach_volume(vol.id) vol.wait_for { ready? } vol.destroy end end
def enable_root(host)
enable root on a single host (the current one presumably) but only
def enable_root(host) if host['user'] != 'root' copy_ssh_to_root(host, @options) enable_root_login(host, @options) host['user'] = 'root' host.close end end
def enable_root_on_hosts
- Api: - private
Returns:
-
(void)
-
def enable_root_on_hosts @hosts.each do |host| enable_root(host) end end
def flavor f
-
(String)
- Openstack id for provided flavor name
Parameters:
-
f
(String
) -- The flavor name
def flavor f @logger.debug "OpenStack: Looking up flavor '#{f}'" @compute_client.flavors.find { |x| x.name == f } || raise("Couldn't find flavor: #{f}") end
def image i
-
(String)
- Openstack id for provided image name
Parameters:
-
i
(String
) -- The image name
def image i @logger.debug "OpenStack: Looking up image '#{i}'" @compute_client.images.find { |x| x.name == i } || raise("Couldn't find image: #{i}") end
def initialize(openstack_hosts, options)
(**options)
-
:timeout
(Integer
) -- The amount of time to attempt execution before quiting and exiting with failure -
:project
(String
) -- Added as metadata to each OpenStack instance -
:department
(String
) -- Added as metadata to each OpenStack instance -
:jenkins_build_url
(String
) -- Added as metadata to each OpenStack instance -
:openstack_keyname
(String
) -- The name of an existing key pair that should be auto-loaded onto each -
:openstack_network
(String
) -- The network that each OpenStack instance should be contacted through (required) -
:openstack_region
(String
) -- The region that each OpenStack instance should be provisioned on (optional) -
:openstack_tenant
(String
) -- The tenant to access the OpenStack instance with (required) -
:openstack_auth_url
(String
) -- The URL to access the OpenStack instance with (required) -
:openstack_username
(String
) -- The username to access the OpenStack instance with (required) -
:openstack_api_key
(String
) -- The key to access the OpenStack instance with (required)
Parameters:
-
options
(Hash{Symbol=>String}
) -- The options hash containing configuration values -
openstack_hosts
(
) -- The array of OpenStack hosts to provision
def initialize(openstack_hosts, options) require 'fog' @options = options @logger = options[:logger] @hosts = openstack_hosts @vms = [] raise 'You must specify an Openstack API key (:openstack_api_key) for OpenStack instances!' unless @options[:openstack_api_key] raise 'You must specify an Openstack username (:openstack_username) for OpenStack instances!' unless @options[:openstack_username] raise 'You must specify an Openstack auth URL (:openstack_auth_url) for OpenStack instances!' unless @options[:openstack_auth_url] raise 'You must specify an Openstack tenant (:openstack_tenant) for OpenStack instances!' unless @options[:openstack_tenant] raise 'You must specify an Openstack network (:openstack_network) for OpenStack instances!' unless @options[:openstack_network] optionhash = {} optionhash[:provider] = :openstack optionhash[:openstack_api_key] = @options[:openstack_api_key] optionhash[:openstack_username] = @options[:openstack_username] optionhash[:openstack_auth_url] = @options[:openstack_auth_url] optionhash[:openstack_tenant] = @options[:openstack_tenant] optionhash[:openstack_region] = @options[:openstack_region] if @options[:openstack_region] @compute_client ||= Fog::Compute.new(optionhash) if not @compute_client raise "Unable to create OpenStack Compute instance (api key: #{@options[:openstack_api_key]}, username: #{@options[:openstack_username]}, auth_url: #{@options[:openstack_auth_url]}, tenant: #{@options[:openstack_tenant]})" end networkoptionhash = {} networkoptionhash[:provider] = :openstack networkoptionhash[:openstack_api_key] = @options[:openstack_api_key] networkoptionhash[:openstack_username] = @options[:openstack_username] networkoptionhash[:openstack_auth_url] = @options[:openstack_auth_url] networkoptionhash[:openstack_tenant] = @options[:openstack_tenant] networkoptionhash[:openstack_region] = @options[:openstack_region] if @options[:openstack_region] @network_client ||= Fog::Network.new(networkoptionhash) if not @network_client raise "Unable to create OpenStack Network instance (api_key: #{@options[:openstack_api_key]}, username: #{@options[:openstack_username]}, auth_url: #{@options[:openstack_auth_url]}, tenant: #{@options[:openstack_tenant]})" end end
def key_name(host)
- Api: - private
Returns:
-
(String)
- key_name
Parameters:
-
host
(Host
) -- The OpenStack host to provision
def key_name(host) if @options[:openstack_keyname] @logger.debug "Adding optional key_name #{@options[:openstack_keyname]} to #{host.name} (#{host[:vmhostname]})" @options[:openstack_keyname] else @logger.debug "Generate a new rsa key" key = OpenSSL::PKey::RSA.new 2048 type = key.ssh_type data = [ key.to_blob ].pack('m0') @logger.debug "Creating Openstack keypair for public key '#{type} #{data}'" @compute_client.create_key_pair host[:vmhostname], "#{type} #{data}" host['ssh'][:key_data] = [ key.to_pem ] host[:vmhostname] end end
def network n
-
(String)
- Openstack id for provided network name
Parameters:
-
n
(String
) -- The network name
def network n @logger.debug "OpenStack: Looking up network '#{n}'" @network_client.networks.find { |x| x.name == n } || raise("Couldn't find network: #{n}") end
def provision
def provision @logger.notify "Provisioning OpenStack" @hosts.each do |host| host[:vmhostname] = generate_host_name @logger.debug "Provisioning #{host.name} (#{host[:vmhostname]})" options = { :flavor_ref => flavor(host[:flavor]).id, :image_ref => image(host[:image]).id, :nics => [ {'net_id' => network(@options[:openstack_network]).id } ], :name => host[:vmhostname], :user_data => "#cloud-config\nmanage_etc_hosts: true\n", } options[:key_name] = key_name(host) vm = @compute_client.servers.create(options) #wait for the new instance to start up start = Time.now try = 1 attempts = @options[:timeout].to_i / SLEEPWAIT while try <= attempts begin vm.wait_for(5) { ready? } break rescue Fog::Errors::TimeoutError => e if try >= attempts @logger.debug "Failed to connect to new OpenStack instance #{host.name} (#{host[:vmhostname]})" raise e end @logger.debug "Timeout connecting to instance #{host.name} (#{host[:vmhostname]}), trying again..." end sleep SLEEPWAIT try += 1 end # Associate a public IP to the server # Create if there are no floating ips available # # Do we already have an address? @logger.debug vm.addresses address=nil begin # Here we try and assign an address from a floating IP pool # This seems to fail on some implementations (FloatingIpPoolNotFound) ip = @compute_client.addresses.find { |ip| ip.instance_id.nil? } if ip.nil? @logger.debug "Creating IP for #{host.name} (#{host[:vmhostname]})" ip = @compute_client.addresses.create end ip.server = vm address = ip.ip rescue Fog::Compute::OpenStack::NotFound # Here, we fail to just trying to use an address that's already assigned if there is one # There may be better logic, but this worked in the original implementation # There might be an argument for checking whether an address is reachable a la # port_open? logic in host.rb but maybe race conditions begin if vm.addresses[@options[:openstack_network]] address = vm.addresses[@options[:openstack_network]].map{ |network| network['addr'] }.first end rescue NoMethodError @logger.debug "No current address retrievable from OpenStack data" end end raise 'Could not find or assign an address to the instance' unless address host[:ip] = address @logger.debug "OpenStack host #{host.name} (#{host[:vmhostname]}) assigned ip: #{host[:ip]}" #set metadata vm.metadata.update({:jenkins_build_url => @options[:jenkins_build_url].to_s, :department => @options[:department].to_s, :project => @options[:project].to_s }) @vms << vm #enable root if user is not root enable_root(host) provision_storage(host, vm) end end
def provision_storage host, vm
-
vm
(Fog::Compute::OpenStack::Server
) -- the server to attach to -
host
(Hash
) -- thet current host defined in the nodeset
def provision_storage host, vm if host['volumes'] # Lazily create the volume client if needed volume_client_create host['volumes'].keys.each_with_index do |volume, index| @logger.debug "Creating volume #{volume} for OpenStack host #{host.name}" # The node defintion file defines volume sizes in MB (due to precedent # with the vagrant virtualbox implementation) however OpenStack requires # this translating into GB openstack_size = host['volumes'][volume]['size'].to_i / 1000 # Create the volume and wait for it to become available vol = @volume_client.volumes.create( :size => openstack_size, :display_name => volume, :description => "Beaker volume: host=#{host.name} volume=#{volume}", ) vol.wait_for { ready? } # Fog needs a device name to attach as, so invent one. The guest # doesn't pay any attention to this device = "/dev/vd#{('b'.ord + index).chr}" vm.attach_volume(vol.id, device) end end end
def volume_client_create
-
(Fog::OpenStack::Volume)
- OpenStack volume client
def volume_client_create options = { :provider => :openstack, :openstack_api_key => @options[:openstack_api_key], :openstack_username => @options[:openstack_username], :openstack_auth_url => @options[:openstack_auth_url], :openstack_tenant => @options[:openstack_tenant], :openstack_region => @options[:openstack_region], } @volume_client ||= Fog::Volume.new(options) unless @volume_client raise "Unable to create OpenStack Volume instance"\ " (api_key: #{@options[:openstack_api_key]},"\ " username: #{@options[:openstack_username]},"\ " auth_url: #{@options[:openstack_auth_url]},"\ " tenant: #{@options[:openstack_tenant]})" end end