chef-provisioning-vsphere 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +5 -0
- data/README.md +79 -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: 0d79d0a2cda2ae2c35a26785805165a0449db2ab
|
4
|
+
data.tar.gz: f70880364d9a538115a8b7eeae608971ea9e4246
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 573d1b2bd5a273e6edd62728e1dd7054f6859d7663f6533b0ad69e89b3af02f45fecd6515066751d3b5ac9afa71041bf9941c5c71aedecca1551974c97d4ccf8
|
7
|
+
data.tar.gz: 2ba913e525d49af3799a44aebca5131ebb4eac8f369158f19c8a299d1045017a9697f5c8e1f38b69f28cb683bc9c467ea911836829b1c34a703a5d9282856b75
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
chef-provisioning-vsphere
|
2
|
+
==================
|
3
|
+
|
4
|
+
This is a [chef-provisioning](https://github.com/opscode/chef-provisioning) provisioner for [VMware vSphere](http://www.vmware.com/products/vsphere).
|
5
|
+
|
6
|
+
Currently, chef-provisioning-vsphere supports provisioning Unix/ssh and Windows/winrm guest VMs.
|
7
|
+
|
8
|
+
Try It Out
|
9
|
+
----------
|
10
|
+
|
11
|
+
### vSphere VM Template
|
12
|
+
|
13
|
+
Create or obtain a 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 or winrm
|
18
|
+
- provide a user account with NOPASSWD sudo/administrator
|
19
|
+
|
20
|
+
### Example recipe
|
21
|
+
|
22
|
+
```
|
23
|
+
chef_gem 'chef-provisioning-vsphere' do
|
24
|
+
action :install
|
25
|
+
compile_time true
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'chef/provisioning/vsphere_driver'
|
29
|
+
|
30
|
+
with_vsphere_driver host: 'vcenter-host-name',
|
31
|
+
insecure: true,
|
32
|
+
user: 'you_user_name',
|
33
|
+
password: 'your_mothers_maiden_name'
|
34
|
+
|
35
|
+
machine_options = {
|
36
|
+
:bootstrap_options => {
|
37
|
+
:num_cpus => 2,
|
38
|
+
:additional_disk_size_gb => 50,
|
39
|
+
:memory_mb => 4096,
|
40
|
+
:network_name => ["vlan_20_172.21.20"],
|
41
|
+
:datacenter 'datacenter_name',
|
42
|
+
:host: 'cluster/host',
|
43
|
+
:resource_pool: 'cluster/resource_pool_name',
|
44
|
+
:datastore: 'datastore_name',
|
45
|
+
:template_name: 'path to template_vm', # may be a VM or a VM Template
|
46
|
+
:vm_folder 'folder_to_clone_vms_into',
|
47
|
+
:customization_spec => {
|
48
|
+
:ipsettings => {
|
49
|
+
:ip => '1.2.3.125',
|
50
|
+
:subnetMask => '255.255.255.0',
|
51
|
+
:gateway => ["1.2.3.1"],
|
52
|
+
:dnsServerList => ["1.2.3.31","1.2.3.41"]
|
53
|
+
},
|
54
|
+
:domain => 'local',
|
55
|
+
:domainAdmin => "administrator@local",
|
56
|
+
:domainAdminPassword => "Password",
|
57
|
+
:org_name => 'my_company',
|
58
|
+
:product_id => 'xxxxx-xxxxx-xxxxx-xxxxx-xxxxx',
|
59
|
+
:win_time_zone => 4
|
60
|
+
}
|
61
|
+
:ssh => {
|
62
|
+
:user => 'administrator',
|
63
|
+
:password => 'password',
|
64
|
+
:paranoid => false,
|
65
|
+
:port => 22
|
66
|
+
},
|
67
|
+
:convergence_options => {
|
68
|
+
:install_msi_url=>"https://opscode-omnibus-packages.s3.amazonaws.com/windows/2008r2/x86_64/chef-windows-11.16.4-1.windows.msi",
|
69
|
+
:install_sh_url=>"/tmp/chef-install.sh -v 11.16.4"
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
machine "my_machine_name" do
|
74
|
+
machine_options machine_options
|
75
|
+
run_list ['my_cookbook::default']
|
76
|
+
end
|
77
|
+
|
78
|
+
```
|
79
|
+
|
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 = '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
|