vagrant-openstack-provider 0.3.4.pre → 0.4.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.
@@ -51,10 +51,17 @@ module VagrantPlugins
51
51
  def create_server(env, options)
52
52
  server = {}.tap do |s|
53
53
  s['name'] = options[:name]
54
- s['imageRef'] = options[:image_ref]
54
+ if options[:image_ref].nil?
55
+ s['block_device_mapping'] = [{ volume_id: options[:volume_boot][:id], device_name: options[:volume_boot][:device] }]
56
+ else
57
+ s['imageRef'] = options[:image_ref]
58
+ end
55
59
  s['flavorRef'] = options[:flavor_ref]
56
60
  s['key_name'] = options[:keypair]
57
61
  s['availability_zone'] = options[:availability_zone] unless options[:availability_zone].nil?
62
+ s['security_groups'] = options[:security_groups] unless options[:security_groups].nil?
63
+ s['user_data'] = options[:user_data] unless options[:user_data].nil?
64
+ s['metadata'] = options[:metadata] unless options[:metadata].nil?
58
65
  unless options[:networks].nil? || options[:networks].empty?
59
66
  s['networks'] = []
60
67
  options[:networks].each do |uuid|
@@ -62,7 +69,9 @@ module VagrantPlugins
62
69
  end
63
70
  end
64
71
  end
65
- server = post(env, "#{@session.endpoints[:compute]}/servers", { server: server }.to_json)
72
+ object = { server: server }
73
+ object[:scheduler_hints] = options[:scheduler_hints] unless options[:scheduler_hints].nil?
74
+ server = post(env, "#{@session.endpoints[:compute]}/servers", object.to_json)
66
75
  JSON.parse(server)['server']['id']
67
76
  end
68
77
 
@@ -133,6 +142,17 @@ module VagrantPlugins
133
142
  JSON.parse(floating_ips)['floating_ips']
134
143
  end
135
144
 
145
+ def attach_volume(env, server_id, volume_id, device = nil)
146
+ attachment = post(env, "#{@session.endpoints[:compute]}/servers/#{server_id}/os-volume_attachments",
147
+ {
148
+ volumeAttachment: {
149
+ volumeId: volume_id,
150
+ device: device
151
+ }
152
+ }.to_json)
153
+ JSON.parse(attachment)['volumeAttachment']
154
+ end
155
+
136
156
  private
137
157
 
138
158
  VM_STATES =
@@ -5,6 +5,7 @@ require 'json'
5
5
  require 'vagrant-openstack-provider/client/keystone'
6
6
  require 'vagrant-openstack-provider/client/nova'
7
7
  require 'vagrant-openstack-provider/client/neutron'
8
+ require 'vagrant-openstack-provider/client/cinder'
8
9
 
9
10
  module VagrantPlugins
10
11
  module Openstack
@@ -41,5 +42,9 @@ module VagrantPlugins
41
42
  def self.neutron
42
43
  Openstack::NeutronClient.instance
43
44
  end
45
+
46
+ def self.cinder
47
+ Openstack::CinderClient.instance
48
+ end
44
49
  end
45
50
  end
@@ -5,7 +5,8 @@ module VagrantPlugins
5
5
  { name: :'image-list', file: 'image_list' , clazz: 'ImageList' },
6
6
  { name: :'flavor-list', file: 'flavor_list', clazz: 'FlavorList' },
7
7
  { name: :'network-list', file: 'network_list', clazz: 'NetworkList' },
8
- { name: :'floatingip-list', file: 'floatingip_list', clazz: 'FloatingIpList' }
8
+ { name: :'floatingip-list', file: 'floatingip_list', clazz: 'FloatingIpList' },
9
+ { name: :'volume-list', file: 'volume_list', clazz: 'VolumeList' }
9
10
  ]
10
11
 
11
12
  class Main < Vagrant.plugin('2', :command)
