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