bosh_vsphere_cpi 0.4.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README +1 -0
- data/Rakefile +50 -0
- data/lib/cloud/vsphere/client.rb +431 -0
- data/lib/cloud/vsphere/cloud.rb +1085 -0
- data/lib/cloud/vsphere/lease_updater.rb +40 -0
- data/lib/cloud/vsphere/models/disk.rb +8 -0
- data/lib/cloud/vsphere/resources.rb +530 -0
- data/lib/cloud/vsphere/version.rb +7 -0
- data/lib/cloud/vsphere.rb +29 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/unit/vsphere_resource_spec.rb +254 -0
- metadata +118 -0
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
BOSH VSphere Cloud Provider
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
$:.unshift(File.expand_path("../../rake", __FILE__))
|
4
|
+
|
5
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __FILE__)
|
6
|
+
|
7
|
+
require "rubygems"
|
8
|
+
require "bundler"
|
9
|
+
Bundler.setup(:default, :test)
|
10
|
+
|
11
|
+
require "rake"
|
12
|
+
begin
|
13
|
+
require "rspec/core/rake_task"
|
14
|
+
rescue LoadError
|
15
|
+
end
|
16
|
+
|
17
|
+
require "bundler_task"
|
18
|
+
require "ci_task"
|
19
|
+
|
20
|
+
gem_helper = Bundler::GemHelper.new(Dir.pwd)
|
21
|
+
|
22
|
+
desc "Build VSphere CPI gem into the pkg directory"
|
23
|
+
task "build" do
|
24
|
+
gem_helper.build_gem
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Build and install VSphere CPI into system gems"
|
28
|
+
task "install" do
|
29
|
+
Rake::Task["bundler:install"].invoke
|
30
|
+
gem_helper.install_gem
|
31
|
+
end
|
32
|
+
|
33
|
+
BundlerTask.new
|
34
|
+
|
35
|
+
if defined?(RSpec)
|
36
|
+
namespace :spec do
|
37
|
+
desc "Run Unit Tests"
|
38
|
+
rspec_task = RSpec::Core::RakeTask.new(:unit) do |t|
|
39
|
+
t.pattern = "spec/unit/**/*_spec.rb"
|
40
|
+
t.rspec_opts = %w(--format progress --colour)
|
41
|
+
end
|
42
|
+
|
43
|
+
CiTask.new do |task|
|
44
|
+
task.rspec_task = rspec_task
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "Run tests"
|
49
|
+
task :spec => %w(spec:unit)
|
50
|
+
end
|
@@ -0,0 +1,431 @@
|
|
1
|
+
module VSphereCloud
|
2
|
+
|
3
|
+
class Client
|
4
|
+
include VimSdk
|
5
|
+
PC = Vmodl::Query::PropertyCollector
|
6
|
+
|
7
|
+
class AlreadyLoggedInException < StandardError; end
|
8
|
+
class NotLoggedInException < StandardError; end
|
9
|
+
|
10
|
+
attr_accessor :service_content
|
11
|
+
attr_accessor :stub
|
12
|
+
attr_accessor :service_instance
|
13
|
+
|
14
|
+
def initialize(host, options = {})
|
15
|
+
http_client = HTTPClient.new
|
16
|
+
if options["soap_log"]
|
17
|
+
log_file = File.open(options["soap_log"], "w")
|
18
|
+
log_file.sync = true
|
19
|
+
http_client.debug_dev = log_file
|
20
|
+
end
|
21
|
+
http_client.send_timeout = 14400
|
22
|
+
http_client.receive_timeout = 14400
|
23
|
+
http_client.connect_timeout = 4
|
24
|
+
http_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
25
|
+
|
26
|
+
@stub = Soap::StubAdapter.new(host, "vim.version.version6", http_client)
|
27
|
+
|
28
|
+
@service_instance = Vim::ServiceInstance.new("ServiceInstance", stub)
|
29
|
+
@service_content = service_instance.content
|
30
|
+
@metrics_cache = {}
|
31
|
+
@lock = Mutex.new
|
32
|
+
@logger = Bosh::Clouds::Config.logger
|
33
|
+
end
|
34
|
+
|
35
|
+
def login(username, password, locale)
|
36
|
+
raise AlreadyLoggedInException if @session
|
37
|
+
@session = @service_content.session_manager.login(username, password, locale)
|
38
|
+
end
|
39
|
+
|
40
|
+
def logout
|
41
|
+
raise NotLoggedInException unless @session
|
42
|
+
@session = nil
|
43
|
+
@service_content.session_manager.logout
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_properties(obj, type, properties, options = {})
|
47
|
+
properties = [properties] if properties.kind_of?(String)
|
48
|
+
property_specs = [PC::PropertySpec.new(:type => type, :all => false, :path_set => properties)]
|
49
|
+
|
50
|
+
if obj.is_a?(Vmodl::ManagedObject)
|
51
|
+
object_spec = PC::ObjectSpec.new(:obj => obj, :skip => false)
|
52
|
+
else
|
53
|
+
object_spec = obj.collect { |o| PC::ObjectSpec.new(:obj => o, :skip => false) }
|
54
|
+
end
|
55
|
+
|
56
|
+
filter_spec = PC::FilterSpec.new(:prop_set => property_specs, :object_set => object_spec)
|
57
|
+
|
58
|
+
# TODO: cache partial results
|
59
|
+
attempts = 0
|
60
|
+
begin
|
61
|
+
properties_response = get_all_properties(filter_spec)
|
62
|
+
result = {}
|
63
|
+
|
64
|
+
properties_response.each do |object_content|
|
65
|
+
object_properties = {:obj => object_content.obj}
|
66
|
+
if options[:ensure_all]
|
67
|
+
remaining_properties = Set.new(properties)
|
68
|
+
else
|
69
|
+
remaining_properties = Set.new(options[:ensure])
|
70
|
+
end
|
71
|
+
if object_content.prop_set
|
72
|
+
object_content.prop_set.each do |property|
|
73
|
+
object_properties[property.name] = property.val
|
74
|
+
remaining_properties.delete(property.name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
unless remaining_properties.empty?
|
78
|
+
raise "The object[s] #{obj.pretty_inspect} " +
|
79
|
+
"should have the following properties: #{properties.pretty_inspect}, " +
|
80
|
+
"but they were missing these: #{remaining_properties.pretty_inspect}."
|
81
|
+
end
|
82
|
+
result[object_content.obj] = object_properties
|
83
|
+
end
|
84
|
+
|
85
|
+
result = result.values.first if obj.is_a?(Vmodl::ManagedObject)
|
86
|
+
result
|
87
|
+
rescue => e
|
88
|
+
attempts += 1
|
89
|
+
if attempts < 8
|
90
|
+
sleep_interval = 2 ** attempts
|
91
|
+
@logger.warn("Error retrieving properties, retrying in #{sleep_interval} seconds: " +
|
92
|
+
"#{e} - #{e.backtrace.join("\n")}")
|
93
|
+
sleep(sleep_interval)
|
94
|
+
retry
|
95
|
+
else
|
96
|
+
raise e
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_property(obj, type, property, options = {})
|
102
|
+
get_properties(obj, type, property, options)[property]
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_managed_objects(type, options={})
|
106
|
+
root = options[:root] || @service_content.root_folder
|
107
|
+
property_specs = [PC::PropertySpec.new(:type => type, :all => false, :path_set => ["name"])]
|
108
|
+
filter_spec = get_search_filter_spec(root, property_specs)
|
109
|
+
object_specs = get_all_properties(filter_spec)
|
110
|
+
|
111
|
+
result = []
|
112
|
+
object_specs.each do |object_spec|
|
113
|
+
name = object_spec.prop_set.first.val
|
114
|
+
if options[:name].nil? || name == options[:name]
|
115
|
+
if options[:include_name]
|
116
|
+
result << [name , object_spec.obj]
|
117
|
+
else
|
118
|
+
result << object_spec.obj
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
result
|
123
|
+
end
|
124
|
+
|
125
|
+
def get_managed_object(type, options)
|
126
|
+
result = get_managed_objects(type, options)
|
127
|
+
raise "Could not find #{type}: #{options.pretty_inspect}" if result.length == 0
|
128
|
+
raise "Found more than one #{type}: #{options.pretty_inspect}" if result.length > 1
|
129
|
+
result.first
|
130
|
+
end
|
131
|
+
|
132
|
+
def find_parent(obj, parent_type)
|
133
|
+
while obj && obj.class != parent_type
|
134
|
+
obj = get_property(obj, obj.class, "parent", :ensure_all => true)
|
135
|
+
end
|
136
|
+
obj
|
137
|
+
end
|
138
|
+
|
139
|
+
def reconfig_vm(vm, config)
|
140
|
+
task = vm.reconfigure(config)
|
141
|
+
wait_for_task(task)
|
142
|
+
end
|
143
|
+
|
144
|
+
def delete_vm(vm)
|
145
|
+
task = vm.destroy
|
146
|
+
wait_for_task(task)
|
147
|
+
end
|
148
|
+
|
149
|
+
def answer_vm(vm, question, answer)
|
150
|
+
vm.answer(question, answer)
|
151
|
+
end
|
152
|
+
|
153
|
+
def power_on_vm(datacenter, vm)
|
154
|
+
task = datacenter.power_on_vm([vm], nil)
|
155
|
+
result = wait_for_task(task)
|
156
|
+
if result.attempted.nil?
|
157
|
+
raise "Could not power on VM: #{result.not_attempted.msg}"
|
158
|
+
else
|
159
|
+
task = result.attempted.first.task
|
160
|
+
wait_for_task(task)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def power_off_vm(vm)
|
165
|
+
task = vm.power_off
|
166
|
+
wait_for_task(task)
|
167
|
+
end
|
168
|
+
|
169
|
+
def delete_path(datacenter, path)
|
170
|
+
task = @service_content.file_manager.delete_file(path, datacenter)
|
171
|
+
wait_for_task(task)
|
172
|
+
end
|
173
|
+
|
174
|
+
def delete_disk(datacenter, path)
|
175
|
+
tasks = []
|
176
|
+
[".vmdk", "-flat.vmdk"].each do |extension|
|
177
|
+
tasks << @service_content.file_manager.delete_file("#{path}#{extension}", datacenter)
|
178
|
+
end
|
179
|
+
tasks.each { |task| wait_for_task(task) }
|
180
|
+
end
|
181
|
+
|
182
|
+
def move_disk(source_datacenter, source_path, dest_datacenter, dest_path)
|
183
|
+
tasks = []
|
184
|
+
[".vmdk", "-flat.vmdk"].each do |extension|
|
185
|
+
tasks << @service_content.file_manager.move_file("#{source_path}#{extension}", source_datacenter,
|
186
|
+
"#{dest_path}#{extension}", dest_datacenter, false)
|
187
|
+
end
|
188
|
+
|
189
|
+
tasks.each { |task| wait_for_task(task) }
|
190
|
+
end
|
191
|
+
|
192
|
+
def copy_disk(source_datacenter, source_path, dest_datacenter, dest_path)
|
193
|
+
tasks = []
|
194
|
+
[".vmdk", "-flat.vmdk"].each do |extension|
|
195
|
+
tasks << @service_content.file_manager.copy_file("#{source_path}#{extension}", source_datacenter,
|
196
|
+
"#{dest_path}#{extension}", dest_datacenter, false)
|
197
|
+
end
|
198
|
+
|
199
|
+
tasks.each { |task| wait_for_task(task) }
|
200
|
+
end
|
201
|
+
|
202
|
+
def find_by_inventory_path(path)
|
203
|
+
path = [path] unless path.kind_of?(Array)
|
204
|
+
path = path.flatten.collect { |name| name.gsub("/", "%2f") }.join("/")
|
205
|
+
@service_content.search_index.find_by_inventory_path(path)
|
206
|
+
end
|
207
|
+
|
208
|
+
def wait_for_task(task)
|
209
|
+
interval = 1.0
|
210
|
+
started = Time.now
|
211
|
+
loop do
|
212
|
+
properties = get_properties([task], Vim::Task, ["info.progress", "info.state", "info.result", "info.error"],
|
213
|
+
:ensure => ["info.state"])[task]
|
214
|
+
|
215
|
+
duration = Time.now - started
|
216
|
+
# TODO: make configurable?
|
217
|
+
raise "Task taking too long" if duration > 3600 # 1 hour
|
218
|
+
|
219
|
+
# Update the polling interval based on task progress
|
220
|
+
if properties["info.progress"] && properties["info.progress"] > 0
|
221
|
+
interval = ((duration * 100 / properties["info.progress"]) - duration) / 5
|
222
|
+
if interval < 1
|
223
|
+
interval = 1
|
224
|
+
elsif interval > 10
|
225
|
+
interval = 10
|
226
|
+
elsif interval > duration
|
227
|
+
interval = duration
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
case properties["info.state"]
|
232
|
+
when Vim::TaskInfo::State::RUNNING
|
233
|
+
sleep(interval)
|
234
|
+
when Vim::TaskInfo::State::QUEUED
|
235
|
+
sleep(interval)
|
236
|
+
when Vim::TaskInfo::State::SUCCESS
|
237
|
+
return properties["info.result"]
|
238
|
+
when Vim::TaskInfo::State::ERROR
|
239
|
+
raise properties["info.error"].msg
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def get_search_filter_spec(obj, property_specs)
|
245
|
+
resource_pool_traversal_spec = PC::TraversalSpec.new(
|
246
|
+
:name => "resourcePoolTraversalSpec",
|
247
|
+
:type => Vim::ResourcePool,
|
248
|
+
:path => "resourcePool",
|
249
|
+
:skip => false,
|
250
|
+
:select_set => [
|
251
|
+
PC::SelectionSpec.new(:name => "resourcePoolTraversalSpec"),
|
252
|
+
PC::SelectionSpec.new(:name => "resourcePoolVmTraversalSpec")
|
253
|
+
]
|
254
|
+
)
|
255
|
+
|
256
|
+
resource_pool_vm_traversal_spec = PC::TraversalSpec.new(
|
257
|
+
:name => "resourcePoolVmTraversalSpec",
|
258
|
+
:type => Vim::ResourcePool,
|
259
|
+
:path => "vm",
|
260
|
+
:skip => false
|
261
|
+
)
|
262
|
+
|
263
|
+
compute_resource_rp_traversal_spec = PC::TraversalSpec.new(
|
264
|
+
:name => "computeResourceRpTraversalSpec",
|
265
|
+
:type => Vim::ComputeResource,
|
266
|
+
:path => "resourcePool",
|
267
|
+
:skip => false,
|
268
|
+
:select_set => [
|
269
|
+
PC::SelectionSpec.new(:name => "resourcePoolTraversalSpec"),
|
270
|
+
PC::SelectionSpec.new(:name => "resourcePoolVmTraversalSpec")
|
271
|
+
]
|
272
|
+
)
|
273
|
+
|
274
|
+
compute_resource_datastore_traversal_spec = PC::TraversalSpec.new(
|
275
|
+
:name => "computeResourceDatastoreTraversalSpec",
|
276
|
+
:type => Vim::ComputeResource,
|
277
|
+
:path => "datastore",
|
278
|
+
:skip => false
|
279
|
+
)
|
280
|
+
|
281
|
+
compute_resource_host_traversal_spec = PC::TraversalSpec.new(
|
282
|
+
:name => "computeResourceHostTraversalSpec",
|
283
|
+
:type => Vim::ComputeResource,
|
284
|
+
:path => "host",
|
285
|
+
:skip => false
|
286
|
+
)
|
287
|
+
|
288
|
+
datacenter_host_traversal_spec = PC::TraversalSpec.new(
|
289
|
+
:name => "datacenterHostTraversalSpec",
|
290
|
+
:type => Vim::Datacenter,
|
291
|
+
:path => "hostFolder",
|
292
|
+
:skip => false,
|
293
|
+
:select_set => [
|
294
|
+
PC::SelectionSpec.new(:name => "folderTraversalSpec")
|
295
|
+
]
|
296
|
+
)
|
297
|
+
|
298
|
+
datacenter_vm_traversal_spec = PC::TraversalSpec.new(
|
299
|
+
:name => "datacenterVmTraversalSpec",
|
300
|
+
:type => Vim::Datacenter,
|
301
|
+
:path => "vmFolder",
|
302
|
+
:skip => false,
|
303
|
+
:select_set => [
|
304
|
+
PC::SelectionSpec.new(:name => "folderTraversalSpec")
|
305
|
+
]
|
306
|
+
)
|
307
|
+
|
308
|
+
host_vm_traversal_spec = PC::TraversalSpec.new(
|
309
|
+
:name => "hostVmTraversalSpec",
|
310
|
+
:type => Vim::HostSystem,
|
311
|
+
:path => "vm",
|
312
|
+
:skip => false,
|
313
|
+
:select_set => [
|
314
|
+
PC::SelectionSpec.new(:name => "folderTraversalSpec")
|
315
|
+
]
|
316
|
+
)
|
317
|
+
|
318
|
+
folder_traversal_spec = PC::TraversalSpec.new(
|
319
|
+
:name => "folderTraversalSpec",
|
320
|
+
:type => Vim::Folder,
|
321
|
+
:path => "childEntity",
|
322
|
+
:skip => false,
|
323
|
+
:select_set => [
|
324
|
+
PC::SelectionSpec.new(:name => "folderTraversalSpec"),
|
325
|
+
PC::SelectionSpec.new(:name => "datacenterHostTraversalSpec"),
|
326
|
+
PC::SelectionSpec.new(:name => "datacenterVmTraversalSpec"),
|
327
|
+
PC::SelectionSpec.new(:name => "computeResourceRpTraversalSpec"),
|
328
|
+
PC::SelectionSpec.new(:name => "computeResourceDatastoreTraversalSpec"),
|
329
|
+
PC::SelectionSpec.new(:name => "computeResourceHostTraversalSpec"),
|
330
|
+
PC::SelectionSpec.new(:name => "hostVmTraversalSpec"),
|
331
|
+
PC::SelectionSpec.new(:name => "resourcePoolVmTraversalSpec")
|
332
|
+
]
|
333
|
+
)
|
334
|
+
|
335
|
+
obj_spec = PC::ObjectSpec.new(
|
336
|
+
:obj => obj,
|
337
|
+
:skip => false,
|
338
|
+
:select_set => [
|
339
|
+
folder_traversal_spec,
|
340
|
+
datacenter_vm_traversal_spec,
|
341
|
+
datacenter_host_traversal_spec,
|
342
|
+
compute_resource_host_traversal_spec,
|
343
|
+
compute_resource_datastore_traversal_spec,
|
344
|
+
compute_resource_rp_traversal_spec,
|
345
|
+
resource_pool_traversal_spec,
|
346
|
+
host_vm_traversal_spec,
|
347
|
+
resource_pool_vm_traversal_spec
|
348
|
+
]
|
349
|
+
)
|
350
|
+
|
351
|
+
PC::FilterSpec.new(:prop_set => property_specs, :object_set => [obj_spec])
|
352
|
+
end
|
353
|
+
|
354
|
+
def get_all_properties(filter_spec)
|
355
|
+
result = []
|
356
|
+
retrieve_result = @service_content.property_collector.retrieve_properties_ex([filter_spec],
|
357
|
+
PC::RetrieveOptions.new)
|
358
|
+
until retrieve_result.nil?
|
359
|
+
retrieve_result.objects.each { |object_content| result << object_content }
|
360
|
+
break if retrieve_result.token.nil?
|
361
|
+
retrieve_result = @service_content.property_collector.continue_retrieve_properties_ex(retrieve_result.token)
|
362
|
+
end
|
363
|
+
result
|
364
|
+
end
|
365
|
+
|
366
|
+
def get_perf_counters(mobs, names, options = {})
|
367
|
+
metrics = find_perf_metric_names(mobs.first, names)
|
368
|
+
metric_ids = metrics.values
|
369
|
+
|
370
|
+
metric_name_by_id = {}
|
371
|
+
metrics.each { |name, metric| metric_name_by_id[metric.counter_id] = name }
|
372
|
+
|
373
|
+
queries = []
|
374
|
+
mobs.each do |mob|
|
375
|
+
queries << Vim::PerformanceManager::QuerySpec.new(
|
376
|
+
:entity => mob,
|
377
|
+
:metric_id => metric_ids,
|
378
|
+
:format => Vim::PerformanceManager::Format::CSV,
|
379
|
+
:interval_id => options[:interval_id] || 20,
|
380
|
+
:max_sample => options[:max_sample])
|
381
|
+
end
|
382
|
+
|
383
|
+
query_perf_response = @service_content.perf_manager.query_stats(queries)
|
384
|
+
|
385
|
+
result = {}
|
386
|
+
query_perf_response.each do |mob_stats|
|
387
|
+
mob_entry = {}
|
388
|
+
counters = mob_stats.value
|
389
|
+
counters.each do |counter_stats|
|
390
|
+
counter_id = counter_stats.id.counter_id
|
391
|
+
values = counter_stats.value
|
392
|
+
mob_entry[metric_name_by_id[counter_id]] = values
|
393
|
+
end
|
394
|
+
result[mob_stats.entity] = mob_entry
|
395
|
+
end
|
396
|
+
result
|
397
|
+
end
|
398
|
+
|
399
|
+
def find_perf_metric_names(mob, names)
|
400
|
+
@lock.synchronize do
|
401
|
+
unless @metrics_cache.has_key?(mob.class)
|
402
|
+
@metrics_cache[mob.class] = fetch_perf_metric_names(mob)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
result = {}
|
407
|
+
@metrics_cache[mob.class].each do |name, metric|
|
408
|
+
result[name] = metric if names.include?(name)
|
409
|
+
end
|
410
|
+
|
411
|
+
result
|
412
|
+
end
|
413
|
+
|
414
|
+
def fetch_perf_metric_names(mob)
|
415
|
+
metrics = @service_content.perf_manager.query_available_metric(mob, nil, nil, 300)
|
416
|
+
metric_ids = metrics.collect { |metric| metric.counter_id }
|
417
|
+
|
418
|
+
metric_names = {}
|
419
|
+
metrics_info = @service_content.perf_manager.query_counter(metric_ids)
|
420
|
+
metrics_info.each do |perf_counter_info|
|
421
|
+
name = "#{perf_counter_info.group_info.key}.#{perf_counter_info.name_info.key}.#{perf_counter_info.rollup_type}"
|
422
|
+
metric_names[perf_counter_info.key] = name
|
423
|
+
end
|
424
|
+
|
425
|
+
result = {}
|
426
|
+
metrics.each { |metric| result[metric_names[metric.counter_id]] = metric }
|
427
|
+
result
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
end
|