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.
@@ -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