@@ -0,0 +1,27 @@
1
+ require 'vagrant-openstack-provider/command/utils'
2
+ require 'vagrant-openstack-provider/command/abstract_command'
3
+
4
+ module VagrantPlugins
5
+ module Openstack
6
+ module Command
7
+ class VolumeList < AbstractCommand
8
+ include VagrantPlugins::Openstack::Command::Utils
9
+
10
+ def self.synopsis
11
+ I18n.t('vagrant_openstack.command.volume_list_synopsis')
12
+ end
13
+ def cmd(name, argv, env)
14
+ fail Errors::NoArgRequiredForCommand, cmd: name unless argv.size == 0 || argv == ['--']
15
+ volumes = env[:openstack_client].cinder.get_all_volumes(env)
16
+
17
+ rows = []
18
+ volumes.each do |v|
19
+ attachment = "#{v.instance_id} (#{v.device})" unless v.instance_id.nil?
20
+ rows << [v.id, v.name, v.size, v.status, attachment]
21
+ end
22
+ display_table(env, ['Id', 'Name', 'Size (Go)', 'Status', 'Attachment (instance id and device)'], rows)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -18,6 +18,11 @@ module VagrantPlugins
18
18
  #
19
19
  attr_accessor :openstack_network_url
20
20
 
21
+ # The block storage service url to access Openstack. If nil, it will read from
22
+ # hypermedia catalog form REST API
23
+ #
24
+ attr_accessor :openstack_volume_url
25
+
21
26
  # The authentication endpoint. This defaults to Openstack's global authentication endpoint.
22
27
  attr_accessor :openstack_auth_url
23
28
 
@@ -29,6 +34,10 @@ module VagrantPlugins
29
34
  # expression to partially match a name.
30
35
  attr_accessor :image
31
36
 
37
+ # Volume to boot the vm from
38
+ #
39
+ attr_accessor :volume_boot
40
+
32
41
  #
33
42
  # The name of the openstack project on witch the vm will be created.
34
43
  #
@@ -76,6 +85,12 @@ module VagrantPlugins
76
85
  # @return [String]
77
86
  attr_accessor :floating_ip_pool
78
87
 
88
+ # if set to true, vagrant will always allocate floating ip instead of trying to reuse unassigned ones
89
+ # default to false
90
+ #
91
+ # @return [Boolean]
92
+ attr_accessor :floating_ip_pool_always_allocate
93
+
79
94
  # Sync folder method. Can be either "rsync" or "none"
80
95
  #
81
96
  # @return [String]
@@ -86,6 +101,11 @@ module VagrantPlugins
86
101
  # @return [Array]
87
102
  attr_accessor :networks
88
103
 
104
+ # Volumes list that will be attached to the VM
105
+ #
106
+ # @return [Array]
107
+ attr_accessor :volumes
108
+
89
109
  # Public key path to create OpenStack keypair
90
110
  #
91
111
  # @return [Array]
@@ -96,13 +116,35 @@ module VagrantPlugins
96
116
  # @return [String]
97
117
  attr_accessor :availability_zone
98
118
 
119
+ # Pass hints to the OpenStack scheduler, e.g. { "cell": "some cell name" }
120
+ attr_accessor :scheduler_hints
121
+
122
+ # List of strings representing the security groups to apply.
123
+ # e.g. ['ssh', 'http']
124
+ #
125
+ # @return [Array[String]]
126
+ attr_accessor :security_groups
127
+
128
+ # User data to be sent to the newly created OpenStack instance. Use this
129
+ # e.g. to inject a script at boot time.
130
+ #
131
+ # @return [String]
132
+ attr_accessor :user_data
133
+
134
+ # A Hash of metadata that will be sent to the instance for configuration
135
+ #
136
+ # @return [Hash]
137
+ attr_accessor :metadata
138
+
99
139
  def initialize
100
140
  @password = UNSET_VALUE
101
141
  @openstack_compute_url = UNSET_VALUE
102
142
  @openstack_network_url = UNSET_VALUE
143
+ @openstack_volume_url = UNSET_VALUE
103
144
  @openstack_auth_url = UNSET_VALUE
104
145
  @flavor = UNSET_VALUE
105
146
  @image = UNSET_VALUE
147
+ @volume_boot = UNSET_VALUE
106
148
  @tenant_name = UNSET_VALUE
107
149
  @server_name = UNSET_VALUE
108
150
  @username = UNSET_VALUE
