vmpooler 0.1.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 +7 -0
- data/bin/vmpooler +54 -0
- data/lib/vmpooler.rb +161 -0
- data/lib/vmpooler/api.rb +53 -0
- data/lib/vmpooler/api/dashboard.rb +143 -0
- data/lib/vmpooler/api/helpers.rb +431 -0
- data/lib/vmpooler/api/reroute.rb +71 -0
- data/lib/vmpooler/api/v1.rb +938 -0
- data/lib/vmpooler/dashboard.rb +14 -0
- data/lib/vmpooler/dummy_statsd.rb +20 -0
- data/lib/vmpooler/generic_connection_pool.rb +53 -0
- data/lib/vmpooler/graphite.rb +42 -0
- data/lib/vmpooler/logger.rb +22 -0
- data/lib/vmpooler/pool_manager.rb +1029 -0
- data/lib/vmpooler/providers.rb +7 -0
- data/lib/vmpooler/providers/base.rb +231 -0
- data/lib/vmpooler/providers/dummy.rb +402 -0
- data/lib/vmpooler/providers/vsphere.rb +929 -0
- data/lib/vmpooler/public/bootstrap.min.css +5 -0
- data/lib/vmpooler/public/img/bg.png +0 -0
- data/lib/vmpooler/public/img/logo.gif +0 -0
- data/lib/vmpooler/public/img/spinner.svg +38 -0
- data/lib/vmpooler/public/img/subtle_dots.png +0 -0
- data/lib/vmpooler/public/img/textured_paper.png +0 -0
- data/lib/vmpooler/public/lib/bootstrap.min.js +7 -0
- data/lib/vmpooler/public/lib/d3.min.js +5 -0
- data/lib/vmpooler/public/lib/dashboard.js +738 -0
- data/lib/vmpooler/public/lib/jquery.min.js +4 -0
- data/lib/vmpooler/public/vmpooler.css +125 -0
- data/lib/vmpooler/statsd.rb +37 -0
- data/lib/vmpooler/version.rb +4 -0
- data/lib/vmpooler/views/dashboard.erb +63 -0
- data/lib/vmpooler/views/layout.erb +48 -0
- metadata +218 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6c0edeb60c2a23b1dc15da9f2535b9420b4de8c3
|
4
|
+
data.tar.gz: 667639acb53c6b83bb530a0134b506da57e51a32
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 647a7292fb7b5c9bc991ce68e2d42998a6604bd77392221baca916c90ca696e58cd6879a4125ce863c97b5ff04dfca98ed6d5d7380c7d9251ff663c8eca3d61b
|
7
|
+
data.tar.gz: bf66abbb2ea1b1ba8e06e82ecaa293f2e160fab25959ce260f1b066b8ddeb5185c3c1fa40b14d3fc39f6f009bdf53b8dbaa8a64ff3380249ddea31af1c30a035
|
data/bin/vmpooler
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'vmpooler'
|
4
|
+
|
5
|
+
config = Vmpooler.config
|
6
|
+
redis_host = config[:redis]['server']
|
7
|
+
redis_port = config[:redis]['port']
|
8
|
+
redis_password = config[:redis]['password']
|
9
|
+
logger_file = config[:config]['logfile']
|
10
|
+
|
11
|
+
metrics = Vmpooler.new_metrics(config)
|
12
|
+
|
13
|
+
torun_threads = []
|
14
|
+
if ARGV.count == 0
|
15
|
+
torun = ['api', 'manager']
|
16
|
+
else
|
17
|
+
torun = []
|
18
|
+
torun << 'api' if ARGV.include? 'api'
|
19
|
+
torun << 'manager' if ARGV.include? 'manager'
|
20
|
+
exit(2) if torun.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
if torun.include? 'api'
|
24
|
+
api = Thread.new do
|
25
|
+
thr = Vmpooler::API.new
|
26
|
+
redis = Vmpooler.new_redis(redis_host, redis_port, redis_password)
|
27
|
+
thr.helpers.configure(config, redis, metrics)
|
28
|
+
thr.helpers.execute!
|
29
|
+
end
|
30
|
+
torun_threads << api
|
31
|
+
end
|
32
|
+
|
33
|
+
if torun.include? 'manager'
|
34
|
+
manager = Thread.new do
|
35
|
+
Vmpooler::PoolManager.new(
|
36
|
+
config,
|
37
|
+
Vmpooler.new_logger(logger_file),
|
38
|
+
Vmpooler.new_redis(redis_host, redis_port, redis_password),
|
39
|
+
metrics
|
40
|
+
).execute!
|
41
|
+
end
|
42
|
+
torun_threads << manager
|
43
|
+
end
|
44
|
+
|
45
|
+
if ENV['VMPOOLER_DEBUG']
|
46
|
+
trap('INT') do
|
47
|
+
puts 'Shutting down.'
|
48
|
+
torun_threads.each(&:exit)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
torun_threads.each do |th|
|
53
|
+
th.join
|
54
|
+
end
|
data/lib/vmpooler.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
module Vmpooler
|
2
|
+
require 'date'
|
3
|
+
require 'json'
|
4
|
+
require 'open-uri'
|
5
|
+
require 'net/ldap'
|
6
|
+
require 'rbvmomi'
|
7
|
+
require 'redis'
|
8
|
+
require 'sinatra/base'
|
9
|
+
require 'time'
|
10
|
+
require 'timeout'
|
11
|
+
require 'yaml'
|
12
|
+
require 'set'
|
13
|
+
|
14
|
+
%w[api graphite logger pool_manager statsd dummy_statsd generic_connection_pool providers].each do |lib|
|
15
|
+
require "vmpooler/#{lib}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.config(filepath = 'vmpooler.yaml')
|
19
|
+
# Take the config either from an ENV config variable or from a config file
|
20
|
+
if ENV['VMPOOLER_CONFIG']
|
21
|
+
config_string = ENV['VMPOOLER_CONFIG']
|
22
|
+
# Parse the YAML config into a Hash
|
23
|
+
# Whitelist the Symbol class
|
24
|
+
parsed_config = YAML.safe_load(config_string, [Symbol])
|
25
|
+
else
|
26
|
+
# Take the name of the config file either from an ENV variable or from the filepath argument
|
27
|
+
config_file = ENV['VMPOOLER_CONFIG_FILE'] || filepath
|
28
|
+
parsed_config = YAML.load_file(config_file) if File.exist? config_file
|
29
|
+
end
|
30
|
+
|
31
|
+
parsed_config ||= { config: {} }
|
32
|
+
|
33
|
+
# Bail out if someone attempts to start vmpooler with dummy authentication
|
34
|
+
# without enbaling debug mode.
|
35
|
+
if parsed_config.has_key? :auth
|
36
|
+
if parsed_config[:auth]['provider'] == 'dummy'
|
37
|
+
unless ENV['VMPOOLER_DEBUG']
|
38
|
+
warning = [
|
39
|
+
'Dummy authentication should not be used outside of debug mode',
|
40
|
+
'please set environment variable VMPOOLER_DEBUG to \'true\' if you want to use dummy authentication'
|
41
|
+
]
|
42
|
+
|
43
|
+
raise warning.join(";\s")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Set some configuration defaults
|
49
|
+
parsed_config[:config]['task_limit'] = ENV['TASK_LIMIT'] || parsed_config[:config]['task_limit'] || 10
|
50
|
+
parsed_config[:config]['migration_limit'] = ENV['MIGRATION_LIMIT'] if ENV['MIGRATION_LIMIT']
|
51
|
+
parsed_config[:config]['vm_checktime'] = ENV['VM_CHECKTIME'] || parsed_config[:config]['vm_checktime'] || 15
|
52
|
+
parsed_config[:config]['vm_lifetime'] = ENV['VM_LIFETIME'] || parsed_config[:config]['vm_lifetime'] || 24
|
53
|
+
parsed_config[:config]['prefix'] = ENV['VM_PREFIX'] || parsed_config[:config]['prefix'] || ''
|
54
|
+
|
55
|
+
parsed_config[:config]['logfile'] = ENV['LOGFILE'] if ENV['LOGFILE']
|
56
|
+
|
57
|
+
parsed_config[:config]['site_name'] = ENV['SITE_NAME'] if ENV['SITE_NAME']
|
58
|
+
parsed_config[:config]['domain'] = ENV['DOMAIN_NAME'] if ENV['DOMAIN_NAME']
|
59
|
+
parsed_config[:config]['clone_target'] = ENV['CLONE_TARGET'] if ENV['CLONE_TARGET']
|
60
|
+
parsed_config[:config]['timeout'] = ENV['TIMEOUT'] if ENV['TIMEOUT']
|
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
|
+
parsed_config[:config]['max_tries'] = ENV['MAX_TRIES'] if ENV['MAX_TRIES']
|
64
|
+
parsed_config[:config]['retry_factor'] = ENV['RETRY_FACTOR'] if ENV['RETRY_FACTOR']
|
65
|
+
parsed_config[:config]['create_folders'] = ENV['CREATE_FOLDERS'] if ENV['CREATE_FOLDERS']
|
66
|
+
parsed_config[:config]['create_template_delta_disks'] = ENV['CREATE_TEMPLATE_DELTA_DISKS'] if ENV['CREATE_TEMPLATE_DELTA_DISKS']
|
67
|
+
parsed_config[:config]['experimental_features'] = ENV['EXPERIMENTAL_FEATURES'] if ENV['EXPERIMENTAL_FEATURES']
|
68
|
+
|
69
|
+
parsed_config[:redis] = parsed_config[:redis] || {}
|
70
|
+
parsed_config[:redis]['server'] = ENV['REDIS_SERVER'] || parsed_config[:redis]['server'] || 'localhost'
|
71
|
+
parsed_config[:redis]['port'] = ENV['REDIS_PORT'] if ENV['REDIS_PORT']
|
72
|
+
parsed_config[:redis]['password'] = ENV['REDIS_PASSWORD'] if ENV['REDIS_PASSWORD']
|
73
|
+
parsed_config[:redis]['data_ttl'] = ENV['REDIS_DATA_TTL'] || parsed_config[:redis]['data_ttl'] || 168
|
74
|
+
|
75
|
+
parsed_config[:statsd] = parsed_config[:statsd] || {} if ENV['STATSD_SERVER']
|
76
|
+
parsed_config[:statsd]['server'] = ENV['STATSD_SERVER'] if ENV['STATSD_SERVER']
|
77
|
+
parsed_config[:statsd]['prefix'] = ENV['STATSD_PREFIX'] if ENV['STATSD_PREFIX']
|
78
|
+
parsed_config[:statsd]['port'] = ENV['STATSD_PORT'] if ENV['STATSD_PORT']
|
79
|
+
|
80
|
+
parsed_config[:graphite] = parsed_config[:graphite] || {} if ENV['GRAPHITE_SERVER']
|
81
|
+
parsed_config[:graphite]['server'] = ENV['GRAPHITE_SERVER'] if ENV['GRAPHITE_SERVER']
|
82
|
+
|
83
|
+
parsed_config[:auth] = parsed_config[:auth] || {} if ENV['AUTH_PROVIDER']
|
84
|
+
if parsed_config.has_key? :auth
|
85
|
+
parsed_config[:auth]['provider'] = ENV['AUTH_PROVIDER'] if ENV['AUTH_PROVIDER']
|
86
|
+
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']
|
88
|
+
parsed_config[:auth][:ldap]['port'] = ENV['LDAP_PORT'] if ENV['LDAP_PORT']
|
89
|
+
parsed_config[:auth][:ldap]['base'] = ENV['LDAP_BASE'] if ENV['LDAP_BASE']
|
90
|
+
parsed_config[:auth][:ldap]['user_object'] = ENV['LDAP_USER_OBJECT'] if ENV['LDAP_USER_OBJECT']
|
91
|
+
end
|
92
|
+
|
93
|
+
# Create an index of pool aliases
|
94
|
+
parsed_config[:pool_names] = Set.new
|
95
|
+
unless parsed_config[:pools]
|
96
|
+
redis = new_redis(parsed_config[:redis]['server'], parsed_config[:redis]['port'], parsed_config[:redis]['password'])
|
97
|
+
parsed_config[:pools] = load_pools_from_redis(redis)
|
98
|
+
end
|
99
|
+
|
100
|
+
parsed_config[:pools].each do |pool|
|
101
|
+
parsed_config[:pool_names] << pool['name']
|
102
|
+
if pool['alias']
|
103
|
+
if pool['alias'].is_a?(Array)
|
104
|
+
pool['alias'].each do |a|
|
105
|
+
parsed_config[:alias] ||= {}
|
106
|
+
parsed_config[:alias][a] = pool['name']
|
107
|
+
parsed_config[:pool_names] << a
|
108
|
+
end
|
109
|
+
elsif pool['alias'].is_a?(String)
|
110
|
+
parsed_config[:alias][pool['alias']] = pool['name']
|
111
|
+
parsed_config[:pool_names] << pool['alias']
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
if parsed_config[:tagfilter]
|
117
|
+
parsed_config[:tagfilter].keys.each do |tag|
|
118
|
+
parsed_config[:tagfilter][tag] = Regexp.new(parsed_config[:tagfilter][tag])
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
parsed_config[:uptime] = Time.now
|
123
|
+
|
124
|
+
parsed_config
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.load_pools_from_redis(redis)
|
128
|
+
pools = []
|
129
|
+
redis.smembers('vmpooler__pools').each do |pool|
|
130
|
+
pool_hash = {}
|
131
|
+
redis.hgetall("vmpooler__pool__#{pool}").each do |k, v|
|
132
|
+
pool_hash[k] = v
|
133
|
+
end
|
134
|
+
pool_hash['alias'] = pool_hash['alias'].split(',')
|
135
|
+
pools << pool_hash
|
136
|
+
end
|
137
|
+
pools
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.new_redis(host = 'localhost', port = nil, password = nil)
|
141
|
+
Redis.new(host: host, port: port, password: password)
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.new_logger(logfile)
|
145
|
+
Vmpooler::Logger.new logfile
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.new_metrics(params)
|
149
|
+
if params[:statsd]
|
150
|
+
Vmpooler::Statsd.new(params[:statsd])
|
151
|
+
elsif params[:graphite]
|
152
|
+
Vmpooler::Graphite.new(params[:graphite])
|
153
|
+
else
|
154
|
+
Vmpooler::DummyStatsd.new
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.pools(conf)
|
159
|
+
conf[:pools]
|
160
|
+
end
|
161
|
+
end
|
data/lib/vmpooler/api.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Vmpooler
|
2
|
+
class API < Sinatra::Base
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
not_found do
|
8
|
+
content_type :json
|
9
|
+
|
10
|
+
result = {
|
11
|
+
ok: false
|
12
|
+
}
|
13
|
+
|
14
|
+
JSON.pretty_generate(result)
|
15
|
+
end
|
16
|
+
|
17
|
+
get '/' do
|
18
|
+
redirect to('/dashboard/')
|
19
|
+
end
|
20
|
+
|
21
|
+
# Load dashboard components
|
22
|
+
begin
|
23
|
+
require 'dashboard'
|
24
|
+
rescue LoadError
|
25
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'dashboard'))
|
26
|
+
end
|
27
|
+
|
28
|
+
use Vmpooler::Dashboard
|
29
|
+
|
30
|
+
# Load API components
|
31
|
+
%w[helpers dashboard reroute v1].each do |lib|
|
32
|
+
begin
|
33
|
+
require "api/#{lib}"
|
34
|
+
rescue LoadError
|
35
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'api', lib))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
use Vmpooler::API::Dashboard
|
40
|
+
use Vmpooler::API::Reroute
|
41
|
+
use Vmpooler::API::V1
|
42
|
+
|
43
|
+
def configure(config, redis, metrics)
|
44
|
+
self.settings.set :config, config
|
45
|
+
self.settings.set :redis, redis
|
46
|
+
self.settings.set :metrics, metrics
|
47
|
+
end
|
48
|
+
|
49
|
+
def execute!
|
50
|
+
self.settings.run!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Vmpooler
|
2
|
+
class API
|
3
|
+
class Dashboard < Sinatra::Base
|
4
|
+
# handle to the App's configuration information
|
5
|
+
def config
|
6
|
+
@config ||= Vmpooler::API.settings.config
|
7
|
+
end
|
8
|
+
|
9
|
+
# configuration setting for server hosting graph URLs to view
|
10
|
+
def graph_server
|
11
|
+
return @graph_server if @graph_server
|
12
|
+
|
13
|
+
if config[:graphs]
|
14
|
+
return false unless config[:graphs]['server']
|
15
|
+
@graph_server = config[:graphs]['server']
|
16
|
+
elsif config[:graphite]
|
17
|
+
return false unless config[:graphite]['server']
|
18
|
+
@graph_server = config[:graphite]['server']
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# configuration setting for URL prefix for graphs to view
|
25
|
+
def graph_prefix
|
26
|
+
return @graph_prefix if @graph_prefix
|
27
|
+
|
28
|
+
if config[:graphs]
|
29
|
+
return 'vmpooler' unless config[:graphs]['prefix']
|
30
|
+
@graph_prefix = config[:graphs]['prefix']
|
31
|
+
elsif config[:graphite]
|
32
|
+
return false unless config[:graphite]['prefix']
|
33
|
+
@graph_prefix = config[:graphite]['prefix']
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# what is the base URL for viewable graphs?
|
40
|
+
def graph_url
|
41
|
+
return false unless graph_server && graph_prefix
|
42
|
+
@graph_url ||= "http://#{graph_server}/render?target=#{graph_prefix}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# return a full URL to a viewable graph for a given metrics target (graphite syntax)
|
46
|
+
def graph_link(target = '')
|
47
|
+
return '' unless graph_url
|
48
|
+
graph_url + target
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
get '/dashboard/stats/vmpooler/pool/?' do
|
53
|
+
content_type :json
|
54
|
+
result = {}
|
55
|
+
|
56
|
+
Vmpooler::API.settings.config[:pools].each do |pool|
|
57
|
+
result[pool['name']] ||= {}
|
58
|
+
result[pool['name']]['size'] = pool['size']
|
59
|
+
result[pool['name']]['ready'] = Vmpooler::API.settings.redis.scard('vmpooler__ready__' + pool['name'])
|
60
|
+
end
|
61
|
+
|
62
|
+
if params[:history]
|
63
|
+
if graph_url
|
64
|
+
history ||= {}
|
65
|
+
|
66
|
+
begin
|
67
|
+
buffer = open(graph_link('.ready.*&from=-1hour&format=json')).read
|
68
|
+
history = JSON.parse(buffer)
|
69
|
+
|
70
|
+
history.each do |pool|
|
71
|
+
if pool['target'] =~ /.*\.(.*)$/
|
72
|
+
pool['name'] = Regexp.last_match[1]
|
73
|
+
|
74
|
+
if result[pool['name']]
|
75
|
+
pool['last'] = result[pool['name']]['size']
|
76
|
+
result[pool['name']]['history'] ||= []
|
77
|
+
|
78
|
+
pool['datapoints'].each do |metric|
|
79
|
+
8.times do |_n|
|
80
|
+
if metric[0]
|
81
|
+
pool['last'] = metric[0].to_i
|
82
|
+
result[pool['name']]['history'].push(metric[0].to_i)
|
83
|
+
else
|
84
|
+
result[pool['name']]['history'].push(pool['last'])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
rescue
|
92
|
+
end
|
93
|
+
else
|
94
|
+
Vmpooler::API.settings.config[:pools].each do |pool|
|
95
|
+
result[pool['name']] ||= {}
|
96
|
+
result[pool['name']]['history'] = [Vmpooler::API.settings.redis.scard('vmpooler__ready__' + pool['name'])]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
JSON.pretty_generate(result)
|
101
|
+
end
|
102
|
+
|
103
|
+
get '/dashboard/stats/vmpooler/running/?' do
|
104
|
+
content_type :json
|
105
|
+
result = {}
|
106
|
+
|
107
|
+
Vmpooler::API.settings.config[:pools].each do |pool|
|
108
|
+
running = Vmpooler::API.settings.redis.scard('vmpooler__running__' + pool['name'])
|
109
|
+
pool['major'] = Regexp.last_match[1] if pool['name'] =~ /^(\w+)\-/
|
110
|
+
result[pool['major']] ||= {}
|
111
|
+
result[pool['major']]['running'] = result[pool['major']]['running'].to_i + running.to_i
|
112
|
+
end
|
113
|
+
|
114
|
+
if params[:history]
|
115
|
+
if graph_url
|
116
|
+
begin
|
117
|
+
buffer = open(graph_link('.running.*&from=-1hour&format=json')).read
|
118
|
+
JSON.parse(buffer).each do |pool|
|
119
|
+
if pool['target'] =~ /.*\.(.*)$/
|
120
|
+
pool['name'] = Regexp.last_match[1]
|
121
|
+
pool['major'] = Regexp.last_match[1] if pool['name'] =~ /^(\w+)\-/
|
122
|
+
result[pool['major']]['history'] ||= []
|
123
|
+
|
124
|
+
for i in 0..pool['datapoints'].length
|
125
|
+
if pool['datapoints'][i] && pool['datapoints'][i][0]
|
126
|
+
pool['last'] = pool['datapoints'][i][0]
|
127
|
+
result[pool['major']]['history'][i] ||= 0
|
128
|
+
result[pool['major']]['history'][i] = result[pool['major']]['history'][i].to_i + pool['datapoints'][i][0].to_i
|
129
|
+
else
|
130
|
+
result[pool['major']]['history'][i] = result[pool['major']]['history'][i].to_i + pool['last'].to_i
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
rescue
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
JSON.pretty_generate(result)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,431 @@
|
|
1
|
+
module Vmpooler
|
2
|
+
|
3
|
+
class API
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
|
7
|
+
def has_token?
|
8
|
+
request.env['HTTP_X_AUTH_TOKEN'].nil? ? false : true
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid_token?(backend)
|
12
|
+
return false unless has_token?
|
13
|
+
|
14
|
+
backend.exists('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN']) ? true : false
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate_token(backend)
|
18
|
+
if valid_token?(backend)
|
19
|
+
backend.hset('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN'], 'last', Time.now)
|
20
|
+
|
21
|
+
return true
|
22
|
+
end
|
23
|
+
|
24
|
+
content_type :json
|
25
|
+
|
26
|
+
result = { 'ok' => false }
|
27
|
+
|
28
|
+
headers['WWW-Authenticate'] = 'Basic realm="Authentication required"'
|
29
|
+
halt 401, JSON.pretty_generate(result)
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_auth(backend)
|
33
|
+
return if authorized?
|
34
|
+
|
35
|
+
content_type :json
|
36
|
+
|
37
|
+
result = { 'ok' => false }
|
38
|
+
|
39
|
+
headers['WWW-Authenticate'] = 'Basic realm="Authentication required"'
|
40
|
+
halt 401, JSON.pretty_generate(result)
|
41
|
+
end
|
42
|
+
|
43
|
+
def authorized?
|
44
|
+
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
45
|
+
|
46
|
+
if @auth.provided? and @auth.basic? and @auth.credentials
|
47
|
+
username, password = @auth.credentials
|
48
|
+
|
49
|
+
if authenticate(Vmpooler::API.settings.config[:auth], username, password)
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
|
57
|
+
def authenticate_ldap(port, host, user_object, base, username_str, password_str)
|
58
|
+
ldap = Net::LDAP.new(
|
59
|
+
:host => host,
|
60
|
+
:port => port,
|
61
|
+
:encryption => {
|
62
|
+
:method => :start_tls,
|
63
|
+
:tls_options => { :ssl_version => 'TLSv1' }
|
64
|
+
},
|
65
|
+
:base => base,
|
66
|
+
:auth => {
|
67
|
+
:method => :simple,
|
68
|
+
:username => "#{user_object}=#{username_str},#{base}",
|
69
|
+
:password => password_str
|
70
|
+
}
|
71
|
+
)
|
72
|
+
|
73
|
+
return true if ldap.bind
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
|
77
|
+
def authenticate(auth, username_str, password_str)
|
78
|
+
case auth['provider']
|
79
|
+
when 'dummy'
|
80
|
+
return (username_str != password_str)
|
81
|
+
when 'ldap'
|
82
|
+
ldap_base = auth[:ldap]['base']
|
83
|
+
ldap_port = auth[:ldap]['port'] || 389
|
84
|
+
|
85
|
+
if ldap_base.is_a? Array
|
86
|
+
ldap_base.each do |search_base|
|
87
|
+
result = authenticate_ldap(
|
88
|
+
ldap_port,
|
89
|
+
auth[:ldap]['host'],
|
90
|
+
auth[:ldap]['user_object'],
|
91
|
+
search_base,
|
92
|
+
username_str,
|
93
|
+
password_str,
|
94
|
+
)
|
95
|
+
return true if result == true
|
96
|
+
end
|
97
|
+
else
|
98
|
+
result = authenticate_ldap(
|
99
|
+
ldap_port,
|
100
|
+
auth[:ldap]['host'],
|
101
|
+
auth[:ldap]['user_object'],
|
102
|
+
ldap_base,
|
103
|
+
username_str,
|
104
|
+
password_str,
|
105
|
+
)
|
106
|
+
return result
|
107
|
+
end
|
108
|
+
|
109
|
+
return false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def export_tags(backend, hostname, tags)
|
114
|
+
tags.each_pair do |tag, value|
|
115
|
+
next if value.nil? or value.empty?
|
116
|
+
|
117
|
+
backend.hset('vmpooler__vm__' + hostname, 'tag:' + tag, value)
|
118
|
+
backend.hset('vmpooler__tag__' + Date.today.to_s, hostname + ':' + tag, value)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def filter_tags(tags)
|
123
|
+
return unless Vmpooler::API.settings.config[:tagfilter]
|
124
|
+
|
125
|
+
tags.each_pair do |tag, value|
|
126
|
+
next unless filter = Vmpooler::API.settings.config[:tagfilter][tag]
|
127
|
+
tags[tag] = value.match(filter).captures.join if value.match(filter)
|
128
|
+
end
|
129
|
+
|
130
|
+
tags
|
131
|
+
end
|
132
|
+
|
133
|
+
def mean(list)
|
134
|
+
s = list.map(&:to_f).reduce(:+).to_f
|
135
|
+
(s > 0 && list.length > 0) ? s / list.length.to_f : 0
|
136
|
+
end
|
137
|
+
|
138
|
+
def validate_date_str(date_str)
|
139
|
+
/^\d{4}-\d{2}-\d{2}$/ === date_str
|
140
|
+
end
|
141
|
+
|
142
|
+
def hostname_shorten(hostname, domain=nil)
|
143
|
+
if domain && hostname =~ /^\w+\.#{domain}$/
|
144
|
+
hostname = hostname[/[^\.]+/]
|
145
|
+
end
|
146
|
+
|
147
|
+
hostname
|
148
|
+
end
|
149
|
+
|
150
|
+
def get_task_times(backend, task, date_str)
|
151
|
+
backend.hvals("vmpooler__#{task}__" + date_str).map(&:to_f)
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_capacity_metrics(pools, backend)
|
155
|
+
capacity = {
|
156
|
+
current: 0,
|
157
|
+
total: 0,
|
158
|
+
percent: 0
|
159
|
+
}
|
160
|
+
|
161
|
+
pools.each do |pool|
|
162
|
+
pool['capacity'] = backend.scard('vmpooler__ready__' + pool['name']).to_i
|
163
|
+
|
164
|
+
capacity[:current] += pool['capacity']
|
165
|
+
capacity[:total] += pool['size'].to_i
|
166
|
+
end
|
167
|
+
|
168
|
+
if capacity[:total] > 0
|
169
|
+
capacity[:percent] = ((capacity[:current].to_f / capacity[:total].to_f) * 100.0).round(1)
|
170
|
+
end
|
171
|
+
|
172
|
+
capacity
|
173
|
+
end
|
174
|
+
|
175
|
+
def get_queue_metrics(pools, backend)
|
176
|
+
queue = {
|
177
|
+
pending: 0,
|
178
|
+
cloning: 0,
|
179
|
+
booting: 0,
|
180
|
+
ready: 0,
|
181
|
+
running: 0,
|
182
|
+
completed: 0,
|
183
|
+
total: 0
|
184
|
+
}
|
185
|
+
|
186
|
+
pools.each do |pool|
|
187
|
+
queue[:pending] += backend.scard('vmpooler__pending__' + pool['name']).to_i
|
188
|
+
queue[:ready] += backend.scard('vmpooler__ready__' + pool['name']).to_i
|
189
|
+
queue[:running] += backend.scard('vmpooler__running__' + pool['name']).to_i
|
190
|
+
queue[:completed] += backend.scard('vmpooler__completed__' + pool['name']).to_i
|
191
|
+
end
|
192
|
+
|
193
|
+
queue[:cloning] = backend.get('vmpooler__tasks__clone').to_i
|
194
|
+
queue[:booting] = queue[:pending].to_i - queue[:cloning].to_i
|
195
|
+
queue[:booting] = 0 if queue[:booting] < 0
|
196
|
+
queue[:total] = queue[:pending].to_i + queue[:ready].to_i + queue[:running].to_i + queue[:completed].to_i
|
197
|
+
|
198
|
+
queue
|
199
|
+
end
|
200
|
+
|
201
|
+
def get_tag_metrics(backend, date_str, opts = {})
|
202
|
+
opts = {:only => false}.merge(opts)
|
203
|
+
|
204
|
+
tags = {}
|
205
|
+
|
206
|
+
backend.hgetall('vmpooler__tag__' + date_str).each do |key, value|
|
207
|
+
hostname = 'unknown'
|
208
|
+
tag = 'unknown'
|
209
|
+
|
210
|
+
if key =~ /\:/
|
211
|
+
hostname, tag = key.split(':', 2)
|
212
|
+
end
|
213
|
+
|
214
|
+
if opts[:only]
|
215
|
+
next unless tag == opts[:only]
|
216
|
+
end
|
217
|
+
|
218
|
+
tags[tag] ||= {}
|
219
|
+
tags[tag][value] ||= 0
|
220
|
+
tags[tag][value] += 1
|
221
|
+
|
222
|
+
tags[tag]['total'] ||= 0
|
223
|
+
tags[tag]['total'] += 1
|
224
|
+
end
|
225
|
+
|
226
|
+
tags
|
227
|
+
end
|
228
|
+
|
229
|
+
def get_tag_summary(backend, from_date, to_date, opts = {})
|
230
|
+
opts = {:only => false}.merge(opts)
|
231
|
+
|
232
|
+
result = {
|
233
|
+
tag: {},
|
234
|
+
daily: []
|
235
|
+
}
|
236
|
+
|
237
|
+
(from_date..to_date).each do |date|
|
238
|
+
daily = {
|
239
|
+
date: date.to_s,
|
240
|
+
tag: get_tag_metrics(backend, date.to_s, opts)
|
241
|
+
}
|
242
|
+
result[:daily].push(daily)
|
243
|
+
end
|
244
|
+
|
245
|
+
result[:daily].each do |daily|
|
246
|
+
daily[:tag].each_key do |tag|
|
247
|
+
result[:tag][tag] ||= {}
|
248
|
+
|
249
|
+
daily[:tag][tag].each do |key, value|
|
250
|
+
result[:tag][tag][key] ||= 0
|
251
|
+
result[:tag][tag][key] += value
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
result
|
257
|
+
end
|
258
|
+
|
259
|
+
def get_task_metrics(backend, task_str, date_str, opts = {})
|
260
|
+
opts = {:bypool => false, :only => false}.merge(opts)
|
261
|
+
|
262
|
+
task = {
|
263
|
+
duration: {
|
264
|
+
average: 0,
|
265
|
+
min: 0,
|
266
|
+
max: 0,
|
267
|
+
total: 0
|
268
|
+
},
|
269
|
+
count: {
|
270
|
+
total: 0
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|
274
|
+
task[:count][:total] = backend.hlen('vmpooler__' + task_str + '__' + date_str).to_i
|
275
|
+
|
276
|
+
if task[:count][:total] > 0
|
277
|
+
if opts[:bypool] == true
|
278
|
+
task_times_bypool = {}
|
279
|
+
|
280
|
+
task[:count][:pool] = {}
|
281
|
+
task[:duration][:pool] = {}
|
282
|
+
|
283
|
+
backend.hgetall('vmpooler__' + task_str + '__' + date_str).each do |key, value|
|
284
|
+
pool = 'unknown'
|
285
|
+
hostname = 'unknown'
|
286
|
+
|
287
|
+
if key =~ /\:/
|
288
|
+
pool, hostname = key.split(':')
|
289
|
+
else
|
290
|
+
hostname = key
|
291
|
+
end
|
292
|
+
|
293
|
+
task[:count][:pool][pool] ||= {}
|
294
|
+
task[:duration][:pool][pool] ||= {}
|
295
|
+
|
296
|
+
task_times_bypool[pool] ||= []
|
297
|
+
task_times_bypool[pool].push(value.to_f)
|
298
|
+
end
|
299
|
+
|
300
|
+
task_times_bypool.each_key do |pool|
|
301
|
+
task[:count][:pool][pool][:total] = task_times_bypool[pool].length
|
302
|
+
|
303
|
+
task[:duration][:pool][pool][:total] = task_times_bypool[pool].reduce(:+).to_f
|
304
|
+
task[:duration][:pool][pool][:average] = (task[:duration][:pool][pool][:total] / task[:count][:pool][pool][:total]).round(1)
|
305
|
+
task[:duration][:pool][pool][:min], task[:duration][:pool][pool][:max] = task_times_bypool[pool].minmax
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
task_times = get_task_times(backend, task_str, date_str)
|
310
|
+
|
311
|
+
task[:duration][:total] = task_times.reduce(:+).to_f
|
312
|
+
task[:duration][:average] = (task[:duration][:total] / task[:count][:total]).round(1)
|
313
|
+
task[:duration][:min], task[:duration][:max] = task_times.minmax
|
314
|
+
end
|
315
|
+
|
316
|
+
if opts[:only]
|
317
|
+
task.each_key do |key|
|
318
|
+
task.delete(key) unless key.to_s == opts[:only]
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
task
|
323
|
+
end
|
324
|
+
|
325
|
+
def get_task_summary(backend, task_str, from_date, to_date, opts = {})
|
326
|
+
opts = {:bypool => false, :only => false}.merge(opts)
|
327
|
+
|
328
|
+
task_sym = task_str.to_sym
|
329
|
+
|
330
|
+
result = {
|
331
|
+
task_sym => {},
|
332
|
+
daily: []
|
333
|
+
}
|
334
|
+
|
335
|
+
(from_date..to_date).each do |date|
|
336
|
+
daily = {
|
337
|
+
date: date.to_s,
|
338
|
+
task_sym => get_task_metrics(backend, task_str, date.to_s, opts)
|
339
|
+
}
|
340
|
+
result[:daily].push(daily)
|
341
|
+
end
|
342
|
+
|
343
|
+
daily_task = {}
|
344
|
+
daily_task_bypool = {} if opts[:bypool] == true
|
345
|
+
|
346
|
+
result[:daily].each do |daily|
|
347
|
+
daily[task_sym].each_key do |type|
|
348
|
+
result[task_sym][type] ||= {}
|
349
|
+
daily_task[type] ||= {}
|
350
|
+
|
351
|
+
['min', 'max'].each do |key|
|
352
|
+
if daily[task_sym][type][key]
|
353
|
+
daily_task[type][:data] ||= []
|
354
|
+
daily_task[type][:data].push(daily[task_sym][type][key])
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
result[task_sym][type][:total] ||= 0
|
359
|
+
result[task_sym][type][:total] += daily[task_sym][type][:total]
|
360
|
+
|
361
|
+
if opts[:bypool] == true
|
362
|
+
result[task_sym][type][:pool] ||= {}
|
363
|
+
daily_task_bypool[type] ||= {}
|
364
|
+
|
365
|
+
next unless daily[task_sym][type][:pool]
|
366
|
+
|
367
|
+
daily[task_sym][type][:pool].each_key do |pool|
|
368
|
+
result[task_sym][type][:pool][pool] ||= {}
|
369
|
+
daily_task_bypool[type][pool] ||= {}
|
370
|
+
|
371
|
+
['min', 'max'].each do |key|
|
372
|
+
if daily[task_sym][type][:pool][pool][key.to_sym]
|
373
|
+
daily_task_bypool[type][pool][:data] ||= []
|
374
|
+
daily_task_bypool[type][pool][:data].push(daily[task_sym][type][:pool][pool][key.to_sym])
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
result[task_sym][type][:pool][pool][:total] ||= 0
|
379
|
+
result[task_sym][type][:pool][pool][:total] += daily[task_sym][type][:pool][pool][:total]
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
result[task_sym].each_key do |type|
|
386
|
+
if daily_task[type][:data]
|
387
|
+
result[task_sym][type][:min], result[task_sym][type][:max] = daily_task[type][:data].minmax
|
388
|
+
result[task_sym][type][:average] = mean(daily_task[type][:data])
|
389
|
+
end
|
390
|
+
|
391
|
+
if opts[:bypool] == true
|
392
|
+
result[task_sym].each_key do |type|
|
393
|
+
result[task_sym][type][:pool].each_key do |pool|
|
394
|
+
if daily_task_bypool[type][pool][:data]
|
395
|
+
result[task_sym][type][:pool][pool][:min], result[task_sym][type][:pool][pool][:max] = daily_task_bypool[type][pool][:data].minmax
|
396
|
+
result[task_sym][type][:pool][pool][:average] = mean(daily_task_bypool[type][pool][:data])
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
result
|
404
|
+
end
|
405
|
+
|
406
|
+
def pool_index(pools)
|
407
|
+
pools_hash = {}
|
408
|
+
index = 0
|
409
|
+
for pool in pools
|
410
|
+
pools_hash[pool['name']] = index
|
411
|
+
index += 1
|
412
|
+
end
|
413
|
+
pools_hash
|
414
|
+
end
|
415
|
+
|
416
|
+
def template_ready?(pool, backend)
|
417
|
+
prepared_template = backend.hget('vmpooler__template__prepared', pool['name'])
|
418
|
+
return false if prepared_template.nil?
|
419
|
+
return true if pool['template'] == prepared_template
|
420
|
+
return false
|
421
|
+
end
|
422
|
+
|
423
|
+
def is_integer?(x)
|
424
|
+
Integer(x)
|
425
|
+
true
|
426
|
+
rescue
|
427
|
+
false
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|