Class: Beaker::OpenStack
- Inherits:
-
Hypervisor
- Object
- Hypervisor
- Beaker::OpenStack
- Defined in:
- lib/beaker/hypervisor/openstack.rb
Overview
Beaker support for OpenStack This code is EXPERIMENTAL! Please file any issues/concerns at github.com/puppetlabs/beaker/issues
Constant Summary collapse
- SLEEPWAIT =
5
Constants inherited from Hypervisor
Constants included from HostPrebuiltSteps
HostPrebuiltSteps::APT_CFG, HostPrebuiltSteps::CUMULUS_PACKAGES, HostPrebuiltSteps::DEBIAN_PACKAGES, HostPrebuiltSteps::ETC_HOSTS_PATH, HostPrebuiltSteps::ETC_HOSTS_PATH_SOLARIS, HostPrebuiltSteps::FREEBSD_PACKAGES, HostPrebuiltSteps::IPS_PKG_REPO, HostPrebuiltSteps::NTPSERVER, HostPrebuiltSteps::OPENBSD_PACKAGES, HostPrebuiltSteps::PSWINDOWS_PACKAGES, HostPrebuiltSteps::ROOT_KEYS_SCRIPT, HostPrebuiltSteps::ROOT_KEYS_SYNC_CMD, HostPrebuiltSteps::ROOT_KEYS_SYNC_CMD_AIX, HostPrebuiltSteps::SLES10_PACKAGES, HostPrebuiltSteps::SLES_PACKAGES, HostPrebuiltSteps::SOLARIS10_PACKAGES, HostPrebuiltSteps::SOLARIS11_PACKAGES, HostPrebuiltSteps::TRIES, HostPrebuiltSteps::UNIX_PACKAGES, HostPrebuiltSteps::WINDOWS_PACKAGES
Instance Method Summary collapse
-
#cleanup ⇒ Object
Destroy any OpenStack instances.
-
#cleanup_storage(vm) ⇒ Object
Detach and delete guest volumes.
-
#enable_root(host) ⇒ Object
enable root on a single host (the current one presumably) but only if the username isn’t ‘root’.
-
#enable_root_on_hosts ⇒ void
private
Enables root access for a host when username is not root This method ripped from the aws_sdk implementation and is probably wrong because it iterates on a collection when there’s no guarantee the collection has all been brought up in openstack yet and will thus explode.
-
#flavor(f) ⇒ String
Provided a flavor name return the OpenStack id for that flavor.
-
#image(i) ⇒ String
Provided an image name return the OpenStack id for that image.
-
#initialize(openstack_hosts, options) ⇒ OpenStack
constructor
Create a new instance of the OpenStack hypervisor object.
-
#key_name(host) ⇒ String
private
Get key_name from options or generate a new rsa key and add it to OpenStack keypairs.
-
#network(n) ⇒ String
Provided a network name return the OpenStack id for that network.
-
#provision ⇒ Object
Create new instances in OpenStack.
-
#provision_storage(host, vm) ⇒ Object
Create and attach dynamic volumes.
-
#volume_client_create ⇒ Fog::OpenStack::Volume
Create a volume client on request.
Methods inherited from Hypervisor
#configure, create, #generate_host_name, #proxy_package_manager, #validate
Methods included from HostPrebuiltSteps
#add_el_extras, #additive_hash_merge, #apt_get_update, #check_and_install_packages_if_needed, #construct_env, #copy_file_to_remote, #copy_ssh_to_root, #disable_iptables, #disable_se_linux, #disable_updates, #enable_root_login, #epel_info_for, #get_domain_name, #get_ip, #hack_etc_hosts, #package_proxy, #proxy_config, #set_env, #set_etc_hosts, #sync_root_keys, #timesync, #validate_host
Methods included from DSL::Patterns
Constructor Details
#initialize(openstack_hosts, options) ⇒ OpenStack
Create a new instance of the OpenStack hypervisor object
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/beaker/hypervisor/openstack.rb', line 24 def initialize(openstack_hosts, ) require 'fog' @options = @logger = [: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 |
Instance Method Details
#cleanup ⇒ Object
Destroy any OpenStack instances
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/beaker/hypervisor/openstack.rb', line 253 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 |
#cleanup_storage(vm) ⇒ Object
Detach and delete guest volumes
152 153 154 155 156 157 158 159 |
# File 'lib/beaker/hypervisor/openstack.rb', line 152 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 |
#enable_root(host) ⇒ Object
enable root on a single host (the current one presumably) but only if the username isn’t ‘root’
286 287 288 289 290 291 292 293 |
# File 'lib/beaker/hypervisor/openstack.rb', line 286 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 |
#enable_root_on_hosts ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Enables root access for a host when username is not root This method ripped from the aws_sdk implementation and is probably wrong because it iterates on a collection when there’s no guarantee the collection has all been brought up in openstack yet and will thus explode
278 279 280 281 282 |
# File 'lib/beaker/hypervisor/openstack.rb', line 278 def enable_root_on_hosts @hosts.each do |host| enable_root(host) end end |
#flavor(f) ⇒ String
Provided a flavor name return the OpenStack id for that flavor
70 71 72 73 |
# File 'lib/beaker/hypervisor/openstack.rb', line 70 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 |
#image(i) ⇒ String
Provided an image name return the OpenStack id for that image
78 79 80 81 |
# File 'lib/beaker/hypervisor/openstack.rb', line 78 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 |
#key_name(host) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Get key_name from options or generate a new rsa key and add it to OpenStack keypairs
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/beaker/hypervisor/openstack.rb', line 301 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 |
#network(n) ⇒ String
Provided a network name return the OpenStack id for that network
86 87 88 89 |
# File 'lib/beaker/hypervisor/openstack.rb', line 86 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 |
#provision ⇒ Object
Create new instances in OpenStack
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/beaker/hypervisor/openstack.rb', line 162 def provision @logger.notify "Provisioning OpenStack" @hosts.each do |host| host[:vmhostname] = generate_host_name @logger.debug "Provisioning #{host.name} (#{host[:vmhostname]})" = { :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 => host[:user_data] || "#cloud-config\nmanage_etc_hosts: true\n", } [:key_name] = key_name(host) vm = @compute_client.servers.create() #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..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 hack_etc_hosts @hosts, @options end |
#provision_storage(host, vm) ⇒ Object
Create and attach dynamic volumes
Creates an array of volumes and attaches them to the current host. The host bus type is determined by the image type, so by default devices appear as /dev/vdb, /dev/vdc etc. Setting the glance properties hw_disk_bus=scsi, hw_scsi_model=virtio-scsi will present them as /dev/sdb, /dev/sdc (or 2:0:0:1, 2:0:0:2 in SCSI addresses)
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/beaker/hypervisor/openstack.rb', line 122 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 |
#volume_client_create ⇒ Fog::OpenStack::Volume
Create a volume client on request
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/beaker/hypervisor/openstack.rb', line 93 def volume_client_create = { :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() 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 |