@@ -112,10 +154,16 @@ module VagrantPlugins
112
154
  @ssh_timeout = UNSET_VALUE
113
155
  @floating_ip = UNSET_VALUE
114
156
  @floating_ip_pool = UNSET_VALUE
157
+ @floating_ip_pool_always_allocate = UNSET_VALUE
115
158
  @sync_method = UNSET_VALUE
116
159
  @availability_zone = UNSET_VALUE
117
160
  @networks = []
161
+ @volumes = []
118
162
  @public_key_path = UNSET_VALUE
163
+ @scheduler_hints = UNSET_VALUE
164
+ @security_groups = UNSET_VALUE
165
+ @user_data = UNSET_VALUE
166
+ @metadata = UNSET_VALUE
119
167
  end
120
168
 
121
169
  # rubocop:disable Style/CyclomaticComplexity
@@ -123,25 +171,33 @@ module VagrantPlugins
123
171
  @password = nil if @password == UNSET_VALUE
124
172
  @openstack_compute_url = nil if @openstack_compute_url == UNSET_VALUE
125
173
  @openstack_network_url = nil if @openstack_network_url == UNSET_VALUE
174
+ @openstack_volume_url = nil if @openstack_volume_url == UNSET_VALUE
126
175
  @openstack_auth_url = nil if @openstack_auth_url == UNSET_VALUE
127
176
  @flavor = nil if @flavor == UNSET_VALUE
128
177
  @image = nil if @image == UNSET_VALUE
178
+ @volume_boot = nil if @volume_boot == UNSET_VALUE
129
179
  @tenant_name = nil if @tenant_name == UNSET_VALUE
130
180
  @server_name = nil if @server_name == UNSET_VALUE
131
181
  @username = nil if @username == UNSET_VALUE
132
182
  @rsync_includes = nil if @rsync_includes.empty?
133
183
  @floating_ip = nil if @floating_ip == UNSET_VALUE
134
184
  @floating_ip_pool = nil if @floating_ip_pool == UNSET_VALUE
185
+ @floating_ip_pool_always_allocate = false if floating_ip_pool_always_allocate == UNSET_VALUE
135
186
  @sync_method = 'rsync' if @sync_method == UNSET_VALUE
136
187
  @keypair_name = nil if @keypair_name == UNSET_VALUE
137
188
  @public_key_path = nil if @public_key_path == UNSET_VALUE
138
189
  @availability_zone = nil if @availability_zone == UNSET_VALUE
190
+ @scheduler_hints = nil if @scheduler_hints == UNSET_VALUE
191
+ @security_groups = nil if @security_groups == UNSET_VALUE
192
+ @user_data = nil if @user_data == UNSET_VALUE
193
+ @metadata = nil if @metadata == UNSET_VALUE
139
194
 
140
195
  # The SSH values by default are nil, and the top-level config
141
196
  # `config.ssh` values are used.
142
197
  @ssh_username = nil if @ssh_username == UNSET_VALUE
143
198
  @ssh_timeout = 180 if @ssh_timeout == UNSET_VALUE
144
199
  @networks = nil if @networks.empty?
200
+ @volumes = nil if @volumes.empty?
145
201
  end
146
202
  # rubocop:enable Style/CyclomaticComplexity
147
203
 
@@ -155,8 +211,9 @@ module VagrantPlugins
155
211
  errors << I18n.t('vagrant_openstack.config.password_required') unless @password
156
212
  errors << I18n.t('vagrant_openstack.config.username_required') unless @username
157
213
 
214
+ validate_ssh_username(machine, errors)
215
+
158
216
  if machine.config.ssh.private_key_path
159
- # Waiting for https://github.com/mitchellh/vagrant/issues/4388 to improve this
160
217
  puts I18n.t('vagrant_openstack.config.keypair_name_required').yellow unless @keypair_name || @public_key_path
161
218
  else
162
219
  errors << I18n.t('vagrant_openstack.config.private_key_missing') if @keypair_name || @public_key_path
@@ -165,6 +222,7 @@ module VagrantPlugins
165
222
  {
166
223
  openstack_compute_url: @openstack_compute_url,
167
224
  openstack_network_url: @openstack_network_url,
225
+ openstack_volume_url: @openstack_volume_url,
168
226
  openstack_auth_url: @openstack_auth_url
169
227
  }.each_pair do |key, value|
