kitchen-openstack 0.4.0 → 0.5.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.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ # ?.?.? / ????-??-??
2
+
3
+ ### Improvements
4
+
5
+ * PR [#19][] - Don't assume `public` and `private` network names exist
6
+ * PR [#19][] - Make IPv4 or IPv6 configurable instead of relying on Fog to pick
7
+
8
+ ### Bug Fixes
9
+
10
+ * PR [#20][] - Limit generated hostnames to 64 characters
11
+
1
12
  # 0.4.0 / 2013-06-06
2
13
 
3
14
  ### New Features
@@ -25,6 +36,8 @@
25
36
 
26
37
  * Initial release! Woo!
27
38
 
39
+ [#20]: https://github.com/RoboticCheese/kitchen-openstack/pull/20
40
+ [#19]: https://github.com/RoboticCheese/kitchen-openstack/pull/19
28
41
  [#12]: https://github.com/RoboticCheese/kitchen-openstack/pull/12
29
42
  [#11]: https://github.com/RoboticCheese/kitchen-openstack/pull/11
30
43
  [#10]: https://github.com/RoboticCheese/kitchen-openstack/pull/10
@@ -18,8 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency 'test-kitchen', '~> 1.0.0.alpha'
22
- spec.add_dependency 'fog', '~> 1.11'
21
+ spec.add_dependency 'test-kitchen', '~> 1.0.0.beta'
22
+ spec.add_dependency 'fog', '~> 1.15'
23
23
 
24
24
  spec.add_development_dependency 'bundler'
25
25
  spec.add_development_dependency 'rake'
@@ -20,6 +20,7 @@ require 'benchmark'
20
20
  require 'fog'
21
21
  require 'kitchen'
22
22
  require 'etc'
23
+ require 'ipaddr'
23
24
  require 'socket'
24
25
 
25
26
  module Kitchen
@@ -32,6 +33,7 @@ module Kitchen
32
33
  default_config :public_key_path, File.expand_path('~/.ssh/id_dsa.pub')
33
34
  default_config :username, 'root'
34
35
  default_config :port, '22'
36
+ default_config :use_ipv6, false
35
37
  default_config :openstack_tenant, nil
36
38
  default_config :openstack_region, nil
37
39
  default_config :openstack_service_name, nil
@@ -42,11 +44,9 @@ module Kitchen
42
44
  config[:disable_ssl_validation] and disable_ssl_validation
43
45
  server = create_server
44
46
  state[:server_id] = server.id
45
- info("OpenStack instance <#{state[:server_id]}> created.")
47
+ info "OpenStack instance <#{state[:server_id]}> created."
46
48
  server.wait_for { print '.'; ready? } ; puts "\n(server ready)"
47
49
  state[:hostname] = get_ip(server)
48
- # As a consequence of IP weirdness, the OpenStack setup() method is
49
- # also borked
50
50
  wait_for_sshd(state[:hostname]) ; puts '(ssh ready)'
51
51
  unless config[:ssh_key] or config[:key_name]
52
52
  do_ssh_setup(state, config, server)
@@ -61,7 +61,7 @@ module Kitchen
61
61
  config[:disable_ssl_validation] and disable_ssl_validation
62
62
  server = compute.servers.get(state[:server_id])
63
63
  server.destroy unless server.nil?
64
- info("OpenStack instance <#{state[:server_id]}> destroyed.")
64
+ info "OpenStack instance <#{state[:server_id]}> destroyed."
65
65
  state.delete(:server_id)
66
66
  state.delete(:hostname)
67
67
  end
@@ -94,24 +94,58 @@ module Kitchen
94
94
  server_def[:public_key_path] = config[:public_key_path]
95
95
  end
96
96
  server_def[:key_name] = config[:key_name] if config[:key_name]
97
+ # Can't use the Fog bootstrap and/or setup methods here; they require a
98
+ # public IP address that can't be guaranteed to exist across all
99
+ # OpenStack deployments (e.g. TryStack ARM only has private IPs).
97
100
  compute.servers.create(server_def)
98
101
  end
99
102
 
100
103
  def generate_name(base)
101
104
  # Generate what should be a unique server name
102
- rand_str = Array.new(8) { rand(36).to_s(36) }.join
103
- "#{base}-#{Etc.getlogin}-#{Socket.gethostname}-#{rand_str}"
105
+ sep = '-'
106
+ pieces = [
107
+ base,
108
+ Etc.getlogin,
109
+ Socket.gethostname,
110
+ Array.new(8) { rand(36).to_s(36) }.join
111
+ ]
112
+ until pieces.join(sep).length <= 64 do
113
+ if pieces[2].length > 24
114
+ pieces[2] = pieces[2][0..-2]
115
+ elsif pieces[1].length > 16
116
+ pieces[1] = pieces[1][0..-2]
117
+ elsif pieces[0].length > 16
118
+ pieces[0] = pieces[0][0..-2]
119
+ end
120
+ end
121
+ pieces.join sep
104
122
  end
105
123
 
106
124
  def get_ip(server)
107
125
  if config[:openstack_network_name]
126
+ debug "Using configured network: #{config[:openstack_network_name]}"
108
127
  return server.addresses[config[:openstack_network_name]].first['addr']
109
- elsif server.addresses['public'] and !server.addresses['public'].empty?
110
- # server.public_ip_address stopped working in Fog 1.10.0
111
- return server.addresses['public'].first['addr']
128
+ end
129
+ begin
130
+ pub, priv = server.public_ip_addresses, server.private_ip_addresses
131
+ rescue Fog::Compute::OpenStack::NotFound
132
+ # See Fog issue: https://github.com/fog/fog/issues/2160
133
+ addrs = server.addresses
134
+ addrs['public'] and pub = addrs['public'].map { |i| i['addr'] }
135
+ addrs['private'] and priv = addrs['private'].map { |i| i['addr'] }
136
+ end
137
+ pub, priv = parse_ips(pub, priv)
138
+ pub.first || priv.first || raise(ActionFailed, 'Could not find an IP')
139
+ end
140
+
141
+ def parse_ips(pub, priv)
142
+ pub, priv = Array(pub), Array(priv)
143
+ if config[:use_ipv6]
144
+ [pub, priv].each { |n| n.select! { |i| IPAddr.new(i).ipv6? } }
112
145
  else
113
- return server.addresses['private'].first['addr']
146
+ [pub, priv].each { |n| n.select! { |i| IPAddr.new(i).ipv4? } }
114
147
  end
148
+ return pub, priv
115
149
  end
116
150
 
117
151
  def do_ssh_setup(state, config, server)
@@ -19,7 +19,7 @@
19
19
  module Kitchen
20
20
  module Driver
21
21
  # Version string for OpenStack Kitchen driver
22
- OPENSTACK_VERSION = '0.4.0'
22
+ OPENSTACK_VERSION = '0.5.0'
23
23
  end
24
24
  end
25
25
 
@@ -29,7 +29,7 @@ describe Kitchen::Driver::Openstack do
29
29
  let(:state) { Hash.new }
30
30
 
31
31
  let(:instance) do
32
- stub(:name => 'potatoes', :logger => logger, :to_str => 'instance')
32
+ double(:name => 'potatoes', :logger => logger, :to_str => 'instance')
33
33
  end
34
34
 
35
35
  let(:driver) do
@@ -98,8 +98,8 @@ describe Kitchen::Driver::Openstack do
98
98
 
99
99
  describe '#create' do
100
100
  let(:server) do
101
- stub(:id => 'test123', :wait_for => true,
102
- :public_ip_address => '1.2.3.4')
101
+ double(:id => 'test123', :wait_for => true,
102
+ :public_ip_addresses => %w{1.2.3.4})
103
103
  end
104
104
  let(:driver) do
105
105
  d = Kitchen::Driver::Openstack.new(config)
@@ -157,9 +157,9 @@ describe Kitchen::Driver::Openstack do
157
157
  let(:server_id) { '12345' }
158
158
  let(:hostname) { 'example.com' }
159
159
  let(:state) { { :server_id => server_id, :hostname => hostname } }
160
- let(:server) { stub(:nil? => false, :destroy => true) }
161
- let(:servers) { stub(:get => server) }
162
- let(:compute) { stub(:servers => servers) }
160
+ let(:server) { double(:nil? => false, :destroy => true) }
161
+ let(:servers) { double(:get => server) }
162
+ let(:compute) { double(:servers => servers) }
163
163
 
164
164
  let(:driver) do
165
165
  d = Kitchen::Driver::Openstack.new(config)
@@ -198,7 +198,7 @@ describe Kitchen::Driver::Openstack do
198
198
  s.stub(:get).with('12345').and_return(nil)
199
199
  s
200
200
  end
201
- let(:compute) { stub(:servers => servers) }
201
+ let(:compute) { double(:servers => servers) }
202
202
  let(:driver) do
203
203
  d = Kitchen::Driver::Openstack.new(config)
204
204
  d.instance = instance
@@ -273,7 +273,7 @@ describe Kitchen::Driver::Openstack do
273
273
  s.stub(:create) { |arg| arg }
274
274
  s
275
275
  end
276
- let(:compute) { stub(:servers => servers) }
276
+ let(:compute) { double(:servers => servers) }
277
277
  let(:driver) do
278
278
  d = Kitchen::Driver::Openstack.new(config)
279
279
  d.instance = instance
@@ -333,41 +333,60 @@ describe Kitchen::Driver::Openstack do
333
333
  expect(driver.send(:generate_name, 'monkey')).to match(
334
334
  /^monkey-user-host-/)
335
335
  end
336
+
337
+ context 'local node with a long hostname' do
338
+ before(:each) do
339
+ Socket.unstub(:gethostname)
340
+ Socket.stub(:gethostname).and_return('ab.c' * 20)
341
+ end
342
+
343
+ it 'limits the generated name to 64-characters' do
344
+ expect(driver.send(:generate_name, 'long').length).to eq(64)
345
+ end
346
+ end
336
347
  end
337
348
 
338
349
  describe '#get_ip' do
339
- let(:addresses) { { 'public' => [], 'private' => [] } }
340
- let(:server) { stub(:addresses => addresses) }
350
+ let(:addresses) { nil }
351
+ let(:public_ip_addresses) { nil }
352
+ let(:private_ip_addresses) { nil }
353
+ let(:parsed_ips) { [[], []] }
354
+ let(:driver) do
355
+ d = Kitchen::Driver::Openstack.new(config)
356
+ d.instance = instance
357
+ d.stub(:parse_ips).and_return(parsed_ips)
358
+ d
359
+ end
360
+ let(:server) do
361
+ double(:addresses => addresses,
362
+ :public_ip_addresses => public_ip_addresses,
363
+ :private_ip_addresses => private_ip_addresses)
364
+ end
341
365
 
342
366
  context 'both public and private IPs' do
343
- let(:addresses) do
344
- {
345
- 'public' => [
346
- { 'addr' => '1.2.3.4' },
347
- { 'addr' => '1.2.3.5' }
348
- ],
349
- 'private' => [
350
- { 'addr' => '5.5.5.5' },
351
- { 'addr' => '6.6.6.6' }
352
- ]
353
- }
354
- end
367
+ let(:public_ip_addresses) { %w{1::1 1.2.3.4} }
368
+ let(:private_ip_addresses) { %w{5.5.5.5} }
369
+ let(:parsed_ips) { [%w{1.2.3.4}, %w{5.5.5.5}] }
355
370
 
356
- it 'returns a public IP' do
371
+ it 'returns a public IPv4 address' do
357
372
  expect(driver.send(:get_ip, server)).to eq('1.2.3.4')
358
373
  end
359
374
  end
360
375
 
361
- context 'only private IPs' do
362
- let(:addresses) do
363
- {
364
- 'private' => [
365
- { 'addr' => '5.5.5.5' },
366
- { 'addr' => '6.6.6.6' }
367
- ]
368
- }
376
+ context 'only public IPs' do
377
+ let(:public_ip_addresses) { %w{4.3.2.1 2::1} }
378
+ let(:parsed_ips) { [%w{4.3.2.1}, []] }
379
+
380
+ it 'returns a public IPv4 address' do
381
+ expect(driver.send(:get_ip, server)).to eq('4.3.2.1')
369
382
  end
370
- it 'returns a private IP' do
383
+ end
384
+
385
+ context 'only private IPs' do
386
+ let(:private_ip_addresses) { %w{3::1 5.5.5.5} }
387
+ let(:parsed_ips) { [[], %w{5.5.5.5}] }
388
+
389
+ it 'returns a private IPv4 address' do
371
390
  expect(driver.send(:get_ip, server)).to eq('5.5.5.5')
372
391
  end
373
392
  end
@@ -378,20 +397,155 @@ describe Kitchen::Driver::Openstack do
378
397
  {
379
398
  'mynetwork' => [
380
399
  { 'addr' => '7.7.7.7' },
381
- { 'addr' => '8.8.8.8' }
400
+ { 'addr' => '4::1' }
382
401
  ]
383
402
  }
384
403
  end
385
- it 'returns a IP in user-defined network group' do
404
+
405
+ it 'returns a IPv4 address in user-defined network group' do
386
406
  expect(driver.send(:get_ip, server)).to eq('7.7.7.7')
387
407
  end
388
408
  end
409
+
410
+ context 'an OpenStack deployment without the floating IP extension' do
411
+ let(:server) do
412
+ s = double('server')
413
+ s.stub(:addresses).and_return(addresses)
414
+ s.stub(:public_ip_addresses).and_raise(
415
+ Fog::Compute::OpenStack::NotFound)
416
+ s.stub(:private_ip_addresses).and_raise(
417
+ Fog::Compute::OpenStack::NotFound)
418
+ s
419
+ end
420
+
421
+ context 'both public and private IPs in the addresses hash' do
422
+ let(:addresses) do
423
+ {
424
+ 'public' => [{ 'addr' => '6.6.6.6' }, { 'addr' => '7.7.7.7' }],
425
+ 'private' => [{ 'addr' => '8.8.8.8' }, { 'addr' => '9.9.9.9' }]
426
+ }
427
+ end
428
+ let(:parsed_ips) { [%w{6.6.6.6 7.7.7.7}, %w{8.8.8.8 9.9.9.9}] }
429
+
430
+ it 'selects the first public IP' do
431
+ expect(driver.send(:get_ip, server)).to eq('6.6.6.6')
432
+ end
433
+ end
434
+
435
+ context 'only public IPs in the address hash' do
436
+ let(:addresses) do
437
+ { 'public' => [{ 'addr' => '6.6.6.6' }, { 'addr' => '7.7.7.7' }] }
438
+ end
439
+ let(:parsed_ips) { [%w{6.6.6.6 7.7.7.7}, []] }
440
+
441
+ it 'selects the first public IP' do
442
+ expect(driver.send(:get_ip, server)).to eq('6.6.6.6')
443
+ end
444
+ end
445
+
446
+ context 'only private IPs in the address hash' do
447
+ let(:addresses) do
448
+ { 'private' => [{ 'addr' => '8.8.8.8' }, { 'addr' => '9.9.9.9' }] }
449
+ end
450
+ let(:parsed_ips) { [[], %w{8.8.8.8 9.9.9.9}] }
451
+
452
+ it 'selects the first private IP' do
453
+ expect(driver.send(:get_ip, server)).to eq('8.8.8.8')
454
+ end
455
+ end
456
+ end
457
+
458
+ context 'no IP addresses whatsoever' do
459
+ it 'raises an exception' do
460
+ expect { driver.send(:get_ip, server) }.to raise_error
461
+ end
462
+ end
463
+ end
464
+
465
+ describe '#parse_ips' do
466
+ let(:pub_v4) { %w{1.1.1.1 2.2.2.2} }
467
+ let(:pub_v6) { %w{1::1 2::2} }
468
+ let(:priv_v4) { %w{3.3.3.3 4.4.4.4} }
469
+ let(:priv_v6) { %w{3::3 4::4} }
470
+ let(:pub) { pub_v4 + pub_v6 }
471
+ let(:priv) { priv_v4 + priv_v6 }
472
+
473
+ context 'both public and private IPs' do
474
+ context 'IPv4 (default)' do
475
+ it 'returns only the v4 IPs' do
476
+ expect(driver.send(:parse_ips, pub, priv)).to eq([pub_v4, priv_v4])
477
+ end
478
+ end
479
+
480
+ context 'IPv6' do
481
+ let(:config) { { :use_ipv6 => true } }
482
+
483
+ it 'returns only the v6 IPs' do
484
+ expect(driver.send(:parse_ips, pub, priv)).to eq([pub_v6, priv_v6])
485
+ end
486
+ end
487
+ end
488
+
489
+ context 'only public IPs' do
490
+ let(:priv) { nil }
491
+
492
+ context 'IPv4 (default)' do
493
+ it 'returns only the v4 IPs' do
494
+ expect(driver.send(:parse_ips, pub, priv)).to eq([pub_v4, []])
495
+ end
496
+ end
497
+
498
+ context 'IPv6' do
499
+ let(:config) { { :use_ipv6 => true } }
500
+
501
+ it 'returns only the v6 IPs' do
502
+ expect(driver.send(:parse_ips, pub, priv)).to eq([pub_v6, []])
503
+ end
504
+ end
505
+ end
506
+
507
+ context 'only private IPs' do
508
+ let(:pub) { nil }
509
+
510
+ context 'IPv4 (default)' do
511
+ it 'returns only the v4 IPs' do
512
+ expect(driver.send(:parse_ips, pub, priv)).to eq([[], priv_v4])
513
+ end
514
+ end
515
+
516
+ context 'IPv6' do
517
+ let(:config) { { :use_ipv6 => true } }
518
+
519
+ it 'returns only the v6 IPs' do
520
+ expect(driver.send(:parse_ips, pub, priv)).to eq([[], priv_v6])
521
+ end
522
+ end
523
+ end
524
+
525
+ context 'no IPs whatsoever' do
526
+ let(:pub) { nil }
527
+ let(:priv) { nil }
528
+
529
+ context 'IPv4 (default)' do
530
+ it 'returns empty lists' do
531
+ expect(driver.send(:parse_ips, pub, priv)).to eq([[], []])
532
+ end
533
+ end
534
+
535
+ context 'IPv6' do
536
+ let(:config) { { :use_ipv6 => true } }
537
+
538
+ it 'returns empty lists' do
539
+ expect(driver.send(:parse_ips, nil, nil)).to eq([[], []])
540
+ end
541
+ end
542
+ end
389
543
  end
390
544
 
391
545
  describe '#do_ssh_setup' do
392
- let(:server) { stub(:password => 'aloha') }
546
+ let(:server) { double(:password => 'aloha') }
393
547
  let(:state) { { :hostname => 'host' } }
394
- let(:read) { stub(:read => 'a_key') }
548
+ let(:read) { double(:read => 'a_key') }
395
549
  let(:ssh) do
396
550
  s = double('ssh')
397
551
  s.stub(:run) { |args| args }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-openstack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-06 00:00:00.000000000 Z
12
+ date: 2013-09-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: test-kitchen
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 1.0.0.alpha
21
+ version: 1.0.0.beta
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 1.0.0.alpha
29
+ version: 1.0.0.beta
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: fog
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - ~>
36
36
  - !ruby/object:Gem::Version
37
- version: '1.11'
37
+ version: '1.15'
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  requirements:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: '1.11'
45
+ version: '1.15'
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: bundler
48
48
  requirement: !ruby/object:Gem::Requirement