tools-cf-plugin 1.0.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.
- 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
|