170
228
  errors << I18n.t('vagrant_openstack.config.invalid_uri', key: key, uri: value) unless value.nil? || valid_uri?(value)
@@ -173,6 +231,11 @@ module VagrantPlugins
173
231
  { 'Openstack Provider' => errors }
174
232
  end
175
233
 
234
+ def validate_ssh_username(machine, errors)
235
+ puts I18n.t('vagrant_openstack.config.ssh_username_deprecated').yellow if @ssh_username
236
+ errors << I18n.t('vagrant_openstack.config.ssh_username_required') unless @ssh_username || machine.config.ssh.username
237
+ end
238
+
176
239
  private
177
240
 
178
241
  def valid_uri?(value)
@@ -63,6 +63,42 @@ module VagrantPlugins
63
63
  class UnableToResolveSSHKey < VagrantOpenstackError
64
64
  error_key(:unable_to_resolve_ssh_key)
65
65
  end
66
+
67
+ class InvalidVolumeObject < VagrantOpenstackError
68
+ error_key(:invalid_volume_format)
69
+ end
70
+
71
+ class UnresolvedVolume < VagrantOpenstackError
72
+ error_key(:unresolved_volume)
73
+ end
74
+
75
+ class UnresolvedVolumeId < VagrantOpenstackError
76
+ error_key(:unresolved_volume_id)
77
+ end
78
+
79
+ class UnresolvedVolumeName < VagrantOpenstackError
80
+ error_key(:unresolved_volume_name)
81
+ end
82
+
83
+ class ConflictVolumeNameId < VagrantOpenstackError
84
+ error_key(:conflict_volume_name_id)
85
+ end
86
+
87
+ class MultipleVolumeName < VagrantOpenstackError
88
+ error_key(:multiple_volume_name)
89
+ end
90
+
91
+ class MissingBootOption < VagrantOpenstackError
92
+ error_key(:missing_boot_option)
93
+ end
94
+
95
+ class ConflictBootOption < VagrantOpenstackError
96
+ error_key(:conflict_boot_option)
97
+ end
98
+
99
+ class NoMatchingSshUsername < VagrantOpenstackError
100
+ error_key(:ssh_username_missing)
101
+ end
66
102
  end
67
103
  end
68
104
  end
@@ -1,5 +1,5 @@
1
1
  module VagrantPlugins
2
2
  module Openstack
3
- VERSION = '0.3.4.pre'
3
+ VERSION = '0.4.0'
4
4
  end
5
5
  end
@@ -12,6 +12,8 @@ en:
12
12
  Finding image for server...
13
13
  finding_networks: |-
14
14
  Finding network(s) for server...
15
+ finding_volumes: |-
16
+ Finding volume(s) to attach on server...
15
17
  launching_server: |-
16
18
  Launching a server with the following settings...
17
19
  not_created: |-
@@ -60,6 +62,12 @@ en:
60
62
  private_key_missing: |-
61
63
  config.ssh.private_key_path is required when either keypair_name or
62
64
  public_key_path is set in Vagrantfile
65
+ ssh_username_deprecated: |-
66
+ ssh_username provider config is deprecated for vagrant-openstack provider.
67
+ If you are using it, it will continue to work but we recommend to switch to the
68
+ standard vagrant configuration option `config.ssh.username` instead
69
+ ssh_username_required: |-
70
+ vagrant standard configuration option `ssh.username` is required
63
71
 
64
72
  errors:
65
73
  default: |-
@@ -106,6 +114,24 @@ en:
106
114
  Vagrant was unable to resolve a valid ip to ssh on your OpenStack instance.
107
115
  unable_to_resolve_ssh_key: |-
108
116
  Vagrant was unable to resolve a valid ssh key to connect to your OpenStack instance. Please specify in your Vagrantfile either `public_key_path` or `keypair_name`.
