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.
@@ -1,3 +1,24 @@
1
+ # 0.4.0 (September 23, 2014)
2
+
3
+ FEATURES:
4
+
5
+ - Enable "metadata" in config for nova create server [#25](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/25)
6
+ - Enable "user_data" in config for nova create server [#78](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/78)
7
+ - Enable "security_groups" in config for nova create server [#82](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/82)
8
+ - Enable "scheduler_hints" in config for nova create server [#83](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/83)
9
+ - Allow attaching an existing volume to the vagrant instance [#24](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/24)
10
+ - Allow booting instance from volume [#44](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/44)
11
+ - Add subcommand volume-list [#75](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/75)
12
+
13
+ IMPROVEMENTS:
14
+
15
+ - Add config param floating_ip_pool_always_allocate [#61](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/61)
16
+
17
+ BUG FIXES:
18
+
19
+ - Enable config option to override SSH port [#88](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/88)
20
+
21
+
1
22
  # 0.3.3 (September 19, 2014)
2
23
 
3
24
  BUG FIXES:
@@ -11,6 +32,10 @@ BUG FIXES:
11
32
  - The provider fails to load colorize gem [#76](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/76)
12
33
  - Sub-command arguments management have change in vagrant 1.5 [#77](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/77)
13
34
 
35
+ IMPROVEMENTS:
36
+
37
+ - Show more informations for command flavor-list [#52](https://github.com/ggiamarchi/vagrant-openstack-provider/issues/52)
38
+
14
39
  # 0.3.0 (August 29, 2014)
15
40
 
16
41
  FEATURES:
data/Gemfile CHANGED
@@ -18,5 +18,8 @@ group :development do
18
18
  # gem dependency because we expect to be installed within the
19
19
  # Vagrant environment itself using `vagrant plugin`.
20
20
  gem 'coveralls', require: false
21
+ end
22
+
23
+ group :debug do
21
24
  gem 'debugger'
22
25
  end
@@ -4,6 +4,7 @@ Vagrant.configure('2') do |config|
4
4
 
5
5
  config.vm.box = 'dummy-openstack'
6
6
  config.vm.box_url = 'https://github.com/ggiamarchi/vagrant-openstack/raw/master/source/dummy.box'
7
+ config.ssh.username = 'stack'
7
8
 
8
9
  config.vm.provider :openstack do |os|
9
10
  os.username = ENV['OS_USERNAME']
@@ -13,6 +14,5 @@ Vagrant.configure('2') do |config|
13
14
  os.image = ENV['OS_IMAGE']
14
15
  os.openstack_auth_url = ENV['OS_AUTH_URL']
15
16
  os.tenant_name = ENV['OS_TENANT_NAME']
16
- os.ssh_username = 'stack'
17
17
  end
18
18
  end
@@ -67,6 +67,7 @@ module VagrantPlugins
67
67
  config = env[:machine].provider_config
68
68
  client.session.endpoints[:compute] = config.openstack_compute_url unless config.openstack_compute_url.nil?
69
69
  client.session.endpoints[:network] = config.openstack_network_url unless config.openstack_network_url.nil?
70
+ client.session.endpoints[:volume] = config.openstack_volume_url unless config.openstack_volume_url.nil?
70
71
  end
71
72
 
72
73
  def log_endpoint_catalog(env)
@@ -19,15 +19,27 @@ module VagrantPlugins
19
19
  def call(env)
20
20
  @logger.info 'Start create server action'
21
21
 
22
+ config = env[:machine].provider_config
23
+
24
+ fail Errors::MissingBootOption if config.image.nil? && config.volume_boot.nil?
25
+ fail Errors::ConflictBootOption unless config.image.nil? || config.volume_boot.nil?
26
+
22
27
  nova = env[:openstack_client].nova
23
28
 
24
29
  options = {
25
30
  flavor: resolve_flavor(env),
26
31
  image: resolve_image(env),
32
+ volume_boot: resolve_volume_boot(env),
27
33
  networks: resolve_networks(env),
34
+ volumes: resolve_volumes(env),
28
35
  keypair_name: resolve_keypair(env),
29
- availability_zone: env[:machine].provider_config.availability_zone
36
+ availability_zone: env[:machine].provider_config.availability_zone,
37
+ scheduler_hints: env[:machine].provider_config.scheduler_hints,
38
+ security_groups: env[:machine].provider_config.security_groups,
39
+ user_data: env[:machine].provider_config.user_data,
40
+ metadata: env[:machine].provider_config.metadata
30
41
  }
42
+
31
43
  server_id = create_server(env, options)
32
44
 
33
45
  # Store the ID right away so we can track it
@@ -42,13 +54,15 @@ module VagrantPlugins
42
54
  nova.add_floating_ip(env, server_id, floating_ip)
43
55
  end
44
56
 
57
+ attach_volumes(env, server_id, options[:volumes]) unless options[:volumes].empty?
58
+
45
59
  unless env[:interrupted]
46
60
  # Clear the line one more time so the progress is removed
47
61
  env[:ui].clear_line
48
62
 
49
63
  # Wait for SSH to become available
50
64
  ssh_timeout = env[:machine].provider_config.ssh_timeout
51
- unless port_open?(env, floating_ip, 22, ssh_timeout)
65
+ unless port_open?(env, floating_ip, resolve_ssh_port(env), ssh_timeout)
52
66
  env[:ui].error(I18n.t('vagrant_openstack.timeout'))
53
67
  fail Errors::SshUnavailable, host: floating_ip, timeout: ssh_timeout
54
68
  end
@@ -62,6 +76,12 @@ module VagrantPlugins
62
76
 
63
77
  private
64
78
 
79
+ def resolve_ssh_port(env)
80
+ machine_config = env[:machine].config
81
+ return machine_config.ssh.port if machine_config.ssh.port
82
+ 22
83
+ end
84
+
65
85
  # 1. if floating_ip is set, use it
66
86
  # 2. if floating_ip_pool is set
67
87
  # GET v2/{{tenant_id}}/os-floating-ips
@@ -79,7 +99,7 @@ module VagrantPlugins
79
99
  if config.floating_ip_pool
80
100
  floating_ips.each do |single|
81
101
  return single.ip if single.pool == config.floating_ip_pool && single.instance_id.nil?
82
- end
102
+ end unless config.floating_ip_pool_always_allocate
83
103
  return nova.allocate_floating_ip(env, config.floating_ip_pool).ip
84
104
  else
85
105
  floating_ips.each do |ip|
@@ -101,9 +121,7 @@ module VagrantPlugins
101
121
  key = SSHKey.generate
102
122
  nova = env[:openstack_client].nova
103
123
  generated_keyname = nova.import_keypair(env, key.ssh_public_key)
104
- file_path = "#{env[:machine].data_dir}/#{generated_keyname}"
105
- File.write(file_path, key.private_key)
106
- File.chmod(0600, file_path)
124
+ File.write("#{env[:machine].data_dir}/#{generated_keyname}", key.private_key)
107
125
  generated_keyname
108
126
  end
109
127
 
@@ -122,6 +140,7 @@ module VagrantPlugins
122
140
  def resolve_image(env)
123
141
  @logger.info 'Resolving image'
124
142
  config = env[:machine].provider_config
143
+ return nil if config.image.nil?
125
144
  nova = env[:openstack_client].nova
126
145
  env[:ui].info(I18n.t('vagrant_openstack.finding_image'))
127
146
  images = nova.get_all_images(env)
@@ -158,41 +177,129 @@ module VagrantPlugins
158
177
  networks
159
178
  end
160
179
 
180
+ def resolve_volume_boot(env)
181
+ @logger.info 'Resolving image'
182
+ config = env[:machine].provider_config
183
+ return nil if config.volume_boot.nil?
184
+
185
+ volume_list = env[:openstack_client].cinder.get_all_volumes(env)
186
+ volume_ids = volume_list.map { |v| v.id }
187
+
188
+ @logger.debug(volume_list)
189
+
190
+ volume = resolve_volume(config.volume_boot, volume_list, volume_ids)
191
+ device = volume[:device].nil? ? 'vda' : volume[:device]
192
+
193
+ { id: volume[:id], device: device }
194
+ end
195
+
196
+ def resolve_volumes(env)
197
+ @logger.info 'Resolving volume(s)'
198
+ config = env[:machine].provider_config
199
+ return [] if config.volumes.nil? || config.volumes.empty?
200
+ env[:ui].info(I18n.t('vagrant_openstack.finding_volumes'))
201
+
202
+ volume_list = env[:openstack_client].cinder.get_all_volumes(env)
203
+ volume_ids = volume_list.map { |v| v.id }
204
+
205
+ @logger.debug(volume_list)
206
+
207
+ volumes = []
208
+ config.volumes.each do |volume|
209
+ volumes << resolve_volume(volume, volume_list, volume_ids)
210
+ end
211
+ @logger.debug("Resolved volumes : #{volumes.to_json}")
212
+ volumes
213
+ end
214
+
215
+ def resolve_volume(volume, volume_list, volume_ids)
216
+ return resolve_volume_from_string(volume, volume_list) if volume.is_a? String
217
+ return resolve_volume_from_hash(volume, volume_list, volume_ids) if volume.is_a? Hash
218
+ fail Errors::InvalidVolumeObject, volume: volume
219
+ end
220
+
221
+ def resolve_volume_from_string(volume, volume_list)
222
+ found_volume = find_matching(volume_list, volume)
223
+ fail Errors::UnresolvedVolume, volume: volume if found_volume.nil?
224
+ { id: found_volume.id, device: nil }
225
+ end
226
+
227
+ def resolve_volume_from_hash(volume, volume_list, volume_ids)
228
+ device = nil
229
+ device = volume[:device] if volume.key?(:device)
230
+ if volume.key?(:id)
231
+ fail Errors::ConflictVolumeNameId, volume: volume if volume.key?(:name)
232
+ volume_id = volume[:id]
233
+ fail Errors::UnresolvedVolumeId, id: volume_id unless volume_ids.include? volume_id
234
+ elsif volume.key?(:name)
235
+ volume_list.each do |v|
236
+ next unless v.name.eql? volume[:name]
237
+ fail Errors::MultipleVolumeName, name: volume[:name] unless volume_id.nil?
238
+ volume_id = v.id
239
+ end
240
+ fail Errors::UnresolvedVolumeName, name: volume[:name] unless volume_ids.include? volume_id
241
+ else
242
+ fail Errors::ConflictVolumeNameId, volume: volume
243
+ end
244
+ { id: volume_id, device: device }
245
+ end
246
+
161
247
  def create_server(env, options)
162
248
  config = env[:machine].provider_config
163
249
  nova = env[:openstack_client].nova
164
250
  server_name = config.server_name || env[:machine].name
165
251
 
166
252
  env[:ui].info(I18n.t('vagrant_openstack.launching_server'))
167
- env[:ui].info(" -- Tenant : #{config.tenant_name}")
168
- env[:ui].info(" -- Name : #{server_name}")
169
- env[:ui].info(" -- Flavor : #{options[:flavor].name}")
170
- env[:ui].info(" -- FlavorRef : #{options[:flavor].id}")
171
- env[:ui].info(" -- Image : #{options[:image].name}")
172
- env[:ui].info(" -- ImageRef : #{options[:image].id}")
173
- env[:ui].info(" -- KeyPair : #{options[:keypair_name]}")
253
+ env[:ui].info(" -- Tenant : #{config.tenant_name}")
254
+ env[:ui].info(" -- Name : #{server_name}")
255
+ env[:ui].info(" -- Flavor : #{options[:flavor].name}")
256
+ env[:ui].info(" -- FlavorRef : #{options[:flavor].id}")
257
+ unless options[:image].nil?
258
+ env[:ui].info(" -- Image : #{options[:image].name}")
259
+ env[:ui].info(" -- ImageRef : #{options[:image].id}")
260
+ end
261
+ env[:ui].info(" -- Boot volume : #{options[:volume_boot][:id]} (#{options[:volume_boot][:device]})") unless options[:volume_boot].nil?
262
+ env[:ui].info(" -- KeyPair : #{options[:keypair_name]}")
263
+
174
264
  unless options[:networks].empty?
175
265
  if options[:networks].size == 1
176
- env[:ui].info(" -- Network : #{options[:networks][0]}")
266
+ env[:ui].info(" -- Network : #{options[:networks][0]}")
177
267
  else
178
- env[:ui].info(" -- Networks : #{options[:networks]}")
268
+ env[:ui].info(" -- Networks : #{options[:networks]}")
269
+ end
270
+ end
271
+
272
+ unless options[:volumes].empty?
273
+ options[:volumes].each do |volume|
274
+ device = volume[:device]
275
+ device = :auto if device.nil?
276
+ env[:ui].info(" -- Volume attached : #{volume[:id]} => #{device}")
179
277
  end
180
278
  end
181
279
 
182
280
  log = "Lauching server '#{server_name}' in project '#{config.tenant_name}' "
183
281
  log << "with flavor '#{options[:flavor].name}' (#{options[:flavor].id}), "
184
- log << "image '#{options[:image].name}' (#{options[:image].id}) "
282
+ unless options[:image].nil?
283
+ log << "image '#{options[:image].name}' (#{options[:image].id}) "
284
+ end
185
285
  log << "and keypair '#{options[:keypair_name]}'"
186
286
 
187
287
  @logger.info(log)
188
288
 
289
+ image_ref = options[:image].id unless options[:image].nil?
290
+
189
291
  create_opts = {
190
292
  name: server_name,
191
- image_ref: options[:image].id,
293
+ image_ref: image_ref,
294
+ volume_boot: options[:volume_boot],
192
295
  flavor_ref: options[:flavor].id,
193
296
  keypair: options[:keypair_name],
194
297
  availability_zone: options[:availability_zone],
195
- networks: options[:networks]
298
+ networks: options[:networks],
299
+ scheduler_hints: options[:scheduler_hints],
300
+ security_groups: options[:security_groups],
301
+ user_data: options[:user_data],
302
+ metadata: options[:metadata]
196
303
  }
197
304
 
198
305
  nova.create_server(env, create_opts)
@@ -210,6 +317,15 @@ module VagrantPlugins
210
317
  end
211
318
  end
212
319
 
320
+ def attach_volumes(env, server_id, volumes)
321
+ @logger.info("Attaching volumes #{volumes} to server #{server_id}")
322
+ nova = env[:openstack_client].nova
323
+ volumes.each do |volume|
324
+ @logger.debug("Attaching volumes #{volume}")
325
+ nova.attach_volume(env, server_id, volume[:id], volume[:device])
326
+ end
327
+ end
328
+
213
329
  def port_open?(env, ip, port, timeout)
214
330
  start_time = Time.now
215
331
  current_time = start_time
@@ -243,7 +359,7 @@ module VagrantPlugins
243
359
  return single if single.name == name
244
360
  return single if name.is_a?(Regexp) && name =~ single.name
245
361
  end
246
- @logger.error "Flavor '#{name}' not found"
362
+ @logger.error "Element '#{name}' not found in collection #{collection}"
247
363
  nil
248
364
  end
249
365
  end
@@ -24,13 +24,27 @@ module VagrantPlugins
24
24
  config = env[:machine].provider_config
25
25
  hash = {
26
26
  host: get_ip_address(env),
27
- port: 22,
28
- username: config.ssh_username
27
+ port: resolve_ssh_port(env),
28
+ username: resolve_ssh_username(env)
29
29
  }
30
30
  hash[:private_key_path] = "#{env[:machine].data_dir}/#{get_keypair_name(env)}" unless config.keypair_name || config.public_key_path
31
31
  hash
32
32
  end
33
33
 
34
+ def resolve_ssh_port(env)
35
+ machine_config = env[:machine].config
36
+ return machine_config.ssh.port if machine_config.ssh.port
37
+ 22
38
+ end
39
+
40
+ def resolve_ssh_username(env)
41
+ config = env[:machine].provider_config
42
+ machine_config = env[:machine].config
43
+ return machine_config.ssh.username if machine_config.ssh.username
44
+ return config.ssh_username if config.ssh_username
45
+ fail Errors::NoMatchingSshUsername
46
+ end
47
+
34
48
  def get_ip_address(env)
35
49
  return env[:machine].provider_config.floating_ip unless env[:machine].provider_config.floating_ip.nil?
36
50
  details = env[:openstack_client].nova.get_server_details(env, env[:machine].id)
@@ -0,0 +1,40 @@
1
+ require 'log4r'
2
+ require 'restclient'
3
+ require 'json'
4
+
5
+ require 'vagrant-openstack-provider/client/http_utils'
6
+ require 'vagrant-openstack-provider/client/domain'
7
+
8
+ module VagrantPlugins
9
+ module Openstack
10
+ class CinderClient
11
+ include Singleton
12
+ include VagrantPlugins::Openstack::HttpUtils
13
+ include VagrantPlugins::Openstack::Domain
14
+
15
+ def initialize
16
+ @logger = Log4r::Logger.new('vagrant_openstack::cinder')
17
+ @session = VagrantPlugins::Openstack.session
18
+ end
19
+
20
+ def get_all_volumes(env)
21
+ volumes_json = get(env, "#{@session.endpoints[:volume]}/volumes/detail")
22
+ JSON.parse(volumes_json)['volumes'].map do |volume|
23
+ name = volume['display_name']
24
+ name = volume['name'] if name.nil? # To be compatible with cinder api v1 and v2
25
+ case volume['attachments'].size
26
+ when 0
27
+ @logger.debug "No attachment found for volume #{volume['id']}"
28
+ else
29
+ attachment = volume['attachments'][0]
30
+ server_id = attachment['server_id']
31
+ device = attachment['device']
32
+ @logger.warn "Found #{attachment.size} attachments for volume #{volume['id']} : " if attachment.size > 1
33
+ @logger.debug "Attachment found for volume #{volume['id']} : #{attachment.to_json}"
34
+ end
35
+ Volume.new(volume['id'], name, volume['size'], volume['status'], volume['bootable'], server_id, device)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -11,6 +11,14 @@ module VagrantPlugins
11
11
  @id = id
12
12
  @name = name
13
13
  end
14
+
15
+ def ==(other)
16
+ other.class == self.class && other.state == state
17
+ end
18
+
19
+ def state
20
+ [@id, @name]
21
+ end
14
22
  end
15
23
 
16
24
  class Flavor < Item
@@ -36,10 +44,6 @@ module VagrantPlugins
36
44
  super(id, name)
37
45
  end
38
46
 
39
- def ==(other)
40
- other.class == self.class && other.state == state
41
- end
42
-
43
47
  protected
44
48
 
45
49
  def state
@@ -55,6 +59,62 @@ module VagrantPlugins
55
59
  @instance_id = instance_id
56
60
  end
57
61
  end
62
+
63
+ class Volume < Item
64
+ #
65
+ # Size in Gigaoctet
66
+ #
67
+ attr_accessor :size
68
+
69
+ #
70
+ # Status (e.g. 'Available', 'In-use')
71
+ #
72
+ attr_accessor :status
73
+
74
+ #
75
+ # Whether volume is bootable or not
76
+ #
77
+ attr_accessor :bootable
78
+
79
+ #
80
+ # instance id volume is attached to
81
+ #
82
+ attr_accessor :instance_id
83
+
84
+ #
85
+ # device (e.g. /dev/sdb) if attached
86
+ #
87
+ attr_accessor :device
88
+
89
+ # rubocop:disable Style/ParameterLists
90
+ def initialize(id, name, size, status, bootable, instance_id, device)
91
+ @size = size
92
+ @status = status
93
+ @bootable = bootable
94
+ @instance_id = instance_id
95
+ @device = device
96
+ super(id, name)
97
+ end
98
+ # rubocop:enable Style/ParameterLists
99
+
100
+ def to_s
101
+ {
102
+ id: @id,
103
+ name: @name,
104
+ size: @size,
105
+ status: @status,
106
+ bootable: @bootable,
107
+ instance_id: @instance_id,
108
+ device: @device
109
+ }.to_json
110
+ end
111
+
112
+ protected
113
+
114
+ def state
115
+ [@id, @name, @size, @status, @bootable, @instance_id, @device]
116
+ end
117
+ end
58
118
  end
59
119
  end
60
120
  end