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 ADDED
@@ -0,0 +1,8 @@
1
+ require "rake"
2
+ require "rspec/core/rake_task"
3
+
4
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
5
+ require "tools-cf-plugin/version"
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ task :default => :spec
@@ -0,0 +1 @@
1
+ require "tools-cf-plugin/watch"
@@ -0,0 +1,3 @@
1
+ module CFTools
2
+ VERSION = "1.0.0".freeze
3
+ end
@@ -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
@@ -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