117
+ invalid_volume_format: |-
118
+ Volume '%{volume}' is not valid.
119
+ unresolved_volume: |-
120
+ No matching volume with id or name '%{volume}'
121
+ unresolved_volume_id: |-
122
+ No matching volume with id '%{id}'
123
+ unresolved_volume_name: |-
124
+ No matching volume with name '%{name}'
125
+ conflict_volume_name_id: |-
126
+ One (and only one) of 'id' or 'name' must be specified in volume definition : %{volume}
127
+ multiple_volume_name: |-
128
+ More than one volume exists with name '%{name}'. In the case you can't use name in volume definition. Please, use id instead.
129
+ missing_boot_option: |-
130
+ Either 'image' or 'volume_boot' configuration must be provided
131
+ conflict_boot_option: |-
132
+ Only one of 'image' and 'volume_boot' configuration must be provided
133
+ ssh_username_missing: |-
134
+ Vagrant was unable to resolve which ssh username to use to connect to the machine. Please provide config parameter `ssh.username`
109
135
 
110
136
  states:
111
137
  short_active: |-
@@ -205,3 +231,5 @@ en:
205
231
  List private networks in project
206
232
  flaotingip_list_synopsis : |-
207
233
  List floating IP and floating IP pools
234
+ volume_list_synopsis : |-
235
+ List existing volumes
@@ -16,6 +16,7 @@ describe VagrantPlugins::Openstack::Action::ConnectOpenstack do
16
16
  config.stub(:openstack_auth_url) { 'http://keystoneAuthV2' }
17
17
  config.stub(:openstack_compute_url) { nil }
18
18
  config.stub(:openstack_network_url) { nil }
19
+ config.stub(:openstack_volume_url) { nil }
19
20
  config.stub(:tenant_name) { 'testTenant' }
20
21
  config.stub(:username) { 'username' }
21
22
  config.stub(:password) { 'password' }
@@ -20,9 +20,11 @@ describe VagrantPlugins::Openstack::Action::CreateServer do
20
20
  config.stub(:availability_zone) { 'AZ-01' }
21
21
  config.stub(:floating_ip) { nil }
22
22
  config.stub(:floating_ip_pool) { nil }
23
+ config.stub(:floating_ip_pool_always_allocate) { false }
23
24
  config.stub(:keypair_name) { nil }
24
25
  config.stub(:public_key_path) { nil }
25
26
  config.stub(:networks) { nil }
27
+ config.stub(:volumes) { nil }
26
28
  end
27
29
  end
28
30
 
@@ -63,6 +65,22 @@ describe VagrantPlugins::Openstack::Action::CreateServer do
63
65
  end
64
66
  end
65
67
 
68
+ let(:cinder) do
69
+ double('cinder').tap do |cinder|
70
+ cinder.stub(:get_all_volumes).with(anything) do
71
+ [Volume.new('001', 'vol-01', '1', 'available', 'true', nil, nil),
72
+ Volume.new('002', 'vol-02', '2', 'available', 'true', nil, nil),
73
+ Volume.new('003', 'vol-03', '3', 'available', 'true', nil, nil),
74
+ Volume.new('004', 'vol-04', '4', 'available', 'false', nil, nil),
75
+ Volume.new('005', 'vol-05', '5', 'available', 'false', nil, nil),
76
+ Volume.new('006', 'vol-06', '6', 'available', 'false', nil, nil),
77
+ Volume.new('007', 'vol-07-08', '6', 'available', 'false', nil, nil),
78
+ Volume.new('008', 'vol-07-08', '6', 'available', 'false', nil, nil)]
79
+
80
+ end
81
+ end
82
+ end
83
+
66
84
  let(:env) do
67
85
  Hash.new.tap do |env|
68
86
  env[:ui] = double('ui')
@@ -73,6 +91,7 @@ describe VagrantPlugins::Openstack::Action::CreateServer do
73
91
  env[:openstack_client] = double('openstack_client')
74
92
  env[:openstack_client].stub(:neutron) { neutron }
75
93
  env[:openstack_client].stub(:nova) { nova }
94
+ env[:openstack_client].stub(:cinder) { cinder }
76
95
  end
77
96
  end
78
97
 
@@ -81,6 +100,23 @@ describe VagrantPlugins::Openstack::Action::CreateServer do
81
100
  @action = CreateServer.new(nil, nil)
