clc-chef-provisioning-vsphere 0.4.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/.gitignore +3 -0
- data/Gemfile +5 -0
- data/README.md +90 -0
- data/Rakefile +17 -0
- data/chef-provisioning-vsphere.gemspec +28 -0
- data/lib/chef/provisioning/driver_init/vsphere.rb +3 -0
- data/lib/chef/provisioning/vsphere_driver.rb +13 -0
- data/lib/chef/provisioning/vsphere_driver/driver.rb +526 -0
- data/lib/chef/provisioning/vsphere_driver/version.rb +3 -0
- data/lib/chef/provisioning/vsphere_driver/vsphere_helpers.rb +506 -0
- data/lib/chef/provisioning/vsphere_driver/vsphere_url.rb +31 -0
- data/lib/kitchen/driver/vsphere.rb +85 -0
- data/spec/integration_tests/.gitignore +1 -0
- data/spec/integration_tests/vsphere_driver_spec.rb +147 -0
- data/spec/unit_tests/VsphereDriver_spec.rb +206 -0
- data/spec/unit_tests/VsphereUrl_spec.rb +60 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ee129d486f718866a0155190131925bb583bb694
|
4
|
+
data.tar.gz: 6c9785aeeddd9172ed6474ff5b8ffa844021084c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 118c1e9179b0eedb57ff4556ce7397eb853bf70feb3ab6b833896395aafbab51c5e25c920ad4dafce05c5051fffb1765c192314da49dbd29b0d02ed67cc4d7d9
|
7
|
+
data.tar.gz: ba0cd2103bfa3e699bd4cb26a451ee0888fdd5dae7cb7175eb6e1df93ce1a80a0abd4f48f30aa677630f995326f7d9b4840d48b591c9b7ca1b19145351d89ffe
|
data/.gitignore
ADDED
data/Gemfile
ADDED
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,17 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
$:.unshift(File.dirname(__FILE__) + '/lib')
|
5
|
+
require 'chef/provisioning/vsphere_driver/version'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:unit) do |task|
|
8
|
+
task.pattern = 'spec/unit_tests/*_spec.rb'
|
9
|
+
task.rspec_opts = ['--color', '-f documentation']
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec::Core::RakeTask.new(:integration) do |task|
|
13
|
+
task.pattern = 'spec/integration_tests/*_spec.rb'
|
14
|
+
task.rspec_opts = ['--color', '-f documentation']
|
15
|
+
end
|
16
|
+
|
17
|
+
task :default => [:unit]
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/lib')
|
2
|
+
require 'chef/provisioning/vsphere_driver/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'clc-chef-provisioning-vsphere'
|
6
|
+
s.version = ChefProvisioningVsphere::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.extra_rdoc_files = ['README.md']
|
9
|
+
s.summary = 'Provisioner for creating vSphere VM instances in Chef Provisioning.'
|
10
|
+
s.description = s.summary
|
11
|
+
s.authors = ['CenturyLink Cloud']
|
12
|
+
s.email = 'matt.wrock@CenturyLinkCloud.com'
|
13
|
+
s.homepage = 'https://github.com/tier3/chef-provisioning-vsphere'
|
14
|
+
s.license = 'Apache 2.0'
|
15
|
+
|
16
|
+
s.bindir = 'bin'
|
17
|
+
s.executables = %w( )
|
18
|
+
|
19
|
+
s.require_path = 'lib'
|
20
|
+
s.files = `git ls-files -z`.split("\x0")
|
21
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
22
|
+
|
23
|
+
s.add_dependency 'rbvmomi', '~> 1.8.0', '>= 1.8.2'
|
24
|
+
s.add_dependency 'chef-provisioning', '~>1.1'
|
25
|
+
|
26
|
+
s.add_development_dependency 'rspec'
|
27
|
+
s.add_development_dependency 'rake'
|
28
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'chef/provisioning'
|
2
|
+
require 'chef/provisioning/vsphere_driver/driver'
|
3
|
+
|
4
|
+
class Chef
|
5
|
+
module DSL
|
6
|
+
module Recipe
|
7
|
+
def with_vsphere_driver(driver_options, &block)
|
8
|
+
url, config = ChefProvisioningVsphere::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,526 @@
|
|
1
|
+
require 'chef'
|
2
|
+
require 'cheffish/merged_config'
|
3
|
+
require 'chef/provisioning/driver'
|
4
|
+
require 'chef/provisioning/machine/windows_machine'
|
5
|
+
require 'chef/provisioning/machine/unix_machine'
|
6
|
+
require 'chef/provisioning/vsphere_driver/version'
|
7
|
+
require 'chef/provisioning/vsphere_driver/vsphere_helpers'
|
8
|
+
require 'chef/provisioning/vsphere_driver/vsphere_url'
|
9
|
+
|
10
|
+
module ChefProvisioningVsphere
|
11
|
+
# Provisions machines in vSphere.
|
12
|
+
class VsphereDriver < Chef::Provisioning::Driver
|
13
|
+
include Chef::Mixin::ShellOut
|
14
|
+
include ChefProvisioningVsphere::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
|
+
:ready_timeout => 300,
|
31
|
+
:bootstrap_options => { :ssh => { :port => 22,
|
32
|
+
:user => 'root' },
|
33
|
+
:key_name => 'metal_default',
|
34
|
+
:tags => {} } }
|
35
|
+
}
|
36
|
+
|
37
|
+
new_connect_options = {}
|
38
|
+
new_connect_options[:provider] = 'vsphere'
|
39
|
+
if !driver_url.nil?
|
40
|
+
uri = URI(driver_url)
|
41
|
+
new_connect_options[:host] = uri.host
|
42
|
+
new_connect_options[:port] = uri.port
|
43
|
+
if uri.path && uri.path.length > 0
|
44
|
+
new_connect_options[:path] = uri.path
|
45
|
+
end
|
46
|
+
new_connect_options[:use_ssl] = uri.use_ssl
|
47
|
+
new_connect_options[:insecure] = uri.insecure
|
48
|
+
end
|
49
|
+
new_connect_options = new_connect_options.merge(config[:driver_options])
|
50
|
+
|
51
|
+
new_config = { :driver_options => { :connect_options => new_connect_options }}
|
52
|
+
config = Cheffish::MergedConfig.new(new_config, config, new_defaults)
|
53
|
+
|
54
|
+
required_options = [:host, :user, :password]
|
55
|
+
missing_options = []
|
56
|
+
required_options.each do |opt|
|
57
|
+
missing_options << opt unless config[:driver_options][:connect_options].has_key?(opt)
|
58
|
+
end
|
59
|
+
unless missing_options.empty?
|
60
|
+
raise "missing required options: #{missing_options.join(', ')}"
|
61
|
+
end
|
62
|
+
|
63
|
+
url = URI::VsphereUrl.from_config(config[:driver_options][:connect_options]).to_s
|
64
|
+
[ url, config ]
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.symbolize_keys(h)
|
68
|
+
Hash === h ?
|
69
|
+
Hash[
|
70
|
+
h.map do |k, v|
|
71
|
+
[k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
|
72
|
+
end
|
73
|
+
] : h
|
74
|
+
end
|
75
|
+
|
76
|
+
# Create a new Vsphere provisioner.
|
77
|
+
#
|
78
|
+
# ## Parameters
|
79
|
+
# connect_options - hash of options to be passed to RbVmomi::VIM.connect
|
80
|
+
# :host - required - hostname of the vSphere API server
|
81
|
+
# :port - optional - port on the vSphere API server (default: 443)
|
82
|
+
# :path - optional - path on the vSphere API server (default: /sdk)
|
83
|
+
# :use_ssl - optional - true to use ssl in connection to vSphere API server (default: true)
|
84
|
+
# :insecure - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
|
85
|
+
# :user - required - user name to use in connection to vSphere API server
|
86
|
+
# :password - required - password to use in connection to vSphere API server
|
87
|
+
# :proxy_host - optional - http proxy host to use in connection to vSphere API server (default: none)
|
88
|
+
# :proxy_port - optional - http proxy port to use in connection to vSphere API server (default: none)
|
89
|
+
def initialize(driver_url, config)
|
90
|
+
super(driver_url, config)
|
91
|
+
@connect_options = config[:driver_options][:connect_options].to_hash
|
92
|
+
end
|
93
|
+
|
94
|
+
attr_reader :connect_options
|
95
|
+
|
96
|
+
# Acquire a machine, generally by provisioning it. Returns a Machine
|
97
|
+
# object pointing at the machine, allowing useful actions like setup,
|
98
|
+
# converge, execute, file and directory. The Machine object will have a
|
99
|
+
# "node" property which must be saved to the server (if it is any
|
100
|
+
# different from the original node object).
|
101
|
+
#
|
102
|
+
# ## Parameters
|
103
|
+
# action_handler - the action_handler object that is calling this method; this
|
104
|
+
# is generally a action_handler, but could be anything that can support the
|
105
|
+
# ChefMetal::ActionHandler interface (i.e., in the case of the test
|
106
|
+
# kitchen metal driver for acquiring and destroying VMs; see the base
|
107
|
+
# class for what needs providing).
|
108
|
+
# node - node object (deserialized json) representing this machine. If
|
109
|
+
# the node has a provisioner_options hash in it, these will be used
|
110
|
+
# instead of options provided by the provisioner. TODO compare and
|
111
|
+
# fail if different?
|
112
|
+
# node will have node['normal']['provisioner_options'] in it with any options.
|
113
|
+
# It is a hash with this format:
|
114
|
+
#
|
115
|
+
# -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
|
116
|
+
# -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
|
117
|
+
# :datacenter
|
118
|
+
# :resource_pool
|
119
|
+
# :cluster
|
120
|
+
# :datastore
|
121
|
+
# :template_name
|
122
|
+
# :template_folder
|
123
|
+
# :vm_folder
|
124
|
+
# :winrm {...} (not yet implemented)
|
125
|
+
# :ssh {...}
|
126
|
+
#
|
127
|
+
# Example bootstrap_options for vSphere:
|
128
|
+
# TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
|
129
|
+
# 'bootstrap_options' => {
|
130
|
+
# 'template_name' =>'centos6.small',
|
131
|
+
# 'template_folder' =>'Templates',
|
132
|
+
# 'vm_folder' => 'MyApp'
|
133
|
+
# }
|
134
|
+
#
|
135
|
+
# node['normal']['provisioner_output'] will be populated with information
|
136
|
+
# about the created machine. For vSphere, it is a hash with this
|
137
|
+
# format:
|
138
|
+
#
|
139
|
+
# -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
|
140
|
+
# -- vm_folder: name of the vSphere folder containing the VM
|
141
|
+
#
|
142
|
+
def allocate_machine(action_handler, machine_spec, machine_options)
|
143
|
+
if machine_spec.location
|
144
|
+
Chef::Log.warn "Checking to see if #{machine_spec.location} has been created..."
|
145
|
+
vm = vm_for(machine_spec)
|
146
|
+
if vm
|
147
|
+
Chef::Log.warn "returning existing machine"
|
148
|
+
return vm
|
149
|
+
else
|
150
|
+
Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.location['server_id']} on #{driver_url}) no longer exists. Recreating ..."
|
151
|
+
end
|
152
|
+
end
|
153
|
+
bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
|
154
|
+
vm = nil
|
155
|
+
|
156
|
+
if bootstrap_options[:ssh]
|
157
|
+
wait_on_port = bootstrap_options[:ssh][:port]
|
158
|
+
raise "Must specify bootstrap_options[:ssh][:port]" if wait_on_port.nil?
|
159
|
+
else
|
160
|
+
raise 'bootstrapping is currently supported for ssh only'
|
161
|
+
# wait_on_port = bootstrap_options['winrm']['port']
|
162
|
+
end
|
163
|
+
|
164
|
+
description = [ "creating machine #{machine_spec.name} on #{driver_url}" ]
|
165
|
+
bootstrap_options.each_pair { |key,value| description << " #{key}: #{value.inspect}" }
|
166
|
+
action_handler.report_progress description
|
167
|
+
|
168
|
+
vm = find_vm(bootstrap_options[:datacenter], bootstrap_options[:vm_folder], machine_spec.name)
|
169
|
+
server_id = nil
|
170
|
+
if vm
|
171
|
+
Chef::Log.info "machine already created: #{bootstrap_options[:vm_folder]}/#{machine_spec.name}"
|
172
|
+
else
|
173
|
+
vm = clone_vm(action_handler, bootstrap_options)
|
174
|
+
end
|
175
|
+
|
176
|
+
machine_spec.location = {
|
177
|
+
'driver_url' => driver_url,
|
178
|
+
'driver_version' => VERSION,
|
179
|
+
'server_id' => vm.config.instanceUuid,
|
180
|
+
'is_windows' => is_windows?(vm),
|
181
|
+
'allocated_at' => Time.now.utc.to_s,
|
182
|
+
'ipaddress' => vm.guest.ipAddress
|
183
|
+
}
|
184
|
+
machine_spec.location['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
|
185
|
+
%w(ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key|
|
186
|
+
machine_spec.location[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
|
187
|
+
end
|
188
|
+
|
189
|
+
action_handler.performed_action "machine #{machine_spec.name} created as #{machine_spec.location['server_id']} on #{driver_url}"
|
190
|
+
vm
|
191
|
+
end
|
192
|
+
|
193
|
+
def ready_machine(action_handler, machine_spec, machine_options)
|
194
|
+
start_machine(action_handler, machine_spec, machine_options)
|
195
|
+
vm = vm_for(machine_spec)
|
196
|
+
if vm.nil?
|
197
|
+
raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
|
198
|
+
end
|
199
|
+
|
200
|
+
wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
201
|
+
|
202
|
+
bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
|
203
|
+
|
204
|
+
transport = nil
|
205
|
+
vm_ip = ip_for(bootstrap_options, vm)
|
206
|
+
if !vm_ip.nil?
|
207
|
+
transport = transport_for(machine_spec, machine_options, vm)
|
208
|
+
end
|
209
|
+
|
210
|
+
if transport.nil? || !transport.available? || !(vm.guest.net.map { |net| net.ipAddress}.flatten).include?(vm_ip)
|
211
|
+
action_handler.report_progress "waiting up to #{machine_options[:ready_timeout]} seconds for customizations to complete and find #{vm_ip}"
|
212
|
+
now = Time.now.utc
|
213
|
+
|
214
|
+
until (Time.now.utc - now) > machine_options[:ready_timeout] || (vm.guest.net.map { |net| net.ipAddress}.flatten).include?(vm_ip) do
|
215
|
+
action_handler.report_progress "IP addresses on #{machine_spec.name} are #{vm.guest.net.map { |net| net.ipAddress}.flatten}"
|
216
|
+
vm_ip = ip_for(bootstrap_options, vm) if vm_ip.nil?
|
217
|
+
sleep 5
|
218
|
+
end
|
219
|
+
if !(vm.guest.net.map { |net| net.ipAddress}.flatten).include?(vm_ip)
|
220
|
+
action_handler.report_progress "rebooting..."
|
221
|
+
if vm.guest.toolsRunningStatus != "guestToolsRunning"
|
222
|
+
action_handler.report_progress "tools have stopped. current power state is #{vm.runtime.powerState} and tools state is #{vm.guest.toolsRunningStatus}. powering up server..."
|
223
|
+
start_vm(vm)
|
224
|
+
else
|
225
|
+
restart_server(action_handler, machine_spec, vm)
|
226
|
+
end
|
227
|
+
now = Time.now.utc
|
228
|
+
until (Time.now.utc - now) > 90 || (vm.guest.net.map { |net| net.ipAddress}.flatten).include?(vm_ip) do
|
229
|
+
vm_ip = ip_for(bootstrap_options, vm) if vm_ip.nil?
|
230
|
+
print "-"
|
231
|
+
sleep 5
|
232
|
+
end
|
233
|
+
end
|
234
|
+
machine_spec.location['ipaddress'] = vm.guest.ipAddress
|
235
|
+
action_handler.report_progress "IP address obtained: #{machine_spec.location['ipaddress']}"
|
236
|
+
end
|
237
|
+
|
238
|
+
domain = bootstrap_options[:customization_spec][:domain]
|
239
|
+
if vm.config.guestId.start_with?('win') && domain != 'local'
|
240
|
+
now = Time.now.utc
|
241
|
+
trimmed_name = machine_spec.name.byteslice(0,15)
|
242
|
+
expected_name="#{trimmed_name}.#{domain}"
|
243
|
+
action_handler.report_progress "waiting to domain join and be named #{expected_name}"
|
244
|
+
until (Time.now.utc - now) > 30 || (vm.guest.hostName == expected_name) do
|
245
|
+
print "."
|
246
|
+
sleep 5
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
begin
|
251
|
+
wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
252
|
+
rescue Timeout::Error
|
253
|
+
# Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
|
254
|
+
if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
|
255
|
+
raise
|
256
|
+
else
|
257
|
+
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 ..."
|
258
|
+
restart_server(action_handler, machine_spec, vm)
|
259
|
+
wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
260
|
+
wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
machine = machine_for(machine_spec, machine_options, vm)
|
265
|
+
|
266
|
+
new_nics = add_extra_nic(action_handler, vm_template_for(bootstrap_options), bootstrap_options, vm)
|
267
|
+
if is_windows?(vm) && !new_nics.nil?
|
268
|
+
new_nics.each do |nic|
|
269
|
+
machine.execute_always("Disable-Netadapter -Name '#{nic.device.deviceInfo.label}' -Confirm:$false")
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
if has_static_ip(bootstrap_options) && !is_windows?(vm)
|
274
|
+
setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
|
275
|
+
end
|
276
|
+
|
277
|
+
machine
|
278
|
+
end
|
279
|
+
|
280
|
+
# Connect to machine without acquiring it
|
281
|
+
def connect_to_machine(machine_spec, machine_options)
|
282
|
+
machine_for(machine_spec, machine_options)
|
283
|
+
end
|
284
|
+
|
285
|
+
def destroy_machine(action_handler, machine_spec, machine_options)
|
286
|
+
vm = vm_for(machine_spec)
|
287
|
+
if vm
|
288
|
+
action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
|
289
|
+
begin
|
290
|
+
vm.PowerOffVM_Task.wait_for_completion unless vm.runtime.powerState == 'poweredOff'
|
291
|
+
vm.Destroy_Task.wait_for_completion
|
292
|
+
rescue RbVmomi::Fault => fault
|
293
|
+
raise fault unless fault.fault.class.wsdl_name == "ManagedObjectNotFound"
|
294
|
+
ensure
|
295
|
+
machine_spec.location = nil
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
strategy = convergence_strategy_for(machine_spec, machine_options)
|
300
|
+
strategy.cleanup_convergence(action_handler, machine_spec)
|
301
|
+
end
|
302
|
+
|
303
|
+
def stop_machine(action_handler, machine_spec, machine_options)
|
304
|
+
vm = vm_for(machine_spec)
|
305
|
+
if vm
|
306
|
+
action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
|
307
|
+
stop_vm(vm)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def start_machine(action_handler, machine_spec, machine_options)
|
313
|
+
vm = vm_for(machine_spec)
|
314
|
+
if vm
|
315
|
+
action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
|
316
|
+
bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
|
317
|
+
start_vm(vm, bootstrap_options[:ssh][:port])
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def restart_server(action_handler, machine_spec, vm)
|
323
|
+
action_handler.perform_action "restart machine #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url})" do
|
324
|
+
stop_machine(action_handler, machine_spec, vm)
|
325
|
+
start_vm(vm)
|
326
|
+
machine_spec.location['started_at'] = Time.now.utc.to_s
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
protected
|
331
|
+
|
332
|
+
def setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
|
333
|
+
host_lookup = machine.execute_always('host google.com')
|
334
|
+
if host_lookup.exitstatus != 0
|
335
|
+
if host_lookup.stdout.include?("setlocale: LC_ALL")
|
336
|
+
machine.execute_always('locale-gen en_US && update-locale LANG=en_US')
|
337
|
+
end
|
338
|
+
distro = machine.execute_always("lsb_release -i | sed -e 's/Distributor ID://g'").stdout.strip
|
339
|
+
Chef::Log.info "Found distro:#{distro}"
|
340
|
+
if distro == 'Ubuntu'
|
341
|
+
distro_version = (machine.execute_always("lsb_release -r | sed -e s/[^0-9.]//g")).stdout.strip.to_f
|
342
|
+
Chef::Log.info "Found distro version:#{distro_version}"
|
343
|
+
if distro_version>= 12.04
|
344
|
+
Chef::Log.info "Ubuntu version 12.04 or greater. Need to patch DNS."
|
345
|
+
interfaces_file = "/etc/network/interfaces"
|
346
|
+
nameservers = bootstrap_options[:customization_spec][:ipsettings][:dnsServerList].join(' ')
|
347
|
+
machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-search ; then echo 'dns-search #{bootstrap_options[:customization_spec][:domain]}' >> #{interfaces_file} ; fi")
|
348
|
+
machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-nameservers ; then echo 'dns-nameservers #{nameservers}' >> #{interfaces_file} ; fi")
|
349
|
+
machine.execute_always('/etc/init.d/networking restart')
|
350
|
+
machine.execute_always('apt-get -qq update')
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def has_static_ip(bootstrap_options)
|
357
|
+
if bootstrap_options.has_key?(:customization_spec)
|
358
|
+
bootstrap_options = bootstrap_options[:customization_spec]
|
359
|
+
if bootstrap_options.has_key?(:ipsettings)
|
360
|
+
bootstrap_options = bootstrap_options[:ipsettings]
|
361
|
+
if bootstrap_options.has_key?(:ip)
|
362
|
+
return true
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
false
|
367
|
+
end
|
368
|
+
|
369
|
+
def remaining_wait_time(machine_spec, machine_options)
|
370
|
+
if machine_spec.location['started_at']
|
371
|
+
machine_options[:start_timeout] - (Time.now.utc - Time.parse(machine_spec.location['started_at']))
|
372
|
+
else
|
373
|
+
machine_options[:create_timeout] - (Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
def wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
378
|
+
if vm.guest.toolsRunningStatus != "guestToolsRunning"
|
379
|
+
perform_action = true
|
380
|
+
if action_handler.should_perform_actions
|
381
|
+
action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
|
382
|
+
until remaining_wait_time(machine_spec, machine_options) < 0 || (vm.guest.toolsRunningStatus == "guestToolsRunning" && (vm.guest.ipAddress.nil? || vm.guest.ipAddress.length > 0)) do
|
383
|
+
print "."
|
384
|
+
sleep 5
|
385
|
+
end
|
386
|
+
action_handler.report_progress "#{machine_spec.name} is now ready"
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def vm_for(machine_spec)
|
392
|
+
if machine_spec.location
|
393
|
+
find_vm_by_id(machine_spec.location['server_id'])
|
394
|
+
else
|
395
|
+
nil
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
def bootstrap_options_for(machine_spec, machine_options)
|
400
|
+
bootstrap_options = machine_options[:bootstrap_options] || {}
|
401
|
+
bootstrap_options = bootstrap_options.to_hash
|
402
|
+
tags = {
|
403
|
+
'Name' => machine_spec.name,
|
404
|
+
'BootstrapId' => machine_spec.id,
|
405
|
+
'BootstrapHost' => Socket.gethostname,
|
406
|
+
'BootstrapUser' => Etc.getlogin
|
407
|
+
}
|
408
|
+
# User-defined tags override the ones we set
|
409
|
+
tags.merge!(bootstrap_options[:tags]) if bootstrap_options[:tags]
|
410
|
+
bootstrap_options.merge!({ :tags => tags })
|
411
|
+
bootstrap_options[:name] ||= machine_spec.name
|
412
|
+
bootstrap_options
|
413
|
+
end
|
414
|
+
|
415
|
+
def clone_vm(action_handler, bootstrap_options)
|
416
|
+
vm_name = bootstrap_options[:name]
|
417
|
+
datacenter = bootstrap_options[:datacenter]
|
418
|
+
|
419
|
+
vm = find_vm(datacenter, bootstrap_options[:vm_folder], vm_name)
|
420
|
+
return vm if vm
|
421
|
+
|
422
|
+
vm_template = vm_template_for(bootstrap_options)
|
423
|
+
|
424
|
+
do_vm_clone(action_handler, datacenter, vm_template, vm_name, bootstrap_options)
|
425
|
+
end
|
426
|
+
|
427
|
+
def vm_template_for(bootstrap_options)
|
428
|
+
datacenter = bootstrap_options[:datacenter]
|
429
|
+
template_folder = bootstrap_options[:template_folder]
|
430
|
+
template_name = bootstrap_options[:template_name]
|
431
|
+
find_vm(datacenter, template_folder, template_name) or raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
|
432
|
+
end
|
433
|
+
|
434
|
+
def machine_for(machine_spec, machine_options, vm = nil)
|
435
|
+
vm ||= vm_for(machine_spec)
|
436
|
+
if !vm
|
437
|
+
raise "Server for node #{machine_spec.name} has not been created!"
|
438
|
+
end
|
439
|
+
|
440
|
+
if machine_spec.location['is_windows']
|
441
|
+
Chef::Provisioning::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, vm), convergence_strategy_for(machine_spec, machine_options))
|
442
|
+
else
|
443
|
+
Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, vm), convergence_strategy_for(machine_spec, machine_options))
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
def is_windows?(vm)
|
448
|
+
return false if vm.nil?
|
449
|
+
vm.config.guestId.start_with?('win')
|
450
|
+
end
|
451
|
+
|
452
|
+
def convergence_strategy_for(machine_spec, machine_options)
|
453
|
+
require 'chef/provisioning/convergence_strategy/install_msi'
|
454
|
+
require 'chef/provisioning/convergence_strategy/install_cached'
|
455
|
+
require 'chef/provisioning/convergence_strategy/no_converge'
|
456
|
+
# Defaults
|
457
|
+
if !machine_spec.location
|
458
|
+
return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
|
459
|
+
end
|
460
|
+
|
461
|
+
if machine_spec.location['is_windows']
|
462
|
+
Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(machine_options[:convergence_options], config)
|
463
|
+
else
|
464
|
+
Chef::Provisioning::ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
469
|
+
transport = transport_for(machine_spec, machine_options, vm)
|
470
|
+
if !transport.available?
|
471
|
+
if action_handler.should_perform_actions
|
472
|
+
action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
|
473
|
+
|
474
|
+
until remaining_wait_time(machine_spec, machine_options) < 0 || transport.available? do
|
475
|
+
print "."
|
476
|
+
sleep 5
|
477
|
+
end
|
478
|
+
|
479
|
+
action_handler.report_progress "#{machine_spec.name} is now connectable"
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def transport_for(machine_spec, machine_options, vm)
|
485
|
+
if is_windows?(vm)
|
486
|
+
create_winrm_transport(machine_spec, machine_options, vm)
|
487
|
+
else
|
488
|
+
create_ssh_transport(machine_spec, machine_options, vm)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
def create_winrm_transport(machine_spec, machine_options, vm)
|
493
|
+
require 'chef/provisioning/transport/winrm'
|
494
|
+
bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
|
495
|
+
ssh_options = bootstrap_options[:ssh]
|
496
|
+
remote_host = machine_spec.location['ipaddress'] || ip_for(bootstrap_options, vm)
|
497
|
+
|
498
|
+
winrm_options = {:user => "#{ssh_options[:user]}", :pass => ssh_options[:password]}
|
499
|
+
if ssh_options[:user].include?("\\")
|
500
|
+
winrm_options[:disable_sspi] = true
|
501
|
+
else
|
502
|
+
winrm_options[:basic_auth_only] = true
|
503
|
+
end
|
504
|
+
|
505
|
+
Chef::Provisioning::Transport::WinRM.new("http://#{remote_host}:5985/wsman", :plaintext, winrm_options, config)
|
506
|
+
end
|
507
|
+
|
508
|
+
def create_ssh_transport(machine_spec, machine_options, vm)
|
509
|
+
require 'chef/provisioning/transport/ssh'
|
510
|
+
bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
|
511
|
+
ssh_options = bootstrap_options[:ssh]
|
512
|
+
ssh_user = ssh_options[:user]
|
513
|
+
remote_host = machine_spec.location['ipaddress'] || ip_for(bootstrap_options, vm)
|
514
|
+
|
515
|
+
Chef::Provisioning::Transport::SSH.new(remote_host, ssh_user, ssh_options, {}, config)
|
516
|
+
end
|
517
|
+
|
518
|
+
def ip_for(bootstrap_options, vm)
|
519
|
+
if has_static_ip(bootstrap_options)
|
520
|
+
bootstrap_options[:customization_spec][:ipsettings][:ip]
|
521
|
+
else
|
522
|
+
vm.guest.ipAddress
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|