kitchen-openstack 0.4.0 → 0.5.0

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