vmpooler 0.1.0 → 0.2.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.
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