vmpooler 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c0edeb60c2a23b1dc15da9f2535b9420b4de8c3
4
- data.tar.gz: 667639acb53c6b83bb530a0134b506da57e51a32
3
+ metadata.gz: 033cdee6f8934932e9e717b8b229b36541fe2f88
4
+ data.tar.gz: d798c6417320e4942bf0965ed3c1e2d303e0bab6
5
5
  SHA512:
6
- metadata.gz: 647a7292fb7b5c9bc991ce68e2d42998a6604bd77392221baca916c90ca696e58cd6879a4125ce863c97b5ff04dfca98ed6d5d7380c7d9251ff663c8eca3d61b
7
- data.tar.gz: bf66abbb2ea1b1ba8e06e82ecaa293f2e160fab25959ce260f1b066b8ddeb5185c3c1fa40b14d3fc39f6f009bdf53b8dbaa8a64ff3380249ddea31af1c30a035
6
+ metadata.gz: a45de99b68aaf08784fbfac2793a9da60d38c9a8af20ae802362015841e6962dfb2ab027dbfcc7792d8808130752991c3419e95a4ea0764dc625c8a1b97dd5d0
7
+ data.tar.gz: 68dbfedba323a249e0a0bace756f2aec21ce1e9a770482b477ea9b197f9ff7a86ba55cdc29a9493756f713c514689f1db66a5196ea01f8f42126315e726f1daa
@@ -1,3 +1,5 @@
1
+ require 'vmpooler/providers'
2
+
1
3
  module Vmpooler
2
4
  class PoolManager
3
5
  CHECK_LOOP_DELAY_MIN_DEFAULT = 5
@@ -26,6 +28,9 @@ module Vmpooler
26
28
  @reconfigure_pool = {}
27
29
 
28
30
  @vm_mutex = {}
31
+
32
+ # load specified providers from config file
33
+ load_used_providers
29
34
  end
30
35
 
31
36
  def config
@@ -298,7 +303,6 @@ module Vmpooler
298
303
  mutex = vm_mutex(vm)
299
304
  return if mutex.locked?
300
305
  mutex.synchronize do
301
- $redis.srem('vmpooler__completed__' + pool, vm)
302
306
  $redis.hdel('vmpooler__active__' + pool, vm)
303
307
  $redis.hset('vmpooler__vm__' + vm, 'destroy', Time.now)
304
308
 
@@ -309,10 +313,60 @@ module Vmpooler
309
313
 
310
314
  provider.destroy_vm(pool, vm)
311
315
 
316
+ $redis.srem('vmpooler__completed__' + pool, vm)
317
+
312
318
  finish = format('%.2f', Time.now - start)
313
319
  $logger.log('s', "[-] [#{pool}] '#{vm}' destroyed in #{finish} seconds")
314
320
  $metrics.timing("destroy.#{pool}", finish)
315
321
  end
322
+ dereference_mutex(vm)
323
+ end
324
+
325
+ def purge_unused_vms_and_folders
326
+ global_purge = $config[:config]['purge_unconfigured_folders']
327
+ providers = $config[:providers].keys
328
+ providers.each do |provider|
329
+ provider_purge = $config[:providers][provider]['purge_unconfigured_folders']
330
+ provider_purge = global_purge if provider_purge.nil?
331
+ if provider_purge
332
+ Thread.new do
333
+ begin
334
+ purge_vms_and_folders($providers[provider.to_s])
335
+ rescue => err
336
+ $logger.log('s', "[!] failed while purging provider #{provider.to_s} VMs and folders with an error: #{err}")
337
+ end
338
+ end
339
+ end
340
+ end
341
+ return
342
+ end
343
+
344
+ # Return a list of pool folders
345
+ def pool_folders(provider)
346
+ provider_name = provider.name
347
+ folders = {}
348
+ $config[:pools].each do |pool|
349
+ next unless pool['provider'] == provider_name
350
+ folder_parts = pool['folder'].split('/')
351
+ datacenter = provider.get_target_datacenter_from_config(pool['name'])
352
+ folders[folder_parts.pop] = "#{datacenter}/vm/#{folder_parts.join('/')}"
353
+ end
354
+ folders
355
+ end
356
+
357
+ def get_base_folders(folders)
358
+ base = []
359
+ folders.each do |key, value|
360
+ base << value
361
+ end
362
+ base.uniq
363
+ end
364
+
365
+ def purge_vms_and_folders(provider)
366
+ configured_folders = pool_folders(provider)
367
+ base_folders = get_base_folders(configured_folders)
368
+ whitelist = provider.provider_config['folder_whitelist']
369
+ provider.purge_unconfigured_folders(base_folders, configured_folders, whitelist)
316
370
  end
