tools-cf-plugin 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +8 -0
- data/lib/tools-cf-plugin/plugin.rb +1 -0
- data/lib/tools-cf-plugin/version.rb +3 -0
- data/lib/tools-cf-plugin/watch.rb +360 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/watch_spec.rb +871 -0
- metadata +245 -0
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "tools-cf-plugin/watch"
|
@@ -0,0 +1,360 @@
|
|
1
|
+
require "cf/cli"
|
2
|
+
require "nats/client"
|
3
|
+
|
4
|
+
module CFTools
|
5
|
+
class Watch < CF::App::Base
|
6
|
+
def precondition
|
7
|
+
check_target
|
8
|
+
end
|
9
|
+
|
10
|
+
REPLY_PREFIX = "`- reply to "
|
11
|
+
COLUMN_WIDTH = 30
|
12
|
+
|
13
|
+
desc "Watch messages going over NATS relevant to an application"
|
14
|
+
group :admin
|
15
|
+
input :app, :argument => :optional, :from_given => by_name(:app),
|
16
|
+
:desc => "Application to watch"
|
17
|
+
input :host, :alias => "-h", :default => "localhost",
|
18
|
+
:desc => "NATS server address"
|
19
|
+
input :port, :alias => "-P", :default => 4222, :type => :integer,
|
20
|
+
:desc => "NATS server port"
|
21
|
+
input :user, :alias => "-u", :default => "nats",
|
22
|
+
:desc => "NATS server user"
|
23
|
+
input :password, :alias => "-p", :default => "nats",
|
24
|
+
:desc => "NATS server password"
|
25
|
+
def watch
|
26
|
+
app = input[:app]
|
27
|
+
host = input[:host]
|
28
|
+
port = input[:port]
|
29
|
+
user = input[:user]
|
30
|
+
pass = input[:password]
|
31
|
+
|
32
|
+
@requests = {}
|
33
|
+
@request_ticker = 0
|
34
|
+
|
35
|
+
$stdout.sync = true
|
36
|
+
|
37
|
+
watching_nats("nats://#{user}:#{pass}@#{host}:#{port}") do |msg, reply, sub|
|
38
|
+
begin
|
39
|
+
if @requests.include?(sub)
|
40
|
+
process_response(sub, reply, msg, app)
|
41
|
+
elsif !app || msg.include?(app.guid)
|
42
|
+
process_message(sub, reply, msg, app)
|
43
|
+
end
|
44
|
+
rescue => e
|
45
|
+
line c("couldn't deal w/ #{sub} '#{msg}': #{e.class}: #{e}", :error)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def timestamp
|
53
|
+
Time.now.strftime("%r")
|
54
|
+
end
|
55
|
+
|
56
|
+
def list(vals)
|
57
|
+
if vals.empty?
|
58
|
+
d("none")
|
59
|
+
else
|
60
|
+
vals.join(", ")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def process_message(sub, reply, msg, app)
|
65
|
+
register_request(sub, reply) if reply
|
66
|
+
|
67
|
+
case sub
|
68
|
+
when "dea.advertise"
|
69
|
+
return if app
|
70
|
+
sub, msg = pretty_dea_advertise(sub, msg)
|
71
|
+
when "staging.advertise"
|
72
|
+
return if app
|
73
|
+
sub, msg = pretty_staging_advertise(sub, msg)
|
74
|
+
when "droplet.exited"
|
75
|
+
sub, msg = pretty_exited(sub, msg)
|
76
|
+
when "dea.heartbeat"
|
77
|
+
sub, msg = pretty_heartbeat(sub, msg, app)
|
78
|
+
when "router.start"
|
79
|
+
sub, msg = pretty_router_start(sub, msg)
|
80
|
+
when "router.register"
|
81
|
+
sub, msg = pretty_register(sub, msg)
|
82
|
+
when "router.unregister"
|
83
|
+
sub, msg = pretty_unregister(sub, msg)
|
84
|
+
when /^dea\.(\d+)-.*\.start$/
|
85
|
+
sub, msg = pretty_start(sub, msg, $1)
|
86
|
+
when "dea.stop"
|
87
|
+
sub, msg = pretty_stop(sub, msg)
|
88
|
+
when "droplet.updated"
|
89
|
+
sub, msg = pretty_updated(sub, msg)
|
90
|
+
when "dea.update"
|
91
|
+
sub, msg = pretty_dea_update(sub, msg)
|
92
|
+
when "dea.find.droplet"
|
93
|
+
sub, msg = pretty_find_droplet(sub, msg)
|
94
|
+
when "healthmanager.status"
|
95
|
+
sub, msg = pretty_healthmanager_status(sub, msg)
|
96
|
+
when "healthmanager.health"
|
97
|
+
sub, msg = pretty_healthmanager_health(sub, msg)
|
98
|
+
when "dea.shutdown"
|
99
|
+
sub, msg = pretty_dea_shutdown(sub, msg)
|
100
|
+
when /^cloudcontrollers\.hm\.requests\.\w+$/
|
101
|
+
sub, msg = process_cloudcontrollers_hm_request(sub, msg)
|
102
|
+
when /([^\.]+)\.announce$/
|
103
|
+
sub, msg = process_service_announcement(sub, msg)
|
104
|
+
end
|
105
|
+
|
106
|
+
if reply
|
107
|
+
sub += " " * REPLY_PREFIX.size
|
108
|
+
sub += " (#{c(@request_ticker, :error)})"
|
109
|
+
end
|
110
|
+
|
111
|
+
sub = sub.ljust(COLUMN_WIDTH)
|
112
|
+
line "#{timestamp}\t#{sub}\t#{msg}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def process_response(sub, _, msg, _)
|
116
|
+
sub, id = @requests[sub]
|
117
|
+
|
118
|
+
case sub
|
119
|
+
when "dea.find.droplet"
|
120
|
+
sub, msg = pretty_find_droplet_response(sub, msg)
|
121
|
+
when "healthmanager.status"
|
122
|
+
sub, msg = pretty_healthmanager_status_response(sub, msg)
|
123
|
+
when "healthmanager.health"
|
124
|
+
sub, msg = pretty_healthmanager_health_response(sub, msg)
|
125
|
+
end
|
126
|
+
|
127
|
+
line "#{timestamp}\t#{REPLY_PREFIX}#{sub} (#{c(id, :error)})\t#{msg}"
|
128
|
+
end
|
129
|
+
|
130
|
+
def pretty_dea_advertise(sub, msg)
|
131
|
+
payload = JSON.parse(msg)
|
132
|
+
dea, _ = payload["id"].split("-", 2)
|
133
|
+
[ d(sub),
|
134
|
+
"dea: #{dea}, stacks: #{list(payload["stacks"])}, available mem: #{human_mb(payload["available_memory"])}, apps: #{pretty_app_count(payload["app_id_to_count"])}"
|
135
|
+
]
|
136
|
+
end
|
137
|
+
|
138
|
+
def pretty_staging_advertise(sub, msg)
|
139
|
+
payload = JSON.parse(msg)
|
140
|
+
dea, _ = payload["id"].split("-", 2)
|
141
|
+
[ d(sub),
|
142
|
+
"dea: #{dea}, stacks: #{list(payload["stacks"])}, available mem: #{human_mb(payload["available_memory"])}"
|
143
|
+
]
|
144
|
+
end
|
145
|
+
|
146
|
+
def pretty_app_count(counts)
|
147
|
+
list(counts.collect { |g, c| "#{c} x #{pretty_app(g)}" })
|
148
|
+
end
|
149
|
+
|
150
|
+
def pretty_exited(sub, msg)
|
151
|
+
payload = JSON.parse(msg)
|
152
|
+
[ c(sub, :bad),
|
153
|
+
"app: #{pretty_app(payload["droplet"])}, reason: #{payload["reason"]}, index: #{payload["index"]}"
|
154
|
+
]
|
155
|
+
end
|
156
|
+
|
157
|
+
def pretty_heartbeat(sub, msg, app)
|
158
|
+
payload = JSON.parse(msg)
|
159
|
+
|
160
|
+
dea, _ = payload["dea"].split("-", 2)
|
161
|
+
|
162
|
+
states = Hash.new(0)
|
163
|
+
payload["droplets"].each do |droplet|
|
164
|
+
next unless !app || droplet["droplet"] == app.guid
|
165
|
+
states[droplet["state"]] += 1
|
166
|
+
end
|
167
|
+
|
168
|
+
[ d(sub),
|
169
|
+
"dea: #{dea}, " + states.collect { |state, count|
|
170
|
+
"#{c(state.downcase, state_color(state))}: #{count}"
|
171
|
+
}.join(", ")
|
172
|
+
]
|
173
|
+
end
|
174
|
+
|
175
|
+
def pretty_router_start(sub, msg)
|
176
|
+
payload = JSON.parse(msg)
|
177
|
+
[c(sub, :neutral), "hosts: #{list(payload["hosts"])}"]
|
178
|
+
end
|
179
|
+
|
180
|
+
def pretty_register(sub, msg)
|
181
|
+
payload = JSON.parse(msg)
|
182
|
+
|
183
|
+
if (dea_id = payload["dea"])
|
184
|
+
dea, _ = dea_id.split("-", 2)
|
185
|
+
message = "app: #{pretty_app(payload["app"])}, dea: #{dea}, "
|
186
|
+
else
|
187
|
+
message = ""
|
188
|
+
end
|
189
|
+
|
190
|
+
message << "uris: #{list(payload["uris"])}, host: #{payload["host"]}, port: #{payload["port"]}"
|
191
|
+
|
192
|
+
[c(sub, :neutral), message]
|
193
|
+
end
|
194
|
+
|
195
|
+
def pretty_unregister(sub, msg)
|
196
|
+
payload = JSON.parse(msg)
|
197
|
+
|
198
|
+
if (dea_id = payload["dea"])
|
199
|
+
dea, _ = dea_id.split("-", 2)
|
200
|
+
message = "app: #{pretty_app(payload["app"])}, dea: #{dea}, "
|
201
|
+
else
|
202
|
+
message = ""
|
203
|
+
end
|
204
|
+
|
205
|
+
message << "uris: #{list(payload["uris"])}, host: #{payload["host"]}, port: #{payload["port"]}"
|
206
|
+
|
207
|
+
[c(sub, :warning), message]
|
208
|
+
end
|
209
|
+
|
210
|
+
def pretty_start(sub, msg, dea)
|
211
|
+
payload = JSON.parse(msg)
|
212
|
+
[ c("dea.#{dea}.start", :good),
|
213
|
+
"app: #{pretty_app(payload["droplet"])}, dea: #{dea}, index: #{payload["index"]}, uris: #{list(payload["uris"])}"
|
214
|
+
]
|
215
|
+
end
|
216
|
+
|
217
|
+
def pretty_stop(sub, msg)
|
218
|
+
payload = JSON.parse(msg)
|
219
|
+
|
220
|
+
message = "app: #{pretty_app(payload["droplet"])}, "
|
221
|
+
if (indices = payload["indices"])
|
222
|
+
message << "scaling down indices: #{indices.join(", ")}"
|
223
|
+
elsif (instances = payload["instances"])
|
224
|
+
message << "killing extra instances: #{instances.join(", ")}"
|
225
|
+
else
|
226
|
+
message << "stopping application"
|
227
|
+
end
|
228
|
+
|
229
|
+
[c(sub, :warning), message]
|
230
|
+
end
|
231
|
+
|
232
|
+
def pretty_dea_update(sub, msg)
|
233
|
+
payload = JSON.parse(msg)
|
234
|
+
[d(sub), "app: #{pretty_app(payload["droplet"])}, uris: #{payload["uris"].join(", ")}"]
|
235
|
+
end
|
236
|
+
|
237
|
+
def pretty_find_droplet(sub, msg)
|
238
|
+
payload = JSON.parse(msg)
|
239
|
+
states = payload["states"].collect { |s| c(s.downcase, state_color(s))}
|
240
|
+
[d(sub), "app: #{pretty_app(payload["droplet"])}, querying states: #{states.join(", ")}"]
|
241
|
+
end
|
242
|
+
|
243
|
+
def pretty_find_droplet_response(sub, msg)
|
244
|
+
payload = JSON.parse(msg)
|
245
|
+
dea, _ = payload["dea"].split("-", 2)
|
246
|
+
index = payload["index"]
|
247
|
+
state = payload["state"]
|
248
|
+
time = Time.at(payload["state_timestamp"])
|
249
|
+
[ sub,
|
250
|
+
"dea: #{dea}, index: #{index}, state: #{c(state.downcase, state_color(state))}, since: #{time}"
|
251
|
+
]
|
252
|
+
end
|
253
|
+
|
254
|
+
def pretty_healthmanager_status(sub, msg)
|
255
|
+
payload = JSON.parse(msg)
|
256
|
+
state = payload["state"]
|
257
|
+
[d(sub), "app: #{pretty_app(payload["droplet"])}, querying states: #{c(state.downcase, state_color(state))}"]
|
258
|
+
end
|
259
|
+
|
260
|
+
def pretty_healthmanager_status_response(sub, msg)
|
261
|
+
payload = JSON.parse(msg)
|
262
|
+
[sub, "indices: #{list(payload["indices"])}"]
|
263
|
+
end
|
264
|
+
|
265
|
+
def pretty_healthmanager_health(sub, msg)
|
266
|
+
payload = JSON.parse(msg)
|
267
|
+
apps = payload["droplets"].collect { |d| client.app(d["droplet"]) }
|
268
|
+
[d(sub), "querying health for: #{name_list(apps)}"]
|
269
|
+
end
|
270
|
+
|
271
|
+
def pretty_healthmanager_health_response(sub, msg)
|
272
|
+
payload = JSON.parse(msg)
|
273
|
+
[sub, "app: #{pretty_app(payload["droplet"])}, healthy: #{payload["healthy"]}"]
|
274
|
+
end
|
275
|
+
|
276
|
+
def pretty_updated(sub, msg)
|
277
|
+
payload = JSON.parse(msg)
|
278
|
+
[d(sub), "app: #{pretty_app(payload["droplet"])}"]
|
279
|
+
end
|
280
|
+
|
281
|
+
def pretty_dea_shutdown(sub, msg)
|
282
|
+
payload = JSON.parse(msg)
|
283
|
+
|
284
|
+
dea, _ = payload["id"].split("-", 2)
|
285
|
+
|
286
|
+
apps = payload["app_id_to_count"].collect do |guid, count|
|
287
|
+
app = client.app(guid)
|
288
|
+
|
289
|
+
if app.exists?
|
290
|
+
"#{count} x #{app.name} (#{guid})"
|
291
|
+
else
|
292
|
+
"#{count} x unknown (#{guid})"
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
[c(sub, :error), "dea: #{dea}, apps: #{list(apps)}"]
|
297
|
+
end
|
298
|
+
|
299
|
+
def process_cloudcontrollers_hm_request(sub, msg)
|
300
|
+
payload = JSON.parse(msg)
|
301
|
+
last_updated = Time.at(payload["last_updated"])
|
302
|
+
|
303
|
+
op = payload["op"]
|
304
|
+
|
305
|
+
message = "app: #{pretty_app(payload["droplet"])}, operation: #{pretty_hm_op(op)}, app last updated: #{last_updated}, "
|
306
|
+
|
307
|
+
case op
|
308
|
+
when "STOP"
|
309
|
+
message << "instances: #{list(payload["instances"])}"
|
310
|
+
when "START"
|
311
|
+
message << "indices: #{list(payload["indices"])}"
|
312
|
+
end
|
313
|
+
|
314
|
+
[c("hm.request", :warning), message]
|
315
|
+
end
|
316
|
+
|
317
|
+
def process_service_announcement(sub, msg)
|
318
|
+
payload = JSON.parse(msg)
|
319
|
+
id = payload["id"]
|
320
|
+
plan = payload["plan"]
|
321
|
+
c_unit = payload["capacity_unit"]
|
322
|
+
c_max = payload["max_capacity"]
|
323
|
+
c_avail = payload["available_capacity"]
|
324
|
+
s_versions = payload["supported_versions"]
|
325
|
+
|
326
|
+
[d(sub), "id: #{id}, plan: #{plan}, supported versions: #{list(s_versions)}, capacity: (available: #{c_avail}, max: #{c_max}, unit: #{c_unit})"]
|
327
|
+
end
|
328
|
+
|
329
|
+
def pretty_hm_op(op)
|
330
|
+
case op
|
331
|
+
when "STOP"
|
332
|
+
c("stop", :bad)
|
333
|
+
when "START"
|
334
|
+
c("start", :good)
|
335
|
+
else
|
336
|
+
op
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def pretty_app(guid)
|
341
|
+
app = client.app(guid)
|
342
|
+
|
343
|
+
if app.exists?
|
344
|
+
c(app.name, :name)
|
345
|
+
else
|
346
|
+
d("unknown")
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def watching_nats(uri, &blk)
|
351
|
+
NATS.start(:uri => uri) do
|
352
|
+
NATS.subscribe(">", &blk)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def register_request(sub, reply)
|
357
|
+
@requests[reply] = [sub, @request_ticker += 1]
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
SPEC_ROOT = File.dirname(__FILE__).freeze
|
2
|
+
|
3
|
+
require "rspec"
|
4
|
+
require "timecop"
|
5
|
+
require "cf"
|
6
|
+
require "cfoundry"
|
7
|
+
require "cfoundry/test_support"
|
8
|
+
require "webmock/rspec"
|
9
|
+
require "cf/test_support"
|
10
|
+
require "blue-shell"
|
11
|
+
require "nats/client"
|
12
|
+
|
13
|
+
require "#{SPEC_ROOT}/../lib/tools-cf-plugin/plugin"
|
14
|
+
|
15
|
+
RSpec.configure do |c|
|
16
|
+
c.include Fake::FakeMethods
|
17
|
+
c.mock_with :rr
|
18
|
+
|
19
|
+
c.include FakeHomeDir
|
20
|
+
c.include CliHelper
|
21
|
+
c.include InteractHelper
|
22
|
+
c.include BlueShell::Matchers
|
23
|
+
end
|