chef-provisioning-fog 0.14.0 → 0.15.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +201 -201
  3. data/README.md +3 -3
  4. data/Rakefile +6 -6
  5. data/lib/chef/provider/fog_key_pair.rb +266 -266
  6. data/lib/chef/provisioning/driver_init/fog.rb +3 -3
  7. data/lib/chef/provisioning/fog_driver/driver.rb +736 -709
  8. data/lib/chef/provisioning/fog_driver/providers/aws.rb +492 -492
  9. data/lib/chef/provisioning/fog_driver/providers/aws/credentials.rb +115 -115
  10. data/lib/chef/provisioning/fog_driver/providers/cloudstack.rb +44 -44
  11. data/lib/chef/provisioning/fog_driver/providers/digitalocean.rb +136 -136
  12. data/lib/chef/provisioning/fog_driver/providers/google.rb +85 -84
  13. data/lib/chef/provisioning/fog_driver/providers/joyent.rb +63 -59
  14. data/lib/chef/provisioning/fog_driver/providers/openstack.rb +117 -41
  15. data/lib/chef/provisioning/fog_driver/providers/rackspace.rb +42 -42
  16. data/lib/chef/provisioning/fog_driver/providers/softlayer.rb +36 -36
  17. data/lib/chef/provisioning/fog_driver/providers/vcair.rb +409 -376
  18. data/lib/chef/provisioning/fog_driver/providers/xenserver.rb +210 -0
  19. data/lib/chef/provisioning/fog_driver/recipe_dsl.rb +32 -32
  20. data/lib/chef/provisioning/fog_driver/version.rb +7 -7
  21. data/lib/chef/resource/fog_key_pair.rb +34 -34
  22. data/spec/spec_helper.rb +18 -18
  23. data/spec/support/aws/config-file.csv +2 -2
  24. data/spec/support/aws/ini-file.ini +10 -10
  25. data/spec/support/chef_metal_fog/providers/testdriver.rb +16 -16
  26. data/spec/unit/chef/provisioning/fog_driver/driver_spec.rb +71 -0
  27. data/spec/unit/fog_driver_spec.rb +32 -32
  28. data/spec/unit/providers/aws/credentials_spec.rb +45 -45
  29. data/spec/unit/providers/rackspace_spec.rb +16 -16
  30. metadata +5 -3