317
371
 
318
372
  def create_vm_disk(pool_name, vm, disk_size, provider)
@@ -408,19 +462,36 @@ module Vmpooler
408
462
  result
409
463
  end
410
464
 
465
+ # load only providers used in config file
466
+ def load_used_providers
467
+ Vmpooler::Providers.load_by_name(used_providers)
468
+ end
469
+
470
+ # @return [Array] - a list of used providers from the config file, defaults to the default providers
471
+ # ie. ["vsphere", "dummy"]
472
+ def used_providers
473
+ pools = config[:pools] || []
474
+ @used_providers ||= (pools.map { |pool| pool[:provider] || pool['provider'] }.compact + default_providers ).uniq
475
+ end
476
+
477
+ # @return [Array] - returns a list of providers that should always be loaded
478
+ # note: vsphere is the default if user does not specify although this should not be
479
+ # if vsphere is to no longer be loaded by default please remove
480
+ def default_providers
481
+ @default_providers ||= %w( vsphere dummy )
482
+ end
483
+
411
484
  def get_pool_name_for_vm(vm_name)
412
485
  # the 'template' is a bad name. Should really be 'poolname'
413
486
  $redis.hget('vmpooler__vm__' + vm_name, 'template')
414
487
  end
415
488
 
489
+ # @param pool_name [String] - the name of the pool
490
+ # @return [Provider] - returns the provider class Object
416
491
  def get_provider_for_pool(pool_name)
417
- provider_name = nil
418
- $config[:pools].each do |pool|
419
- next unless pool['name'] == pool_name
420
- provider_name = pool['provider']
421
- end
422
- return nil if provider_name.nil?
423
-
492
+ pool = $config[:pools].find { |pool| pool['name'] == pool_name }
493
+ return nil unless pool
494
+ provider_name = pool.fetch('provider', nil)
424
495
  $providers[provider_name]
425
496
  end
426
497
 
@@ -634,6 +705,14 @@ module Vmpooler
634
705
  @vm_mutex[vmname] || @vm_mutex[vmname] = Mutex.new
635
706
  end
636
707
 
708
+ def dereference_mutex(vmname)
709
+ if @vm_mutex.delete(vmname)
710
+ return true
711
+ else
712
+ return
713
+ end
714
+ end
715
+
637
716
  def sync_pool_template(pool)
638
717
  pool_template = $redis.hget('vmpooler__config__template', pool['name'])
639
718
  if pool_template
@@ -992,6 +1071,8 @@ module Vmpooler
992
1071
  end
993
1072
  end
994
1073
 
1074
+ purge_unused_vms_and_folders
1075
+
995
1076
  loop_count = 1
996
1077
  loop do
997
1078
  if !$threads['disk_manager']
@@ -225,6 +225,18 @@ module Vmpooler
225
225
  def create_template_delta_disks(pool)
226
226
  raise("#{self.class.name} does not implement create_template_delta_disks")
227
227
  end
228
+
229
+ # inputs
230
+ # [String] provider_name : Name of the provider
231
+ # returns
232
+ # Hash of folders
233
+ def get_target_datacenter_from_config(provider_name)
234
+ raise("#{self.class.name} does not implement get_target_datacenter_from_config")
235
+ end
236
+
237
+ def purge_unconfigured_folders(base_folders, configured_folders, whitelist)
238
+ raise("#{self.class.name} does not implement purge_unconfigured_folders")
239
+ end
228
240
  end
229
241
  end
230
242
  end
@@ -1,4 +1,5 @@
1
1
  require 'yaml'
2
+ require 'vmpooler/providers/base'
2
3
 
3
4
  module Vmpooler
4
5
  class PoolManager
@@ -1,3 +1,5 @@
1
+ require 'vmpooler/providers/base'
2
+
1
3
  module Vmpooler
2
4
  class PoolManager
3
5
  class Provider
@@ -42,6 +44,109 @@ module Vmpooler
42
44
  'vsphere'
43
45
  end
44
46
 
