cf_light_api 2.0.0.pre1 → 2.0.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cf_light_api/lib.rb +60 -0
- data/lib/cf_light_api/worker.rb +31 -167
- metadata +3 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d66b56d2709d066bd7bdf1a87ebbf8b1e240a219
|
4
|
+
data.tar.gz: 6aebb9a1303ba0e48927e361a38ac1796b87a983
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2030c3344aea542a4faee1e364f5c2242fea81f764baa41dfc3236db8635fda2f88ef5827b8d0f17640dfa2bcbeae8af77e37649f17bf03373bfa7560ed16372
|
7
|
+
data.tar.gz: f2730633fc1827d02937eda58b7b591fea38e0604dc8357d17caf34747639caff977f232a646feb0a97acb04373ddad93f165efd9799c4b7168a882ecbf219c2
|
@@ -0,0 +1,60 @@
|
|
1
|
+
def formatted_instance_stats_for_app app
|
2
|
+
instances = cf_rest("/v2/apps/#{app['metadata']['guid']}/stats")[0]
|
3
|
+
raise "Unable to retrieve app instance stats: '#{instances['error_code']}'" if instances['error_code']
|
4
|
+
instances.map{|key,value|value}
|
5
|
+
end
|
6
|
+
|
7
|
+
def cf_rest(path, method='GET')
|
8
|
+
@logger.info "Making #{method} request for #{path}..."
|
9
|
+
|
10
|
+
resources = []
|
11
|
+
response = JSON.parse(@cf_client.base.rest_client.request(method, path)[1][:body])
|
12
|
+
|
13
|
+
# Some endpoints return a 'resources' array, others are flat, depending on the path.
|
14
|
+
if response['resources']
|
15
|
+
resources << response['resources']
|
16
|
+
else
|
17
|
+
resources << response
|
18
|
+
end
|
19
|
+
|
20
|
+
# Handle the pagination by recursing over myself until we get a response which doesn't contain a 'next_url'
|
21
|
+
# at which point all the resources are returned up the stack and flattened.
|
22
|
+
resources << cf_rest(response['next_url'], method) unless response['next_url'] == nil
|
23
|
+
resources.flatten
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_client(cf_api=ENV['CF_API'], cf_user=ENV['CF_USER'], cf_password=ENV['CF_PASSWORD'])
|
27
|
+
client = CFoundry::Client.get(cf_api)
|
28
|
+
client.login({:username => cf_user, :password => cf_password})
|
29
|
+
client
|
30
|
+
end
|
31
|
+
|
32
|
+
def send_instance_usage_data_to_graphite(instance_stats, org, space, app_name)
|
33
|
+
app_name.gsub! ".", "_" # Some apps have dots in the app name
|
34
|
+
|
35
|
+
instance_stats.each_with_index do |instance_data, index|
|
36
|
+
graphite_base_key = "cf_apps.#{org}.#{space}.#{app_name}.#{index}"
|
37
|
+
@logger.info " Exporting app instance \##{index} usage statistics to Graphite, path '#{graphite_base_key}'"
|
38
|
+
|
39
|
+
# Quota data
|
40
|
+
['mem_quota', 'disk_quota'].each do |key|
|
41
|
+
@graphite.metrics "#{graphite_base_key}.#{key}" => instance_data['stats'][key]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Usage data
|
45
|
+
['mem', 'disk', 'cpu'].each do |key|
|
46
|
+
@graphite.metrics "#{graphite_base_key}.#{key}" => instance_data['stats']['usage'][key]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def put_in_redis(key, data)
|
52
|
+
REDIS.set key, data.to_json
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_duration(elapsed_seconds)
|
56
|
+
seconds = elapsed_seconds % 60
|
57
|
+
minutes = (elapsed_seconds / 60) % 60
|
58
|
+
hours = elapsed_seconds / (60 * 60)
|
59
|
+
format("%02d hrs, %02d mins, %02d secs", hours, minutes, seconds)
|
60
|
+
end
|
data/lib/cf_light_api/worker.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
require 'cfoundry'
|
2
2
|
require 'json'
|
3
3
|
require 'rufus-scheduler'
|
4
|
-
require 'parallel'
|
5
4
|
require 'redlock'
|
6
5
|
require 'logger'
|
7
6
|
require 'graphite-api'
|
8
7
|
require 'date'
|
9
8
|
|
9
|
+
require_relative 'lib.rb'
|
10
|
+
|
10
11
|
@logger = Logger.new(STDOUT)
|
11
12
|
@logger.formatter = proc do |severity, datetime, progname, msg|
|
12
13
|
"#{datetime} [cf_light_api:worker]: #{msg}\n"
|
@@ -19,62 +20,34 @@ end
|
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
22
|
-
|
23
|
+
# If either of the Graphite settings are set, verify that they are both set, or exit with an error.
|
24
|
+
if ENV['GRAPHITE_HOST'] or ENV['GRAPHITE_PORT']
|
25
|
+
['GRAPHITE_HOST', 'GRAPHITE_PORT'].each do |env|
|
26
|
+
unless ENV[env]
|
27
|
+
@logger.info "Error: please set the '#{env}' environment variable to enable exporting to Graphite."
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
23
33
|
UPDATE_INTERVAL = (ENV['UPDATE_INTERVAL'] || '5m').to_s # If you change the default '5m' here, also remember to change the default age validity in sinatra/cf_light_api.rb:31
|
24
34
|
UPDATE_TIMEOUT = (ENV['UPDATE_TIMEOUT'] || '5m').to_s
|
25
35
|
|
26
36
|
lock_manager = Redlock::Client.new([ENV['REDIS_URI']])
|
27
37
|
scheduler = Rufus::Scheduler.new
|
28
38
|
|
29
|
-
@logger.info "Parallel maps: '#{PARALLEL_MAPS}'"
|
30
39
|
@logger.info "Update interval: '#{UPDATE_INTERVAL}'"
|
31
40
|
@logger.info "Update timeout: '#{UPDATE_TIMEOUT}'"
|
32
|
-
@logger.info "Graphite server: #{ENV['GRAPHITE']}" if ENV['GRAPHITE']
|
33
|
-
|
34
|
-
#@domains = {}
|
35
|
-
# @domains = cf_rest('/v2/domains?results-per-page=100') # We're retrieving this from the app instance 'uris' key instead.
|
36
|
-
# @routes = cf_rest('/v2/routes?results-per-page=100')
|
37
|
-
|
38
|
-
# def formatted_routes_for_app app
|
39
|
-
# routes = cf_rest(app['entity']['routes_url'])
|
40
|
-
|
41
|
-
# routes.collect do |route|
|
42
|
-
# domain = @domains.find{|a_domain| a_domain['metadata']['guid'] == route['entity']['domain_guid']}['entity']['name']
|
43
|
-
# # Suffix the hostname with a period for concatenation, unless it's blank (which can happen for apex routes).
|
44
|
-
# host = route['entity']['host'] != '' ? "#{route['entity']['host']}." : ''
|
45
|
-
# path = route['entity']['path']
|
46
|
-
# "#{host}#{domain}#{path}"
|
47
|
-
# end
|
48
|
-
# end
|
49
|
-
|
50
|
-
def formatted_instance_stats_for_app app
|
51
|
-
instances = cf_rest("/v2/apps/#{app['metadata']['guid']}/stats")[0]
|
52
|
-
raise "Unable to retrieve app instance stats: '#{instances['error_code']}'" if instances['error_code']
|
53
|
-
instances.map{|key,value|value}
|
54
|
-
end
|
55
|
-
|
56
|
-
def cf_rest(path, method='GET')
|
57
|
-
@logger.info "Making #{method} request for #{path}..."
|
58
41
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
if response['resources']
|
64
|
-
resources << response['resources']
|
65
|
-
else
|
66
|
-
resources << response
|
67
|
-
end
|
68
|
-
|
69
|
-
# Handle the pagination by recursing over myself until we get a response which doesn't contain a 'next_url'
|
70
|
-
# at which point all the resources are returned up the stack and flattened.
|
71
|
-
resources << cf_rest(response['next_url'], method) unless response['next_url'] == nil
|
72
|
-
resources.flatten
|
42
|
+
if ENV['GRAPHITE_HOST'] and ENV['GRAPHITE_PORT']
|
43
|
+
@logger.info "Graphite server: #{ENV['GRAPHITE_HOST']}:#{ENV['GRAPHITE_PORT']}"
|
44
|
+
else
|
45
|
+
@logger.info 'Graphite server: Disabled'
|
73
46
|
end
|
74
47
|
|
75
48
|
scheduler.every UPDATE_INTERVAL, :first_in => '5s', :overlap => false, :timeout => UPDATE_TIMEOUT do
|
76
49
|
@cf_client = nil
|
77
|
-
|
50
|
+
@graphite = GraphiteAPI.new(graphite: "#{ENV['GRAPHITE_HOST']}:#{ENV['GRAPHITE_PORT']}") if ENV['GRAPHITE_HOST'] and ENV['GRAPHITE_PORT']
|
78
51
|
|
79
52
|
begin
|
80
53
|
lock_manager.lock("#{ENV['REDIS_KEY_PREFIX']}:lock", 5*60*1000) do |lock|
|
@@ -91,9 +64,6 @@ scheduler.every UPDATE_INTERVAL, :first_in => '5s', :overlap => false, :timeout
|
|
91
64
|
@spaces = cf_rest('/v2/spaces?results-per-page=100')
|
92
65
|
@stacks = cf_rest('/v2/stacks?results-per-page=100')
|
93
66
|
|
94
|
-
# org_data = get_org_data(@cf_client)
|
95
|
-
# app_data = get_app_data(@cf_client, graphite)
|
96
|
-
|
97
67
|
formatted_orgs = @orgs.map do |org|
|
98
68
|
quota = @quotas.find{|a_quota| a_quota['metadata']['guid'] == org['entity']['quota_definition_guid']}
|
99
69
|
|
@@ -110,7 +80,7 @@ scheduler.every UPDATE_INTERVAL, :first_in => '5s', :overlap => false, :timeout
|
|
110
80
|
end
|
111
81
|
|
112
82
|
formatted_apps = @apps.map do |app|
|
113
|
-
# TODO: This is a bit repetative, could improve
|
83
|
+
# TODO: This is a bit repetative, could maybe improve?
|
114
84
|
space = @spaces.find{|a_space| a_space['metadata']['guid'] == app['entity']['space_guid']}
|
115
85
|
org = @orgs.find{|an_org| an_org['metadata']['guid'] == space['entity']['organization_guid']}
|
116
86
|
stack = @stacks.find{|a_stack| a_stack['metadata']['guid'] == app['entity']['stack_guid']}
|
@@ -124,23 +94,28 @@ scheduler.every UPDATE_INTERVAL, :first_in => '5s', :overlap => false, :timeout
|
|
124
94
|
:space => space['entity']['name'],
|
125
95
|
:stack => stack['entity']['name'],
|
126
96
|
:buildpack => app['entity']['buildpack'],
|
127
|
-
# This requires a call to /v2/apps/[guid]/routes for each app, or we can just use the 'uris' key from /v2/apps/[guid]/stats
|
128
|
-
# which we have to call anyway, to get app instance usage stats..
|
129
|
-
# :routes => running ? formatted_routes_for_app(app) : [],
|
130
97
|
:data_from => Time.now.to_i,
|
131
98
|
:last_uploaded => app['metadata']['updated_at'] ? DateTime.parse(app['metadata']['updated_at']).strftime('%Y-%m-%d %T %z') : nil
|
132
99
|
}
|
133
100
|
|
101
|
+
# Add additional data, such as instance usage statistics, and routes - but this is only possible
|
102
|
+
# if the instance is running.
|
134
103
|
additional_data = {}
|
104
|
+
|
135
105
|
begin
|
136
106
|
instance_stats = []
|
137
107
|
routes = []
|
138
108
|
if running
|
109
|
+
# Finds the first running app instance that has a set of routes, in case there are stopped/crashed app instances that don't have any routes.
|
139
110
|
instance_stats = formatted_instance_stats_for_app(app)
|
140
|
-
# Finds the first running app instance that has a set of routes, in case there are stopped/crashed app instances that don't have any.
|
141
111
|
running_instances = instance_stats.select{|instance| instance['stats']['uris'] if instance['state'] == 'RUNNING'}
|
142
112
|
raise "Unable to retrieve app routes - no app instances are running." if running_instances.empty?
|
113
|
+
|
143
114
|
routes = running_instances.first['stats']['uris']
|
115
|
+
|
116
|
+
if @graphite
|
117
|
+
send_instance_usage_data_to_graphite(instance_stats, org['entity']['name'], space['entity']['name'], app['entity']['name'])
|
118
|
+
end
|
144
119
|
end
|
145
120
|
|
146
121
|
additional_data = {
|
@@ -149,7 +124,11 @@ scheduler.every UPDATE_INTERVAL, :first_in => '5s', :overlap => false, :timeout
|
|
149
124
|
:routes => routes,
|
150
125
|
:error => nil
|
151
126
|
}
|
127
|
+
|
152
128
|
rescue => e
|
129
|
+
# Most exceptions here will be caused by the app or one of the instances being in a non-standard state,
|
130
|
+
# for example, trying to query an app which was present when the worker began updating, but was stopped
|
131
|
+
# before we reached this section, so we just catch all exceptions, log the reason and move on.
|
153
132
|
@logger.info " #{org['entity']['name']} #{space['entity']['name']}: '#{app['entity']['name']}' error: #{e.message}"
|
154
133
|
additional_data = {
|
155
134
|
:running => 'error',
|
@@ -178,118 +157,3 @@ scheduler.every UPDATE_INTERVAL, :first_in => '5s', :overlap => false, :timeout
|
|
178
157
|
@cf_client.logout
|
179
158
|
end
|
180
159
|
end
|
181
|
-
|
182
|
-
def get_client(cf_api=ENV['CF_API'], cf_user=ENV['CF_USER'], cf_password=ENV['CF_PASSWORD'])
|
183
|
-
client = CFoundry::Client.get(cf_api)
|
184
|
-
client.login({:username => cf_user, :password => cf_password})
|
185
|
-
client
|
186
|
-
end
|
187
|
-
|
188
|
-
# def send_data_to_graphite(data, graphite)
|
189
|
-
# org = data[:org]
|
190
|
-
# space = data[:space]
|
191
|
-
# name = data[:name].sub ".", "_" # Some apps have dots in the app name
|
192
|
-
|
193
|
-
# data[:instances].each_with_index do |instance_data, index|
|
194
|
-
# graphite_base_key = "cf_apps.#{org}.#{space}.#{name}.#{index}"
|
195
|
-
|
196
|
-
# # Quota data
|
197
|
-
# [:mem_quota, :disk_quota].each do |key|
|
198
|
-
# graphite.metrics "#{graphite_base_key}.#{key}" => instance_data[:stats][key]
|
199
|
-
# end
|
200
|
-
|
201
|
-
# # Usage data
|
202
|
-
# [:mem, :disk, :cpu].each do |key|
|
203
|
-
# graphite.metrics "#{graphite_base_key}.#{key}" => instance_data[:stats][:usage][key]
|
204
|
-
# end
|
205
|
-
# end
|
206
|
-
# end
|
207
|
-
|
208
|
-
# def get_app_data(cf_client, graphite)
|
209
|
-
# Parallel.map(cf_client.organizations, :in_threads=> PARALLEL_MAPS) do |org|
|
210
|
-
# org_name = org.name
|
211
|
-
# Parallel.map(org.spaces, :in_threads => PARALLEL_MAPS) do |space|
|
212
|
-
# space_name = space.name
|
213
|
-
# @logger.info "Getting app data for apps in #{org_name}:#{space_name}..."
|
214
|
-
# Parallel.map(space.apps, :in_threads=> PARALLEL_MAPS) do |app|
|
215
|
-
# begin
|
216
|
-
# # It's possible for an app to have been terminated before this stage is reached.
|
217
|
-
# formatted_app_data = format_app_data(app, org_name, space_name)
|
218
|
-
# if graphite
|
219
|
-
# send_data_to_graphite(formatted_app_data, graphite)
|
220
|
-
# end
|
221
|
-
# formatted_app_data
|
222
|
-
# rescue CFoundry::AppNotFound
|
223
|
-
# next
|
224
|
-
# end
|
225
|
-
# end
|
226
|
-
# end
|
227
|
-
# end.flatten.compact
|
228
|
-
# end
|
229
|
-
|
230
|
-
# def get_org_data(cf_client)
|
231
|
-
# Parallel.map( cf_client.organizations, :in_threads=> PARALLEL_MAPS) do |org|
|
232
|
-
# @logger.info "Getting org data for #{org.name}..."
|
233
|
-
# # The CFoundry client returns memory_limit in MB, so we need to normalise to bytes to match the Apps.
|
234
|
-
# {
|
235
|
-
# :guid => org.guid,
|
236
|
-
# :name => org.name,
|
237
|
-
# :quota => {
|
238
|
-
# :total_services => org.quota_definition.total_services,
|
239
|
-
# :memory_limit => org.quota_definition.memory_limit * 1024 * 1024
|
240
|
-
# }
|
241
|
-
# }
|
242
|
-
# end.flatten.compact
|
243
|
-
# end
|
244
|
-
|
245
|
-
# def format_app_data(app, org_name, space_name)
|
246
|
-
|
247
|
-
# last_uploaded = (app.manifest[:entity][:package_updated_at] ||= nil)
|
248
|
-
|
249
|
-
# base_data = {
|
250
|
-
# :guid => app.guid,
|
251
|
-
# :name => app.name,
|
252
|
-
# :org => org_name,
|
253
|
-
# :space => space_name,
|
254
|
-
# :stack => app.stack.name,
|
255
|
-
# :buildpack => app.buildpack,
|
256
|
-
# :routes => app.running? ? routes_for_app(app) : [],
|
257
|
-
# :data_from => Time.now.to_i,
|
258
|
-
# :last_uploaded => last_uploaded ? DateTime.parse(last_uploaded).strftime('%Y-%m-%d %T %z') : nil
|
259
|
-
# }
|
260
|
-
|
261
|
-
# additional_data = {}
|
262
|
-
# begin
|
263
|
-
# additional_data = {
|
264
|
-
# :running => app.running?,
|
265
|
-
# :instances => app.running? ? app.stats.map{|key, value| value} : [],
|
266
|
-
# :error => nil
|
267
|
-
# }
|
268
|
-
# rescue => e
|
269
|
-
# @logger.info " #{org_name} #{space_name}: '#{app.name}'' error: #{e.message}"
|
270
|
-
# additional_data = {
|
271
|
-
# :running => 'error',
|
272
|
-
# :instances => [],
|
273
|
-
# :error => e.message
|
274
|
-
# }
|
275
|
-
# end
|
276
|
-
|
277
|
-
# base_data.merge additional_data
|
278
|
-
# end
|
279
|
-
|
280
|
-
# def routes_for_app app
|
281
|
-
# guids = space.apps.first.routes.collect{|route| route.guid}
|
282
|
-
# guids.collect{|guid| @domains.select{|domain| domain[:guid] == guid}}
|
283
|
-
# @domains.collect
|
284
|
-
# end
|
285
|
-
|
286
|
-
def put_in_redis(key, data)
|
287
|
-
REDIS.set key, data.to_json
|
288
|
-
end
|
289
|
-
|
290
|
-
def format_duration(elapsed_seconds)
|
291
|
-
seconds = elapsed_seconds % 60
|
292
|
-
minutes = (elapsed_seconds / 60) % 60
|
293
|
-
hours = elapsed_seconds / (60 * 60)
|
294
|
-
format("%02d hrs, %02d mins, %02d secs", hours, minutes, seconds)
|
295
|
-
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cf_light_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.
|
4
|
+
version: 2.0.0.pre2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Springer Platform Engineering
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cfoundry
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 3.2.1
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: parallel
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ~>
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 1.4.1
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ~>
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 1.4.1
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: rufus-scheduler
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -117,6 +103,7 @@ extensions: []
|
|
117
103
|
extra_rdoc_files: []
|
118
104
|
files:
|
119
105
|
- ./lib/sinatra/cf_light_api.rb
|
106
|
+
- ./lib/cf_light_api/lib.rb
|
120
107
|
- ./lib/cf_light_api/redis.rb
|
121
108
|
- ./lib/cf_light_api/worker.rb
|
122
109
|
- bin/cf_light_api
|