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 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