47
+ def folder_configured?(folder_title, base_folder, configured_folders, whitelist)
48
+ if whitelist
49
+ return true if whitelist.include?(folder_title)
50
+ end
51
+ return false unless configured_folders.keys.include?(folder_title)
52
+ return false unless configured_folders[folder_title] == base_folder
53
+ return true
54
+ end
55
+
56
+ def destroy_vm_and_log(vm_name, vm_object, pool, data_ttl)
57
+ try = 0 if try.nil?
58
+ max_tries = 3
59
+ $redis.srem("vmpooler__completed__#{pool}", vm_name)
60
+ $redis.hdel("vmpooler__active__#{pool}", vm_name)
61
+ $redis.hset("vmpooler__vm__#{vm_name}", 'destroy', Time.now)
62
+
63
+ # Auto-expire metadata key
64
+ $redis.expire('vmpooler__vm__' + vm_name, (data_ttl * 60 * 60))
65
+
66
+ start = Time.now
67
+
68
+ if vm_object.is_a? RbVmomi::VIM::Folder
69
+ logger.log('s', "[!] [#{pool}] '#{vm_name}' is a folder, bailing on destroying")
70
+ raise('Expected VM, but received a folder object')
71
+ end
72
+ vm_object.PowerOffVM_Task.wait_for_completion if vm_object.runtime && vm_object.runtime.powerState && vm_object.runtime.powerState == 'poweredOn'
73
+ vm_object.Destroy_Task.wait_for_completion
74
+
75
+ finish = format('%.2f', Time.now - start)
76
+ logger.log('s', "[-] [#{pool}] '#{vm_name}' destroyed in #{finish} seconds")
77
+ metrics.timing("destroy.#{pool}", finish)
78
+ rescue RuntimeError
79
+ raise
80
+ rescue => err
81
+ try += 1
82
+ logger.log('s', "[!] [#{pool}] failed to destroy '#{vm_name}' with an error: #{err}")
83
+ try >= max_tries ? raise : retry
84
+ end
85
+
86
+ def destroy_folder_and_children(folder_object)
87
+ vms = {}
88
+ data_ttl = $config[:redis]['data_ttl'].to_i
89
+ folder_name = folder_object.name
90
+ unless folder_object.childEntity.count == 0
91
+ folder_object.childEntity.each do |vm|
92
+ vms[vm.name] = vm
93
+ end
94
+
95
+ vms.each do |vm_name, vm_object|
96
+ destroy_vm_and_log(vm_name, vm_object, folder_name, data_ttl)
97
+ end
98
+ end
99
+ destroy_folder(folder_object)
100
+ end
101
+
102
+ def destroy_folder(folder_object)
103
+ try = 0 if try.nil?
104
+ max_tries = 3
105
+ logger.log('s', "[-] [#{folder_object.name}] removing unconfigured folder")
106
+ folder_object.Destroy_Task.wait_for_completion
107
+ rescue
108
+ try += 1
109
+ try >= max_tries ? raise : retry
110
+ end
111
+
112
+ def purge_unconfigured_folders(base_folders, configured_folders, whitelist)
113
+ @connection_pool.with_metrics do |pool_object|
114
+ connection = ensured_vsphere_connection(pool_object)
115
+
116
+ base_folders.each do |base_folder|
117
+ folder_children = get_folder_children(base_folder, connection)
118
+ unless folder_children.empty?
119
+ folder_children.each do |folder_hash|
120
+ folder_hash.each do |folder_title, folder_object|
121
+ unless folder_configured?(folder_title, base_folder, configured_folders, whitelist)
122
+ destroy_folder_and_children(folder_object)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ def get_folder_children(folder_name, connection)
132
+ folders = []
133
+
134
+ propSpecs = {
135
+ :entity => self,
136
+ :inventoryPath => folder_name
137
+ }
138
+ folder_object = connection.searchIndex.FindByInventoryPath(propSpecs)
139
+
140
+ return folders if folder_object.nil?
141
+
142
+ folder_object.childEntity.each do |folder|
143
+ next unless folder.is_a? RbVmomi::VIM::Folder
144
+ folders << { folder.name => folder }
145
+ end
146
+
147
+ folders
148
+ end
149
+
45
150
  def vms_in_pool(pool_name)
46
151
  vms = []
47
152
  @connection_pool.with_metrics do |pool_object|
