beaker 2.10.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +8 -8
  2. data/HISTORY.md +292 -4
  3. data/acceptance/tests/base/host.rb +1 -0
  4. data/lib/beaker/answers/version30.rb +10 -10
  5. data/lib/beaker/cli.rb +10 -8
  6. data/lib/beaker/command.rb +1 -1
  7. data/lib/beaker/dsl/helpers/facter_helpers.rb +10 -1
  8. data/lib/beaker/dsl/helpers/hiera_helpers.rb +0 -11
  9. data/lib/beaker/dsl/helpers/host_helpers.rb +12 -3
  10. data/lib/beaker/dsl/helpers/puppet_helpers.rb +11 -3
  11. data/lib/beaker/dsl/helpers/tk_helpers.rb +0 -12
  12. data/lib/beaker/dsl/helpers/web_helpers.rb +0 -12
  13. data/lib/beaker/dsl/install_utils/module_utils.rb +9 -6
  14. data/lib/beaker/dsl/install_utils/pe_utils.rb +60 -8
  15. data/lib/beaker/dsl/install_utils/puppet_utils.rb +15 -2
  16. data/lib/beaker/host.rb +11 -145
  17. data/lib/beaker/host/mac.rb +3 -7
  18. data/lib/beaker/host/mac/pkg.rb +43 -0
  19. data/lib/beaker/host/pswindows.rb +1 -1
  20. data/lib/beaker/host/pswindows/exec.rb +83 -2
  21. data/lib/beaker/host/pswindows/pkg.rb +9 -6
  22. data/lib/beaker/host/unix/exec.rb +105 -0
  23. data/lib/beaker/host/unix/pkg.rb +22 -9
  24. data/lib/beaker/host/windows.rb +2 -1
  25. data/lib/beaker/host/windows/exec.rb +1 -1
  26. data/lib/beaker/host/windows/pkg.rb +4 -7
  27. data/lib/beaker/host_prebuilt_steps.rb +14 -14
  28. data/lib/beaker/hypervisor/aws_sdk.rb +198 -114
  29. data/lib/beaker/hypervisor/openstack.rb +48 -25
  30. data/lib/beaker/shared/host_manager.rb +11 -2
  31. data/lib/beaker/ssh_connection.rb +26 -0
  32. data/lib/beaker/version.rb +1 -1
  33. data/spec/beaker/answers_spec.rb +56 -0
  34. data/spec/beaker/cli_spec.rb +16 -12
  35. data/spec/beaker/command_spec.rb +3 -0
  36. data/spec/beaker/dsl/install_utils/module_utils_spec.rb +2 -2
  37. data/spec/beaker/dsl/install_utils/pe_utils_spec.rb +71 -3
  38. data/spec/beaker/dsl/install_utils/puppet_utils_spec.rb +4 -1
  39. data/spec/beaker/host/unix/pkg_spec.rb +10 -10
  40. data/spec/beaker/host_prebuilt_steps_spec.rb +3 -1
  41. data/spec/beaker/host_spec.rb +8 -2
  42. data/spec/beaker/hypervisor/vagrant_spec.rb +1 -0
  43. metadata +3 -2
@@ -46,9 +46,6 @@ module Beaker
46
46
  # Perform the main launch work
47
47
  launch_all_nodes()
48
48
 
49
- # Wait for each node to reach status :running
50
- wait_for_status(:running)
51
-
52
49
  # Add metadata tags to each instance
53
50
  add_tags()
54
51
 
@@ -69,26 +66,30 @@ module Beaker
69
66
  nil #void
70
67
  end
71
68
 
72
- # Cleanup all earlier provisioned hosts on EC2 using the AWS::EC2 library
73
- #
74
- # It goes without saying, but a #cleanup does nothing without a #provision
75
- # method call first.
69
+ # Kill all instances.
76
70
  #
71
+ # @param instances [Enumerable<EC2::Instance>]
77
72
  # @return [void]
78
- def cleanup
79
- @logger.notify("aws-sdk: Cleanup, iterating across all hosts and terminating them")
80
- @hosts.each do |host|
81
- # This was set previously during provisioning
82
- instance = host['instance']
83
-
84
- # Only attempt a terminate if the instance actually is set by provision
85
- # and the instance actually 'exists'.
73
+ def kill_instances(instances)
74
+ instances.each do |instance|
86
75
  if !instance.nil? and instance.exists?
