vmpooler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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