@@ -1,7 +1,119 @@
1
- %w[base dummy vsphere].each do |lib|
2
- begin
3
- require "vmpooler/providers/#{lib}"
4
- rescue LoadError
5
- require File.expand_path(File.join(File.dirname(__FILE__), 'providers', lib))
1
+ require 'pathname'
2
+
3
+ module Vmpooler
4
+ class Providers
5
+
6
+ # @param names [Array] - an array of names or string name of a provider
7
+ # @return [Array] - list of provider files loaded
8
+ # ie. ["lib/vmpooler/providers/base.rb", "lib/vmpooler/providers/dummy.rb", "lib/vmpooler/providers/vsphere.rb"]
9
+ def self.load_by_name(names)
10
+ names = Array(names)
11
+ instance = self.new
12
+ names.map {|name| instance.load_from_gems(name)}.flatten
13
+ end
14
+
15
+ # @return [Array] - array of provider files
16
+ # ie. ["lib/vmpooler/providers/base.rb", "lib/vmpooler/providers/dummy.rb", "lib/vmpooler/providers/vsphere.rb"]
17
+ # although these files can come from any gem
18
+ def self.load_all_providers
19
+ self.new.load_from_gems
20
+ end
21
+
22
+ # @return [Array] - returns an array of gem names that contain a provider
23
+ def self.installed_providers
24
+ self.new.vmpooler_provider_gem_list.map(&:name)
25
+ end
26
+
27
+ # @return [Array] returns a list of vmpooler providers gem plugin specs
28
+ def vmpooler_provider_gem_list
29
+ gemspecs.find_all { |spec| File.directory?(File.join(spec.full_gem_path, provider_path)) } + included_lib_dirs
30
+ end
31
+
32
+ # Internal: Find any gems containing vmpooler provider plugins and load the main file in them.
33
+ #
34
+ # @return [Array[String]] - a array of provider files
35
+ # @param name [String] - the name of the provider to load
36
+ def load_from_gems(name = nil)
37
+ paths = gem_directories.map do |gem_path|
38
+ # we don't exactly know if the provider name matches the main file name that should be loaded
39
+ # so we use globs to get everything like the name
40
+ # this could mean that vsphere5 and vsphere6 are loaded when only vsphere5 is used
41
+ Dir.glob(File.join(gem_path, "*#{name}*.rb")).each do |file|
42
+ require file
43
+ end
44
+ end
45
+ paths.flatten
46
+ end
47
+
48
+ private
49
+
50
+ # @return [String] - the relative path to the vmpooler provider dir
51
+ # this is used when searching gems for this path
52
+ def provider_path
53
+ File.join('lib','vmpooler','providers')
54
+ end
55
+
56
+ # Add constants to array to skip over classes, ie. Vmpooler::PoolManager::Provider::Dummy
57
+ def excluded_classes
58
+ []
59
+ end
60
+
61
+ # paths to include in the search path
62
+ def included_lib_dirs
63
+ []
64
+ end
65
+
66
+ # returns an array of plugin classes by looking in the object space for all loaded classes
67
+ # that start with Vmpooler::PoolManager::Provider
68
+ def plugin_classes
69
+ unless @plugin_classes
70
+ load_plugins
71
+ # weed out any subclasses in the formatter
72
+ klasses = ObjectSpace.each_object(Class).find_all do |c|
73
+ c.name && c.name.split('::').count == 3 && c.name =~ /Vmpooler::PoolManager::Provider/
74
+ end
75
+ @plugin_classes = klasses - excluded_classes || []
76
+ end
77
+ @plugin_classes
78
+ end
79
+
80
+ def plugin_map
81
+ @plugin_map ||= Hash[plugin_classes.map { |gem| [gem.send(:name), gem] }]
82
+ end
83
+
84
+
85
+
86
+ # Internal: Retrieve a list of available gem paths from RubyGems.
87
+ #
88
+ # Returns an Array of Pathname objects.
89
+ def gem_directories
90
+ dirs = []
91
+ if has_rubygems?
92
+ dirs = gemspecs.map do |spec|
93
+ lib_path = File.expand_path(File.join(spec.full_gem_path,provider_path))
94
+ lib_path if File.exists? lib_path
95
+ end + included_lib_dirs
96
+ end
97
+ dirs.reject { |dir| dir.nil? }.uniq
98
+ end
99
+
100
+ # Internal: Check if RubyGems is loaded and available.
101
+ #
102
+ # Returns true if RubyGems is available, false if not.
103
+ def has_rubygems?
104
+ defined? ::Gem
105
+ end
106
+
107
+ # Internal: Retrieve a list of available gemspecs.
108
+ #
109
+ # Returns an Array of Gem::Specification objects.
110
+ def gemspecs
111
+ @gemspecs ||= if Gem::Specification.respond_to?(:latest_specs)
112
+ Gem::Specification.latest_specs
113
+ else
114
+ Gem.searcher.init_gemspecs
115
+ end
116
+ end
117
+
6
118
  end
