vagrant-openstack-provider 0.2.0 → 0.3.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.
Files changed (55) hide show
  1. checksums.yaml +8 -8
  2. data/Appraisals +6 -6
  3. data/CHANGELOG.md +44 -1
  4. data/Gemfile +10 -6
  5. data/Vagrantfile +7 -15
  6. data/functional_tests/Vagrantfile +58 -0
  7. data/functional_tests/keys/vagrant-openstack +27 -0
  8. data/functional_tests/keys/vagrant-openstack.pub +1 -0
  9. data/functional_tests/run_tests.sh +142 -0
  10. data/gemfiles/latest_stable.gemfile +5 -0
  11. data/gemfiles/oldest_current.gemfile +5 -0
  12. data/gemfiles/previous_release.gemfile +5 -0
  13. data/lib/vagrant-openstack-provider/action.rb +17 -2
  14. data/lib/vagrant-openstack-provider/action/connect_openstack.rb +60 -8
  15. data/lib/vagrant-openstack-provider/action/create_server.rb +131 -43
  16. data/lib/vagrant-openstack-provider/action/delete_server.rb +2 -0
  17. data/lib/vagrant-openstack-provider/action/read_ssh_info.rb +22 -3
  18. data/lib/vagrant-openstack-provider/action/resume.rb +2 -0
  19. data/lib/vagrant-openstack-provider/action/stop_server.rb +1 -0
  20. data/lib/vagrant-openstack-provider/action/suspend.rb +2 -0
  21. data/lib/vagrant-openstack-provider/action/sync_folders.rb +3 -2
  22. data/lib/vagrant-openstack-provider/action/wait_active.rb +29 -0
  23. data/lib/vagrant-openstack-provider/action/wait_stop.rb +1 -1
  24. data/lib/vagrant-openstack-provider/client/domain.rb +26 -0
  25. data/lib/vagrant-openstack-provider/client/http_utils.rb +96 -0
  26. data/lib/vagrant-openstack-provider/client/keystone.rb +30 -41
  27. data/lib/vagrant-openstack-provider/client/neutron.rb +24 -11
  28. data/lib/vagrant-openstack-provider/client/nova.rb +99 -104
  29. data/lib/vagrant-openstack-provider/client/openstack.rb +4 -0
  30. data/lib/vagrant-openstack-provider/client/request_logger.rb +24 -0
  31. data/lib/vagrant-openstack-provider/command/abstract_command.rb +31 -0
  32. data/lib/vagrant-openstack-provider/command/flavor_list.rb +21 -0
  33. data/lib/vagrant-openstack-provider/command/floatingip_list.rb +34 -0
  34. data/lib/vagrant-openstack-provider/command/image_list.rb +21 -0
  35. data/lib/vagrant-openstack-provider/command/main.rb +51 -0
  36. data/lib/vagrant-openstack-provider/command/network_list.rb +21 -0
  37. data/lib/vagrant-openstack-provider/command/utils.rb +22 -0
  38. data/lib/vagrant-openstack-provider/config.rb +31 -3
  39. data/lib/vagrant-openstack-provider/errors.rb +28 -0
  40. data/lib/vagrant-openstack-provider/plugin.rb +5 -0
  41. data/lib/vagrant-openstack-provider/version.rb +1 -1
  42. data/locales/en.yml +45 -1
  43. data/spec/vagrant-openstack-provider/action/connect_openstack_spec.rb +190 -0
  44. data/spec/vagrant-openstack-provider/action/create_server_spec.rb +166 -1
  45. data/spec/vagrant-openstack-provider/action/read_ssh_info_spec.rb +109 -0
  46. data/spec/vagrant-openstack-provider/client/keystone_spec.rb +32 -48
  47. data/spec/vagrant-openstack-provider/client/neutron_spec.rb +42 -4
  48. data/spec/vagrant-openstack-provider/client/nova_spec.rb +247 -6
  49. data/spec/vagrant-openstack-provider/client/utils_spec.rb +58 -5
  50. data/spec/vagrant-openstack-provider/command/floatingip_list_spec.rb +67 -0
  51. data/spec/vagrant-openstack-provider/config_spec.rb +21 -6
  52. data/spec/vagrant-openstack-provider/spec_helper.rb +7 -0
  53. data/{numergyrc → stackrc} +4 -1
  54. metadata +24 -4
  55. data/lib/vagrant-openstack-provider/client/utils.rb +0 -38
