kitchen-openstack 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,4 +1,20 @@
1
- # ?.?.? / ????-??-??
1
+ # 1.0.0 / 2013-10-16
2
+
3
+ ### New Features
4
+
5
+ * PR [#26][] - Support image and flavor names and regexes; via [@jgawor][]
6
+ * PR [#25][] - Support specific floating IPs, in addition to named pools
7
+ * PR [#14][] - Add support for floating IP pools; via [@hufman][]
8
+
9
+ ### Improvements
10
+
11
+ * PR [#15][] - Improved SSH key support, support RSA and DSA; via [@hufman][]
12
+
13
+ ### Bug Fixes
14
+
15
+ * PR [#27][] - Prevent IP contention in TK parallel mode; via [@jgawor][]
16
+
17
+ # 0.5.0 / 2013-09-23
2
18
 
3
19
  ### Improvements
4
20
 
@@ -36,8 +52,13 @@
36
52
 
37
53
  * Initial release! Woo!
38
54
 
55
+ [#27]: https://github.com/RoboticCheese/kitchen-openstack/pull/27
56
+ [#26]: https://github.com/RoboticCheese/kitchen-openstack/pull/26
57
+ [#25]: https://github.com/RoboticCheese/kitchen-openstack/pull/25
39
58
  [#20]: https://github.com/RoboticCheese/kitchen-openstack/pull/20
40
59
  [#19]: https://github.com/RoboticCheese/kitchen-openstack/pull/19
60
+ [#15]: https://github.com/RoboticCheese/kitchen-openstack/pull/15
61
+ [#14]: https://github.com/RoboticCheese/kitchen-openstack/pull/14
41
62
  [#12]: https://github.com/RoboticCheese/kitchen-openstack/pull/12
42
63
  [#11]: https://github.com/RoboticCheese/kitchen-openstack/pull/11
43
64
  [#10]: https://github.com/RoboticCheese/kitchen-openstack/pull/10
@@ -45,5 +66,7 @@
45
66
  [#7]: https://github.com/RoboticCheese/kitchen-openstack/pull/7
46
67
  [#2]: https://github.com/RoboticCheese/kitchen-openstack/pull/2
47
68
 
69
+ [@jgawor]: https://github.com/jgawor
70
+ [@hufman]: https://github.com/hufman
48
71
  [@saketoba]: https://github.com/saketoba
49
72
  [@stevendanna]: https://github.com/stevendanna
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://travis-ci.org/RoboticCheese/kitchen-openstack.png?branch=master)](https://travis-ci.org/RoboticCheese/kitchen-openstack) [![Code Climate](https://codeclimate.com/github/RoboticCheese/kitchen-openstack.png)](https://codeclimate.com/github/RoboticCheese/kitchen-openstack)
1
+ [![Gem Version](https://badge.fury.io/rb/kitchen-openstack.png)](http://badge.fury.io/rb/kitchen-openstack) [![Dependency Status](https://gemnasium.com/RoboticCheese/kitchen-openstack.png)](https://gemnasium.com/RoboticCheese/kitchen-openstack) [![Build Status](https://travis-ci.org/RoboticCheese/kitchen-openstack.png?branch=master)](https://travis-ci.org/RoboticCheese/kitchen-openstack) [![Code Climate](https://codeclimate.com/github/RoboticCheese/kitchen-openstack.png)](https://codeclimate.com/github/RoboticCheese/kitchen-openstack)
2
2
 
3
3
  # Kitchen::OpenStack
4
4
 
@@ -34,12 +34,15 @@ Provide, at a minimum, the required driver options in your `.kitchen.yml` file:
34
34
  image_ref: [SERVER IMAGE ID]
35
35
  flavor_ref: [SERVER FLAVOR ID]
36
36
 
37
+ The `image_ref` and `flavor_ref` options can be specified as an exact id,
38
+ an exact name, or as a regular expression matching the name of the image or flavor.
39
+
37
40
  By default, a unique server name will be generated and the current user's SSH
38
- key will be used, though that behavior can be overridden with additional
39
- options:
41
+ key will be used (with an RSA key taking precedence over a DSA), though that
42
+ behavior can be overridden with additional options:
40
43
 
41
44
  name: [A UNIQUE SERVER NAME]
42
- ssh_key: [PATH TO YOUR PRIVATE SSH KEY]
45
+ private_key_path: [PATH TO YOUR PRIVATE SSH KEY]
43
46
  public_key_path: [PATH TO YOUR SSH PUBLIC KEY]
44
47
  username: [SSH USER]
45
48
  port: [SSH PORT]
@@ -48,9 +51,15 @@ options:
48
51
  openstack_region: [A VALID OPENSTACK REGION]
49
52
  openstack_service_name: [YOUR OPENSTACK COMPUTE SERVICE NAME]
50
53
  openstack_network_name: [YOUR OPENSTACK NETWORK NAME]
54
+ floating_ip: [A SPECIFIC FLOATING IP TO ASSIGN]
55
+ floating_ip_pool: [AN OPENSTACK POOL NAME TO ASSIGN THE NEXT IP FROM]
56
+
57
+ If a `key_name` is provided it will be used instead of any
58
+ `public_key_path` that is specified.
51
59
 
52
- If a key\_name is provided it will be used instead of any
53
- public\_key\_path that is specified.
60
+ If a `key_name` is provided without any `private_key_path`, unexpected
61
+ behavior may result if your local RSA/DSA private key doesn't match that
62
+ OpenStack key.
54
63
 
55
64
  disable_ssl_validation: true
56
65
 
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ Cane::RakeTask.new
8
8
 
9
9
  desc 'Run Tailor to lint check code'
10
10
  Tailor::RakeTask.new do |task|
11
- task.file_set '**/**/*.rb'
11
+ task.file_set '**/*.rb'
12
12
  end
13
13
 
14
14
  desc 'Display LOC stats'
@@ -29,8 +29,19 @@ module Kitchen
29
29
  #
30
30
  # @author Jonathan Hartman <j@p4nt5.com>
31
31
  class Openstack < Kitchen::Driver::SSHBase
32
+ @@ip_pool_lock = Mutex.new
33
+
32
34
  default_config :name, nil
33
- default_config :public_key_path, File.expand_path('~/.ssh/id_dsa.pub')
35
+ default_config :key_name, nil
36
+ default_config :private_key_path do |driver|
37
+ %w{id_rsa id_dsa}.collect do |k|
38
+ f = File.expand_path "~/.ssh/#{k}"
39
+ f if File.exists? f
40
+ end.compact.first
41
+ end
42
+ default_config :public_key_path do |driver|
43
+ driver[:private_key_path] + '.pub'
44
+ end
34
45
  default_config :username, 'root'
35
46
  default_config :port, '22'
36
47
  default_config :use_ipv6, false
@@ -38,6 +49,8 @@ module Kitchen
38
49
  default_config :openstack_region, nil
39
50
  default_config :openstack_service_name, nil
40
51
  default_config :openstack_network_name, nil
52
+ default_config :floating_ip_pool, nil
53
+ default_config :floating_ip, nil
41
54
 
42
55
  def create(state)
43
56
  config[:name] ||= generate_name(instance.name)
@@ -45,10 +58,21 @@ module Kitchen
45
58
  server = create_server
46
59
  state[:server_id] = server.id
47
60
  info "OpenStack instance <#{state[:server_id]}> created."
48
- server.wait_for { print '.'; ready? } ; puts "\n(server ready)"
61
+ server.wait_for { print '.'; ready? } ; info "\n(server ready)"
62
+ if config[:floating_ip_pool]
63
+ attach_ip_from_pool(server, config[:floating_ip_pool])
64
+ elsif config[:floating_ip]
65
+ attach_ip(server, config[:floating_ip])
66
+ end
49
67
  state[:hostname] = get_ip(server)
50
- wait_for_sshd(state[:hostname]) ; puts '(ssh ready)'
51
- unless config[:ssh_key] or config[:key_name]
68
+ state[:ssh_key] = config[:private_key_path]
69
+ wait_for_sshd(state[:hostname]) ; info '(ssh ready)'
70
+ if config[:key_name]
71
+ info "Using OpenStack keypair <#{config[:key_name]}>"
72
+ end
73
+ info "Using public SSH key <#{config[:public_key_path]}>"
74
+ info "Using private SSH key <#{config[:private_key_path]}>"
75
+ unless config[:key_name]
52
76
  do_ssh_setup(state, config, server)
53
77
  end
54
78
  rescue Fog::Errors::Error, Excon::Errors::Error => ex
@@ -85,10 +109,18 @@ module Kitchen
85
109
  end
86
110
 
87
111
  def create_server
112
+ image = find_matching(compute.images, config[:image_ref])
113
+ raise ActionFailed, "Image not found" if !image
114
+ debug "Selected image: #{image.id} #{image.name}"
115
+
116
+ flavor = find_matching(compute.flavors, config[:flavor_ref])
117
+ raise ActionFailed, "Flavor not found" if !flavor
118
+ debug "Selected flavor: #{flavor.id} #{flavor.name}"
119
+
88
120
  server_def = {
89
121
  :name => config[:name],
90
- :image_ref => config[:image_ref],
91
- :flavor_ref => config[:flavor_ref]
122
+ :image_ref => image.id,
123
+ :flavor_ref => flavor.id
92
124
  }
93
125
  if config[:public_key_path]
94
126
  server_def[:public_key_path] = config[:public_key_path]
@@ -121,6 +153,25 @@ module Kitchen
121
153
  pieces.join sep
122
154
  end
123
155
 
156
+ def attach_ip_from_pool(server, pool)
157
+ @@ip_pool_lock.synchronize do
158
+ info "Attaching floating IP from <#{pool}> pool"
159
+ free_addrs = compute.addresses.collect do |i|
160
+ i.ip if i.fixed_ip.nil? and i.instance_id.nil? and i.pool == pool
161
+ end.compact
162
+ if free_addrs.empty?
163
+ raise ActionFailed, "No available IPs in pool <#{pool}>"
164
+ end
165
+ attach_ip(server, free_addrs[0])
166
+ end
167
+ end
168
+
169
+ def attach_ip(server, ip)
170
+ info "Attaching floating IP <#{ip}>"
171
+ server.associate_address ip
172
+ (server.addresses['public'] ||= []) << { 'version' => 4, 'addr' => ip }
173
+ end
174
+
124
175
  def get_ip(server)
125
176
  if config[:openstack_network_name]
126
177
  debug "Using configured network: #{config[:openstack_network_name]}"
@@ -149,6 +200,7 @@ module Kitchen
149
200
  end
150
201
 
151
202
  def do_ssh_setup(state, config, server)
203
+ info "Setting up SSH access for key <#{config[:public_key_path]}>"
152
204
  ssh = Fog::SSH.new(state[:hostname], config[:username],
153
205
  { :password => server.password })
154
206
  pub_key = open(config[:public_key_path]).read
@@ -163,6 +215,28 @@ module Kitchen
163
215
  require 'excon'
164
216
  Excon.defaults[:ssl_verify_peer] = false
165
217
  end
218
+
219
+ def find_matching(collection, name)
220
+ name = name.to_s
221
+ if name.start_with?('/')
222
+ regex = eval(name)
223
+ # check for regex name match
224
+ collection.each do |single|
225
+ return single if regex =~ single.name
226
+ end
227
+ else
228
+ # check for exact id match
229
+ collection.each do |single|
230
+ return single if single.id == name
231
+ end
232
+ # check for exact name match
233
+ collection.each do |single|
234
+ return single if single.name == name
235
+ end
236
+ end
237
+ nil
238
+ end
239
+
166
240
  end
167
241
  end
168
242
  end
@@ -19,7 +19,7 @@
19
19
  module Kitchen
20
20
  module Driver
21
21
  # Version string for OpenStack Kitchen driver
22
- OPENSTACK_VERSION = '0.5.0'
22
+ OPENSTACK_VERSION = '1.0.0'
23
23
  end
24
24
  end
25
25
 
@@ -27,6 +27,8 @@ describe Kitchen::Driver::Openstack do
27
27
  let(:logger) { Logger.new(logged_output) }
28
28
  let(:config) { Hash.new }
29
29
  let(:state) { Hash.new }
30
+ let(:dsa) { File.expand_path('~/.ssh/id_dsa') }
31
+ let(:rsa) { File.expand_path('~/.ssh/id_rsa') }
30
32
 
31
33
  let(:instance) do
32
34
  double(:name => 'potatoes', :logger => logger, :to_str => 'instance')
@@ -38,32 +40,73 @@ describe Kitchen::Driver::Openstack do
38
40
  d
39
41
  end
40
42
 
43
+ before(:each) do
44
+ File.stub(:exists?).and_call_original
45
+ File.stub(:exists?).with(dsa).and_return(true)
46
+ File.stub(:exists?).with(rsa).and_return(true)
47
+ end
48
+
41
49
  describe '#initialize'do
42
50
  context 'default options' do
43
- it 'defaults to local user\'s SSH public key' do
44
- expect(driver[:public_key_path]).to eq(File.expand_path(
45
- '~/.ssh/id_dsa.pub'))
46
- end
51
+ context 'both DSA and RSA SSH keys available for the user' do
52
+ it 'prefers the local user\'s RSA private key' do
53
+ expect(driver[:private_key_path]).to eq(rsa)
54
+ end
47
55
 
48
- it 'defaults to SSH with root user on port 22' do
49
- expect(driver[:username]).to eq('root')
50
- expect(driver[:port]).to eq('22')
56
+ it 'prefers the local user\'s RSA public key' do
57
+ expect(driver[:public_key_path]).to eq(rsa + '.pub')
58
+ end
51
59
  end
52
60
 
53
- it 'defaults to no server name' do
54
- expect(driver[:name]).to eq(nil)
61
+ context 'only a DSA SSH key available for the user' do
62
+ before(:each) do
63
+ File.unstub(:exists?)
64
+ File.stub(:exists?).and_return(false)
65
+ File.stub(:exists?).with(dsa).and_return(true)
66
+ end
67
+
68
+ it 'uses the local user\'s DSA private key' do
69
+ expect(driver[:private_key_path]).to eq(dsa)
70
+ end
71
+
72
+ it 'uses the local user\'s DSA public key' do
73
+ expect(driver[:public_key_path]).to eq(dsa + '.pub')
74
+ end
55
75
  end
56
76
 
57
- it 'defaults to no tenant' do
58
- expect(driver[:openstack_tenant]).to eq(nil)
77
+ context 'only a RSA SSH key available for the user' do
78
+ before(:each) do
79
+ File.unstub(:exists?)
80
+ File.stub(:exists?).and_return(false)
81
+ File.stub(:exists?).with(rsa).and_return(true)
82
+ end
83
+
84
+ it 'uses the local user\'s RSA private key' do
85
+ expect(driver[:private_key_path]).to eq(rsa)
86
+ end
87
+
88
+ it 'uses the local user\'s RSA public key' do
89
+ expect(driver[:public_key_path]).to eq(rsa + '.pub')
90
+ end
59
91
  end
60
92
 
61
- it 'defaults to no region' do
62
- expect(driver[:openstack_region]).to eq(nil)
93
+ it 'defaults to SSH with root user on port 22' do
94
+ expect(driver[:username]).to eq('root')
95
+ expect(driver[:port]).to eq('22')
63
96
  end
64
97
 
65
- it 'defaults to no service name' do
66
- expect(driver[:openstack_service_name]).to eq(nil)
98
+ nils = [
99
+ :name,
100
+ :openstack_tenant,
101
+ :openstack_region,
102
+ :openstack_service_name,
103
+ :floating_ip_pool,
104
+ :floating_ip
105
+ ]
106
+ nils.each do |i|
107
+ it "defaults to no #{i}" do
108
+ expect(driver[i]).to eq(nil)
109
+ end
67
110
  end
68
111
  end
69
112
 
@@ -79,7 +122,9 @@ describe Kitchen::Driver::Openstack do
79
122
  :openstack_tenant => 'that_one',
80
123
  :openstack_region => 'atlantis',
81
124
  :openstack_service_name => 'the_service',
82
- :ssh_key => '/path/to/id_rsa'
125
+ :private_key_path => '/path/to/id_rsa',
126
+ :floating_ip_pool => 'swimmers',
127
+ :floating_ip => '11111'
83
128
  }
84
129
  end
85
130
 
@@ -89,10 +134,6 @@ describe Kitchen::Driver::Openstack do
89
134
  expect(drv[k]).to eq(v)
90
135
  end
91
136
  end
92
-
93
- it 'SSH with user-specified private key' do
94
- expect(driver[:ssh_key]).to eq('/path/to/id_rsa')
95
- end
96
137
  end
97
138
  end
98
139
 
@@ -263,8 +304,8 @@ describe Kitchen::Driver::Openstack do
263
304
  let(:config) do
264
305
  {
265
306
  :name => 'hello',
266
- :image_ref => 'there',
267
- :flavor_ref => 'captain',
307
+ :image_ref => '111',
308
+ :flavor_ref => '1',
268
309
  :public_key_path => 'tarpals'
269
310
  }
270
311
  end
@@ -273,7 +314,14 @@ describe Kitchen::Driver::Openstack do
273
314
  s.stub(:create) { |arg| arg }
274
315
  s
275
316
  end
276
- let(:compute) { double(:servers => servers) }
317
+ let(:ubuntu_image) { double(:id => '111', :name => 'ubuntu') }
318
+ let(:fedora_image) { double(:id => '222', :name => 'fedora') }
319
+ let(:tiny_flavor) { double(:id => '1', :name => 'tiny') }
320
+ let(:small_flavor) { double(:id => '2', :name => 'small') }
321
+ let(:compute) do
322
+ double(:servers => servers, :images => [ubuntu_image, fedora_image],
323
+ :flavors => [tiny_flavor, small_flavor])
324
+ end
277
325
  let(:driver) do
278
326
  d = Kitchen::Driver::Openstack.new(config)
279
327
  d.instance = instance
@@ -293,8 +341,8 @@ describe Kitchen::Driver::Openstack do
293
341
  let(:config) do
294
342
  {
295
343
  :name => 'hello',
296
- :image_ref => 'there',
297
- :flavor_ref => 'captain',
344
+ :image_ref => '111',
345
+ :flavor_ref => '1',
298
346
  :public_key_path => 'tarpals'
299
347
  }
300
348
  end
@@ -309,8 +357,8 @@ describe Kitchen::Driver::Openstack do
309
357
  let(:config) do
310
358
  {
311
359
  :name => 'hello',
312
- :image_ref => 'there',
313
- :flavor_ref => 'captain',
360
+ :image_ref => '111',
361
+ :flavor_ref => '1',
314
362
  :public_key_path => 'montgomery',
315
363
  :key_name => 'tarpals'
316
364
  }
@@ -321,6 +369,61 @@ describe Kitchen::Driver::Openstack do
321
369
  expect(driver.send(:create_server)).to eq(@config)
322
370
  end
323
371
  end
372
+
373
+ context 'image/flavor specifies id' do
374
+ let(:config) do
375
+ {
376
+ :name => 'hello',
377
+ :image_ref => '111',
378
+ :flavor_ref => '1',
379
+ :public_key_path => 'tarpals'
380
+ }
381
+ end
382
+
383
+ it 'exact id match' do
384
+ servers.should_receive(:create).with(:name => 'hello',
385
+ :image_ref => '111', :flavor_ref => '1',
386
+ :public_key_path => 'tarpals')
387
+ driver.send(:create_server)
388
+ end
389
+ end
390
+
391
+ context 'image/flavor specifies name' do
392
+ let(:config) do
393
+ {
394
+ :name => 'hello',
395
+ :image_ref => 'fedora',
396
+ :flavor_ref => 'small',
397
+ :public_key_path => 'tarpals'
398
+ }
399
+ end
400
+
401
+ it 'exact name match' do
402
+ servers.should_receive(:create).with(:name => 'hello',
403
+ :image_ref => '222', :flavor_ref => '2',
404
+ :public_key_path => 'tarpals')
405
+ driver.send(:create_server)
406
+ end
407
+ end
408
+
409
+ context 'image/flavor specifies regex' do
410
+ let(:config) do
411
+ {
412
+ :name => 'hello',
413
+ # pass regex as string as yml returns string values
414
+ :image_ref => '/edo/',
415
+ :flavor_ref => '/in/',
416
+ :public_key_path => 'tarpals'
417
+ }
418
+ end
419
+
420
+ it 'regex name match' do
421
+ servers.should_receive(:create).with(:name => 'hello',
422
+ :image_ref => '222', :flavor_ref => '1',
423
+ :public_key_path => 'tarpals')
424
+ driver.send(:create_server)
425
+ end
426
+ end
324
427
  end
325
428
 
326
429
  describe '#generate_name' do
@@ -346,6 +449,50 @@ describe Kitchen::Driver::Openstack do
346
449
  end
347
450
  end
348
451
 
452
+ describe '#attach_ip_from_pool' do
453
+ let(:server) { nil }
454
+ let(:pool) { 'swimmers' }
455
+ let(:ip) { '1.1.1.1' }
456
+ let(:address) { double(:ip => ip, :fixed_ip => nil, :instance_id => nil,
457
+ :pool => pool) }
458
+ let(:compute) { double(:addresses => [address]) }
459
+
460
+ before(:each) do
461
+ driver.stub(:attach_ip).with(server, ip).and_return('bing!')
462
+ driver.stub(:compute).and_return(compute)
463
+ end
464
+
465
+ it 'determines an IP to attempt to attach' do
466
+ expect(driver.send(:attach_ip_from_pool, server, pool)).to eq('bing!')
467
+ end
468
+
469
+ context 'no free addresses in the specified pool' do
470
+ let(:address) { double(:ip => ip, :fixed_ip => nil, :instance_id => nil,
471
+ :pool => 'some_other_pool') }
472
+
473
+ it 'raises an exception' do
474
+ expect { driver.send(:attach_ip_from_pool, server, pool) }.to \
475
+ raise_error
476
+ end
477
+ end
478
+ end
479
+
480
+ describe '#attach_ip' do
481
+ let(:ip) { '1.1.1.1' }
482
+ let(:addresses) { {} }
483
+ let(:server) do
484
+ s = double('server')
485
+ s.should_receive(:associate_address).with(ip).and_return(true)
486
+ s.stub(:addresses).and_return(addresses)
487
+ s
488
+ end
489
+
490
+ it 'associates the IP address with the server' do
491
+ expect(driver.send(:attach_ip, server, ip)).to eq(
492
+ [{ 'version' => 4, 'addr' => ip }])
493
+ end
494
+ end
495
+
349
496
  describe '#get_ip' do
350
497
  let(:addresses) { nil }
351
498
  let(:public_ip_addresses) { nil }
@@ -543,6 +690,7 @@ describe Kitchen::Driver::Openstack do
543
690
  end
544
691
 
545
692
  describe '#do_ssh_setup' do
693
+ let(:config) { { :public_key_path => '/pub_key' } }
546
694
  let(:server) { double(:password => 'aloha') }
547
695
  let(:state) { { :hostname => 'host' } }
548
696
  let(:read) { double(:read => 'a_key') }
@@ -555,8 +703,7 @@ describe Kitchen::Driver::Openstack do
555
703
  it 'opens an SSH session to the server' do
556
704
  Fog::SSH.stub(:new).with('host', 'root',
557
705
  { :password => 'aloha' }).and_return(ssh)
558
- driver.stub(:open).with(File.expand_path(
559
- '~/.ssh/id_dsa.pub')).and_return(read)
706
+ driver.stub(:open).with('/pub_key').and_return(read)
560
707
  read.stub(:read).and_return('a_key')
561
708
  res = driver.send(:do_ssh_setup, state, config, server)
562
709
  expected = [
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.5.0
4
+ version: 1.0.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-09-23 00:00:00.000000000 Z
12
+ date: 2013-10-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: test-kitchen