76
+ @logger.notify("aws-sdk: killing EC2 instance #{instance.id}")
87
77
  instance.terminate
88
78
  end
89
79
  end
80
+ nil
81
+ end
90
82
 
91
- nil #void
83
+ # Cleanup all earlier provisioned hosts on EC2 using the AWS::EC2 library
84
+ #
85
+ # It goes without saying, but a #cleanup does nothing without a #provision
86
+ # method call first.
87
+ #
88
+ # @return [void]
89
+ def cleanup
90
+ # Provisioning should have set the host 'instance' values.
91
+ kill_instances(@hosts.map{|h| h['instance']}.select{|x| !x.nil?})
92
+ nil
92
93
  end
93
94
 
94
95
  # Print instances to the logger. Instances will be from all regions
@@ -220,125 +221,209 @@ module Beaker
220
221
 
221
222
  end
222
223
 
223
- # Launch all nodes
224
- #
225
- # This is where the main launching work occurs for each node. Here we take
226
- # care of feeding the information from the image required into the config
227
- # for the new host, we perform the launch operation and ensure that the
228
- # instance is properly tagged for identification.
224
+ # Create an EC2 instance for host, tag it, and return it.
229
225
  #
230
- # @return [void]
226
+ # @return [AWS::EC2::Instance)]
231
227
  # @api private
232
- def launch_all_nodes
233
- # Load the ec2_yaml spec file
234
- ami_spec = YAML.load_file(@options[:ec2_yaml])["AMI"]
235
-
236
- # Iterate across all hosts and launch them, adding tags along the way
237
- @logger.notify("aws-sdk: Iterate across all hosts in configuration and launch them")
238
- @hosts.each do |host|
239
- amitype = host['vmname'] || host['platform']
240
- amisize = host['amisize'] || 'm1.small'
241
- subnet_id = host['subnet_id'] || @options['subnet_id'] || nil
242
- vpc_id = host['vpc_id'] || @options['vpc_id'] || nil
228
+ def create_instance(host, ami_spec, subnet_id)
229
+ amitype = host['vmname'] || host['platform']
230
+ amisize = host['amisize'] || 'm1.small'
231
+ vpc_id = host['vpc_id'] || @options['vpc_id'] || nil
243
232
 
244
- if vpc_id and !subnet_id
245
- raise RuntimeError, "A subnet_id must be provided with a vpc_id"
246
- end
233
+ if vpc_id and !subnet_id
234
+ raise RuntimeError, "A subnet_id must be provided with a vpc_id"
235
+ end
247
236
 
248
- # Use snapshot provided for this host
249
- image_type = host['snapshot']
250
- if not image_type
251
- raise RuntimeError, "No snapshot/image_type provided for EC2 provisioning"
252
- end
253
- ami = ami_spec[amitype]
254
- ami_region = ami[:region]
255
-
256
- # Main region object for ec2 operations
257
- region = @ec2.regions[ami_region]
258
-
259
- # If we haven't defined a vpc_id then we use the default vpc for the provided region
260
- if !vpc_id
261
- @logger.notify("aws-sdk: filtering available vpcs in region by 'isDefault")
262
- filtered_vpcs = region.client.describe_vpcs(:filters => [{:name => 'isDefault', :values => ['true']}])
263
- if !filtered_vpcs[:vpc_set].empty?
264
- vpc_id = filtered_vpcs[:vpc_set].first[:vpc_id]
265
- else #there's no default vpc, use nil
266
- vpc_id = nil
267
- end
237
+ # Use snapshot provided for this host
238
+ image_type = host['snapshot']
239
+ if not image_type
240
+ raise RuntimeError, "No snapshot/image_type provided for EC2 provisioning"
241
+ end
242
+ ami = ami_spec[amitype]
243
+ ami_region = ami[:region]
244
+
245
+ # Main region object for ec2 operations
246
+ region = @ec2.regions[ami_region]
247
+
248
+ # If we haven't defined a vpc_id then we use the default vpc for the provided region
249
+ if !vpc_id
250
+ @logger.notify("aws-sdk: filtering available vpcs in region by 'isDefault")
251
+ filtered_vpcs = region.client.describe_vpcs(:filters => [{:name => 'isDefault', :values => ['true']}])
252
+ if !filtered_vpcs[:vpc_set].empty?
253
+ vpc_id = filtered_vpcs[:vpc_set].first[:vpc_id]
254
+ else #there's no default vpc, use nil
255
+ vpc_id = nil
268
256
  end