@@ -95,10 +95,15 @@ module VagrantPlugins
95
95
  b.use ConnectOpenstack
96
96
 
97
97
  b.use Call, ReadState do |env, b2|
98
- if env[:machine_state_id] == :not_created
98
+ case env[:machine_state_id]
99
+ when :not_created
99
100
  b2.use Provision
100
101
  b2.use SyncFolders
101
102
  b2.use CreateServer
103
+ when :shutoff
104
+ b2.use StartServer
105
+ when :suspended
106
+ b2.use Resume
102
107
  else
103
108
  b2.use Message, I18n.t('vagrant_openstack.already_created')
104
109
  end
@@ -159,8 +164,17 @@ module VagrantPlugins
159
164
  b.use ConfigValidate
160
165
  b.use ConnectOpenstack
161
166
  b.use Call, ReadState do |env, b2|
162
- if env[:machine_state_id] == :not_created
167
+ case env[:machine_state_id]
168
+ when :not_created
163
169
  b2.use Message, I18n.t('vagrant_openstack.not_created')
170
+ when :suspended
171
+ b2.use Resume
172
+ b2.use WaitForServerToBeActive
173
+ b2.use StopServer
174
+ b2.use WaitForServerToStop
175
+ b2.use StartServer
176
+ when :shutoff
177
+ b2.use StartServer
164
178
  else
165
179
  b2.use StopServer
166
180
  b2.use WaitForServerToStop
@@ -184,6 +198,7 @@ module VagrantPlugins
184
198
  autoload :Suspend, action_root.join('suspend')
185
199
  autoload :Resume, action_root.join('resume')
186
200
  autoload :WaitForServerToStop, action_root.join('wait_stop')
201
+ autoload :WaitForServerToBeActive, action_root.join('wait_active')
187
202
  end
188
203
  end
189
204
  end
@@ -3,24 +3,76 @@ require 'restclient'
3
3
  require 'json'
4
4
 
5
5
  require 'vagrant-openstack-provider/client/openstack'
6
+ require 'vagrant-openstack-provider/client/request_logger'
6
7
 
7
8
  module VagrantPlugins
8
9
  module Openstack
9
10
  module Action
10
- # This action connects to Openstack, verifies credentials work, and
11
- # puts the Openstack connection object into the `:openstack_compute` key
12
- # in the environment.
13
11
  class ConnectOpenstack
14
- def initialize(app, _env)
12
+ include VagrantPlugins::Openstack::HttpUtils::RequestLogger
13
+
14
+ def initialize(app, env)
15
15
  @app = app
16
16
  @logger = Log4r::Logger.new('vagrant_openstack::action::connect_openstack')
17
+ env[:openstack_client] = VagrantPlugins::Openstack
17
18
  end
18
19
 
19
20
  def call(env)
