clc-chef-metal-vsphere 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.
- checksums.yaml +7 -0
- data/README.md +90 -0
- data/Rakefile +30 -0
- data/lib/chef_metal/driver_init/vsphere.rb +3 -0
- data/lib/chef_metal_vsphere.rb +13 -0
- data/lib/chef_metal_vsphere/version.rb +3 -0
- data/lib/chef_metal_vsphere/vsphere_driver.rb +464 -0
- data/lib/chef_metal_vsphere/vsphere_helpers.rb +250 -0
- data/lib/chef_metal_vsphere/vsphere_url.rb +31 -0
- data/spec/integration_tests/.gitignore +1 -0
- data/spec/integration_tests/vsphere_driver_spec.rb +123 -0
- data/spec/unit_tests/VsphereDriver_spec.rb +206 -0
- data/spec/unit_tests/VsphereUrl_spec.rb +60 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 634a2fe8b1d2735ef8ec84b818d05126732212b5
|
4
|
+
data.tar.gz: a0f9a967f9d358c8d8ad0fddee9de3f90dafb0ef
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 48acfe4b1a2317fdfa6c4496f292b085ba0c6a35d459ecf5d0934dcda28184497f65f3e2ec3979fbc95785af59af2bab2a1668db1b7a4184ccdac5f593e1d389
|
7
|
+
data.tar.gz: ceb714b3b94168d2eb98cb14a0b7c5c1502b30de47a5005cc5eb2b2ed377a0596f923c5e649bedb51c4516d74a1cc8247635e5270db3daa4b01747c39bd26c31
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
chef-metal-vsphere
|
2
|
+
==================
|
3
|
+
|
4
|
+
This is a [chef-metal](https://github.com/opscode/chef-metal) provisioner for [VMware vSphere](http://www.vmware.com/products/vsphere).
|
5
|
+
|
6
|
+
Currently, chef-metal-vsphere supports provisioning Unix/ssh guest VMs.
|
7
|
+
|
8
|
+
Try It Out
|
9
|
+
----------
|
10
|
+
|
11
|
+
### vSphere VM Template
|
12
|
+
|
13
|
+
Create or obtain a unix/linux VM template. The VM template must:
|
14
|
+
|
15
|
+
- be capable of installing Chef 11.8 or newer
|
16
|
+
- run vmware-tools on system boot (provides visiblity to ip address of the running VM)
|
17
|
+
- provide access via ssh
|
18
|
+
- provide a user account with NOPASSWD sudo
|
19
|
+
|
20
|
+
### Example recipe
|
21
|
+
require 'chef_metal_vsphere'
|
22
|
+
|
23
|
+
with_vsphere_provisioner vsphere_host: 'vcenter-host-name',
|
24
|
+
vsphere_insecure: true,
|
25
|
+
vsphere_user: 'you_user_name',
|
26
|
+
vsphere_password: 'your_mothers_maiden_name' # consider using a chef-vault
|
27
|
+
|
28
|
+
with_provisioner_options('bootstrap_options' => {
|
29
|
+
datacenter: 'datacenter_name',
|
30
|
+
cluster: 'cluster_name',
|
31
|
+
resource_pool: 'resource_pool_name', # often the same as the cluster_name
|
32
|
+
datastore: 'datastore_name',
|
33
|
+
template_name: 'name_of_template_vm', # may be a VM or a VM Template
|
34
|
+
template_folder: 'folder_containing_template_vm',
|
35
|
+
vm_folder: 'folder_to_clone_vms_into',
|
36
|
+
customization_spec: 'standard-config', # optional
|
37
|
+
|
38
|
+
ssh: { # net-ssh start() options
|
39
|
+
user: 'username_on_vm', # must have nopasswd sudo
|
40
|
+
password: 'name_of_your_first_pet', # consider using a chef-vault
|
41
|
+
port: 22,
|
42
|
+
auth_methods: ['password'],
|
43
|
+
user_known_hosts_file: '/dev/null', # don't do this in production
|
44
|
+
paranoid: false, # don't do this in production, either
|
45
|
+
keys: [ ], # consider using a chef-vault
|
46
|
+
keys_only: false
|
47
|
+
}
|
48
|
+
})
|
49
|
+
|
50
|
+
1.upto 2 do |n|
|
51
|
+
machine "metal_#{n}" do
|
52
|
+
action [:create]
|
53
|
+
|
54
|
+
## optionally add options per-machine customizations
|
55
|
+
add_provisioner_options('bootstrap_options' => {
|
56
|
+
num_cpus: n,
|
57
|
+
memory_mb: 1024 * n,
|
58
|
+
annotation: "metal_#{n} created by chef-metal-vsphere"
|
59
|
+
})
|
60
|
+
end
|
61
|
+
|
62
|
+
machine_file "/tmp/metal_#{n}.txt" do
|
63
|
+
machine "metal_#{n}"
|
64
|
+
content "Hello machine #{n}!"
|
65
|
+
end
|
66
|
+
|
67
|
+
machine "metal_#{n}" do
|
68
|
+
action [:stop]
|
69
|
+
end
|
70
|
+
|
71
|
+
machine "metal_#{n}" do
|
72
|
+
# note: no need to :stop before :delete
|
73
|
+
action [:delete]
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
This will clone your VM template to create two VMware Virtual Machines, "metal_1" and "metal_2", in the vSphere Folder specified by vm_folder, bootstrapped to an empty runlist. It will then stop (guest shutdown) and delete the vms.
|
79
|
+
|
80
|
+
Roadmap
|
81
|
+
-------
|
82
|
+
|
83
|
+
Check out [TODO.md](TODO.md)
|
84
|
+
|
85
|
+
Bugs and Contact
|
86
|
+
----------------
|
87
|
+
|
88
|
+
Please submit bugs at [chef-metal-vpshere](https://github.com/RallySoftware-cookbooks/chef-metal-vsphere), contact Brian Dupras on Twitter at @briandupras, email at rallysoftware-cookbooks@rallydev.com.
|
89
|
+
|
90
|
+
*Warning* if you get an rbvmomi error regarding VMODL::AnyType, add `gem 'nokogiri', '1.5.5'` to your dependencies.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
def gem_server
|
5
|
+
@gem_server ||= ENV['GEM_SERVER'] || 'rubygems.org'
|
6
|
+
end
|
7
|
+
|
8
|
+
module Bundler
|
9
|
+
class GemHelper
|
10
|
+
unless gem_server == 'rubygems.org'
|
11
|
+
unless method_defined?(:rubygem_push)
|
12
|
+
raise NoMethodError, "Monkey patching Bundler::GemHelper#rubygem_push failed: did the Bundler API change???"
|
13
|
+
end
|
14
|
+
|
15
|
+
def rubygem_push(path)
|
16
|
+
print "Username: "
|
17
|
+
username = STDIN.gets.chomp
|
18
|
+
print "Password: "
|
19
|
+
password = STDIN.gets.chomp
|
20
|
+
|
21
|
+
gem_server_url = "https://#{username}:#{password}@#{gem_server}/"
|
22
|
+
sh %{gem push #{path} --host #{gem_server_url}}
|
23
|
+
|
24
|
+
Bundler.ui.confirm "Pushed #{name} #{version} to #{gem_server}"
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "Monkey patched Bundler::GemHelper#rubygem_push to push to #{gem_server}."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'chef_metal'
|
2
|
+
require 'chef_metal_vsphere/vsphere_driver'
|
3
|
+
|
4
|
+
class Chef
|
5
|
+
module DSL
|
6
|
+
module Recipe
|
7
|
+
def with_vsphere_driver(driver_options, &block)
|
8
|
+
url, config = ChefMetalVsphere::VsphereDriver.canonicalize_url(nil, {:driver_options => driver_options})
|
9
|
+
with_driver url, driver_options, &block
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,464 @@
|
|
1
|
+
require 'chef'
|
2
|
+
require 'chef_metal/driver'
|
3
|
+
require 'cheffish/merged_config'
|
4
|
+
require 'chef_metal/machine/windows_machine'
|
5
|
+
require 'chef_metal/machine/unix_machine'
|
6
|
+
require 'chef_metal_vsphere/version'
|
7
|
+
require 'chef_metal_vsphere/vsphere_helpers'
|
8
|
+
require 'chef_metal_vsphere/vsphere_url'
|
9
|
+
|
10
|
+
module ChefMetalVsphere
|
11
|
+
# Provisions machines in vSphere.
|
12
|
+
class VsphereDriver < ChefMetal::Driver
|
13
|
+
include Chef::Mixin::ShellOut
|
14
|
+
include ChefMetalVsphere::Helpers
|
15
|
+
|
16
|
+
def self.from_url(driver_url, config)
|
17
|
+
VsphereDriver.new(driver_url, config)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.canonicalize_url(driver_url, config)
|
21
|
+
config = symbolize_keys(config)
|
22
|
+
new_defaults = {
|
23
|
+
:driver_options => { :connect_options => { :port => 443,
|
24
|
+
:use_ssl => true,
|
25
|
+
:insecure => false,
|
26
|
+
:path => '/sdk'
|
27
|
+
} },
|
28
|
+
:machine_options => { :start_timeout => 600,
|
29
|
+
:create_timeout => 600,
|
30
|
+
:bootstrap_options => { :ssh => { :port => 22,
|
31
|
+
:user => 'root' },
|
32
|
+
:key_name => 'metal_default',
|
33
|
+
:tags => {} } }
|
34
|
+
}
|
35
|
+
|
36
|
+
new_connect_options = {}
|
37
|
+
new_connect_options[:provider] = 'vsphere'
|
38
|
+
if !driver_url.nil?
|
39
|
+
uri = URI(driver_url)
|
40
|
+
new_connect_options[:host] = uri.host
|
41
|
+
new_connect_options[:port] = uri.port
|
42
|
+
if uri.path && uri.path.length > 0
|
43
|
+
new_connect_options[:path] = uri.path
|
44
|
+
end
|
45
|
+
new_connect_options[:use_ssl] = uri.use_ssl
|
46
|
+
new_connect_options[:insecure] = uri.insecure
|
47
|
+
end
|
48
|
+
new_connect_options = new_connect_options.merge(config[:driver_options])
|
49
|
+
|
50
|
+
new_config = { :driver_options => { :connect_options => new_connect_options }}
|
51
|
+
config = Cheffish::MergedConfig.new(new_config, config, new_defaults)
|
52
|
+
|
53
|
+
required_options = [:host, :user, :password]
|
54
|
+
missing_options = []
|
55
|
+
required_options.each do |opt|
|
56
|
+
missing_options << opt unless config[:driver_options][:connect_options].has_key?(opt)
|
57
|
+
end
|
58
|
+
unless missing_options.empty?
|
59
|
+
raise "missing required options: #{missing_options.join(', ')}"
|
60
|
+
end
|
61
|
+
|
62
|
+
url = URI::VsphereUrl.from_config(config[:driver_options][:connect_options]).to_s
|
63
|
+
[ url, config ]
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.symbolize_keys(h)
|
67
|
+
Hash === h ?
|
68
|
+
Hash[
|
69
|
+
h.map do |k, v|
|
70
|
+
[k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
|
71
|
+
end
|
72
|
+
] : h
|
73
|
+
end
|
74
|
+
|
75
|
+
# Create a new Vsphere provisioner.
|
76
|
+
#
|
77
|
+
# ## Parameters
|
78
|
+
# connect_options - hash of options to be passed to RbVmomi::VIM.connect
|
79
|
+
# :host - required - hostname of the vSphere API server
|
80
|
+
# :port - optional - port on the vSphere API server (default: 443)
|
81
|
+
# :path - optional - path on the vSphere API server (default: /sdk)
|
82
|
+
# :use_ssl - optional - true to use ssl in connection to vSphere API server (default: true)
|
83
|
+
# :insecure - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
|
84
|
+
# :user - required - user name to use in connection to vSphere API server
|
85
|
+
# :password - required - password to use in connection to vSphere API server
|
86
|
+
# :proxy_host - optional - http proxy host to use in connection to vSphere API server (default: none)
|
87
|
+
# :proxy_port - optional - http proxy port to use in connection to vSphere API server (default: none)
|
88
|
+
def initialize(driver_url, config)
|
89
|
+
super(driver_url, config)
|
90
|
+
@connect_options = config[:driver_options][:connect_options].to_hash
|
91
|
+
end
|
92
|
+
|
93
|
+
attr_reader :connect_options
|
94
|
+
|
95
|
+
# Acquire a machine, generally by provisioning it. Returns a Machine
|
96
|
+
# object pointing at the machine, allowing useful actions like setup,
|
97
|
+
# converge, execute, file and directory. The Machine object will have a
|
98
|
+
# "node" property which must be saved to the server (if it is any
|
99
|
+
# different from the original node object).
|
100
|
+
#
|
101
|
+
# ## Parameters
|
102
|
+
# action_handler - the action_handler object that is calling this method; this
|
103
|
+
# is generally a action_handler, but could be anything that can support the
|
104
|
+
# ChefMetal::ActionHandler interface (i.e., in the case of the test
|
105
|
+
# kitchen metal driver for acquiring and destroying VMs; see the base
|
106
|
+
# class for what needs providing).
|
107
|
+
# node - node object (deserialized json) representing this machine. If
|
108
|
+
# the node has a provisioner_options hash in it, these will be used
|
109
|
+
# instead of options provided by the provisioner. TODO compare and
|
110
|
+
# fail if different?
|
111
|
+
# node will have node['normal']['provisioner_options'] in it with any options.
|
112
|
+
# It is a hash with this format:
|
113
|
+
#
|
114
|
+
# -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
|
115
|
+
# -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
|
116
|
+
# :datacenter
|
117
|
+
# :resource_pool
|
118
|
+
# :cluster
|
119
|
+
# :datastore
|
120
|
+
# :template_name
|
121
|
+
# :template_folder
|
122
|
+
# :vm_folder
|
123
|
+
# :winrm {...} (not yet implemented)
|
124
|
+
# :ssh {...}
|
125
|
+
#
|
126
|
+
# Example bootstrap_options for vSphere:
|
127
|
+
# TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
|
128
|
+
# 'bootstrap_options' => {
|
129
|
+
# 'template_name' =>'centos6.small',
|
130
|
+
# 'template_folder' =>'Templates',
|
131
|
+
# 'vm_folder' => 'MyApp'
|
132
|
+
# }
|
133
|
+
#
|
134
|
+
# node['normal']['provisioner_output'] will be populated with information
|
135
|
+
# about the created machine. For vSphere, it is a hash with this
|
136
|
+
# format:
|
137
|
+
#
|
138
|
+
# -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
|
139
|
+
# -- vm_folder: name of the vSphere folder containing the VM
|
140
|
+
#
|
141
|
+
def allocate_machine(action_handler, machine_spec, machine_options)
|
142
|
+
if machine_spec.location
|
143
|
+
Chef::Log.warn "Checking to see if #{machine_spec.location} has been created..."
|
144
|
+
vm = vm_for(machine_spec)
|
145
|
+
if vm
|
146
|
+
Chef::Log.warn "returning existing machine"
|
147
|
+
return vm
|
148
|
+
else
|
149
|
+
Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.location['server_id']} on #{driver_url}) no longer exists. Recreating ..."
|
150
|
+
end
|
151
|
+
end
|
152
|
+
bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
|
153
|
+
vm = nil
|
154
|
+
|
155
|
+
if bootstrap_options[:ssh]
|
156
|
+
wait_on_port = bootstrap_options[:ssh][:port]
|
157
|
+
raise "Must specify bootstrap_options[:ssh][:port]" if wait_on_port.nil?
|
158
|
+
else
|
159
|
+
raise 'bootstrapping is currently supported for ssh only'
|
160
|
+
# wait_on_port = bootstrap_options['winrm']['port']
|
161
|
+
end
|
162
|
+
|
163
|
+
description = [ "creating machine #{machine_spec.name} on #{driver_url}" ]
|
164
|
+
bootstrap_options.each_pair { |key,value| description << " #{key}: #{value.inspect}" }
|
165
|
+
action_handler.report_progress description
|
166
|
+
|
167
|
+
# TODO compare new options to existing and fail if we cannot change it
|
168
|
+
# over (perhaps introduce a boolean that will force a delete and recreate
|
169
|
+
# in such a case)
|
170
|
+
|
171
|
+
vm = clone_vm(bootstrap_options)
|
172
|
+
|
173
|
+
machine_spec.location = {
|
174
|
+
'driver_url' => driver_url,
|
175
|
+
'driver_version' => VERSION,
|
176
|
+
'server_id' => vm.config.instanceUuid,
|
177
|
+
'is_windows' => is_windows?(vm),
|
178
|
+
'allocated_at' => Time.now.utc.to_s
|
179
|
+
}
|
180
|
+
machine_spec.location['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
|
181
|
+
%w(ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key|
|
182
|
+
machine_spec.location[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
|
183
|
+
end
|
184
|
+
|
185
|
+
action_handler.performed_action "machine #{machine_spec.name} created as #{machine_spec.location['server_id']} on #{driver_url}"
|
186
|
+
vm
|
187
|
+
end
|
188
|
+
|
189
|
+
def ready_machine(action_handler, machine_spec, machine_options)
|
190
|
+
start_machine(action_handler, machine_spec, machine_options)
|
191
|
+
vm = vm_for(machine_spec)
|
192
|
+
if vm.nil?
|
193
|
+
raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
|
194
|
+
end
|
195
|
+
|
196
|
+
wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
197
|
+
|
198
|
+
|
199
|
+
bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
|
200
|
+
is_static = false
|
201
|
+
if has_static_ip(bootstrap_options)
|
202
|
+
is_static = true
|
203
|
+
transport = transport_for(machine_spec, machine_options, vm)
|
204
|
+
if !transport.available?
|
205
|
+
Chef::Log.info "waiting for customizations to complete"
|
206
|
+
now = Time.now.utc
|
207
|
+
until (Time.now.utc - now) > 45 || (!vm.guest.ipAddress.nil? && vm.guest.ipAddress.length > 0) do
|
208
|
+
print "-"
|
209
|
+
sleep 5
|
210
|
+
end
|
211
|
+
if vm.guest.ipAddress.nil? || vm.guest.ipAddress.length == 0
|
212
|
+
Chef::Log.info "rebooting..."
|
213
|
+
if vm.guest.toolsRunningStatus != "guestToolsRunning"
|
214
|
+
Chef::Log.info "tools have stopped. current power state is #{vm.runtime.powerState} and tools state is #{vm.toolsRunningStatus}. powering up server..."
|
215
|
+
start_vm(vm)
|
216
|
+
else
|
217
|
+
restart_server(action_handler, machine_spec, vm)
|
218
|
+
end
|
219
|
+
now = Time.now.utc
|
220
|
+
until (Time.now.utc - now) > 60 || (!vm.guest.ipAddress.nil? && vm.guest.ipAddress.length > 0) do
|
221
|
+
print "-"
|
222
|
+
sleep 5
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
begin
|
229
|
+
wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
230
|
+
rescue Timeout::Error
|
231
|
+
# Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
|
232
|
+
if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
|
233
|
+
raise
|
234
|
+
else
|
235
|
+
Chef::Log.warn "Machine #{machine_spec.name} (#{server.config.instanceUuid} on #{driver_url}) was started but SSH did not come up. Rebooting machine in an attempt to unstick it ..."
|
236
|
+
restart_server(action_handler, machine_spec, vm)
|
237
|
+
wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
238
|
+
wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
machine = machine_for(machine_spec, machine_options, vm)
|
243
|
+
|
244
|
+
if is_static
|
245
|
+
if machine.execute_always('host google.com').exitstatus != 0
|
246
|
+
distro = machine.execute_always("lsb_release -i | sed -e 's/Distributor ID:\t//g'").stdout.strip
|
247
|
+
if distro == 'Ubuntu'
|
248
|
+
distro_version = (machine.execute_always("lsb_release -r | sed -e s/[^0-9.]//g")).stdout.strip.to_f
|
249
|
+
if distro_version>= 12.04
|
250
|
+
Chef::Log.info "Ubuntu version 12.04 or greater. Need to patch DNS."
|
251
|
+
interfaces_file = "/etc/network/interfaces"
|
252
|
+
nameservers = bootstrap_options[:customization_spec][:ipsettings][:dnsServerList].join(' ')
|
253
|
+
machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-search ; then echo 'dns-search #{machine_spec.name}' >> #{interfaces_file} ; fi")
|
254
|
+
machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-nameservers ; then echo 'dns-nameservers #{nameservers}' >> #{interfaces_file} ; fi")
|
255
|
+
machine.execute_always('/etc/init.d/networking restart')
|
256
|
+
machine.execute_always('echo "ACTION=="add", SUBSYSTEM=="cpu", ATTR{online}="1"" > /etc/udev/rules.d/99-vmware-cpuhotplug-udev.rules')
|
257
|
+
machine.execute_always('apt-get -qq update')
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
machine
|
264
|
+
end
|
265
|
+
|
266
|
+
# Connect to machine without acquiring it
|
267
|
+
def connect_to_machine(machine_spec, machine_options)
|
268
|
+
machine_for(machine_spec, machine_options)
|
269
|
+
end
|
270
|
+
|
271
|
+
def destroy_machine(action_handler, machine_spec, machine_options)
|
272
|
+
vm = vm_for(machine_spec)
|
273
|
+
if vm
|
274
|
+
action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
|
275
|
+
vm.PowerOffVM_Task.wait_for_completion unless vm.runtime.powerState == 'poweredOff'
|
276
|
+
vm.Destroy_Task.wait_for_completion
|
277
|
+
machine_spec.location = nil
|
278
|
+
end
|
279
|
+
end
|
280
|
+
strategy = convergence_strategy_for(machine_spec, machine_options)
|
281
|
+
begin
|
282
|
+
strategy.cleanup_convergence(action_handler, machine_spec)
|
283
|
+
rescue URI::InvalidURIError
|
284
|
+
raise unless Chef::Config.local_mode
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def stop_machine(action_handler, machine_spec, machine_options)
|
289
|
+
vm = vm_for(machine_spec)
|
290
|
+
if vm
|
291
|
+
action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
|
292
|
+
stop_vm(vm)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def start_machine(action_handler, machine_spec, machine_options)
|
298
|
+
vm = vm_for(machine_spec)
|
299
|
+
if vm
|
300
|
+
action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
|
301
|
+
bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
|
302
|
+
start_vm(vm, bootstrap_options[:ssh][:port])
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def restart_server(action_handler, machine_spec, vm)
|
308
|
+
action_handler.perform_action "restart machine #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url})" do
|
309
|
+
stop_machine(action_handler, machine_spec, vm)
|
310
|
+
start_vm(vm)
|
311
|
+
machine_spec.location['started_at'] = Time.now.utc.to_s
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
protected
|
316
|
+
|
317
|
+
def has_static_ip(bootstrap_options)
|
318
|
+
if bootstrap_options.has_key?(:customization_spec)
|
319
|
+
bootstrap_options = bootstrap_options[:customization_spec]
|
320
|
+
if bootstrap_options.has_key?(:ipsettings)
|
321
|
+
bootstrap_options = bootstrap_options[:ipsettings]
|
322
|
+
if bootstrap_options.has_key?(:ip)
|
323
|
+
return true
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
false
|
328
|
+
end
|
329
|
+
|
330
|
+
def remaining_wait_time(machine_spec, machine_options)
|
331
|
+
if machine_spec.location['started_at']
|
332
|
+
machine_options[:start_timeout] - (Time.now.utc - Time.parse(machine_spec.location['started_at']))
|
333
|
+
else
|
334
|
+
machine_options[:create_timeout] - (Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
339
|
+
if vm.guest.toolsRunningStatus != "guestToolsRunning"
|
340
|
+
perform_action = true
|
341
|
+
if action_handler.should_perform_actions
|
342
|
+
action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
|
343
|
+
until remaining_wait_time(machine_spec, machine_options) < 0 || (vm.guest.toolsRunningStatus == "guestToolsRunning" && (vm.guest.ipAddress.nil? || vm.guest.ipAddress.length > 0)) do
|
344
|
+
print "."
|
345
|
+
sleep 5
|
346
|
+
end
|
347
|
+
action_handler.report_progress "#{machine_spec.name} is now ready"
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def vm_for(machine_spec)
|
353
|
+
if machine_spec.location
|
354
|
+
find_vm_by_id(machine_spec.location['server_id'])
|
355
|
+
else
|
356
|
+
nil
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def bootstrap_options_for(machine_spec, machine_options)
|
361
|
+
bootstrap_options = machine_options[:bootstrap_options] || {}
|
362
|
+
bootstrap_options = bootstrap_options.to_hash
|
363
|
+
tags = {
|
364
|
+
'Name' => machine_spec.name,
|
365
|
+
'BootstrapId' => machine_spec.id,
|
366
|
+
'BootstrapHost' => Socket.gethostname,
|
367
|
+
'BootstrapUser' => Etc.getlogin
|
368
|
+
}
|
369
|
+
# User-defined tags override the ones we set
|
370
|
+
tags.merge!(bootstrap_options[:tags]) if bootstrap_options[:tags]
|
371
|
+
bootstrap_options.merge!({ :tags => tags })
|
372
|
+
bootstrap_options[:name] ||= machine_spec.name
|
373
|
+
bootstrap_options
|
374
|
+
end
|
375
|
+
|
376
|
+
def clone_vm(bootstrap_options)
|
377
|
+
vm_name = bootstrap_options[:name]
|
378
|
+
datacenter = bootstrap_options[:datacenter]
|
379
|
+
template_folder = bootstrap_options[:template_folder]
|
380
|
+
template_name = bootstrap_options[:template_name]
|
381
|
+
|
382
|
+
vm = find_vm(datacenter, bootstrap_options[:vm_folder], vm_name)
|
383
|
+
return vm if vm
|
384
|
+
|
385
|
+
vm_template = find_vm(datacenter, template_folder, template_name) or raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
|
386
|
+
|
387
|
+
do_vm_clone(datacenter, vm_template, vm_name, bootstrap_options)
|
388
|
+
end
|
389
|
+
|
390
|
+
def machine_for(machine_spec, machine_options, vm = nil)
|
391
|
+
vm ||= vm_for(machine_spec)
|
392
|
+
if !vm
|
393
|
+
raise "Server for node #{machine_spec.name} has not been created!"
|
394
|
+
end
|
395
|
+
|
396
|
+
if machine_spec.location['is_windows']
|
397
|
+
ChefMetal::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, vm), convergence_strategy_for(machine_spec, machine_options))
|
398
|
+
else
|
399
|
+
ChefMetal::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, vm), convergence_strategy_for(machine_spec, machine_options))
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def is_windows?(vm)
|
404
|
+
return false if vm.nil?
|
405
|
+
vm.guest.guestFamily == 'windowsGuest'
|
406
|
+
end
|
407
|
+
|
408
|
+
def convergence_strategy_for(machine_spec, machine_options)
|
409
|
+
require 'chef_metal/convergence_strategy/install_msi'
|
410
|
+
require 'chef_metal/convergence_strategy/install_cached'
|
411
|
+
require 'chef_metal/convergence_strategy/no_converge'
|
412
|
+
# Defaults
|
413
|
+
if !machine_spec.location
|
414
|
+
return ChefMetal::ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
|
415
|
+
end
|
416
|
+
|
417
|
+
if machine_spec.location['is_windows']
|
418
|
+
ChefMetal::ConvergenceStrategy::InstallMsi.new(machine_options[:convergence_options], config)
|
419
|
+
else
|
420
|
+
ChefMetal::ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
425
|
+
transport = transport_for(machine_spec, machine_options, vm)
|
426
|
+
if !transport.available?
|
427
|
+
if action_handler.should_perform_actions
|
428
|
+
action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
|
429
|
+
|
430
|
+
_self = self
|
431
|
+
|
432
|
+
until remaining_wait_time(machine_spec, machine_options) || transport.available? do
|
433
|
+
print "."
|
434
|
+
sleep 5
|
435
|
+
end
|
436
|
+
|
437
|
+
action_handler.report_progress "#{machine_spec.name} is now connectable"
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def transport_for(machine_spec, machine_options, vm)
|
443
|
+
if is_windows?(vm)
|
444
|
+
create_winrm_transport(machine_spec, machine_options, vm)
|
445
|
+
else
|
446
|
+
create_ssh_transport(machine_spec, machine_options, vm)
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
def create_winrm_transport(machine_spec, machine_options, vm)
|
451
|
+
raise 'Windows guest VMs are not yet supported'
|
452
|
+
end
|
453
|
+
|
454
|
+
def create_ssh_transport(machine_spec, machine_options, vm)
|
455
|
+
require 'chef_metal/transport/ssh'
|
456
|
+
bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
|
457
|
+
ssh_options = bootstrap_options[:ssh]
|
458
|
+
ssh_user = ssh_options[:user]
|
459
|
+
remote_host = has_static_ip(bootstrap_options) ? bootstrap_options[:customization_spec][:ipsettings][:ip] : vm.guest.ipaddress
|
460
|
+
|
461
|
+
ChefMetal::Transport::SSH.new(remote_host, ssh_user, ssh_options, {}, config)
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|