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 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