20
- client = VagrantPlugins::Openstack
21
- env[:openstack_client] = client
22
- client.keystone.authenticate(env) if client.session.token.nil?
23
- @app.call(env)
21
+ client = env[:openstack_client]
22
+ if client.session.token.nil?
23
+ catalog = client.keystone.authenticate(env)
24
+ read_endpoint_catalog(env, catalog)
25
+ override_endpoint_catalog_with_user_config(env)
26
+ log_endpoint_catalog(env)
27
+ end
28
+ @app.call(env) unless @app.nil?
29
+ end
30
+
31
+ private
32
+
33
+ def read_endpoint_catalog(env, catalog)
34
+ config = env[:machine].provider_config
35
+ client = env[:openstack_client]
36
+ @logger.info(I18n.t('vagrant_openstack.client.looking_for_available_endpoints'))
37
+
38
+ catalog.each do |service|
39
+ se = service['endpoints']
40
+ if se.size > 1
41
+ env[:ui].warn I18n.t('vagrant_openstack.client.multiple_endpoint', size: se.size, type: service['type'])
42
+ env[:ui].warn " => #{service['endpoints'][0]['publicURL']}"
43
+ end
44
+ url = se[0]['publicURL'].strip
45
+ client.session.endpoints[service['type'].to_sym] = url unless url.empty?
46
+ end
47
+
48
+ read_network_api_version(env) if config.openstack_network_url.nil?
49
+ end
50
+
51
+ def read_network_api_version(env)
52
+ client = env[:openstack_client]
53
+ versions = client.neutron.get_api_version_list(env)
54
+ if versions.size > 1
55
+ version_list = ''
56
+ versions.each do |version|
57
+ links = version['links'].map { |l| l['href'] }
58
+ version_list << "#{version['id'].ljust(6)} #{version['status'].ljust(10)} #{links}\n"
59
+ end
60
+ fail Errors::MultipleApiVersion, api_name: 'Neutron', url_property: 'openstack_network_url', version_list: version_list
61
+ end
62
+ client.session.endpoints[:network] = versions.first['links'].first['href']
63
+ end
64
+
65
+ def override_endpoint_catalog_with_user_config(env)
66
+ client = env[:openstack_client]
67
+ config = env[:machine].provider_config
68
+ client.session.endpoints[:compute] = config.openstack_compute_url unless config.openstack_compute_url.nil?
69
+ client.session.endpoints[:network] = config.openstack_network_url unless config.openstack_network_url.nil?
70
+ end
71
+
72
+ def log_endpoint_catalog(env)
73
+ env[:openstack_client].session.endpoints.each do |key, value|
74
+ @logger.info(" -- #{key.to_s.ljust 15}: #{value}")
75
+ end
24
76
  end
25
77
  end
26
78
  end
@@ -1,6 +1,7 @@
1
1
  require 'log4r'
2
2
  require 'socket'
3
3
  require 'timeout'
4
+ require 'sshkey'
4
5
 
5
6
  require 'vagrant/util/retryable'
6
7
 
@@ -16,47 +17,29 @@ module VagrantPlugins
16
17
  end
17
18
 
18
19
  def call(env)
19
- config = env[:machine].provider_config
20
- nova = env[:openstack_client].nova
21
-
22
- flavor = resolve_flavor(env)
23
- image = resolve_image(env)
24
- networks = resolve_networks(env)
25
- server_name = config.server_name || env[:machine].name
20
+ @logger.info 'Start create server action'
26
21
 
27
- env[:ui].info(I18n.t('vagrant_openstack.launching_server'))
28
- env[:ui].info(" -- Tenant : #{config.tenant_name}")
29
- env[:ui].info(" -- Name : #{server_name}")
30
- env[:ui].info(" -- Flavor : #{flavor.name}")
31
- env[:ui].info(" -- FlavorRef : #{flavor.id}")
32
- env[:ui].info(" -- Image : #{image.name}")
33
- env[:ui].info(" -- ImageRef : #{image.id}")
34
- env[:ui].info(" -- KeyPair : #{config.keypair_name}")
35
- unless networks.empty?
36
- if networks.size == 1
37
- env[:ui].info(" -- Network : #{config.networks[0]}")
38
- else
39
- env[:ui].info(" -- Networks : #{config.networks}")
40
- end
41
- end
22
+ nova = env[:openstack_client].nova
42
23
 
43
- server_id = nova.create_server(env, server_name, image.id, flavor.id, networks, config.keypair_name)
24
+ options = {
25
+ flavor: resolve_flavor(env),
26
+ image: resolve_image(env),
27
+ networks: resolve_networks(env),
28
+ keypair_name: resolve_keypair(env),
29
+ availability_zone: env[:machine].provider_config.availability_zone
30
+ }
31
+ server_id = create_server(env, options)
44
32
 
45
33
  # Store the ID right away so we can track it
46
34
  env[:machine].id = server_id
47
35
 