82
101
  end
83
102
 
103
+ describe 'call' do
104
+ context 'with both image and volume_boot specified' do
105
+ it 'should raise an error' do
106
+ config.stub(:image) { 'linux-image' }
107
+ config.stub(:volume_boot) { 'linux-volume' }
108
+ expect { @action.call(env) }.to raise_error Errors::ConflictBootOption
109
+ end
110
+ end
111
+ context 'with neither image nor volume_boot specified' do
112
+ it 'should raise an error' do
113
+ config.stub(:image) { nil }
114
+ config.stub(:volume_boot) { nil }
115
+ expect { @action.call(env) }.to raise_error Errors::MissingBootOption
116
+ end
117
+ end
118
+ end
119
+
84
120
  describe 'create_server' do
85
121
  context 'with all options specified' do
86
122
  it 'calls nova with all the options' do
@@ -89,18 +125,29 @@ describe VagrantPlugins::Openstack::Action::CreateServer do
89
125
  name: 'testName',
90
126
  flavor_ref: flavor.id,
91
127
  image_ref: image.id,
128
+ volume_boot: nil,
92
129
  networks: ['test-networks'],
93
130
  keypair: 'test-keypair',
94
- availability_zone: 'test-az') do '1234'
131
+ availability_zone: 'test-az',
132
+ scheduler_hints: 'test-sched-hints',
133
+ security_groups: ['test-sec-groups'],
134
+ user_data: 'test-user_data',
135
+ metadata: 'test-metadata') do '1234'
95
136
  end
96
137
 
97
138
  options = {
98
139
  flavor: flavor,
99
140
  image: image,
100
141
  networks: ['test-networks'],
142
+ volumes: [{ id: '001', device: :auto }, { id: '002', device: '/dev/vdc' }],
101
143
  keypair_name: 'test-keypair',
102
- availability_zone: 'test-az'
144
+ availability_zone: 'test-az',
145
+ scheduler_hints: 'test-sched-hints',
146
+ security_groups: ['test-sec-groups'],
147
+ user_data: 'test-user_data',
148
+ metadata: 'test-metadata'
103
149
  }
150
+
104
151
  expect(@action.create_server(env, options)).to eq '1234'
105
152
  end
106
153
  end
@@ -116,13 +163,31 @@ describe VagrantPlugins::Openstack::Action::CreateServer do
116
163
 
117
164
  context 'with config.floating_pool specified' do
118
165
  context 'if any ip in the same pool is available' do
119
- it 'return one of the available ips' do
120
- nova.stub(:get_all_floating_ips).with(anything) do
121
- [FloatingIP.new('80.81.82.84', 'pool-1', '1234'),
122
- FloatingIP.new('80.81.82.83', 'pool-1', nil)]
166
+ context 'with config.floating_pool_always_allocate true' do
167
+ it 'allocate a new floating_ip from the pool' do
168
+ config.stub(:floating_ip_pool_always_allocate) { true }
169
+ nova.stub(:get_all_floating_ips).with(anything) do
170
+ [FloatingIP.new('80.81.82.84', 'pool-1', '1234'),
171
+ FloatingIP.new('80.81.82.83', 'pool-1', nil)]
172
+ end
173
+ nova.stub(:allocate_floating_ip).with(env, 'pool-1') do
174
+ FloatingIP.new('80.81.82.84', 'pool-1', nil)
175
+ end
176
+ config.stub(:floating_ip_pool) { 'pool-1' }
177
+ @action.resolve_floating_ip(env).should eq('80.81.82.84')
178
+ end
179
+ end
180
+
181
+ context 'with config.floating_pool_always_allocate false' do
182
+ it 'return one of the available ips' do
183
+ config.stub(:floating_ip_pool_always_allocate) { false }
184
+ nova.stub(:get_all_floating_ips).with(anything) do
185
+ [FloatingIP.new('80.81.82.84', 'pool-1', '1234'),
186
+ FloatingIP.new('80.81.82.83', 'pool-1', nil)]
187
+ end
188
+ config.stub(:floating_ip_pool) { 'pool-1' }
189
+ @action.resolve_floating_ip(env).should eq('80.81.82.83')
123
190
  end
