chef-provisioning-fog 0.14.0 → 0.15.0

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