vagrant-openstack-provider 0.3.4.pre → 0.4.0

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