48
- # Wait for the server to finish building
49
- env[:ui].info(I18n.t('vagrant_openstack.waiting_for_build'))
50
- timeout(200) do
51
- while nova.get_server_details(env, server_id)['status'] != 'ACTIVE'
52
- sleep 3
53
- @logger.debug('Waiting for server to be ACTIVE')
54
- end
55
- end
36
+ waiting_for_server_to_be_build(env, server_id)
56
37
 
57
- if config.floating_ip
58
- env[:ui].info("Using floating IP #{config.floating_ip}")
59
- nova.add_floating_ip(env, server_id, config.floating_ip)
38
+ floating_ip = resolve_floating_ip(env)
39
+ if floating_ip && !floating_ip.empty?
40
+ @logger.info "Using floating IP #{floating_ip}"
41
+ env[:ui].info(I18n.t('vagrant_openstack.using_floating_ip', floating_ip: floating_ip))
42
+ nova.add_floating_ip(env, server_id, floating_ip)
60
43
  end
61
44
 
62
45
  unless env[:interrupted]
@@ -64,13 +47,13 @@ module VagrantPlugins
64
47
  env[:ui].clear_line
65
48
 
66
49
  # Wait for SSH to become available
67
- host = env[:machine].provider_config.floating_ip
68
50
  ssh_timeout = env[:machine].provider_config.ssh_timeout
69
- unless port_open?(env, host, 22, ssh_timeout)
51
+ unless port_open?(env, floating_ip, 22, ssh_timeout)
70
52
  env[:ui].error(I18n.t('vagrant_openstack.timeout'))
71
- fail Errors::SshUnavailable, host: host, timeout: ssh_timeout
53
+ fail Errors::SshUnavailable, host: floating_ip, timeout: ssh_timeout
72
54
  end
73
55
 
56
+ @logger.info 'The server is ready'
74
57
  env[:ui].info(I18n.t('vagrant_openstack.ready'))
75
58
  end
76
59
 
@@ -79,33 +62,81 @@ module VagrantPlugins
79
62
 
80
63
  private
81
64
 
65
+ # 1. if floating_ip is set, use it
66
+ # 2. if floating_ip_pool is set
67
+ # GET v2/{{tenant_id}}/os-floating-ips
68
+ # If any IP with the same pool is available, use it
69
+ # Else Allocate a new IP from the pool
70
+ # Manage error case
71
+ # 3. GET v2/{{tenant_id}}/os-floating-ips
72
+ # If any IP is available, use it
73
+ # Else fail
74
+ def resolve_floating_ip(env)
75
+ config = env[:machine].provider_config
76
+ nova = env[:openstack_client].nova
77
+ return config.floating_ip if config.floating_ip
78
+ floating_ips = nova.get_all_floating_ips(env)
79
+ if config.floating_ip_pool
80
+ floating_ips.each do |single|
81
+ return single.ip if single.pool == config.floating_ip_pool && single.instance_id.nil?
82
+ end
83
+ return nova.allocate_floating_ip(env, config.floating_ip_pool).ip
84
+ else
85
+ floating_ips.each do |ip|
86
+ return ip.ip if ip.instance_id.nil?
87
+ end
88
+ end
89
+ fail Errors::UnableToResolveFloatingIP
90
+ end
91
+
92
+ def resolve_keypair(env)
93
+ config = env[:machine].provider_config
94
+ nova = env[:openstack_client].nova
95
+ return config.keypair_name if config.keypair_name
96
+ return nova.import_keypair_from_file(env, config.public_key_path) if config.public_key_path
97
+ generate_keypair(env)
98
+ end
99
+
100
+ def generate_keypair(env)
101
+ key = SSHKey.generate
102
+ nova = env[:openstack_client].nova
103
+ generated_keyname = nova.import_keypair(env, key.ssh_public_key)
104
+ File.write("#{env[:machine].data_dir}/#{generated_keyname}", key.private_key)
105
+ generated_keyname
106
+ end
107
+
82
108
  def resolve_flavor(env)
109
+ @logger.info 'Resolving flavor'
83
110
  config = env[:machine].provider_config
84
111
  nova = env[:openstack_client].nova