124
- config.stub(:floating_ip_pool) { 'pool-1' }
125
- @action.resolve_floating_ip(env).should eq('80.81.82.83')
126
191
  end
127
192
  end
128
193
 
@@ -200,7 +265,6 @@ describe VagrantPlugins::Openstack::Action::CreateServer do
200
265
  nova.stub(:import_keypair) { 'my-keypair-imported' }
201
266
  SSHKey.stub(:generate) { ssh_key }
202
267
  File.should_receive(:write).with('/data/dir/my-keypair-imported', 'private key')
203
- File.should_receive(:chmod).with(0600, '/data/dir/my-keypair-imported')
204
268
  @action.generate_keypair(env).should eq('my-keypair-imported')
205
269
  end
206
270
  end
@@ -252,4 +316,181 @@ describe VagrantPlugins::Openstack::Action::CreateServer do
252
316
  end
253
317
 
254
318
  end
319
+
320
+ describe 'resolve_volume_boot' do
321
+ context 'with string volume id' do
322
+ it 'returns normalized volume' do
323
+ config.stub(:volume_boot) { '001' }
324
+ expect(@action.resolve_volume_boot(env)).to eq id: '001', device: 'vda'
325
+ end
326
+ end
327
+
328
+ context 'with string volume name' do
329
+ it 'returns normalized volume' do
330
+ config.stub(:volume_boot) { 'vol-01' }
331
+ expect(@action.resolve_volume_boot(env)).to eq id: '001', device: 'vda'
332
+ end
333
+ end
334
+
335
+ context 'with hash volume id' do
336
+ it 'returns normalized volume' do
337
+ config.stub(:volume_boot) { { id: '001' } }
338
+ expect(@action.resolve_volume_boot(env)).to eq id: '001', device: 'vda'
339
+ end
340
+ end
341
+
342
+ context 'with hash volume name' do
343
+ it 'returns normalized volume' do
344
+ config.stub(:volume_boot) { { name: 'vol-01' } }
345
+ expect(@action.resolve_volume_boot(env)).to eq id: '001', device: 'vda'
346
+ end
347
+ end
348
+
349
+ context 'with hash volume id and device' do
350
+ it 'returns normalized volume' do
351
+ config.stub(:volume_boot) { { id: '001', device: 'vdb' } }
352
+ expect(@action.resolve_volume_boot(env)).to eq id: '001', device: 'vdb'
353
+ end
354
+ end
355
+
356
+ context 'with hash volume name and device' do
357
+ it 'returns normalized volume' do
358
+ config.stub(:volume_boot) { { name: 'vol-01', device: 'vdb' } }
359
+ expect(@action.resolve_volume_boot(env)).to eq id: '001', device: 'vdb'
360
+ end
361
+ end
362
+
363
+ context 'with empty hash' do
364
+ it 'raises an error' do
365
+ config.stub(:volume_boot) { {} }
366
+ expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::ConflictVolumeNameId)
367
+ end
368
+ end
369
+
370
+ context 'with invalid volume object' do
371
+ it 'raises an error' do
372
+ config.stub(:volume_boot) { 1 }
373
+ expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::InvalidVolumeObject)
374
+ end
375
+ end
376
+
377
+ context 'with hash containing a bad id' do
378
+ it 'raises an error' do
379
+ config.stub(:volume_boot) { { id: 'not-exist' } }
380
+ expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::UnresolvedVolumeId)
381
+ end
382
+ end
383
+
384
+ context 'with hash containing a bad name' do
385
+ it 'raises an error' do
386
+ config.stub(:volume_boot) { { name: 'not-exist' } }
387
+ expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::UnresolvedVolumeName)
388
+ end
389
+ end
390
+
391
+ context 'with hash containing both id and name' do
392
+ it 'raises an error' do
393
+ config.stub(:volume_boot) { { id: '001', name: 'vol-01' } }
394
+ expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::ConflictVolumeNameId)
395
+ end
396
+ end
397
+
398
+ context 'with hash containing a name matching more than one volume' do
399
+ it 'raises an error' do
400
+ config.stub(:volume_boot) { { name: 'vol-07-08' } }
401
+ expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::MultipleVolumeName)
402
+ end
403
+ end
404
+ end
405
+
406
+ describe 'resolve_volumes' do
407
+ context 'with volume attached in all possible ways' do
408
+ it 'returns normalized volume list' do
409
+
410
+ config.stub(:volumes) do
411
+ ['001',
412
+ 'vol-02',
413
+ { id: '003', device: '/dev/vdz' },
414
+ { name: 'vol-04', device: '/dev/vdy' },
415
+ { name: 'vol-05' },
416
+ { id: '006' }]
417
+ end
418
+
419
+ expect(@action.resolve_volumes(env)).to eq [{ id: '001', device: nil },
420
+ { id: '002', device: nil },
421
+ { id: '003', device: '/dev/vdz' },
422
+ { id: '004', device: '/dev/vdy' },
423
+ { id: '005', device: nil },
424
+ { id: '006', device: nil }]
425
+ end
426
+ end
427
+
428
+ context 'with invalid volume object' do
429
+ it 'raises an error' do
430
+ config.stub(:volumes) { [1] }
431
+ expect { @action.resolve_volumes(env) }.to raise_error(Errors::InvalidVolumeObject)
432
+ end
433
+ end
434
+
435
+ context 'with string that is neither an id nor name matching a volume' do
436
+ it 'raises an error' do
437
+ config.stub(:volumes) { ['not-exist'] }
438
+ expect { @action.resolve_volumes(env) }.to raise_error(Errors::UnresolvedVolume)
439
+ end
440
+ end
441
+
442
+ context 'with hash containing a bad id' do
443
+ it 'raises an error' do
444
+ config.stub(:volumes) { [{ id: 'not-exist' }] }
445
+ expect { @action.resolve_volumes(env) }.to raise_error(Errors::UnresolvedVolumeId)
446
+ end
447
+ end
448
+
449
+ context 'with hash containing a bad name' do
450
+ it 'raises an error' do
451
+ config.stub(:volumes) { [{ name: 'not-exist' }] }
452
+ expect { @action.resolve_volumes(env) }.to raise_error(Errors::UnresolvedVolumeName)
453
+ end
454
+ end
455
+
456
+ context 'with empty hash' do
457
+ it 'raises an error' do
458
+ config.stub(:volumes) { [{}] }
459
+ expect { @action.resolve_volumes(env) }.to raise_error(Errors::ConflictVolumeNameId)
460
+ end
461
+ end
462
+
463
+ context 'with hash containing both id and name' do
464
+ it 'raises an error' do
465
+ config.stub(:volumes) { [{ id: '001', name: 'vol-01' }] }
466
+ expect { @action.resolve_volumes(env) }.to raise_error(Errors::ConflictVolumeNameId)
467
+ end
468
+ end
469
+
470
+ context 'with hash containing both id and name' do
471
+ it 'raises an error' do
472
+ config.stub(:volumes) { [{ id: '001', name: 'vol-01' }] }
473
+ expect { @action.resolve_volumes(env) }.to raise_error(Errors::ConflictVolumeNameId)
474
+ end
475
+ end
476
+
477
+ context 'with hash containing a name matching more than one volume' do
478
+ it 'raises an error' do
479
+ config.stub(:volumes) { [{ name: 'vol-07-08' }] }
480
+ expect { @action.resolve_volumes(env) }.to raise_error(Errors::MultipleVolumeName)
481
+ end
482
+ end
483
+ end
484
+
485
+ describe 'attach_volumes' do
486
+ context 'with volume attached in all possible ways' do
487
+ it 'returns normalized volume list' do
488
+ nova.stub(:attach_volume).with(anything, anything, anything, anything) {}
489
+ nova.should_receive(:attach_volume).with(env, 'server-01', '001', nil)
490
+ nova.should_receive(:attach_volume).with(env, 'server-01', '002', '/dev/vdb')
491
+
492
+ @action.attach_volumes(env, 'server-01', [{ id: '001', device: nil }, { id: '002', device: '/dev/vdb' }])
493
+ end
494
+ end
495
+ end
255
496
  end