@@ -1,42 +1,42 @@
1
- # fog:Rackspace:https://identity.api.rackspacecloud.com/v2.0
2
- class Chef
3
- module Provisioning
4
- module FogDriver
5
- module Providers
6
- class Rackspace < FogDriver::Driver
7
-
8
- Driver.register_provider_class('Rackspace', FogDriver::Providers::Rackspace)
9
-
10
- def creator
11
- compute_options[:rackspace_username]
12
- end
13
-
14
- def self.compute_options_for(provider, id, config)
15
- new_compute_options = {}
16
- new_compute_options[:provider] = provider
17
- new_config = { :driver_options => { :compute_options => new_compute_options }}
18
- new_defaults = {
19
- :driver_options => { :compute_options => {} },
20
- :machine_options => { :bootstrap_options => {} }
21
- }
22
- result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
23
-
24
- new_compute_options[:rackspace_auth_url] = id if (id && id != '')
25
- credential = Fog.credentials
26
-
27
- new_compute_options[:rackspace_username] ||= credential[:rackspace_username]
28
- new_compute_options[:rackspace_api_key] ||= credential[:rackspace_api_key]
29
- new_compute_options[:rackspace_auth_url] ||= credential[:rackspace_auth_url]
30
- new_compute_options[:rackspace_region] ||= credential[:rackspace_region]
31
- new_compute_options[:rackspace_endpoint] ||= credential[:rackspace_endpoint]
32
-
33
- id = result[:driver_options][:compute_options][:rackspace_auth_url]
34
-
35
- [result, id]
36
- end
37
-
38
- end
39
- end
40
- end
41
- end
42
- end
1
+ # fog:Rackspace:https://identity.api.rackspacecloud.com/v2.0
2
+ class Chef
3
+ module Provisioning
4
+ module FogDriver
5
+ module Providers
6
+ class Rackspace < FogDriver::Driver
7
+
8
+ Driver.register_provider_class('Rackspace', FogDriver::Providers::Rackspace)
9
+
10
+ def creator
11
+ compute_options[:rackspace_username]
12
+ end
13
+
14
+ def self.compute_options_for(provider, id, config)
15
+ new_compute_options = {}
16
+ new_compute_options[:provider] = provider
17
+ new_config = { :driver_options => { :compute_options => new_compute_options }}
18
+ new_defaults = {
19
+ :driver_options => { :compute_options => {} },
20
+ :machine_options => { :bootstrap_options => {} }
21
+ }
22
+ result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
23
+
24
+ new_compute_options[:rackspace_auth_url] = id if (id && id != '')
25
+ credential = Fog.credentials
26
+
27
+ new_compute_options[:rackspace_username] ||= credential[:rackspace_username]
28
+ new_compute_options[:rackspace_api_key] ||= credential[:rackspace_api_key]
29
+ new_compute_options[:rackspace_auth_url] ||= credential[:rackspace_auth_url]
30
+ new_compute_options[:rackspace_region] ||= credential[:rackspace_region]
31
+ new_compute_options[:rackspace_endpoint] ||= credential[:rackspace_endpoint]
32
+
33
+ id = result[:driver_options][:compute_options][:rackspace_auth_url]
34
+
35
+ [result, id]
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,36 +1,36 @@
1
- class Chef
2
- module Provisioning
3
- module FogDriver
4
- module Providers
5
- class SoftLayer < FogDriver::Driver
6
- Driver.register_provider_class('SoftLayer', FogDriver::Providers::SoftLayer)
7
-
8
- def creator
9
- compute_options[:softlayer_username]
10
- end
11
-
12
- def self.compute_options_for(provider, id, config)
13
- new_compute_options = {}
14
- new_compute_options[:provider] = provider
15
- new_config = { :driver_options => { :compute_options => new_compute_options }}
16
- new_defaults = {
17
- :driver_options => { :compute_options => {} },
18
- :machine_options => { :bootstrap_options => {} }
19
- }
20
- result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
21
-
22
- credential = Fog.credentials
23
-
24
- new_compute_options[:softlayer_username] ||= credential[:softlayer_username]
25
- new_compute_options[:softlayer_api_key] ||= credential[:softlayer_api_key]
26
-
27
- id = result[:driver_options][:compute_options][:softlayer_auth_url]
28
-
29
- [result, id]
30
- end
31
- end
32
- end
33
- end
34
- end
35
- end
36
-
1
+ class Chef
2
+ module Provisioning
3
+ module FogDriver
4
+ module Providers
5
+ class SoftLayer < FogDriver::Driver
6
+ Driver.register_provider_class('SoftLayer', FogDriver::Providers::SoftLayer)
7
+
8
+ def creator
9
+ compute_options[:softlayer_username]
10
+ end
11
+
12
+ def self.compute_options_for(provider, id, config)
13
+ new_compute_options = {}
14
+ new_compute_options[:provider] = provider
15
+ new_config = { :driver_options => { :compute_options => new_compute_options }}
16
+ new_defaults = {
17
+ :driver_options => { :compute_options => {} },
18
+ :machine_options => { :bootstrap_options => {} }
19
+ }
20
+ result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
21
+
22
+ credential = Fog.credentials
23
+
24
+ new_compute_options[:softlayer_username] ||= credential[:softlayer_username]
25
+ new_compute_options[:softlayer_api_key] ||= credential[:softlayer_api_key]
26
+
27
+ id = result[:driver_options][:compute_options][:softlayer_auth_url]
28
+
29
+ [result, id]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -1,376 +1,409 @@
1
- # fog:Vcair:<client id>
2
- class Chef
3
- module Provisioning
4
- module FogDriver
5
- module Providers
6
- class Vcair < FogDriver::Driver
7
- Driver.register_provider_class('Vcair', FogDriver::Providers::Vcair)
8
-
9
- def creator
10
- Chef::Config[:knife][:vcair_username]
11
- end
12
-
13
- def compute
14
- @compute ||= begin
15
- Chef::Log.debug("vcair_username #{Chef::Config[:knife][:vcair_username]}")
16
- Chef::Log.debug("vcair_org #{Chef::Config[:knife][:vcair_org]}")
17
- Chef::Log.debug("vcair_api_host #{Chef::Config[:knife][:vcair_api_host]}")
18
- #Chef::Log.debug("vcair_api_version #{Chef::Config[:knife][:vcair_api_version]}")
19
- Chef::Log.debug("vcair_show_progress #{Chef::Config[:knife][:vcair_show_progress]}")
20
-
21
- username = [
22
- Chef::Config[:knife][:vcair_username],
23
- Chef::Config[:knife][:vcair_org]
24
- ].join('@')
25
-
26
- @auth_params = {
27
- :provider => 'vclouddirector', #TODO: see compute_options_for, and grab else where
28
- :vcloud_director_username => username,
29
- :vcloud_director_password => Chef::Config[:knife][:vcair_password],
30
- :vcloud_director_host => Chef::Config[:knife][:vcair_api_host],
31
- #:vcair_api_host => Chef::Config[:knife][:vcair_api_host],
32
- :vcloud_director_api_version => Chef::Config[:knife][:vcair_api_version],
33
- :vcloud_director_show_progress => false
34
- }
35
-
36
- Fog::Compute.new(@auth_params)
37
- rescue Excon::Errors::Unauthorized => e
38
- error_message = "Connection failure, please check your username and password."
39
- Chef::Log.error(error_message)
40
- raise "#{e.message}. #{error_message}"
41
- rescue Excon::Errors::SocketError => e
42
- error_message = "Connection failure, please check your authentication URL."
43
- Chef::Log.error(error_message)
44
- raise "#{e.message}. #{error_message}"
45
- end
46
- end
47
-
48
-
49
- def create_many_servers(num_servers, bootstrap_options, parallelizer)
50
- parallelizer.parallelize(1.upto(num_servers)) do |i|
51
- clean_bootstrap_options = Marshal.load(Marshal.dump(bootstrap_options)) # Prevent destructive operations on bootstrap_options.
52
- vm=nil
53
- begin
54
- instantiate(clean_bootstrap_options)
55
-
56
- vapp = vdc.vapps.get_by_name(bootstrap_options[:name])
57
- vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]}
58
-
59
- update_customization(clean_bootstrap_options, vm)
60
-
61
- if bootstrap_options[:cpus]
62
- vm.cpu = bootstrap_options[:cpus]
63
- end
64
- if bootstrap_options[:memory]
65
- vm.memory = bootstrap_options[:memory]
66
- end
67
- update_network(bootstrap_options, vapp, vm)
68
-
69
- rescue Excon::Errors::BadRequest => e
70
- response = Chef::JSONCompat.from_json(e.response.body)
71
- if response['badRequest']['code'] == 400
72
- message = "Bad request (400): #{response['badRequest']['message']}"
73
- Chef::Log.error(message)
74
- else
75
- message = "Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}"
76
- Chef::Log.error(message)
77
- end
78
- raise message
79
- rescue Fog::Errors::Error => e
80
- raise e.message
81
- end
82
-
83
- yield vm if block_given?
84
- vm
85
-
86
- end.to_a
87
- end
88
-
89
-
90
- def start_server(action_handler, machine_spec, server)
91
-
92
- # If it is stopping, wait for it to get out of "stopping" transition state before starting
93
- if server.status == 'stopping'
94
- action_handler.report_progress "wait for #{machine_spec.name} (#{server.id} on #{driver_url}) to finish stopping ..."
95
- # vCloud Air
96
- # NOTE: vCloud Air Fog does not get server.status via http every time
97
- server.wait_for { server.reload ; server.status != 'stopping' }
98
- action_handler.report_progress "#{machine_spec.name} is now stopped"
99
- end
100
-
101
- # NOTE: vCloud Air Fog does not get server.status via http every time
102
- server.reload
103
-
104
- if server.status == 'off' or server.status != 'on'
105
- action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
106
- server.power_on
107
- machine_spec.location['started_at'] = Time.now.to_i
108
- end
109
- machine_spec.save(action_handler)
110
- end
111
- end
112
-
113
-
114
- def server_for(machine_spec)
115
- if machine_spec.location
116
- vapp = vdc.vapps.get_by_name(machine_spec.name)
117
-
118
- server = unless vapp.nil?
119
- unless vapp.vms.first.nil?
120
- vapp.vms.find{|vm| vm.id == machine_spec.location['server_id'] }
121
- end
122
- end
123
- else
124
- nil
125
- end
126
- end
127
-
128
- def servers_for(machine_specs)
129
- result = {}
130
- machine_specs.each do |machine_spec|
131
- server_for(machine_spec)
132
- end
133
- result
134
- end
135
-
136
- def ssh_options_for(machine_spec, machine_options, server)
137
- { auth_methods: [ 'password' ],
138
- timeout: (machine_options[:ssh_timeout] || 600),
139
- password: machine_options[:ssh_password]
140
- }.merge(machine_options[:ssh_options] || {})
141
- end
142
-
143
- def create_ssh_transport(machine_spec, machine_options, server)
144
- ssh_options = ssh_options_for(machine_spec, machine_options, server)
145
- username = machine_spec.location['ssh_username'] || default_ssh_username
146
- options = {}
147
- if machine_spec.location[:sudo] || (!machine_spec.location.has_key?(:sudo) && username != 'root')
148
- options[:prefix] = 'sudo '
149
- end
150
-
151
- remote_host = nil
152
- # vCloud Air networking is funky
153
- #if machine_options[:use_private_ip_for_ssh] # vCloud Air probably needs private ip for now
154
- if server.ip_address
155
- remote_host = server.ip_address
156
- else
157
- raise "Server #{server.id} has no private or public IP address!"
158
- end
159
-
160
- #Enable pty by default
161
- options[:ssh_pty_enable] = true
162
- options[:ssh_gateway] = machine_spec.location['ssh_gateway'] if machine_spec.location.has_key?('ssh_gateway')
163
-
164
- Transport::SSH.new(remote_host, username, ssh_options, options, config)
165
- end
166
-
167
- def ready_machine(action_handler, machine_spec, machine_options)
168
- server = server_for(machine_spec)
169
- if server.nil?
170
- raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
171
- end
172
-
173
- # Start the server if needed, and wait for it to start
174
- start_server(action_handler, machine_spec, server)
175
- wait_until_ready(action_handler, machine_spec, machine_options, server)
176
-
177
- # Attach/detach floating IPs if necessary
178
- # vCloud Air is funky for network. VM has to be powered off or you get this error:
179
- # Primary NIC cannot be changed when the VM is not in Powered-off state
180
- # See code in update_network()
181
- #DISABLED: converge_floating_ips(action_handler, machine_spec, machine_options, server)
182
-
183
- begin
184
- wait_for_transport(action_handler, machine_spec, machine_options, server)
185
- rescue Fog::Errors::TimeoutError
186
- # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
187
- if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
188
- raise
189
- else
190
- # Sometimes (on EC2) the machine comes up but gets stuck or has
191
- # some other problem. If this is the case, we restart the server
192
- # to unstick it. Reboot covers a multitude of sins.
193
- Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) was started but SSH did not come up. Rebooting machine in an attempt to unstick it ..."
194
- restart_server(action_handler, machine_spec, server)
195
- wait_until_ready(action_handler, machine_spec, machine_options, server)
196
- wait_for_transport(action_handler, machine_spec, machine_options, server)
197
- end
198
- end
199
-
200
- machine_for(machine_spec, machine_options, server)
201
- end
202
-
203
- def org
204
- @org ||= compute.organizations.get_by_name(Chef::Config[:knife][:vcair_org])
205
- end
206
-
207
- def vdc
208
- if Chef::Config[:knife][:vcair_vdc]
209
- @vdc ||= org.vdcs.get_by_name(Chef::Config[:knife][:vcair_vdc])
210
- else
211
- @vdc ||= org.vdcs.first
212
- end
213
- end
214
-
215
- def net
216
- if Chef::Config[:knife][:vcair_net]
217
- @net ||= org.networks.get_by_name(Chef::Config[:knife][:vcair_net])
218
- else
219
- # Grab first non-isolated (bridged, natRouted) network
220
- @net ||= org.networks.find { |n| n if !n.fence_mode.match("isolated") }
221
- end
222
- end
223
-
224
- def template(bootstrap_options)
225
- # TODO: find by catalog item ID and/or NAME
226
- # TODO: add option to search just public and/or private catalogs
227
-
228
- #TODO: maybe make a hash for caching
229
- org.catalogs.map do |cat|
230
- #cat.catalog_items.get_by_name(config_value(:image))
231
- cat.catalog_items.get_by_name(bootstrap_options[:image_name])
232
- end.compact.first
233
- end
234
-
235
- def instantiate(bootstrap_options)
236
- begin
237
- #node_name = config_value(:chef_node_name)
238
- node_name = bootstrap_options[:name]
239
- template(bootstrap_options).instantiate(
240
- node_name,
241
- vdc_id: vdc.id,
242
- network_id: net.id,
243
- description: "id:#{node_name}")
244
- #rescue CloudExceptions::ServerCreateError => e
245
- rescue => e
246
- raise e
247
- end
248
- end
249
-
250
- def update_customization(bootstrap_options, server)
251
- ## Initialization before first power on.
252
- c=server.customization
253
-
254
- if bootstrap_options[:customization_script]
255
- c.script = open(bootstrap_options[:customization_script]).read
256
- end
257
-
258
- # TODO: check machine type and pick accordingly for Chef provisioning
259
- # password = case config_value(:bootstrap_protocol)
260
- # when 'winrm'
261
- # config_value(:winrm_password)
262
- # when 'ssh'
263
- # config_value(:ssh_password)
264
- # end
265
-
266
- password = bootstrap_options[:ssh_options][:password]
267
- if password
268
- c.admin_password = password
269
- c.admin_password_auto = false
270
- c.reset_password_required = false
271
- else
272
- # Password will be autogenerated
273
- c.admin_password_auto=true
274
- # API will force password resets when auto is enabled
275
- c.reset_password_required = true
276
- end
277
-
278
- # TODO: Add support for admin_auto_logon to Fog
279
- # c.admin_auto_logon_count = 100
280
- # c.admin_auto_logon_enabled = true
281
-
282
- # DNS and Windows want AlphaNumeric and dashes for hostnames
283
- # Windows can only handle 15 character hostnames
284
- # TODO: only change name for Windows!
285
- #c.computer_name = config_value(:chef_node_name).gsub(/\W/,"-").slice(0..14)
286
- c.computer_name = bootstrap_options[:name].gsub(/\W/,"-").slice(0..14)
287
- c.enabled = true
288
- c.save
289
- end
290
-
291
- ## vCloud Air
292
- ## TODO: make work with floating_ip junk currently used
293
- ## NOTE: current vCloud Air networking changes require VM to be powered off
294
- def update_network(bootstrap_options, vapp, vm)
295
- ## TODO: allow user to specify network to connect to (see above net used)
296
- # Define network connection for vm based on existing routed network
297
-
298
- # vCloud Air inlining vapp() and vm()
299
- #vapp = vdc.vapps.get_by_name(bootstrap_options[:name])
300
- #vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]}
301
- nc = vapp.network_config.find { |n| n if n[:networkName].match(net.name) }
302
- networks_config = [nc]
303
- section = {PrimaryNetworkConnectionIndex: 0}
304
- section[:NetworkConnection] = networks_config.compact.each_with_index.map do |network, i|
305
- connection = {
306
- network: network[:networkName],
307
- needsCustomization: true,
308
- NetworkConnectionIndex: i,
309
- IsConnected: true
310
- }
311
- ip_address = network[:ip_address]
312
- ## TODO: support config options for allocation mode
313
- #allocation_mode = network[:allocation_mode]
314
- #allocation_mode = 'manual' if ip_address
315
- #allocation_mode = 'dhcp' unless %w{dhcp manual pool}.include?(allocation_mode)
316
- #allocation_mode = 'POOL'
317
- #connection[:Dns1] = dns1 if dns1
318
- allocation_mode = 'pool'
319
- connection[:IpAddressAllocationMode] = allocation_mode.upcase
320
- connection[:IpAddress] = ip_address if ip_address
321
- connection
322
- end
323
-
324
- ## attach the network to the vm
325
- nc_task = compute.put_network_connection_system_section_vapp(
326
- vm.id,section).body
327
- compute.process_task(nc_task)
328
- end
329
-
330
- def bootstrap_options_for(action_handler, machine_spec, machine_options)
331
- bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
332
-
333
- bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})
334
- bootstrap_options[:name] ||= machine_spec.name
335
-
336
- bootstrap_options = bootstrap_options.merge(machine_options.configs[1])
337
- bootstrap_options
338
- end
339
-
340
- def destroy_machine(action_handler, machine_spec, machine_options)
341
- server = server_for(machine_spec)
342
- if server && server.status != 'archive' # TODO: does vCloud Air do archive?
343
- action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['server_id']} at #{driver_url})" do
344
- #NOTE: currently doing 1 vm for 1 vapp
345
- vapp = vdc.vapps.get_by_name(machine_spec.name)
346
- if vapp
347
- vapp.power_off
348
- vapp.undeploy
349
- vapp.destroy
350
- else
351
- Chef::Log.warn "No VApp named '#{server_name}' was found."
352
- end
353
- end
354
- end
355
- machine_spec.location = nil
356
- strategy = convergence_strategy_for(machine_spec, machine_options)
357
- strategy.cleanup_convergence(action_handler, machine_spec)
358
- end
359
-
360
- def self.compute_options_for(provider, id, config)
361
- new_compute_options = {}
362
- new_compute_options[:provider] = 'vclouddirector'
363
- new_config = { :driver_options => { :compute_options => new_compute_options }}
364
- new_defaults = {
365
- :driver_options => { :compute_options => {} },
366
- :machine_options => { :bootstrap_options => {}, :ssh_options => {} }
367
- }
368
- result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
369
-
370
- [result, id]
371
- end
372
- end
373
- end
374
- end
375
- end
376
- end
1
+ # fog:Vcair:<client id>
2
+ class Chef
3
+ module Provisioning
4
+ module FogDriver
5
+ module Providers
6
+ class Vcair < FogDriver::Driver
7
+ Driver.register_provider_class('Vcair', FogDriver::Providers::Vcair)
8
+
9
+ def creator
10
+ Chef::Config[:knife][:vcair_username]
11
+ end
12
+
13
+ def compute
14
+ @compute ||= begin
15
+ Chef::Log.debug("vcair_username #{Chef::Config[:knife][:vcair_username]}")
16
+ Chef::Log.debug("vcair_org #{Chef::Config[:knife][:vcair_org]}")
17
+ Chef::Log.debug("vcair_api_host #{Chef::Config[:knife][:vcair_api_host]}")
18
+ #Chef::Log.debug("vcair_api_version #{Chef::Config[:knife][:vcair_api_version]}")
19
+ Chef::Log.debug("vcair_show_progress #{Chef::Config[:knife][:vcair_show_progress]}")
20
+
21
+ username = [
22
+ Chef::Config[:knife][:vcair_username],
23
+ Chef::Config[:knife][:vcair_org]
24
+ ].join('@')
25
+
26
+ @auth_params = {
27
+ :provider => 'vclouddirector', #TODO: see compute_options_for, and grab else where
28
+ :vcloud_director_username => username,
29
+ :vcloud_director_password => Chef::Config[:knife][:vcair_password],
30
+ :vcloud_director_host => Chef::Config[:knife][:vcair_api_host],
31
+ #:vcair_api_host => Chef::Config[:knife][:vcair_api_host],
32
+ :vcloud_director_api_version => Chef::Config[:knife][:vcair_api_version],
33
+ :vcloud_director_show_progress => false
34
+ }
35
+
36
+ Fog::Compute.new(@auth_params)
37
+ rescue Excon::Errors::Unauthorized => e
38
+ error_message = "Connection failure, please check your username and password."
39
+ Chef::Log.error(error_message)
40
+ raise "#{e.message}. #{error_message}"
41
+ rescue Excon::Errors::SocketError => e
42
+ error_message = "Connection failure, please check your authentication URL."
43
+ Chef::Log.error(error_message)
44
+ raise "#{e.message}. #{error_message}"
45
+ end
46
+ end
47
+
48
+
49
+ def create_many_servers(num_servers, bootstrap_options, parallelizer)
50
+ parallelizer.parallelize(1.upto(num_servers)) do |i|
51
+ clean_bootstrap_options = Marshal.load(Marshal.dump(bootstrap_options)) # Prevent destructive operations on bootstrap_options.
52
+ vm=nil
53
+ begin
54
+ begin
55
+ instantiate(clean_bootstrap_options)
56
+ rescue Fog::Errors::Error => e
57
+ unless e.minor_error_code == "DUPLICATE_NAME"
58
+ # if it's already there, just use the current one
59
+ raise e
60
+ end
61
+ end
62
+
63
+ vapp = vdc.vapps.get_by_name(bootstrap_options[:name])
64
+ vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]}
65
+ update_customization(clean_bootstrap_options, vm)
66
+
67
+ if bootstrap_options[:cpus]
68
+ vm.cpu = bootstrap_options[:cpus]
69
+ end
70
+ if bootstrap_options[:memory]
71
+ vm.memory = bootstrap_options[:memory]
72
+ end
73
+ update_network(bootstrap_options, vapp, vm)
74
+
75
+ rescue Excon::Errors::BadRequest => e
76
+ response = Chef::JSONCompat.from_json(e.response.body)
77
+ if response['badRequest']['code'] == 400
78
+ message = "Bad request (400): #{response['badRequest']['message']}"
79
+ Chef::Log.error(message)
80
+ else
81
+ message = "Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}"
82
+ Chef::Log.error(message)
83
+ end
84
+ raise message
85
+ rescue Fog::Errors::Error => e
86
+ raise e.message
87
+ end
88
+
89
+ yield vm if block_given?
90
+ vm
91
+
92
+ end.to_a
93
+ end
94
+
95
+
96
+ def start_server(action_handler, machine_spec, server)
97
+
98
+ # If it is stopping, wait for it to get out of "stopping" transition state before starting
99
+ if server.status == 'stopping'
100
+ action_handler.report_progress "wait for #{machine_spec.name} (#{server.id} on #{driver_url}) to finish stopping ..."
101
+ # vCloud Air
102
+ # NOTE: vCloud Air Fog does not get server.status via http every time
103
+ server.wait_for { server.reload ; server.status != 'stopping' }
104
+ action_handler.report_progress "#{machine_spec.name} is now stopped"
105
+ end
106
+
107
+ # NOTE: vCloud Air Fog does not get server.status via http every time
108
+ server.reload
109
+
110
+ if server.status == 'off' or server.status != 'on'
111
+ action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
112
+ server.power_on
113
+ machine_spec.location['started_at'] = Time.now.to_i
114
+ end
115
+ machine_spec.save(action_handler)
116
+ end
117
+ end
118
+
119
+
120
+ def server_for(machine_spec)
121
+ if machine_spec.location
122
+ vapp = vdc.vapps.get_by_name(machine_spec.name)
123
+
124
+ server = unless vapp.nil?
125
+ unless vapp.vms.first.nil?
126
+ vapp.vms.find{|vm| vm.id == machine_spec.location['server_id'] }
127
+ end
128
+ end
129
+ else
130
+ nil
131
+ end
132
+ end
133
+
134
+ def servers_for(machine_specs)
135
+ result = {}
136
+ machine_specs.each do |machine_spec|
137
+ server_for(machine_spec)
138
+ end
139
+ result
140
+ end
141
+
142
+ def ssh_options_for(machine_spec, machine_options, server)
143
+ { auth_methods: [ 'password' ],
144
+ timeout: (machine_options[:ssh_timeout] || 600),
145
+ password: machine_options[:ssh_password]
146
+ }.merge(machine_options[:ssh_options] || {})
147
+ end
148
+
149
+ def create_ssh_transport(machine_spec, machine_options, server)
150
+ ssh_options = ssh_options_for(machine_spec, machine_options, server)
151
+ username = machine_spec.location['ssh_username'] || default_ssh_username
152
+ options = {}
153
+ if machine_spec.location[:sudo] || (!machine_spec.location.has_key?(:sudo) && username != 'root')
154
+ options[:prefix] = 'sudo '
155
+ end
156
+
157
+ remote_host = nil
158
+ # vCloud Air networking is funky
159
+ #if machine_options[:use_private_ip_for_ssh] # vCloud Air probably needs private ip for now
160
+ if server.ip_address
161
+ remote_host = server.ip_address
162
+ else
163
+ raise "Server #{server.id} has no private or public IP address!"
164
+ end
165
+
166
+ #Enable pty by default
167
+ options[:ssh_pty_enable] = true
168
+ options[:ssh_gateway] = machine_spec.location['ssh_gateway'] if machine_spec.location.has_key?('ssh_gateway')
169
+
170
+ Transport::SSH.new(remote_host, username, ssh_options, options, config)
171
+ end
172
+
173
+ def ready_machine(action_handler, machine_spec, machine_options)
174
+ server = server_for(machine_spec)
175
+ if server.nil?
176
+ raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
177
+ end
178
+
179
+ # Start the server if needed, and wait for it to start
180
+ start_server(action_handler, machine_spec, server)
181
+ wait_until_ready(action_handler, machine_spec, machine_options, server)
182
+
183
+ # Attach/detach floating IPs if necessary
184
+ # vCloud Air is funky for network. VM has to be powered off or you get this error:
185
+ # Primary NIC cannot be changed when the VM is not in Powered-off state
186
+ # See code in update_network()
187
+ #DISABLED: converge_floating_ips(action_handler, machine_spec, machine_options, server)
188
+
189
+ begin
190
+ wait_for_transport(action_handler, machine_spec, machine_options, server)
191
+ rescue Fog::Errors::TimeoutError
192
+ # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
193
+ if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
194
+ raise
195
+ else
196
+ # Sometimes (on EC2) the machine comes up but gets stuck or has
197
+ # some other problem. If this is the case, we restart the server
198
+ # to unstick it. Reboot covers a multitude of sins.
199
+ Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) was started but SSH did not come up. Rebooting machine in an attempt to unstick it ..."
200
+ restart_server(action_handler, machine_spec, server)
201
+ wait_until_ready(action_handler, machine_spec, machine_options, server)
202
+ wait_for_transport(action_handler, machine_spec, machine_options, server)
203
+ end
204
+ end
205
+
206
+ machine_for(machine_spec, machine_options, server)
207
+ end
208
+
209
+ def org
210
+ @org ||= compute.organizations.get_by_name(Chef::Config[:knife][:vcair_org])
211
+ end
212
+
213
+ def vdc
214
+ if Chef::Config[:knife][:vcair_vdc]
215
+ @vdc ||= org.vdcs.get_by_name(Chef::Config[:knife][:vcair_vdc])
216
+ else
217
+ @vdc ||= org.vdcs.first
218
+ end
219
+ end
220
+
221
+ def net
222
+ if Chef::Config[:knife][:vcair_net]
223
+ @net ||= org.networks.get_by_name(Chef::Config[:knife][:vcair_net])
224
+ else
225
+ # Grab first non-isolated (bridged, natRouted) network
226
+ @net ||= org.networks.find { |n| n if !n.fence_mode.match("isolated") }
227
+ end
228
+ end
229
+
230
+ def template(bootstrap_options)
231
+ # TODO: find by catalog item ID and/or NAME
232
+ # TODO: add option to search just public and/or private catalogs
233
+
234
+ #TODO: maybe make a hash for caching
235
+ org.catalogs.map do |cat|
236
+ #cat.catalog_items.get_by_name(config_value(:image))
237
+ cat.catalog_items.get_by_name(bootstrap_options[:image_name])
238
+ end.compact.first
239
+ end
240
+
241
+ def instantiate(bootstrap_options)
242
+ begin
243
+ #node_name = config_value(:chef_node_name)
244
+ node_name = bootstrap_options[:name]
245
+ template(bootstrap_options).instantiate(
246
+ node_name,
247
+ vdc_id: vdc.id,
248
+ network_id: net.id,
249
+ description: "id:#{node_name}")
250
+ #rescue CloudExceptions::ServerCreateError => e
251
+ rescue => e
252
+ raise e
253
+ end
254
+ end
255
+
256
+ # Create a WinRM transport for a vCloud Air Vapp VM instance
257
+ # @param [Hash] machine_spec Machine-spec hash
258
+ # @param [Hash] machine_options Machine options (from the recipe)
259
+ # @param [Fog::Compute::Server] server A Fog mapping to the AWS instance
260
+ # @return [ChefMetal::Transport::WinRM] A WinRM Transport object to talk to the server
261
+ def create_winrm_transport(machine_spec, machine_options, server)
262
+ port = machine_spec.location['winrm_port'] || 5985
263
+ endpoint = "http://#{server.ip_address}:#{port}/wsman"
264
+ type = :plaintext
265
+
266
+ # Use basic HTTP auth - this is required for the WinRM setup we
267
+ # are using
268
+ # TODO: Improve that and support different users
269
+ options = {
270
+ :user => 'Administrator',
271
+ :pass => machine_options[:winrm_options][:password],
272
+ :disable_sspi => true,
273
+ :basic_auth_only => true
274
+ }
275
+ Chef::Provisioning::Transport::WinRM.new(endpoint, type, options, {})
276
+ end
277
+
278
+ def update_customization(bootstrap_options, server)
279
+ ## Initialization before first power on.
280
+ custom=server.customization
281
+
282
+ if bootstrap_options[:customization_script]
283
+ custom.script = open(bootstrap_options[:customization_script]).read
284
+ end
285
+
286
+ bootstrap_options[:protocol] ||= case server.operating_system
287
+ when /Windows/
288
+ 'winrm'
289
+ else
290
+ 'ssh'
291
+ end
292
+ password = case bootstrap_options[:protocol]
293
+ when 'ssh'
294
+ bootstrap_options[:ssh_options][:password]
295
+ when 'winrm'
296
+ bootstrap_options[:winrm_options][:password]
297
+ end
298
+
299
+ if password
300
+ custom.admin_password = password
301
+ custom.admin_password_auto = false
302
+ custom.reset_password_required = false
303
+ else
304
+ # Password will be autogenerated
305
+ custom.admin_password_auto=true
306
+ # API will force password resets when auto is enabled
307
+ custom.reset_password_required = true
308
+ end
309
+
310
+ # TODO: Add support for admin_auto_logon to Fog
311
+ # c.admin_auto_logon_count = 100
312
+ # c.admin_auto_logon_enabled = true
313
+
314
+ # DNS and Windows want AlphaNumeric and dashes for hostnames
315
+ # Windows can only handle 15 character hostnames
316
+ # TODO: only change name for Windows!
317
+ #c.computer_name = config_value(:chef_node_name).gsub(/\W/,"-").slice(0..14)
318
+ custom.computer_name = bootstrap_options[:name].gsub(/\W/,"-").slice(0..14)
319
+ custom.enabled = true
320
+ custom.save
321
+ end
322
+
323
+ ## vCloud Air
324
+ ## TODO: make work with floating_ip junk currently used
325
+ ## NOTE: current vCloud Air networking changes require VM to be powered off
326
+ def update_network(bootstrap_options, vapp, vm)
327
+ ## TODO: allow user to specify network to connect to (see above net used)
328
+ # Define network connection for vm based on existing routed network
329
+
330
+ # vCloud Air inlining vapp() and vm()
331
+ #vapp = vdc.vapps.get_by_name(bootstrap_options[:name])
332
+ #vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]}
333
+ return if vm.ip_address != "" # return if ip address is set, as this isn't a new VM
334
+ nc = vapp.network_config.find { |netc| netc if netc[:networkName].match(net.name) }
335
+ networks_config = [nc]
336
+ section = {PrimaryNetworkConnectionIndex: 0}
337
+ section[:NetworkConnection] = networks_config.compact.each_with_index.map do |network, i|
338
+ connection = {
339
+ network: network[:networkName],
340
+ needsCustomization: true,
341
+ NetworkConnectionIndex: i,
342
+ IsConnected: true
343
+ }
344
+ ip_address = network[:ip_address]
345
+ ## TODO: support config options for allocation mode
346
+ #allocation_mode = network[:allocation_mode]
347
+ #allocation_mode = 'manual' if ip_address
348
+ #allocation_mode = 'dhcp' unless %w{dhcp manual pool}.include?(allocation_mode)
349
+ #allocation_mode = 'POOL'
350
+ #connection[:Dns1] = dns1 if dns1
351
+ allocation_mode = 'pool'
352
+ connection[:IpAddressAllocationMode] = allocation_mode.upcase
353
+ connection[:IpAddress] = ip_address if ip_address
354
+ connection
355
+ end
356
+
357
+ ## attach the network to the vm
358
+ nc_task = compute.put_network_connection_system_section_vapp(
359
+ vm.id,section).body
360
+ compute.process_task(nc_task)
361
+ end
362
+
363
+ def bootstrap_options_for(action_handler, machine_spec, machine_options)
364
+ bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
365
+
366
+ bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})
367
+ bootstrap_options[:name] ||= machine_spec.name
368
+
369
+ bootstrap_options = bootstrap_options.merge(machine_options.configs[1])
370
+ bootstrap_options
371
+ end
372
+
373
+ def destroy_machine(action_handler, machine_spec, machine_options)
374
+ server = server_for(machine_spec)
375
+ if server && server.status != 'archive' # TODO: does vCloud Air do archive?
376
+ action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['server_id']} at #{driver_url})" do
377
+ #NOTE: currently doing 1 vm for 1 vapp
378
+ vapp = vdc.vapps.get_by_name(machine_spec.name)
379
+ if vapp
380
+ vapp.power_off
381
+ vapp.undeploy
382
+ vapp.destroy
383
+ else
384
+ Chef::Log.warn "No VApp named '#{server_name}' was found."
385
+ end
386
+ end
387
+ end
388
+ machine_spec.location = nil
389
+ strategy = convergence_strategy_for(machine_spec, machine_options)
390
+ strategy.cleanup_convergence(action_handler, machine_spec)
391
+ end
392
+
393
+ def self.compute_options_for(provider, id, config)
394
+ new_compute_options = {}
395
+ new_compute_options[:provider] = 'vclouddirector'
396
+ new_config = { :driver_options => { :compute_options => new_compute_options }}
397
+ new_defaults = {
398
+ :driver_options => { :compute_options => {} },
399
+ :machine_options => { :bootstrap_options => {}, :ssh_options => {} }
400
+ }
401
+ result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
402
+
403
+ [result, id]
404
+ end
405
+ end
406
+ end
407
+ end
408
+ end
409
+ end