7
119
  end
@@ -1,4 +1,3 @@
1
-
2
1
  module Vmpooler
3
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
4
3
  end
data/lib/vmpooler.rb CHANGED
@@ -11,7 +11,7 @@ module Vmpooler
11
11
  require 'yaml'
12
12
  require 'set'
13
13
 
14
- %w[api graphite logger pool_manager statsd dummy_statsd generic_connection_pool providers].each do |lib|
14
+ %w[api graphite logger pool_manager statsd dummy_statsd generic_connection_pool].each do |lib|
15
15
  require "vmpooler/#{lib}"
16
16
  end
17
17
 
@@ -59,12 +59,12 @@ module Vmpooler
59
59
  parsed_config[:config]['clone_target'] = ENV['CLONE_TARGET'] if ENV['CLONE_TARGET']
60
60
  parsed_config[:config]['timeout'] = ENV['TIMEOUT'] if ENV['TIMEOUT']
61
61
  parsed_config[:config]['vm_lifetime_auth'] = ENV['VM_LIFETIME_AUTH'] if ENV['VM_LIFETIME_AUTH']
62
- parsed_config[:config]['ssh_key'] = ENV['SSH_KEY'] if ENV['SSH_KEY']
63
62
  parsed_config[:config]['max_tries'] = ENV['MAX_TRIES'] if ENV['MAX_TRIES']
64
63
  parsed_config[:config]['retry_factor'] = ENV['RETRY_FACTOR'] if ENV['RETRY_FACTOR']
65
64
  parsed_config[:config]['create_folders'] = ENV['CREATE_FOLDERS'] if ENV['CREATE_FOLDERS']
66
65
  parsed_config[:config]['create_template_delta_disks'] = ENV['CREATE_TEMPLATE_DELTA_DISKS'] if ENV['CREATE_TEMPLATE_DELTA_DISKS']
67
66
  parsed_config[:config]['experimental_features'] = ENV['EXPERIMENTAL_FEATURES'] if ENV['EXPERIMENTAL_FEATURES']
67
+ parsed_config[:config]['purge_unconfigured_folders'] = ENV['PURGE_UNCONFIGURED_FOLDERS'] if ENV['PURGE_UNCONFIGURED_FOLDERS']
68
68
 
69
69
  parsed_config[:redis] = parsed_config[:redis] || {}
70
70
  parsed_config[:redis]['server'] = ENV['REDIS_SERVER'] || parsed_config[:redis]['server'] || 'localhost'
@@ -79,12 +79,14 @@ module Vmpooler
79
79
 
80
80
  parsed_config[:graphite] = parsed_config[:graphite] || {} if ENV['GRAPHITE_SERVER']
81
81
  parsed_config[:graphite]['server'] = ENV['GRAPHITE_SERVER'] if ENV['GRAPHITE_SERVER']
82
+ parsed_config[:graphite]['prefix'] = ENV['GRAPHITE_PREFIX'] if ENV['GRAPHITE_PREFIX']
83
+ parsed_config[:graphite]['port'] = ENV['GRAPHITE_PORT'] if ENV['GRAPHITE_PORT']
82
84
 
83
85
  parsed_config[:auth] = parsed_config[:auth] || {} if ENV['AUTH_PROVIDER']
84
86
  if parsed_config.has_key? :auth
85
87
  parsed_config[:auth]['provider'] = ENV['AUTH_PROVIDER'] if ENV['AUTH_PROVIDER']
86
88
  parsed_config[:auth][:ldap] = parsed_config[:auth][:ldap] || {} if parsed_config[:auth]['provider'] == 'ldap'
87
- parsed_config[:auth][:ldap]['server'] = ENV['LDAP_SERVER'] if ENV['LDAP_SERVER']
89
+ parsed_config[:auth][:ldap]['host'] = ENV['LDAP_HOST'] if ENV['LDAP_HOST']
88
90
  parsed_config[:auth][:ldap]['port'] = ENV['LDAP_PORT'] if ENV['LDAP_PORT']
89
91
  parsed_config[:auth][:ldap]['base'] = ENV['LDAP_BASE'] if ENV['LDAP_BASE']
90
92
  parsed_config[:auth][:ldap]['user_object'] = ENV['LDAP_USER_OBJECT'] if ENV['LDAP_USER_OBJECT']
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vmpooler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-17 00:00:00.000000000 Z
11
+ date: 2018-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: puma