257
+ end
269
258
 
270
- # Grab the vpc object based upon provided id
271
- vpc = vpc_id ? region.vpcs[vpc_id] : nil
259
+ # Grab the vpc object based upon provided id
260
+ vpc = vpc_id ? region.vpcs[vpc_id] : nil
272
261
 
273
- # Grab image object
274
- image_id = ami[:image][image_type.to_sym]
275
- @logger.notify("aws-sdk: Checking image #{image_id} exists and getting its root device")
276
- image = region.images[image_id]
277
- if image.nil? and not image.exists?
278
- raise RuntimeError, "Image not found: #{image_id}"
279
- end
262
+ # Grab image object
263
+ image_id = ami[:image][image_type.to_sym]
264
+ @logger.notify("aws-sdk: Checking image #{image_id} exists and getting its root device")
265
+ image = region.images[image_id]
266
+ if image.nil? and not image.exists?
267
+ raise RuntimeError, "Image not found: #{image_id}"
268
+ end
280
269
 
281
- # Transform the images block_device_mappings output into a format
282
- # ready for a create.
283
- orig_bdm = image.block_device_mappings()
284
- @logger.notify("aws-sdk: Image block_device_mappings: #{orig_bdm.to_hash}")
285
- block_device_mappings = []
286
- orig_bdm.each do |device_name, rest|
287
- block_device_mappings << {
288
- :device_name => device_name,
289
- :ebs => {
290
- # Change the default size of the root volume.
291
- :volume_size => host['volume_size'] || rest[:volume_size],
292
- # This is required to override the images default for
293
- # delete_on_termination, forcing all volumes to be deleted once the
294
- # instance is terminated.
295
- :delete_on_termination => true,
296
- }
270
+ # Transform the images block_device_mappings output into a format
271
+ # ready for a create.
272
+ orig_bdm = image.block_device_mappings()
273
+ @logger.notify("aws-sdk: Image block_device_mappings: #{orig_bdm.to_hash}")
274
+ block_device_mappings = []
275
+ orig_bdm.each do |device_name, rest|
276
+ block_device_mappings << {
277
+ :device_name => device_name,
278
+ :ebs => {
279
+ # Change the default size of the root volume.
280
+ :volume_size => host['volume_size'] || rest[:volume_size],
281
+ # This is required to override the images default for
282
+ # delete_on_termination, forcing all volumes to be deleted once the
283
+ # instance is terminated.
284
+ :delete_on_termination => true,
297
285
  }
298
- end
299
-
300
- security_group = ensure_group(vpc || region, Beaker::EC2Helper.amiports(host))
301
-
302
- # Launch the node, filling in the blanks from previous work.
303
- @logger.notify("aws-sdk: Launch instance")
304
- config = {
305
- :count => 1,
306
- :image_id => image_id,
307
- :monitoring_enabled => true,
308
- :key_pair => ensure_key_pair(region),
309
- :security_groups => [security_group],
310
- :instance_type => amisize,
311
- :disable_api_termination => false,
312
- :instance_initiated_shutdown_behavior => "terminate",
313
- :block_device_mappings => block_device_mappings,
314
- :subnet => subnet_id,
315
286
  }
316
- instance = region.instances.create(config)
287
+ end
317
288
 
318
- # Persist the instance object for this host, so later it can be
319
- # manipulated by 'cleanup' for example.
320
- host['instance'] = instance
289
+ security_group = ensure_group(vpc || region, Beaker::EC2Helper.amiports(host))
321
290
 
322
- @logger.notify("aws-sdk: Launched #{host.name} (#{amitype}:#{amisize}) using snapshot/image_type #{image_type}")
291
+ msg = "aws-sdk: launching %p on %p using %p/%p%s" %
292
+ [host.name, amitype, amisize, image_type,
293
+ subnet_id ? ("in %p" % subnet_id) : '']
294
+ @logger.notify(msg)
295
+ config = {
296
+ :count => 1,
297
+ :image_id => image_id,
298
+ :monitoring_enabled => true,
299
+ :key_pair => ensure_key_pair(region),
300
+ :security_groups => [security_group],
301
+ :instance_type => amisize,
302
+ :disable_api_termination => false,
303
+ :instance_initiated_shutdown_behavior => "terminate",
304
+ :block_device_mappings => block_device_mappings,
305
+ :subnet => subnet_id,
306
+ }
307
+ region.instances.create(config)
308
+ end
309
+
310
+ # For each host, create an EC2 instance in one of the specified
311
+ # subnets and push it onto instances_created. Each subnet will be
312
+ # tried at most once for each host, and more than one subnet may
313
+ # be tried if capacity constraints are encountered. Each Hash in
314
+ # instances_created will contain an :instance and :host value.
315
+ #
316
+ # @param hosts [Enumerable<Host>]
317
+ # @param subnets [Enumerable<String>]
318
+ # @param ami_spec [Hash]
319
+ # @param instances_created Enumerable<Hash{Symbol=>EC2::Instance,Host}>
320
+ # @return [void]
321
+ # @api private
322
+ def launch_nodes_on_some_subnet(hosts, subnets, ami_spec, instances_created)
323
+ # Shuffle the subnets so we don't always hit the same one
324
+ # first, and cycle though the subnets independently of the
325
+ # host, so we stick with one that's working. Try each subnet
326
+ # once per-host.
327
+ if subnets.nil? or subnets.empty?
328
+ return
323
329
  end
330
+ subnet_i = 0
331
+ shuffnets = subnets.shuffle
332
+ hosts.each do |host|
333
+ instance = nil
334
+ shuffnets.length.times do
335
+ begin
336
+ subnet_id = shuffnets[subnet_i]
337
+ instance = create_instance(host, ami_spec, subnet_id)
338
+ instances_created.push({:instance => instance, :host => host})
339
+ break
340
+ rescue AWS::EC2::Errors::InsufficientInstanceCapacity => ex
341
+ @logger.notify("aws-sdk: hit #{subnet_id} capacity limit; moving on")
342
+ subnet_i = (subnet_i + 1) % shuffnets.length
343
+ end
344
+ end
345
+ if instance.nil?
346
+ raise RuntimeError, "unable to launch host in any requested subnet"
347
+ end
348
+ end
349
+ end
324
350
 
351
+ # Create EC2 instances for all hosts, tag them, and wait until
352
+ # they're running. When a host provides a subnet_id, create the
353
+ # instance in that subnet, otherwise prefer a CONFIG subnet_id.
354
+ # If neither are set but there is a CONFIG subnet_ids list,
355
+ # attempt to create the host in each specified subnet, which might
356
+ # fail due to capacity constraints, for example. Specifying both
357
+ # a CONFIG subnet_id and subnet_ids will provoke an error.
358
+ #
359
+ # @return [void]
360
+ # @api private
361
+ def launch_all_nodes
362
+ @logger.notify("aws-sdk: launch all hosts in configuration")
363
+ ami_spec = YAML.load_file(@options[:ec2_yaml])["AMI"]
364
+ global_subnet_id = @options['subnet_id']
365
+ global_subnets = @options['subnet_ids']
366
+ if global_subnet_id and global_subnets
367
+ raise RuntimeError, 'Config specifies both subnet_id and subnet_ids'
368
+ end
369
+ no_subnet_hosts = []
370
+ specific_subnet_hosts = []
371
+ some_subnet_hosts = []
372
+ @hosts.each do |host|
373
+ if global_subnet_id or host['subnet_id']
374
+ specific_subnet_hosts.push(host)
375
+ elsif global_subnets
376
+ some_subnet_hosts.push(host)
377
+ else
378
+ no_subnet_hosts.push(host)
379
+ end
380
+ end
381
+ instances = [] # Each element is {:instance => i, :host => h}
382
+ begin
383
+ @logger.notify("aws-sdk: launch instances not particular about subnet")
384
+ launch_nodes_on_some_subnet(some_subnet_hosts, global_subnets, ami_spec,
385
+ instances)
386
+ @logger.notify("aws-sdk: launch instances requiring a specific subnet")
387
+ specific_subnet_hosts.each do |host|
388
+ subnet_id = host['subnet_id'] || global_subnet_id
389
+ instance = create_instance(host, ami_spec, subnet_id)
390
+ instances.push({:instance => instance, :host => host})
391
+ end
392
+ @logger.notify("aws-sdk: launch instances requiring no subnet")
393
+ no_subnet_hosts.each do |host|
394
+ instance = create_instance(host, ami_spec, nil)
395
+ instances.push({:instance => instance, :host => host})
396
+ end
397
+ wait_for_status(:running, instances)
398
+ rescue Exception => ex
399
+ @logger.notify("aws-sdk: exception - #{ex}")
400
+ kill_instances(instances.map{|x| x[:instance]})
401
+ raise ex
402
+ end
403
+ # At this point, all instances should be running since wait
404
+ # either returns on success or throws an exception.
405
+ if instances.empty?
406
+ raise RuntimeError, "Didn't manage to launch any EC2 instances"
407
+ end
408
+ # Assign the now known running instances to their hosts.
409
+ instances.each {|x| x[:host]['instance'] = x[:instance]}
325
410
  nil
326
411
  end
327
412
 
328
- # Waits until all boxes reach the desired status
413
+ # Wait until all instances reach the desired state. Each Hash in
414
+ # instances must contain an :instance and :host value.
329
415
  #
330
416
  # @param status [Symbol] EC2 state to wait for, :running :stopped etc.
417
+ # @param instances Enumerable<Hash{Symbol=>EC2::Instance,Host}>
331
418
  # @return [void]
332
419
  # @api private
333
- def wait_for_status(status)
420
+ def wait_for_status(status, instances)
334
421
  # Wait for each node to reach status :running
335
- @logger.notify("aws-sdk: Now wait for all hosts to reach state #{status}")
336
- @hosts.each do |host|
337
- instance = host['instance']
338
- name = host.name
339
-
340
- @logger.notify("aws-sdk: Wait for status #{status} for node #{name}")
341
-
422
+ @logger.notify("aws-sdk: Waiting for all hosts to be #{status}")
423
+ instances.each do |x|
424
+ name = x[:name]
425
+ instance = x[:instance]
426
+ @logger.notify("aws-sdk: Wait for node #{name} to be #{status}")
342
427
  # Here we keep waiting for the machine state to reach ':running' with an
343
428
  # exponential backoff for each poll.
344
429
  # TODO: should probably be a in a shared method somewhere
@@ -356,7 +441,6 @@ module Beaker
356
441
  end
357
442
  backoff_sleep(tries)
358
443
  end
359
-
360
444
  end
361
445
  end
362
446
 
@@ -13,8 +13,9 @@ module Beaker
13
13
  #@option options [String] :openstack_username The username to access the OpenStack instance with (required)
14
14
  #@option options [String] :openstack_auth_url The URL to access the OpenStack instance with (required)
15
15
  #@option options [String] :openstack_tenant The tenant to access the OpenStack instance with (required)
16
+ #@option options [String] :openstack_region The region that each OpenStack instance should be provisioned on (optional)
16
17
  #@option options [String] :openstack_network The network that each OpenStack instance should be contacted through (required)
17
- #@option options [String] :openstack_keyname The name of an existing key pair that should be auto-loaded onto each
18
+ #@option options [String] :openstack_keyname The name of an existing key pair that should be auto-loaded onto each
18
19
  # OpenStack instance (optional)
19
20
  #@option options [String] :jenkins_build_url Added as metadata to each OpenStack instance
20
21
  #@option options [String] :department Added as metadata to each OpenStack instance
@@ -27,25 +28,36 @@ module Beaker
27
28
  @hosts = openstack_hosts
28
29
  @vms = []
29
30
 
30
- raise 'You must specify an Openstack API key (:oopenstack_api_key) for OpenStack instances!' unless @options[:openstack_api_key]
31
+ raise 'You must specify an Openstack API key (:openstack_api_key) for OpenStack instances!' unless @options[:openstack_api_key]
31
32
  raise 'You must specify an Openstack username (:openstack_username) for OpenStack instances!' unless @options[:openstack_username]
32
33
  raise 'You must specify an Openstack auth URL (:openstack_auth_url) for OpenStack instances!' unless @options[:openstack_auth_url]
33
34
  raise 'You must specify an Openstack tenant (:openstack_tenant) for OpenStack instances!' unless @options[:openstack_tenant]
34
35
  raise 'You must specify an Openstack network (:openstack_network) for OpenStack instances!' unless @options[:openstack_network]
35
- @compute_client ||= Fog::Compute.new(:provider => :openstack,
36
- :openstack_api_key => @options[:openstack_api_key],
37
- :openstack_username => @options[:openstack_username],
38
- :openstack_auth_url => @options[:openstack_auth_url],
39
- :openstack_tenant => @options[:openstack_tenant])
36
+
37
+ optionhash = {}
38
+ optionhash[:provider] = :openstack
39
+ optionhash[:openstack_api_key] = @options[:openstack_api_key]
40
+ optionhash[:openstack_username] = @options[:openstack_username]
41
+ optionhash[:openstack_auth_url] = @options[:openstack_auth_url]
42
+ optionhash[:openstack_tenant] = @options[:openstack_tenant]
43
+ optionhash[:openstack_region] = @options[:openstack_region] if @options[:openstack_region]
44
+
45
+ @compute_client ||= Fog::Compute.new(optionhash)
46
+
40
47
  if not @compute_client
41
48
  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]})"