85
112
  env[:ui].info(I18n.t('vagrant_openstack.finding_flavor'))
86
113
  flavors = nova.get_all_flavors(env)
114
+ @logger.info "Finding flavor matching name '#{config.flavor}'"
87
115
  flavor = find_matching(flavors, config.flavor)
88
116
  fail Errors::NoMatchingFlavor unless flavor
89
117
  flavor
90
118
  end
91
119
 
92
120
  def resolve_image(env)
121
+ @logger.info 'Resolving image'
93
122
  config = env[:machine].provider_config
94
123
  nova = env[:openstack_client].nova
95
124
  env[:ui].info(I18n.t('vagrant_openstack.finding_image'))
96
125
  images = nova.get_all_images(env)
126
+ @logger.info "Finding image matching name '#{config.image}'"
97
127
  image = find_matching(images, config.image)
98
128
  fail Errors::NoMatchingImage unless image
99
129
  image
100
130
  end
101
131
 
102
132
  def resolve_networks(env)
133
+ @logger.info 'Resolving network(s)'
103
134
  config = env[:machine].provider_config
104
135
  return [] if config.networks.nil? || config.networks.empty?
105
136
  env[:ui].info(I18n.t('vagrant_openstack.finding_networks'))
106
137
 
107
138
  private_networks = env[:openstack_client].neutron.get_private_networks(env)
108
- private_network_ids = private_networks.map { |n| n[:id] }
139
+ private_network_ids = private_networks.map { |n| n.id }
109
140
 
110
141
  networks = []
111
142
  config.networks.each do |network|
@@ -115,9 +146,9 @@ module VagrantPlugins
115
146
  end
116
147
  net_id = nil
117
148
  private_networks.each do |n| # Bad algorithm complexity, but here we don't care...
118
- next unless n[:name].eql? network
119
- fail "Multiple networks with name '#{n[:name]}'" unless net_id.nil?
120
- net_id = n[:id]
149
+ next unless n.name.eql? network
150
+ fail "Multiple networks with name '#{n.id}'" unless net_id.nil?
151
+ net_id = n.id
121
152
  end
122
153
  fail "No matching network with name '#{network}'" if net_id.nil?
123
154
  networks << net_id
@@ -125,16 +156,73 @@ module VagrantPlugins
125
156
  networks
126
157
  end
127
158
 
159
+ def create_server(env, options)
160
+ config = env[:machine].provider_config
161
+ nova = env[:openstack_client].nova
162
+ server_name = config.server_name || env[:machine].name
163
+
164
+ env[:ui].info(I18n.t('vagrant_openstack.launching_server'))
165
+ env[:ui].info(" -- Tenant : #{config.tenant_name}")
166
+ env[:ui].info(" -- Name : #{server_name}")
167
+ env[:ui].info(" -- Flavor : #{options[:flavor].name}")
168
+ env[:ui].info(" -- FlavorRef : #{options[:flavor].id}")
169
+ env[:ui].info(" -- Image : #{options[:image].name}")
170
+ env[:ui].info(" -- ImageRef : #{options[:image].id}")
171
+ env[:ui].info(" -- KeyPair : #{options[:keypair_name]}")
172
+ unless options[:networks].empty?
173
+ if options[:networks].size == 1
174
+ env[:ui].info(" -- Network : #{options[:networks][0]}")
175
+ else
176
+ env[:ui].info(" -- Networks : #{options[:networks]}")
177
+ end
178
+ end
179
+
180
+ log = "Lauching server '#{server_name}' in project '#{config.tenant_name}' "
181
+ log << "with flavor '#{options[:flavor].name}' (#{options[:flavor].id}), "
182
+ log << "image '#{options[:image].name}' (#{options[:image].id}) "
183
+ log << "and keypair '#{options[:keypair_name]}'"
184
+
185
+ @logger.info(log)
186
+
187
+ create_opts = {
188
+ name: server_name,
189
+ image_ref: options[:image].id,
190
+ flavor_ref: options[:flavor].id,
191
+ keypair: options[:keypair_name],
192
+ availability_zone: options[:availability_zone],
193
+ networks: options[:networks]
194
+ }
195
+
196
+ nova.create_server(env, create_opts)
197
+ end
198
+
199
+ def waiting_for_server_to_be_build(env, server_id)
200
+ @logger.info 'Waiting for the server to be built...'
201
+ env[:ui].info(I18n.t('vagrant_openstack.waiting_for_build'))
202
+ nova = env[:openstack_client].nova
203
+ timeout(200) do
204
+ while nova.get_server_details(env, server_id)['status'] != 'ACTIVE'
205
+ sleep 3
206
+ @logger.debug('Waiting for server to be ACTIVE')
207
+ end
208
+ end
209
+ end
210
+
128
211
  def port_open?(env, ip, port, timeout)
