beaker 2.10.0 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
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]}"