42
49
  end
43
- @network_client ||= Fog::Network.new(
44
- :provider => :openstack,
45
- :openstack_api_key => @options[:openstack_api_key],
46
- :openstack_username => @options[:openstack_username],
47
- :openstack_auth_url => @options[:openstack_auth_url],
48
- :openstack_tenant => @options[:openstack_tenant])
50
+
51
+ networkoptionhash = {}
52
+ networkoptionhash[:provider] = :openstack
53
+ networkoptionhash[:openstack_api_key] = @options[:openstack_api_key]
54
+ networkoptionhash[:openstack_username] = @options[:openstack_username]
55
+ networkoptionhash[:openstack_auth_url] = @options[:openstack_auth_url]
56
+ networkoptionhash[:openstack_tenant] = @options[:openstack_tenant]
57
+ networkoptionhash[:openstack_region] = @options[:openstack_region] if @options[:openstack_region]
58
+
59
+ @network_client ||= Fog::Network.new(networkoptionhash)
60
+
49
61
  if not @network_client
50
62
 
51
63
  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]})"
@@ -85,9 +97,9 @@ module Beaker
85
97
  @logger.debug "Provisioning #{host.name} (#{host[:vmhostname]})"
86
98
  options = {
87
99
  :flavor_ref => flavor(host[:flavor]).id,
88
- :image_ref => image(host[:image]).id,
89
- :nics => [ {'net_id' => network(@options[:openstack_network]).id } ],
90
- :name => host[:vmhostname],
100
+ :image_ref => image(host[:image]).id,
101
+ :nics => [ {'net_id' => network(@options[:openstack_network]).id } ],
102
+ :name => host[:vmhostname],
91
103
  }
