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.
@@ -0,0 +1,31 @@
1
+ require 'uri'
2
+
3
+ module URI
4
+ class VsphereUrl < Generic
5
+ DEFAULT_PORT = 443
6
+ DEFAULT_PATH = '/sdk'
7
+
8
+ def self.from_config(options)
9
+ URI("vsphere://#{options[:host]}:#{options[:port]}#{options[:path]}?use_ssl=#{options[:use_ssl]}&insecure=#{options[:insecure]}")
10
+ end
11
+
12
+ def use_ssl
13
+ if query
14
+ ssl_query = query.split('&').each.select{|q| q.start_with?('use_ssl=')}.first
15
+ ssl_query == 'use_ssl=true'
16
+ else
17
+ true
18
+ end
19
+ end
20
+
21
+ def insecure
22
+ if query
23
+ insecure_query = query.split('&').each.select{|q| q.start_with?('insecure=')}.first
24
+ insecure_query == 'insecure=true'
25
+ else
26
+ false
27
+ end
28
+ end
29
+ end
30
+ @@schemes['VSPHERE'] = VsphereUrl
31
+ end
@@ -0,0 +1,85 @@
1
+ require 'json'
2
+ require 'kitchen'
3
+ require 'chef/provisioning/vsphere_driver'
4
+ require 'chef/provisioning/chef_machine_spec'
5
+
6
+ module Kitchen
7
+ module Driver
8
+ class Vsphere < Kitchen::Driver::Base
9
+
10
+ default_config :machine_options,
11
+ :start_timeout => 600,
12
+ :create_timeout => 600,
13
+ :ready_timeout => 90,
14
+ :bootstrap_options => {
15
+ :use_linked_clone => true,
16
+ :ssh => {
17
+ :user => 'root',
18
+ :paranoid => false,
19
+ :port => 22
20
+ },
21
+ :convergence_options => {},
22
+ :customization_spec => {
23
+ :domain => 'local'
24
+ }
25
+ }
26
+
27
+ def create(state)
28
+ config[:server_name] ||= "kitchen-#{SecureRandom.hex(4)}"
29
+ state[:username] = config[:machine_options][:bootstrap_options][:ssh][:user]
30
+ state[:password] = config[:machine_options][:bootstrap_options][:ssh][:password]
31
+
32
+ machine = with_provisioning_driver(config[:server_name]) do | action_handler, driver, machine_spec|
33
+ driver.allocate_machine(action_handler, machine_spec, config[:machine_options])
34
+ driver.ready_machine(action_handler, machine_spec, config[:machine_options])
35
+ state[:server_id] = machine_spec.location['server_id']
36
+ state[:hostname] = machine_spec.location['ipaddress']
37
+ state[:vsphere_name] = config[:server_name]
38
+ end
39
+
40
+ node_dir = File.join(instance.verifier[:test_base_path], "nodes")
41
+ Dir.mkdir(node_dir) unless Dir.exist?(node_dir)
42
+ node_file = File.join(node_dir, "#{instance.suite.name}.json")
43
+ node = {
44
+ :id => instance.suite.name,
45
+ :automatic => {
46
+ :ipaddress => state[:hostname]
47
+ }
48
+ }
49
+ File.open(node_file, 'w') do |out|
50
+ out << JSON.pretty_generate(node)
51
+ end
52
+
53
+ end
54
+
55
+ def destroy(state)
56
+ return if state[:server_id].nil?
57
+
58
+ with_provisioning_driver(state[:vsphere_name]) do | action_handler, driver, machine_spec|
59
+ machine_spec.location = { 'driver_url' => driver.driver_url,
60
+ 'server_id' => state[:server_id]}
61
+ driver.destroy_machine(action_handler, machine_spec, config[:machine_options])
62
+ end
63
+
64
+ state.delete(:server_id)
65
+ state.delete(:hostname)
66
+ state.delete(:vsphere_name)
67
+
68
+ node_file = File.join(instance.verifier[:test_base_path], "nodes/#{instance.suite.name}.json")
69
+ File.delete(node_file) if File.exist?(node_file)
70
+ end
71
+
72
+ def with_provisioning_driver(name, &block)
73
+ Cheffish.honor_local_mode do
74
+ chef_server = Cheffish.default_chef_server
75
+ config[:machine_options][:convergence_options] = {:chef_server => chef_server}
76
+ machine_spec = Chef::Provisioning.chef_managed_entry_store(chef_server).new_entry(:machine, name)
77
+ driver = Chef::Provisioning.driver_for_url("vsphere://#{config[:driver_options][:host]}", config)
78
+ action_handler = Chef::Provisioning::ActionHandler.new
79
+ block.call(action_handler, driver, machine_spec)
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1 @@
1
+ config.rb
@@ -0,0 +1,147 @@
1
+ require 'chef/provisioning/vsphere_driver'
2
+ require 'chef/provisioning/machine_spec'
3
+
4
+ # A file named config.rb in the same directory as this spec file
5
+ # must exist containing the driver options to use for the test.
6
+ # Here is an example:
7
+ # {
8
+ # :driver_options => {
9
+ # :host => '213.45.67.88',
10
+ # :user => 'vmapi',
11
+ # :password => 'SuperSecureP@ssw0rd',
12
+ # :insecure => true
13
+ # },
14
+ # :machine_options => {
15
+ # :start_timeout => 600,
16
+ # :create_timeout => 600,
17
+ # :bootstrap_options => {
18
+ # :datacenter => 'QA1',
19
+ # :template_name => 'UBUNTU-12-64-TEMPLATE',
20
+ # :vm_folder => 'DLAB',
21
+ # :num_cpus => 2,
22
+ # :network_name => 'vlan152_172.21.152',
23
+ # :memory_mb => 4096,
24
+ # :resource_pool => 'CLSTR02/DLAB',
25
+ # :ssh => {
26
+ # :user => 'root',
27
+ # :password => 'SuperSecureP@ssw0rd',
28
+ # :paranoid => false,
29
+ # :port => 22
30
+ # },
31
+ # :convergence_options => {}
32
+ # }
33
+ # }
34
+ # }
35
+
36
+ describe "vsphere_driver" do
37
+ include ChefProvisioningVsphere::Helpers
38
+
39
+ before :all do
40
+ @vm_name = "cmvd-test-#{SecureRandom.hex}"
41
+ @metal_config = eval File.read(File.expand_path('../config.rb', __FILE__))
42
+ Cheffish.honor_local_mode do
43
+ chef_server = Cheffish.default_chef_server
44
+ @machine_spec = Chef::Provisioning.chef_managed_entry_store(chef_server).new_entry(:machine, @vm_name)
45
+ @driver = Chef::Provisioning.driver_for_url("vsphere://#{@metal_config[:driver_options][:host]}", @metal_config)
46
+ action_handler = Chef::Provisioning::ActionHandler.new
47
+ @driver.allocate_machine(action_handler, @machine_spec, @metal_config[:machine_options])
48
+ @metal_config[:machine_options][:convergence_options] = {:chef_server => chef_server}
49
+ machine = @driver.ready_machine(action_handler, @machine_spec, @metal_config[:machine_options])
50
+ @server_id = @machine_spec.location['server_id']
51
+ @connection = vim(@metal_config[:driver_options])
52
+ @vm = find_vm_by_id(@server_id, @connection)
53
+ end
54
+ end
55
+
56
+
57
+ context 'when allocating a machine' do
58
+
59
+ it "adds machine to the correct folder" do
60
+ expect(@vm.parent.name).to eq(@metal_config[:machine_options][:bootstrap_options][:vm_folder])
61
+ end
62
+ it "has a matching id with the machine_spec" do
63
+ expect(@vm.config.instanceUuid).to eq(@machine_spec.location['server_id'])
64
+ end
65
+ it "has the correct name" do
66
+ now = Time.now.utc
67
+ trimmed_name = @vm.config.guestId.start_with?('win') ? @vm_name.byteslice(0,15) : @vm_name
68
+ expected_name="#{trimmed_name}.#{@metal_config[:machine_options][:bootstrap_options][:customization_spec][:domain]}"
69
+ until (Time.now.utc - now) > 30 || (@vm.guest.hostName == expected_name) do
70
+ print "."
71
+ sleep 5
72
+ end
73
+ expect(@vm.guest.hostName).to eq(expected_name)
74
+ end
75
+ it "has the correct number of CPUs" do
76
+ expect(@vm.config.hardware.numCPU).to eq(@metal_config[:machine_options][:bootstrap_options][:num_cpus])
77
+ end
78
+ it "has the correct amount of memory" do
79
+ expect(@vm.config.hardware.memoryMB).to eq(@metal_config[:machine_options][:bootstrap_options][:memory_mb])
80
+ end
81
+ it "is on the correct networks" do
82
+ expect(@vm.network.map {|n| n.name}).to include(@metal_config[:machine_options][:bootstrap_options][:network_name][0])
83
+ expect(@vm.network.map {|n| n.name}).to include(@metal_config[:machine_options][:bootstrap_options][:network_name][1])
84
+ end
85
+ it "is on the correct datastore" do
86
+ expect(@vm.datastore[0].name).to eq(@metal_config[:machine_options][:bootstrap_options][:datastore])
87
+ end
88
+ it "is in the correct resource pool" do
89
+ if @metal_config[:machine_options][:bootstrap_options].has_key?(:resource_pool)
90
+ expect(@vm.resourcePool.name).to eq(@metal_config[:machine_options][:bootstrap_options][:resource_pool].split('/')[1])
91
+ end
92
+ end
93
+ it "is in the correct host" do
94
+ if @metal_config[:machine_options][:bootstrap_options].has_key?(:host)
95
+ expect(@vm.runtime.host.name).to eq(@metal_config[:machine_options][:bootstrap_options][:host].split('/')[1])
96
+ end
97
+ end
98
+ it "is in the correct cluster" do
99
+ if @metal_config[:machine_options][:bootstrap_options].has_key?(:resource_pool)
100
+ expect(@vm.resourcePool.owner.name).to eq(@metal_config[:machine_options][:bootstrap_options][:resource_pool].split('/')[0])
101
+ end
102
+ end
103
+ it "is in the correct datacenter" do
104
+ expect(@connection.serviceInstance.find_datacenter(@metal_config[:machine_options][:bootstrap_options][:datacenter]).find_vm("#{@vm.parent.name}/#{@vm_name}")).not_to eq(nil)
105
+ end
106
+ it "has an added disk of the correct size" do
107
+ disk_count = @vm.disks.count
108
+ expect(@vm.disks[disk_count-1].capacityInKB).to eq(@metal_config[:machine_options][:bootstrap_options][:additional_disk_size_gb] * 1024 * 1024)
109
+ end
110
+ it "has hot add cpu enabled" do
111
+ expect(@vm.config.cpuHotAddEnabled).to eq(true)
112
+ end
113
+ it "has hot remove cpu enabled" do
114
+ expect(@vm.config.cpuHotRemoveEnabled).to eq(true)
115
+ end
116
+ it "has hot add memory enabled" do
117
+ expect(@vm.config.memoryHotAddEnabled).to eq(true)
118
+ end
119
+ it "has the correct IP address" do
120
+ if @vm.guest.toolsRunningStatus != "guestToolsRunning"
121
+ now = Time.now.utc
122
+ until (Time.now.utc - now) > 30 || (@vm.guest.toolsRunningStatus == "guestToolsRunning" && @vm.guest.net.count == 2 && @vm.guest.net[1].ipAddress[1] == @metal_config[:machine_options][:bootstrap_options][:customization_spec][:ipsettings][:ip]) do
123
+ print "."
124
+ sleep 5
125
+ end
126
+ end
127
+ expect(@vm.guest.net.map { |net| net.ipAddress}.flatten).to include(@metal_config[:machine_options][:bootstrap_options][:customization_spec][:ipsettings][:ip])
128
+ end
129
+ end
130
+
131
+ context "destroy_machine" do
132
+
133
+ it "removes the machine" do
134
+ Cheffish.honor_local_mode do
135
+ chef_server = Cheffish.default_chef_server
136
+ driver = Chef::Provisioning.driver_for_url("vsphere://#{@metal_config[:driver_options][:host]}", @metal_config)
137
+ action_handler = Chef::Provisioning::ActionHandler.new
138
+ machine_spec = Chef::Provisioning.chef_managed_entry_store(chef_server).new_entry(:machine, @vm_name)
139
+ machine_spec.location = { 'driver_url' => driver.driver_url,
140
+ 'server_id' => @server_id}
141
+ driver.destroy_machine(action_handler, machine_spec, @metal_config[:machine_options])
142
+ end
143
+ vm = find_vm_by_id(@server_id, @connection)
144
+ expect(vm).to eq(nil)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,206 @@
1
+ require 'chef/provisioning/vsphere_driver'
2
+
3
+ describe "canonicalize_url" do
4
+
5
+ context "when config does not include the properties included in the url" do
6
+ metal_config = {
7
+ :driver_options => {
8
+ :user => 'vmapi',
9
+ :password => '<password>'
10
+ },
11
+ :machine_options => {
12
+ :ssh => {
13
+ :password => '<password>',
14
+ :paranoid => false
15
+ },
16
+ :bootstrap_options => {
17
+ :datacenter => 'QA1',
18
+ :template_name => 'UBUNTU-12-64-TEMPLATE',
19
+ :vm_folder => 'DLAB',
20
+ :num_cpus => 2,
21
+ :memory_mb => 4096,
22
+ :resource_pool => 'CLSTR02/DLAB'
23
+ }
24
+ },
25
+ :log_level => :debug
26
+ }
27
+
28
+ let(:results) {
29
+ ChefProvisioningVsphere::VsphereDriver.canonicalize_url('vsphere://3.3.3.3:999/crazyapi?use_ssl=false&insecure=true', metal_config)
30
+ }
31
+
32
+ it "populates the config with correct host from the driver url" do
33
+ expect(results[1][:driver_options][:connect_options][:host]).to eq('3.3.3.3')
34
+ end
35
+ it "populates the config with correct port from the driver url" do
36
+ expect(results[1][:driver_options][:connect_options][:port]).to eq(999)
37
+ end
38
+ it "populates the config with correct path from the driver url" do
39
+ expect(results[1][:driver_options][:connect_options][:path]).to eq('/crazyapi')
40
+ end
41
+ it "populates the config with correct use_ssl setting from the driver url" do
42
+ expect(results[1][:driver_options][:connect_options][:use_ssl]).to eq(false)
43
+ end
44
+ it "populates the config with correct insecure setting from the driver url" do
45
+ expect(results[1][:driver_options][:connect_options][:insecure]).to eq(true)
46
+ end
47
+ end
48
+
49
+ context "when config keys are stringified" do
50
+ metal_config = {
51
+ 'driver_options' => {
52
+ 'user' => 'vmapi',
53
+ 'password' => '<password>'
54
+ },
55
+ 'machine_options' => {
56
+ 'ssh' => {
57
+ 'password' => '<password>'
58
+ },
59
+ 'bootstrap_options' => {
60
+ 'datacenter' => 'QA1'
61
+ }
62
+ }
63
+ }
64
+
65
+ let(:results) {
66
+ ChefProvisioningVsphere::VsphereDriver.canonicalize_url('vsphere://3.3.3.3:999/crazyapi?use_ssl=false&insecure=true', metal_config)
67
+ }
68
+
69
+ it "will symbolize user" do
70
+ expect(results[1][:driver_options][:connect_options][:user]).to eq('vmapi')
71
+ end
72
+ it "will symbolize password" do
73
+ expect(results[1][:driver_options][:connect_options][:password]).to eq('<password>')
74
+ end
75
+ it "will symbolize ssh password" do
76
+ expect(results[1][:machine_options][:ssh][:password]).to eq('<password>')
77
+ end
78
+ it "will symbolize ssh bootstrap options" do
79
+ expect(results[1][:machine_options][:bootstrap_options][:datacenter]).to eq('QA1')
80
+ end
81
+ end
82
+
83
+ context "when no url is in the config" do
84
+ metal_config = {
85
+ :driver_options => {
86
+ :user => 'vmapi',
87
+ :password => '<password>',
88
+ :host => '4.4.4.4',
89
+ :port => 888,
90
+ :path => '/yoda',
91
+ :use_ssl => 'false',
92
+ :insecure => 'true'
93
+ },
94
+ :machine_options => {
95
+ :ssh => {
96
+ :password => '<password>',
97
+ :paranoid => false
98
+ },
99
+ :bootstrap_options => {
100
+ :datacenter => 'QA1',
101
+ :template_name => 'UBUNTU-12-64-TEMPLATE',
102
+ :vm_folder => 'DLAB',
103
+ :num_cpus => 2,
104
+ :memory_mb => 4096,
105
+ :resource_pool => 'CLSTR02/DLAB'
106
+ }
107
+ },
108
+ :log_level => :debug
109
+ }
110
+
111
+ let(:results) {
112
+ ChefProvisioningVsphere::VsphereDriver.canonicalize_url(nil, metal_config)
113
+ }
114
+
115
+ it "creates the correct driver url from config settings" do
116
+ expect(results[0]).to eq('vsphere://4.4.4.4:888/yoda?use_ssl=false&insecure=true')
117
+ end
118
+ end
119
+
120
+ context "when no url is in the config and config is missing defaulted values" do
121
+ metal_config = {
122
+ :driver_options => {
123
+ :user => 'vmapi',
124
+ :password => '<password>',
125
+ :host => '4.4.4.4'
126
+ },
127
+ :machine_options => {
128
+ :bootstrap_options => {
129
+ :datacenter => 'QA1',
130
+ :template_name => 'UBUNTU-12-64-TEMPLATE',
131
+ :vm_folder => 'DLAB',
132
+ :num_cpus => 2,
133
+ :memory_mb => 4096,
134
+ :resource_pool => 'CLSTR02/DLAB',
135
+ :ssh => {
136
+ :password => '<password>',
137
+ :paranoid => false
138
+ }
139
+ }
140
+ },
141
+ :log_level => :debug
142
+ }
143
+
144
+ let(:results) {
145
+ ChefProvisioningVsphere::VsphereDriver.canonicalize_url(nil, metal_config)
146
+ }
147
+
148
+ it "creates the correct driver url from default settings" do
149
+ expect(results[0]).to eq('vsphere://4.4.4.4/sdk?use_ssl=true&insecure=false')
150
+ end
151
+ it "populates the config with correct port from default settings" do
152
+ expect(results[1][:driver_options][:connect_options][:port]).to eq(443)
153
+ end
154
+ it "populates the config with correct path from default settings" do
155
+ expect(results[1][:driver_options][:connect_options][:path]).to eq('/sdk')
156
+ end
157
+ it "populates the config with correct use_ssl setting from default settings" do
158
+ expect(results[1][:driver_options][:connect_options][:use_ssl]).to eq(true)
159
+ end
160
+ it "populates the config with correct insecure setting from default settings" do
161
+ expect(results[1][:driver_options][:connect_options][:insecure]).to eq(false)
162
+ end
163
+ it "populates the config with correct ssh port from default settings" do
164
+ expect(results[1][:machine_options][:bootstrap_options][:ssh][:port]).to eq(22)
165
+ end
166
+ end
167
+
168
+ context "when missing host" do
169
+ metal_config = {
170
+ :driver_options => {
171
+ :user => 'vmapi',
172
+ :password => '<password>',
173
+ }
174
+ }
175
+
176
+ it "should raise an error" do
177
+ expect{ChefProvisioningVsphere::VsphereDriver.canonicalize_url(nil,metal_config)}.to raise_error(RuntimeError)
178
+ end
179
+ end
180
+
181
+ context "when missing user" do
182
+ metal_config = {
183
+ :driver_options => {
184
+ :host => 'host',
185
+ :password => '<password>',
186
+ }
187
+ }
188
+
189
+ it "should raise an error" do
190
+ expect{ChefProvisioningVsphere::VsphereDriver.canonicalize_url(nil,metal_config)}.to raise_error(RuntimeError)
191
+ end
192
+ end
193
+
194
+ context "when missing password" do
195
+ metal_config = {
196
+ :driver_options => {
197
+ :host => 'host',
198
+ :user => 'user',
199
+ }
200
+ }
201
+
202
+ it "should raise an error" do
203
+ expect{ChefProvisioningVsphere::VsphereDriver.canonicalize_url(nil,metal_config)}.to raise_error(RuntimeError)
204
+ end
205
+ end
206
+ end