129
212
  start_time = Time.now
130
213
  current_time = start_time
131
214
  nb_retry = 0
132
215
  while (current_time - start_time) <= timeout
133
216
  begin
134
- env[:ui].info(I18n.t('vagrant_openstack.waiting_for_ssh')) if nb_retry % 5 == 0
217
+ @logger.debug "Checking if SSH port is open... Attempt number #{nb_retry}"
218
+ if nb_retry % 5 == 0
219
+ @logger.info 'Waiting for SSH to become available...'
220
+ env[:ui].info(I18n.t('vagrant_openstack.waiting_for_ssh'))
221
+ end
135
222
  TCPSocket.new(ip, port)
136
223
  return true
137
224
  rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT
225
+ @logger.debug 'SSH port is not open... new retry in in 1 second'
138
226
  nb_retry += 1
139
227
  sleep 1
140
228
  end
@@ -153,7 +241,7 @@ module VagrantPlugins
153
241
  return single if single.name == name
154
242
  return single if name.is_a?(Regexp) && name =~ single.name
155
243
  end
156
-
244
+ @logger.error "Flavor '#{name}' not found"
157
245
  nil
158
246
  end
159
247
  end
@@ -12,8 +12,10 @@ module VagrantPlugins
12
12
 
13
13
  def call(env)
14
14
  if env[:machine].id
15
+ @logger.info "Deleting server #{env[:machine].id}..."
15
16
  env[:ui].info(I18n.t('vagrant_openstack.deleting_server'))
16
17
  env[:openstack_client].nova.delete_server(env, env[:machine].id)
18
+ env[:openstack_client].nova.delete_keypair_if_vagrant(env, env[:machine].id)
17
19
  env[:machine].id = nil
18
20
  end
19
21
 
@@ -12,19 +12,38 @@ module VagrantPlugins
12
12
  end
13
13
 
14
14
  def call(env)
15
+ @logger.info 'Reading SSH info'
15
16
  env[:machine_ssh_info] = read_ssh_info(env)
16
17
 
17
18
  @app.call(env)
18
19
  end
19
20
 
21
+ private
22
+
20
23
  def read_ssh_info(env)
21
24
  config = env[:machine].provider_config
22
- {
23
- # Usually there should only be one public IP
24
- host: config.floating_ip,
25
+ hash = {
26
+ host: get_ip_address(env),
25
27
  port: 22,
26
28
  username: config.ssh_username
27
29
  }
30
+ hash[:private_key_path] = "#{env[:machine].data_dir}/#{get_keypair_name(env)}" unless config.keypair_name || config.public_key_path
31
+ hash
32
+ end
33
+
34
+ def get_ip_address(env)
35
+ return env[:machine].provider_config.floating_ip unless env[:machine].provider_config.floating_ip.nil?
36
+ details = env[:openstack_client].nova.get_server_details(env, env[:machine].id)
37
+ details['addresses'].each do |network|
38
+ network[1].each do |network_detail|
39
+ return network_detail['addr'] if network_detail['OS-EXT-IPS:type'] == 'floating'
40
+ end
41
+ end
42
+ fail Errors::UnableToResolveIP
43
+ end
44
+
45
+ def get_keypair_name(env)
46
+ env[:openstack_client].nova.get_server_details(env, env[:machine].id)['key_name']
28
47
  end
29
48
  end
30
49
  end