clc-chef-provisioning-vsphere 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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