92
104
  options[:key_name] = key_name(host)
93
105
  vm = @compute_client.servers.create(options)
@@ -117,24 +129,35 @@ module Beaker
117
129
  #
118
130
  # Do we already have an address?
119
131
  @logger.debug vm.addresses
120
-
132
+ address=nil
121
133
  begin
122
- if vm.addresses[@options[:openstack_network]]
123
- address = vm.addresses[@options[:openstack_network]].map{ |network| network['addr'] }.first
124
- end
125
- rescue NoMethodError
126
- @logger.debug "No current address retrievable from OpenStack data"
127
- end
128
- unless address
134
+ # Here we try and assign an address from a floating IP pool
135
+ # This seems to fail on some implementations (FloatingIpPoolNotFound)
129
136
  ip = @compute_client.addresses.find { |ip| ip.instance_id.nil? }
130
-
131
137
  if ip.nil?
132
138
  @logger.debug "Creating IP for #{host.name} (#{host[:vmhostname]})"
133
139
  ip = @compute_client.addresses.create
134
140
  end
135
141
  ip.server = vm
136
142
  address = ip.ip
143
+
144
+ rescue Fog::Compute::OpenStack::NotFound
145
+ # Here, we fail to just trying to use an address that's already assigned if there is one
146
+ # There may be better logic, but this worked in the original implementation
147
+ # There might be an argument for checking whether an address is reachable a la
148
+ # port_open? logic in host.rb but maybe race conditions
149
+
150
+ begin
151
+ if vm.addresses[@options[:openstack_network]]
152
+ address = vm.addresses[@options[:openstack_network]].map{ |network| network['addr'] }.first
153
+ end
154
+ rescue NoMethodError
155
+ @logger.debug "No current address retrievable from OpenStack data"
156
+ end
157
+
137
158
  end
159
+
160
+ raise 'Could not find or assign an address to the instance' unless address
138
161
  host[:ip] = address
139
162
 
140
163
  @logger.debug "OpenStack host #{host.name} (#{host[:vmhostname]}) assigned ip: #{host[:ip]}"