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