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 +13 -0
- data/kitchen-openstack.gemspec +2 -2
- data/lib/kitchen/driver/openstack.rb +44 -10
- data/lib/kitchen/driver/openstack_version.rb +1 -1
- data/spec/kitchen/driver/openstack_spec.rb +190 -36
- metadata +6 -6
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
|
data/kitchen-openstack.gemspec
CHANGED
@@ -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.
|
22
|
-
spec.add_dependency 'fog', '~> 1.
|
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
|
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
|
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
|
-
|
103
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
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)
|
@@ -29,7 +29,7 @@ describe Kitchen::Driver::Openstack do
|
|
29
29
|
let(:state) { Hash.new }
|
30
30
|
|
31
31
|
let(:instance) do
|
32
|
-
|
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
|
-
|
102
|
-
:
|
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) {
|
161
|
-
let(:servers) {
|
162
|
-
let(:compute) {
|
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) {
|
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) {
|
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) {
|
340
|
-
let(:
|
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(:
|
344
|
-
|
345
|
-
|
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
|
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
|
362
|
-
let(:
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
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
|
-
|
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' => '
|
400
|
+
{ 'addr' => '4::1' }
|
382
401
|
]
|
383
402
|
}
|
384
403
|
end
|
385
|
-
|
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) {
|
546
|
+
let(:server) { double(:password => 'aloha') }
|
393
547
|
let(:state) { { :hostname => 'host' } }
|
394
|
-
let(:read) {
|
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
|
+
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-
|
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.
|
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.
|
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.
|
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.
|
45
|
+
version: '1.15'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: bundler
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|