chef-metal-vsphere 0.2.0 → 0.3.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,310 +0,0 @@
1
- require 'chef_metal/provisioner'
2
- require 'chef_metal/machine/windows_machine'
3
- require 'chef_metal/machine/unix_machine'
4
- require 'chef_metal/convergence_strategy/install_msi'
5
- require 'chef_metal/convergence_strategy/install_cached'
6
- require 'chef_metal/transport/ssh'
7
- require 'chef_metal_vsphere/version'
8
- require 'rbvmomi'
9
- require 'chef_metal_vsphere/vsphere_helpers'
10
-
11
- module ChefMetalVsphere
12
- # Provisions machines in vSphere.
13
- class VsphereProvisioner < ChefMetal::Provisioner
14
-
15
- include Chef::Mixin::ShellOut
16
- include ChefMetalVsphere::Helpers
17
-
18
- def self.inflate(node)
19
- url = node['normal']['provisioner_output']['provisioner_url']
20
- scheme, provider, id = url.split(':', 3)
21
- VsphereProvisioner.new({ :provider => provider }, id)
22
- end
23
-
24
- # Create a new Vsphere provisioner.
25
- #
26
- # ## Parameters
27
- # connect_options - hash of options to be passed to RbVmomi::VIM.connect
28
- # :vsphere_host - required - hostname of the vSphere API server
29
- # :vsphere_port - optional - port on the vSphere API server (default: 443)
30
- # :vshere_path - optional - path on the vSphere API server (default: /sdk)
31
- # :vsphere_ssl - optional - true to use ssl in connection to vSphere API server (default: true)
32
- # :vsphere_insecure - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
33
- # :vsphere_user - required - user name to use in connection to vSphere API server
34
- # :vsphere_password - required - password to use in connection to vSphere API server
35
- # :proxy_host - optional - http proxy host to use in connection to vSphere API server (default: none)
36
- # :proxy_port - optional - http proxy port to use in connection to vSphere API server (default: none)
37
- def initialize(connect_options)
38
- connect_options = stringify_keys(connect_options)
39
- default_connect_options = {
40
- 'vsphere_port' => 443,
41
- 'vsphere_ssl' => true,
42
- 'vsphere_insecure' => false,
43
- 'vsphere_path' => '/sdk'
44
- }
45
-
46
- @connect_options = default_connect_options.merge(connect_options)
47
-
48
- required_options = %w( vsphere_host vsphere_user vsphere_password )
49
- missing_options = []
50
- required_options.each do |opt|
51
- missing_options << opt unless @connect_options.has_key?(opt)
52
- end
53
- unless missing_options.empty?
54
- raise "missing required options: #{missing_options.join(', ')}"
55
- end
56
-
57
- # test vim connection
58
- vim || raise("cannot connect to [#{provisioner_url}]")
59
-
60
- @connect_options
61
- end
62
-
63
- attr_reader :connect_options
64
-
65
- # Acquire a machine, generally by provisioning it. Returns a Machine
66
- # object pointing at the machine, allowing useful actions like setup,
67
- # converge, execute, file and directory. The Machine object will have a
68
- # "node" property which must be saved to the server (if it is any
69
- # different from the original node object).
70
- #
71
- # ## Parameters
72
- # action_handler - the action_handler object that is calling this method; this
73
- # is generally a action_handler, but could be anything that can support the
74
- # ChefMetal::ActionHandler interface (i.e., in the case of the test
75
- # kitchen metal driver for acquiring and destroying VMs; see the base
76
- # class for what needs providing).
77
- # node - node object (deserialized json) representing this machine. If
78
- # the node has a provisioner_options hash in it, these will be used
79
- # instead of options provided by the provisioner. TODO compare and
80
- # fail if different?
81
- # node will have node['normal']['provisioner_options'] in it with any options.
82
- # It is a hash with this format:
83
- #
84
- # -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
85
- # -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
86
- # :datacenter
87
- # :resource_pool
88
- # :cluster
89
- # :datastore
90
- # :template_name
91
- # :template_folder
92
- # :vm_folder
93
- # :winrm {...} (not yet implemented)
94
- # :ssh {...}
95
- #
96
- # Example bootstrap_options for vSphere:
97
- # TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
98
- # 'bootstrap_options' => {
99
- # 'template_name' =>'centos6.small',
100
- # 'template_folder' =>'Templates',
101
- # 'vm_folder' => 'MyApp'
102
- # }
103
- #
104
- # node['normal']['provisioner_output'] will be populated with information
105
- # about the created machine. For vSphere, it is a hash with this
106
- # format:
107
- #
108
- # -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
109
- # -- vm_folder: name of the vSphere folder containing the VM
110
- #
111
- def acquire_machine(action_handler, node)
112
- # Set up the provisioner output
113
- provisioner_options = stringify_keys(node['normal']['provisioner_options'])
114
-
115
- vm_name = node['name']
116
- old_provisioner_output = node['normal']['provisioner_output']
117
- node['normal']['provisioner_output'] = provisioner_output = {
118
- 'provisioner_url' => provisioner_url,
119
- 'vm_name' => vm_name,
120
- 'bootstrap_options' => provisioner_options['bootstrap_options']
121
- }
122
-
123
- bootstrap_options = node['normal']['provisioner_output']['bootstrap_options']
124
- vm_folder = bootstrap_options['vm_folder']
125
-
126
- if bootstrap_options['ssh']
127
- wait_on_port = bootstrap_options['ssh']['port']
128
- raise "Must specify bootstrap_options[:ssh][:port]" if wait_on_port.nil?
129
- else
130
- raise 'bootstrapping is currently supported for ssh only'
131
- # wait_on_port = bootstrap_options['winrm']['port']
132
- end
133
-
134
- # TODO compare new options to existing and fail if we cannot change it
135
- # over (perhaps introduce a boolean that will force a delete and recreate
136
- # in such a case)
137
-
138
- vm = vm_instance(action_handler, node)
139
-
140
- unless vm_started?(vm, wait_on_port)
141
- action_handler.perform_action "Start VM and wait for port #{wait_on_port}" do
142
- start_vm(vm, wait_on_port)
143
- end
144
- end
145
-
146
- machine = machine_for(node)
147
-
148
- machine
149
- end
150
-
151
- # Connect to machine without acquiring it
152
- def connect_to_machine(node)
153
- machine_for(node)
154
- end
155
-
156
- def delete_machine(action_handler, node)
157
- if node['normal'] && node['normal']['provisioner_output']
158
- provisioner_output = node['normal']['provisioner_output']
159
- else
160
- provisioner_output = {}
161
- end
162
- vm_name = provisioner_output['vm_name'] || node['name']
163
- vm_folder = provisioner_output['bootstrap_options']['vm_folder']
164
- vm = vm_for(node)
165
-
166
- unless vm.nil?
167
- action_handler.perform_action "Delete VM [#{vm_folder}/#{vm_name}]" do
168
- vm.PowerOffVM_Task.wait_for_completion unless vm.runtime.powerState == 'poweredOff'
169
- vm.Destroy_Task.wait_for_completion
170
- end
171
- end
172
- end
173
-
174
- def stop_machine(action_handler, node)
175
- if node['normal'] && node['normal']['provisioner_output']
176
- provisioner_output = node['normal']['provisioner_output']
177
- else
178
- provisioner_output = {}
179
- end
180
- vm_name = provisioner_output['vm_name'] || node['name']
181
- vm_folder = provisioner_output['bootstrap_options']['vm_folder']
182
- vm = vm_for(node)
183
-
184
- unless vm_stopped?(vm)
185
- action_handler.perform_action "Shutdown guest OS and power off VM [#{vm_folder}/#{vm_name}]" do
186
- stop_vm(vm)
187
- end
188
- end
189
- end
190
-
191
- protected
192
-
193
- def provisioner_url
194
- "vsphere://#{connect_options['vsphere_host']}:#{connect_options['vsphere_port']}#{connect_options['vsphere_path']}?ssl=#{connect_options['vsphere_ssl']}&insecure=#{connect_options['vsphere_insecure']}"
195
- end
196
-
197
- def vm_instance(action_handler, node)
198
- bootstrap_options = node['normal']['provisioner_output']['bootstrap_options']
199
-
200
- datacenter = bootstrap_options['datacenter']
201
- vm_name = node['normal']['provisioner_output']['vm_name']
202
- vm_folder = bootstrap_options['vm_folder']
203
-
204
- vm = find_vm(datacenter, vm_folder, vm_name)
205
- return vm unless vm.nil?
206
-
207
- action_handler.perform_action "Clone a new VM instance from [#{bootstrap_options['template_folder']}/#{bootstrap_options['template_name']}]" do
208
- vm = clone_vm(vm_name, bootstrap_options)
209
- end
210
-
211
- vm
212
- end
213
-
214
- def clone_vm(vm_name, bootstrap_options)
215
- datacenter = bootstrap_options['datacenter']
216
- template_folder = bootstrap_options['template_folder']
217
- template_name = bootstrap_options['template_name']
218
-
219
- vm_template = find_vm(datacenter, template_folder, template_name) or raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
220
-
221
- vm = do_vm_clone(datacenter, vm_template, vm_name, bootstrap_options)
222
- end
223
-
224
- def machine_for(node)
225
- vm = vm_for(node) or raise "VM for node #{node['name']} has not been created!"
226
-
227
- if is_windows?(vm)
228
- ChefMetal::Machine::WindowsMachine.new(node, transport_for(node), convergence_strategy_for(node))
229
- else
230
- ChefMetal::Machine::UnixMachine.new(node, transport_for(node), convergence_strategy_for(node))
231
- end
232
- end
233
-
234
-
235
- def vm_for(node)
236
- bootstrap_options = node['normal']['provisioner_output']['bootstrap_options']
237
- datacenter = bootstrap_options['datacenter']
238
- vm_folder = bootstrap_options['vm_folder']
239
- vm_name = node['normal']['provisioner_output']['vm_name']
240
- vm = find_vm(datacenter, vm_folder, vm_name)
241
- vm
242
- end
243
-
244
- def is_windows?(vm)
245
- return false if vm.nil?
246
- vm.guest.guestFamily == 'windowsGuest'
247
- end
248
-
249
- def convergence_strategy_for(node)
250
- if is_windows?(vm_for(node))
251
- @windows_convergence_strategy ||= begin
252
- options = {}
253
- provisioner_options = node['normal']['provisioner_options'] || {}
254
- options[:chef_client_timeout] = provisioner_options['chef_client_timeout'] if provisioner_options.has_key?('chef_client_timeout')
255
- ChefMetal::ConvergenceStrategy::InstallMsi.new(options)
256
- end
257
- else
258
- @unix_convergence_strategy ||= begin
259
- options = {}
260
- provisioner_options = node['normal']['provisioner_options'] || {}
261
- options[:chef_client_timeout] = provisioner_options['chef_client_timeout'] if provisioner_options.has_key?('chef_client_timeout')
262
- ChefMetal::ConvergenceStrategy::InstallCached.new(options)
263
- end
264
- end
265
- end
266
-
267
- def transport_for(node)
268
- if is_windows?(vm_for(node))
269
- create_winrm_transport(node)
270
- else
271
- create_ssh_transport(node)
272
- end
273
- end
274
-
275
- def create_winrm_transport(node)
276
- raise 'Windows guest VMs are not yet supported'
277
- end
278
-
279
- def create_ssh_transport(node)
280
- bootstrap_options = node['normal']['provisioner_output']['bootstrap_options']
281
- vm = vm_for(node) or raise "VM for node #{node['name']} has not been created!"
282
-
283
- hostname = vm.guest.ipAddress
284
- ssh_user = bootstrap_options['ssh']['user']
285
- ssh_options = symbolize_keys(bootstrap_options['ssh'])
286
- transport_options = {
287
- :prefix => 'sudo '
288
- }
289
- ChefMetal::Transport::SSH.new(hostname, ssh_user, ssh_options, transport_options)
290
- end
291
-
292
- def stringify_keys(h)
293
- Hash === h ?
294
- Hash[
295
- h.map do |k, v|
296
- [k.respond_to?(:to_s) ? k.to_s : k, stringify_keys(v)]
297
- end
298
- ] : h
299
- end
300
-
301
- def symbolize_keys(h)
302
- Hash === h ?
303
- Hash[
304
- h.map do |k, v|
305
- [k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
306
- end
307
- ] : h
308
- end
309
- end
310
- end