morpheus-cli 4.1.8 → 4.1.9

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +24 -0
  4. data/lib/morpheus/api/{old_cypher_interface.rb → budgets_interface.rb} +10 -11
  5. data/lib/morpheus/api/cloud_datastores_interface.rb +7 -0
  6. data/lib/morpheus/api/cloud_resource_pools_interface.rb +2 -2
  7. data/lib/morpheus/api/cypher_interface.rb +18 -12
  8. data/lib/morpheus/api/health_interface.rb +72 -0
  9. data/lib/morpheus/api/instances_interface.rb +1 -1
  10. data/lib/morpheus/api/library_instance_types_interface.rb +7 -0
  11. data/lib/morpheus/api/log_settings_interface.rb +6 -0
  12. data/lib/morpheus/api/network_security_servers_interface.rb +30 -0
  13. data/lib/morpheus/api/price_sets_interface.rb +42 -0
  14. data/lib/morpheus/api/prices_interface.rb +68 -0
  15. data/lib/morpheus/api/provisioning_settings_interface.rb +29 -0
  16. data/lib/morpheus/api/servers_interface.rb +1 -1
  17. data/lib/morpheus/api/service_plans_interface.rb +34 -11
  18. data/lib/morpheus/api/task_sets_interface.rb +8 -0
  19. data/lib/morpheus/api/tasks_interface.rb +8 -0
  20. data/lib/morpheus/cli.rb +6 -3
  21. data/lib/morpheus/cli/appliance_settings_command.rb +13 -5
  22. data/lib/morpheus/cli/approvals_command.rb +1 -1
  23. data/lib/morpheus/cli/apps.rb +88 -28
  24. data/lib/morpheus/cli/backup_settings_command.rb +1 -1
  25. data/lib/morpheus/cli/blueprints_command.rb +2 -0
  26. data/lib/morpheus/cli/budgets_command.rb +672 -0
  27. data/lib/morpheus/cli/cli_command.rb +13 -2
  28. data/lib/morpheus/cli/cli_registry.rb +1 -0
  29. data/lib/morpheus/cli/clusters.rb +40 -274
  30. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +114 -66
  31. data/lib/morpheus/cli/commands/standard/coloring_command.rb +12 -0
  32. data/lib/morpheus/cli/commands/standard/curl_command.rb +31 -6
  33. data/lib/morpheus/cli/commands/standard/echo_command.rb +8 -3
  34. data/lib/morpheus/cli/commands/standard/set_prompt_command.rb +1 -1
  35. data/lib/morpheus/cli/containers_command.rb +37 -24
  36. data/lib/morpheus/cli/cypher_command.rb +191 -150
  37. data/lib/morpheus/cli/health_command.rb +903 -0
  38. data/lib/morpheus/cli/hosts.rb +43 -32
  39. data/lib/morpheus/cli/instances.rb +119 -68
  40. data/lib/morpheus/cli/jobs_command.rb +1 -1
  41. data/lib/morpheus/cli/library_instance_types_command.rb +61 -11
  42. data/lib/morpheus/cli/library_option_types_command.rb +2 -2
  43. data/lib/morpheus/cli/log_settings_command.rb +46 -3
  44. data/lib/morpheus/cli/logs_command.rb +24 -17
  45. data/lib/morpheus/cli/mixins/accounts_helper.rb +2 -0
  46. data/lib/morpheus/cli/mixins/logs_helper.rb +73 -19
  47. data/lib/morpheus/cli/mixins/print_helper.rb +29 -1
  48. data/lib/morpheus/cli/mixins/provisioning_helper.rb +554 -96
  49. data/lib/morpheus/cli/mixins/whoami_helper.rb +13 -1
  50. data/lib/morpheus/cli/networks_command.rb +3 -0
  51. data/lib/morpheus/cli/option_types.rb +83 -53
  52. data/lib/morpheus/cli/price_sets_command.rb +543 -0
  53. data/lib/morpheus/cli/prices_command.rb +669 -0
  54. data/lib/morpheus/cli/processes_command.rb +0 -2
  55. data/lib/morpheus/cli/provisioning_settings_command.rb +237 -0
  56. data/lib/morpheus/cli/remote.rb +9 -4
  57. data/lib/morpheus/cli/reports_command.rb +10 -4
  58. data/lib/morpheus/cli/roles.rb +93 -38
  59. data/lib/morpheus/cli/security_groups.rb +10 -0
  60. data/lib/morpheus/cli/service_plans_command.rb +736 -0
  61. data/lib/morpheus/cli/tasks.rb +220 -8
  62. data/lib/morpheus/cli/tenants_command.rb +3 -16
  63. data/lib/morpheus/cli/users.rb +2 -25
  64. data/lib/morpheus/cli/version.rb +1 -1
  65. data/lib/morpheus/cli/whitelabel_settings_command.rb +18 -18
  66. data/lib/morpheus/cli/whoami.rb +28 -10
  67. data/lib/morpheus/cli/workflows.rb +488 -36
  68. data/lib/morpheus/formatters.rb +22 -0
  69. data/morpheus-cli.gemspec +1 -0
  70. metadata +28 -5
  71. data/lib/morpheus/cli/accounts.rb +0 -335
  72. data/lib/morpheus/cli/old_cypher_command.rb +0 -412
@@ -0,0 +1,903 @@
1
+ require 'io/console'
2
+ require 'rest_client'
3
+ require 'optparse'
4
+ require 'morpheus/cli/cli_command'
5
+ require 'morpheus/cli/mixins/logs_helper'
6
+ require 'morpheus/cli/option_types'
7
+ require 'json'
8
+
9
+ class Morpheus::Cli::HealthCommand
10
+ include Morpheus::Cli::CliCommand
11
+ include Morpheus::Cli::LogsHelper
12
+ set_command_name :health
13
+ register_subcommands :get, :alarms, :'get-alarm', :'acknowledge-alarms', :'unacknowledge-alarms', :logs
14
+
15
+ def connect(opts)
16
+ @api_client = establish_remote_appliance_connection(opts)
17
+ @health_interface = @api_client.health
18
+ end
19
+
20
+ def handle(args)
21
+ handle_subcommand(args)
22
+ end
23
+
24
+ def get(args)
25
+ options = {}
26
+ params = {}
27
+ live_health = false
28
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
29
+ opts.banner = subcommand_usage("[-a] [options]")
30
+ opts.on('-a', '--all', "Display all details: CPU, Memory, Database, etc." ) do
31
+ options[:details] = true
32
+ options[:show_cpu] = true
33
+ options[:show_memory] = true
34
+ options[:show_database] = true
35
+ options[:show_elastic] = true
36
+ options[:show_queue] = true
37
+ end
38
+ opts.on('--details', '--details', "Display all details: CPU, Memory, Database, etc." ) do
39
+ options[:details] = true
40
+ options[:show_cpu] = true
41
+ options[:show_memory] = true
42
+ options[:show_database] = true
43
+ options[:show_elastic] = true
44
+ options[:show_queue] = true
45
+ end
46
+ opts.add_hidden_option('--details') # prefer -a, --all
47
+ opts.on('--cpu', "Display CPU details" ) do
48
+ options[:show_cpu] = true
49
+ end
50
+ opts.on('--memory', "Display Memory details" ) do
51
+ options[:show_memory] = true
52
+ end
53
+ opts.on('--database', "Display Database details" ) do
54
+ options[:show_database] = true
55
+ end
56
+ opts.on('--elastic', "Display Elasticsearch details" ) do
57
+ options[:show_elastic] = true
58
+ end
59
+ opts.on('--queue', "Display Queue (Rabbit) details" ) do
60
+ options[:show_queue] = true
61
+ end
62
+ opts.on('--queues', "Display Queue (Rabbit) details" ) do
63
+ options[:show_queue] = true
64
+ end
65
+ opts.on('--rabbit', "Display Queue (Rabbit) details" ) do
66
+ options[:show_queue] = true
67
+ end
68
+ opts.add_hidden_option('--queues')
69
+ opts.add_hidden_option('--rabbit')
70
+ opts.on('--live', "Fetch Live Health Data. By default the last cached health data is returned. This also retrieves all elastic indices." ) do
71
+ live_health = true
72
+ end
73
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
74
+ opts.footer = "Get appliance health information." + "\n" +
75
+ "By default, only the health status and levels are displayed." + "\n" +
76
+ "Display more details with the options --cpu, --database, --memory, etc." + "\n" +
77
+ "Display all details with the -a option."
78
+ end
79
+ optparse.parse!(args)
80
+
81
+ if args.count != 0
82
+ raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
83
+ end
84
+
85
+ connect(options)
86
+ begin
87
+ @health_interface.setopts(options)
88
+ if options[:dry_run]
89
+ print_dry_run(live_health ? @health_interface.dry.live(params) : @health_interface.dry.get(params))
90
+ return 0
91
+ end
92
+ json_response = live_health ? @health_interface.live(params) : @health_interface.get(params)
93
+ render_result = render_with_format(json_response, options, 'health')
94
+ exit_code = json_response['success'] == true ? 0 : 1
95
+ return exit_code if render_result
96
+
97
+ health = json_response['health']
98
+ subtitles = []
99
+ if options[:details]
100
+ subtitles << "Details"
101
+ end
102
+ if live_health
103
+ subtitles << "(Live)"
104
+ end
105
+ print_h1 "Morpheus Health", subtitles, options
106
+ # thin print below here
107
+ options.merge!({thin:true})
108
+
109
+ if health.nil?
110
+ print yellow,"No health data returned.",reset,"\n"
111
+ return 1
112
+ end
113
+ if health['elastic'] && health['elastic']['noticeMessage'].to_s != ""
114
+ print cyan,health['elastic']['noticeMessage'],reset,"\n"
115
+ print "\n"
116
+ end
117
+
118
+ #print_h2 "Health Summary", options
119
+ print cyan
120
+ health_summary_columns = {
121
+ "Overall" => lambda {|it| format_health_status(it['cpu']['status']) rescue '' },
122
+ "CPU" => lambda {|it| format_health_status(it['cpu']['status']) rescue '' },
123
+ "Memory" => lambda {|it| format_health_status(it['memory']['status']) rescue '' },
124
+ "Database" => lambda {|it| format_health_status(it['database']['status']) rescue '' },
125
+ "Elastic" => lambda {|it| format_health_status(it['elastic']['status']) rescue '' },
126
+ "Queue" => lambda {|it| format_health_status(it['rabbit']['status']) rescue '' },
127
+ }
128
+ print as_pretty_table(health, health_summary_columns, options)
129
+ print "\n"
130
+
131
+ # flash warnings
132
+ if health['cpu'] && health['cpu']['status'] != 'ok' && health['cpu']['statusMessage']
133
+ status_color = health['cpu']['status'] == 'error' ? red : yellow
134
+ print status_color,health['cpu']['statusMessage'],reset,"\n"
135
+ end
136
+ if health['memory'] && health['memory']['status'] != 'ok' && health['memory']['statusMessage']
137
+ status_color = health['memory']['status'] == 'error' ? red : yellow
138
+ print status_color,health['memory']['statusMessage'],reset,"\n"
139
+ end
140
+ if health['database'] && health['database']['status'] != 'ok' && health['database']['statusMessage']
141
+ status_color = health['database']['status'] == 'error' ? red : yellow
142
+ print status_color,health['database']['statusMessage'],reset,"\n"
143
+ end
144
+ # if health['elastic'] && health['elastic']['noticeMessage'].to_s != ""
145
+ # print cyan,health['elastic']['noticeMessage'],reset,"\n"
146
+ # end
147
+ if health['elastic'] && health['elastic']['status'] != 'ok' && health['elastic']['statusMessage']
148
+ status_color = health['elastic']['status'] == 'error' ? red : yellow
149
+ print status_color,health['elastic']['statusMessage'],reset,"\n"
150
+ end
151
+ if health['rabbit'] && health['rabbit']['status'] != 'ok' && health['rabbit']['statusMessage']
152
+ status_color = health['rabbit']['status'] == 'error' ? red : yellow
153
+ print status_color,health['rabbit']['statusMessage'],reset,"\n"
154
+ end
155
+
156
+ print_h2 "Health Levels", options
157
+ print cyan
158
+ health_levels_columns = {
159
+ "Morpheus CPU" => lambda {|it| format_percent(it['cpu']['cpuLoad'].to_f, 0) rescue '' },
160
+ "System CPU" => lambda {|it| format_percent(it['cpu']['cpuTotalLoad'].to_f, 0) rescue '' },
161
+ "Morpheus Memory" => lambda {|it| format_percent(it['memory']['memoryPercent'].to_f * 100, 0) rescue '' },
162
+ "System Memory" => lambda {|it| format_percent(it['memory']['systemMemoryPercent'].to_f * 100, 0) rescue '' },
163
+ "Used Swap" => lambda {|it| format_percent(it['memory']['swapPercent'].to_f * 100, 0) rescue '' },
164
+ }
165
+ print as_pretty_table(health, health_levels_columns, options)
166
+ # print "\n"
167
+
168
+ if options[:show_cpu]
169
+ # CPU
170
+ if health['cpu'].nil?
171
+ print yellow,"No cpu health information returned.",reset,"\n"
172
+ else
173
+ print_h2 "CPU", options
174
+ print cyan
175
+ cpu_columns = {
176
+ "Processor Count" => lambda {|it| it['processorCount'] rescue '' },
177
+ "Process Time" => lambda {|it| format_human_duration(it['processTime'].to_f / 1000) rescue '' },
178
+ "Morpheus CPU" => lambda {|it| (it['cpuLoad'].to_f.round(2).to_s + '%') rescue '' },
179
+ "System CPU" => lambda {|it| (it['cpuTotalLoad'].to_f.round(2).to_s + '%') rescue '' },
180
+ "System Load" => lambda {|it| (it['systemLoad'].to_f.round(3)) rescue '' },
181
+ }
182
+ #print as_pretty_table(health['cpu'], cpu_columns, options)
183
+ print_description_list(cpu_columns, health['cpu'], options)
184
+ end
185
+ end
186
+
187
+ # Memory
188
+ if options[:show_memory]
189
+ if health['memory'].nil?
190
+ print yellow,"No memory health information returned.",reset,"\n"
191
+ else
192
+ print_h2 "Memory", options
193
+ print cyan
194
+ memory_columns = {
195
+ "Morpheus Memory" => lambda {|it| format_bytes_short(it['totalMemory']) rescue '' },
196
+ "Morpheus Used Memory" => lambda {|it| format_bytes_short(it['usedMemory']) rescue '' },
197
+ "Morpheus Free Memory" => lambda {|it| format_bytes_short(it['freeMemory']) rescue '' },
198
+ "Morpheus Memory Usage" => lambda {|it| format_percent(it['memoryPercent'].to_f * 100) rescue '' },
199
+ "System Memory" => lambda {|it| format_bytes_short(it['systemMemory']) rescue '' },
200
+ "System Used Memory" => lambda {|it| format_bytes_short(it['committedMemory']) rescue '' },
201
+ "System Free Memory" => lambda {|it| format_bytes_short(it['systemFreeMemory']) rescue '' },
202
+ "System Memory Usage" => lambda {|it| format_percent(it['systemMemoryPercent'].to_f * 100) rescue '' },
203
+ "System Swap" => lambda {|it| format_bytes_short(it['systemSwap']) rescue '' },
204
+ "Free Swap" => lambda {|it| format_bytes_short(it['systemFreeSwap']) rescue '' },
205
+ #"Used Swap" => lambda {|it| format_percent(it['swapPercent'].to_f * 100) rescue '' }
206
+ # "System Load" => lambda {|it| (it['systemLoad'].to_f(3)) rescue '' },
207
+ }
208
+ #print as_pretty_table(health['memory'], memory_columns, options)
209
+ print_description_list(memory_columns, health['memory'], options)
210
+ end
211
+ end
212
+
213
+ # Database (mysql)
214
+ if options[:show_database]
215
+ if health['database'].nil?
216
+ print yellow,"No database health information returned.",reset,"\n"
217
+ else
218
+ print_h2 "Database", options
219
+ print cyan
220
+ database_columns = {
221
+ "Lifetime Connections" => lambda {|it| it['stats']['Connections'] rescue '' },
222
+ "Aborted Connections" => lambda {|it| it['stats']['Aborted_connects'] rescue '' },
223
+ "Max Used Connections" => lambda {|it| it['stats']['Max_used_connections'] rescue '' },
224
+ "Max Connections" => lambda {|it| it['maxConnections'] rescue '' },
225
+ "Threads Running" => lambda {|it| it['stats']['Threads_running'] rescue '' },
226
+ "Threads Connected" => lambda {|it| it['stats']['Threads_connected'] rescue '' },
227
+ "Slow Queries" => lambda {|it| it['stats']['Slow_queries'] rescue '' },
228
+ "Temp Tables" => lambda {|it| it['stats']['Created_tmp_disk_tables'] rescue '' },
229
+ "Handler Read First" => lambda {|it| it['stats']['Handler_read_first'] rescue '' },
230
+ "Buffer Pool Free" => lambda {|it| it['stats']['Innodb_buffer_pool_wait_free'] rescue '' },
231
+ "Open Tables" => lambda {|it| it['stats']['Open_tables'] rescue '' },
232
+ "Table Scans" => lambda {|it| it['stats']['Select_scan'] rescue '' },
233
+ "Full Joins" => lambda {|it| it['stats']['Select_full_join'] rescue '' },
234
+ "Key Read Requests" => lambda {|it| it['stats']['Key_read_requests'] rescue '' },
235
+ "Key Reads" => lambda {|it| it['stats']['Key_reads'] rescue '' },
236
+ "Engine Waits" => lambda {|it| it['stats']['Innodb_log_waits'] rescue '' },
237
+ "Lock Waits" => lambda {|it| it['stats']['Table_locks_waited'] rescue '' },
238
+ "Handler Read Rnd" => lambda {|it| it['stats']['Handler_read_rnd'] rescue '' },
239
+ "Engine IO Writes" => lambda {|it| it['stats']['Innodb_data_writes'] rescue '' },
240
+ "Engine IO Reads" => lambda {|it| it['stats']['Innodb_data_reads'] rescue '' },
241
+ "Engine IO Double Writes" => lambda {|it| it['stats']['Innodb_dblwr_writes'] rescue '' },
242
+ "Engine Log Writes" => lambda {|it| it['stats']['Innodb_log_writes'] rescue '' },
243
+ "Engine Memory" => lambda {|it| format_bytes_short(it['innodbStats']['largeMemory']) rescue '' },
244
+ "Dictionary Memory" => lambda {|it| format_bytes_short(it['innodbStats']['dictionaryMemory']) rescue '' },
245
+ "Buffer Pool Size" => lambda {|it| it['innodbStats']['bufferPoolSize'] rescue '' },
246
+ "Free Buffers" => lambda {|it| it['innodbStats']['freeBuffers'] rescue '' },
247
+ "Database Pages" => lambda {|it| it['innodbStats']['databasePages'] rescue '' },
248
+ "Old Pages" => lambda {|it| it['innodbStats']['oldPages'] rescue '' },
249
+ "Dirty Page Percent" => lambda {|it| format_percent(it['innodbStats']['dirtyPagePercent'] ? it['innodbStats']['dirtyPagePercent'] : '') rescue '' },
250
+ "Max Dirty Pages" => lambda {|it| format_percent(it['innodbStats']['maxDirtyPagePercent'].to_f) rescue '' },
251
+ "Pending Reads" => lambda {|it| format_number(it['innodbStats']['pendingReads']) rescue '' },
252
+ "Insert Rate" => lambda {|it| format_rate(it['innodbStats']['insertsPerSecond'].to_f) rescue '' },
253
+ "Update Rate" => lambda {|it| format_rate(it['innodbStats']['updatesPerSecond'].to_f) rescue '' },
254
+ "Delete Rate" => lambda {|it| format_rate(it['innodbStats']['deletesPerSecond'].to_f) rescue '' },
255
+ "Read Rate" => lambda {|it| format_rate(it['innodbStats']['readsPerSecond']) rescue '' },
256
+ "Buffer Hit Rate" => lambda {|it| format_percent(it['innodbStats']['bufferHitRate'].to_f * 100) rescue '' },
257
+ "Read Write Ratio" => lambda {|it|
258
+ rw_ratio = ""
259
+ begin
260
+ total_writes = (it['stats']['Com_update'].to_i) + (it['stats']['Com_insert'].to_i) + (it['stats']['Com_delete'].to_f)
261
+ total_reads = (it['stats']['Com_select'].to_i)
262
+ if total_writes > 0
263
+ rw_ratio = (total_reads.to_f / total_writes.to_f).round(2).to_s
264
+ end
265
+ rescue => ex
266
+ puts ex
267
+ end
268
+ rw_ratio
269
+ },
270
+ "Uptime" => lambda {|it| (it['stats']['Uptime'] ? format_human_duration(it['stats']['Uptime'].to_i) : '') rescue '' },
271
+ }
272
+
273
+ print_description_list(database_columns, health['database'], options)
274
+ #print as_pretty_table(health['database'], database_columns, options)
275
+
276
+ end
277
+ end
278
+
279
+ # Elasticsearch
280
+ if options[:show_elastic]
281
+ if health['elastic'].nil?
282
+ print yellow,"No elastic health information returned.",reset,"\n\n"
283
+ else
284
+ print_h2 "Elastic", options
285
+ print cyan
286
+
287
+ elastic_columns = {
288
+ "Status" => 'status',
289
+ # "Status" => lambda {|it| format_health_status(it['status']) rescue '' },
290
+ # "Status" => lambda {|it|
291
+ # begin
292
+ # if it['statusMessage'].to_s != ""
293
+ # format_health_status(it['status']).to_s + " - " + it['statusMessage']
294
+ # else
295
+ # format_health_status(it['status'])
296
+ # end
297
+ # rescue => ex
298
+ # ''
299
+ # end
300
+ # },
301
+ "Cluster" => lambda {|it| it['stats']['clusterName'] rescue '' },
302
+ "Node Count" => lambda {|it| it['stats']['nodeTotal'] rescue '' },
303
+ "Data Nodes" => lambda {|it| it['stats']['nodeData'] rescue '' },
304
+ "Shards" => lambda {|it| it['stats']['shards'] rescue '' },
305
+ "Primary Shards" => lambda {|it| it['stats']['primary'] rescue '' },
306
+ "Relocating Shards" => lambda {|it| it['stats']['relocating'] rescue '' },
307
+ "Initializing" => lambda {|it| it['stats']['initializing'] rescue '' },
308
+ "Unassigned" => lambda {|it| it['stats']['unassigned'] rescue '' },
309
+ "Pending Tasks" => lambda {|it| it['stats']['pendingTasks'] rescue '' },
310
+ "Active Shards" => lambda {|it| it['stats']['activePercent'] rescue '' },
311
+ }
312
+
313
+ print_description_list(elastic_columns, health['elastic'], options)
314
+ #print as_pretty_table(health['elastic'], elastic_columns, options)
315
+
316
+ elastic_nodes_columns = [
317
+ {"NODE" => lambda {|it| it['name'] } },
318
+ {"MASTER" => lambda {|it| it['master'] == '*' } },
319
+ {"LOCATION" => lambda {|it| it['ip'] } },
320
+ {"RAM" => lambda {|it| it['ramPercent'] } },
321
+ {"HEAP" => lambda {|it| it['heapPercent'] } },
322
+ {"CPU USAGE" => lambda {|it| it['cpuCount'] } },
323
+ {"1M LOAD" => lambda {|it| it['loadOne'] } },
324
+ {"5M LOAD" => lambda {|it| it['loadFive'] } },
325
+ {"15M LOAD" => lambda {|it| it['loadFifteen'] } }
326
+ ]
327
+
328
+ print_h2 "Elastic Nodes"
329
+ if health['elastic']['nodes'].nil? || health['elastic']['nodes'].empty?
330
+ print yellow,"No nodes found.",reset,"\n\n"
331
+ else
332
+ print as_pretty_table(health['elastic']['nodes'], elastic_nodes_columns, options)
333
+ end
334
+
335
+ elastic_indices_columns = [
336
+ {"Health".upcase => lambda {|it| format_index_health(it['health']) } },
337
+ {"Index".upcase => lambda {|it| it['index']} },
338
+ {"Status".upcase => lambda {|it| it['status'] } },
339
+ {"Primary".upcase => lambda {|it| it['primary'] } },
340
+ {"Replicas".upcase => lambda {|it| it['replicas'] } },
341
+ {"Doc Count".upcase => lambda {|it| format_number(it['count']) } },
342
+ {"Primary Size".upcase => lambda {|it| it['primarySize'] } },
343
+ {"Total Size".upcase => lambda {|it| it['totalSize'] } },
344
+ ]
345
+
346
+ # when the api returns indices, it will include badIndices, so don't show both.
347
+ if health['elastic']['indices'] && health['elastic']['indices'].size > 0
348
+ print_h2 "Elastic Indices"
349
+ if health['elastic']['indices'].nil? || health['elastic']['indices'].empty?
350
+ print yellow,"No indices found.",reset,"\n\n"
351
+ else
352
+ print cyan
353
+ print as_pretty_table(health['elastic']['indices'], elastic_indices_columns, options)
354
+ end
355
+ else
356
+ print_h2 "Bad Elastic Indices"
357
+ if health['elastic']['badIndices'].nil? || health['elastic']['badIndices'].empty?
358
+ # print cyan,"No bad indices found.",reset,"\n\n"
359
+ else
360
+ print cyan
361
+ print as_pretty_table(health['elastic']['badIndices'], elastic_indices_columns, options)
362
+ end
363
+ end
364
+
365
+
366
+ end
367
+ end
368
+
369
+ # Queues (rabbit)
370
+ if options[:show_queue]
371
+ print_h2 "Queue (Rabbit)", options
372
+ if health['rabbit'].nil?
373
+ print yellow,"No rabbit queue health information returned.",reset,"\n\n"
374
+ else
375
+ print cyan
376
+
377
+ rabbit_summary_columns = {
378
+ "Status" => lambda {|it|
379
+ begin
380
+ if it['statusMessage'].to_s != ""
381
+ format_health_status(it['status']).to_s + " - " + it['statusMessage']
382
+ else
383
+ format_health_status(it['status'])
384
+ end
385
+ rescue => ex
386
+ ''
387
+ end
388
+ },
389
+ "Queues" => lambda {|it| it['queues'].size rescue '' },
390
+ "Busy Queues" => lambda {|it| it['busyQueues'].size rescue '' },
391
+ "Error Queues" => lambda {|it| it['errorQueues'].size rescue '' }
392
+ }
393
+
394
+ print_description_list(rabbit_summary_columns, health['rabbit'], options)
395
+ #print as_pretty_table(health['rabbit'], rabbit_summary_columns, options)
396
+
397
+ print_h2 "Queues"
398
+ queue_columns = [
399
+ {"Status".upcase => lambda {|it|
400
+ # hrmm
401
+ status_string = it['status'].to_s.downcase # || 'green'
402
+ if status_string == 'warning'
403
+ "#{yellow}WARNING#{cyan}"
404
+ elsif status_string == 'error'
405
+ "#{red}ERROR#{cyan}"
406
+ elsif status_string == 'ok'
407
+ "#{green}OK#{cyan}"
408
+ else
409
+ # hrmm
410
+ it['status']
411
+ end
412
+ } },
413
+ {"Name".upcase => lambda {|it| it['name']} },
414
+ {"Count".upcase => lambda {|it| format_number(it['count']) } }
415
+ ]
416
+
417
+ if health['rabbit'].nil? || health['rabbit']['queues'].nil? || health['rabbit']['queues'].empty?
418
+ print yellow,"No queues found.",reset,"\n\n"
419
+ else
420
+ print cyan
421
+ print as_pretty_table(health['rabbit']['queues'], queue_columns, options)
422
+ end
423
+
424
+ if health['rabbit'].nil? || health['rabbit']['busyQueues'].nil? || health['rabbit']['busyQueues'].empty?
425
+ # print cyan,"No busy queues found.",reset,"\n"
426
+ else
427
+ print_h2 "Busy Queues"
428
+ print cyan
429
+ print as_pretty_table(health['rabbit']['busyQueues'], queue_columns, options)
430
+ end
431
+
432
+ if health['rabbit'].nil? || health['rabbit']['errorQueues'].nil? || health['rabbit']['errorQueues'].empty?
433
+ # print cyan,"No error queues found.",reset,"\n"
434
+ else
435
+ print_h2 "Error Queues"
436
+ print cyan
437
+ print as_pretty_table(health['rabbit']['errorQueues'], queue_columns, options)
438
+ end
439
+
440
+ end
441
+
442
+ end
443
+
444
+
445
+ print "\n"
446
+ return 0
447
+ rescue RestClient::Exception => e
448
+ print_rest_exception(e, options)
449
+ exit 1
450
+ end
451
+ end
452
+
453
+ def logs(args)
454
+ options = {}
455
+ params = {}
456
+ start_date, end_date = nil, nil
457
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
458
+ opts.banner = subcommand_usage()
459
+ opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
460
+ params['level'] = params['level'] ? [params['level'], val].flatten : val
461
+ end
462
+ opts.on('--start TIMESTAMP','--start TIMESTAMP', "Start timestamp. Default is 30 days ago.") do |val|
463
+ start_date = parse_time(val) #.utc.iso8601
464
+ end
465
+ opts.on('--end TIMESTAMP','--end TIMESTAMP', "End timestamp. Default is now.") do |val|
466
+ end_date = parse_time(val) #.utc.iso8601
467
+ end
468
+ opts.on('--table', '--table', "Format output as a table.") do
469
+ options[:table] = true
470
+ end
471
+ opts.on('-a', '--all', "Display all details: entire message." ) do
472
+ options[:details] = true
473
+ end
474
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
475
+ opts.footer = "List health logs. These are the logs of the morpheus appliance itself."
476
+ end
477
+ optparse.parse!(args)
478
+ if args.count != 0
479
+ raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
480
+ end
481
+ connect(options)
482
+ begin
483
+ # params['startDate'] = start_date.utc.iso8601 if start_date
484
+ # params['endDate'] = end_date.utc.iso8601 if end_date
485
+ params['startMs'] = (start_date.to_i * 1000) if start_date
486
+ params['endMs'] = (end_date.to_i * 1000) if end_date
487
+ params.merge!(parse_list_options(options))
488
+ @health_interface.setopts(options)
489
+ if options[:dry_run]
490
+ print_dry_run @health_interface.dry.logs(params)
491
+ return 0
492
+ end
493
+ json_response = @health_interface.logs(params)
494
+ render_result = json_response['logs'] ? render_with_format(json_response, options, 'logs') : render_with_format(json_response, options, 'data')
495
+ return 0 if render_result
496
+ logs = json_response['data'] || json_response['logs']
497
+ title = "Morpheus Health Logs"
498
+ subtitles = []
499
+ if params['level']
500
+ subtitles << "Level: #{params['level']}"
501
+ end
502
+ if start_date
503
+ subtitles << "Start: #{start_date}"
504
+ end
505
+ if end_date
506
+ subtitles << "End: #{end_date}"
507
+ end
508
+ subtitles += parse_list_subtitles(options)
509
+ print_h1 title, subtitles
510
+
511
+ if logs.empty?
512
+ print "#{cyan}No logs found.#{reset}\n"
513
+ else
514
+ print format_log_records(logs, options, false)
515
+ print_results_pagination(json_response)
516
+ end
517
+ print reset,"\n"
518
+ return 0
519
+ rescue RestClient::Exception => e
520
+ print_rest_exception(e, options)
521
+ exit 1
522
+ end
523
+ end
524
+
525
+ def alarms(args)
526
+ options = {}
527
+ params = {}
528
+ start_date, end_date = nil, nil
529
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
530
+ opts.banner = subcommand_usage()
531
+ opts.on('--category VALUE', String, "Filter by Alarm Category. datastore, computeZone, computeServer, etc.") do |val|
532
+ params['alarmCategory'] = params['alarmCategory'] ? [params['alarmCategory'], val].flatten : val
533
+ end
534
+ opts.on('--status VALUE', String, "Filter by status. warning, error") do |val|
535
+ params['status'] = params['status'] ? [params['status'], val].flatten : val
536
+ end
537
+ opts.on('--acknowledged', '--acknowledged', "Filter by acknowledged. By default only open alarms are returned.") do
538
+ params['alarmStatus'] = 'acknowledged'
539
+ end
540
+ opts.on('--start TIMESTAMP','--start TIMESTAMP', "Start timestamp. Default is 30 days ago.") do |val|
541
+ start_date = parse_time(val) #.utc.iso8601
542
+ end
543
+ opts.on('--end TIMESTAMP','--end TIMESTAMP', "End timestamp. Default is now.") do |val|
544
+ end_date = parse_time(val) #.utc.iso8601
545
+ end
546
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
547
+ opts.footer = "List health alarms."
548
+ end
549
+ optparse.parse!(args)
550
+ if args.count != 0
551
+ raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
552
+ end
553
+ connect(options)
554
+ begin
555
+ params['startDate'] = start_date.utc.iso8601 if start_date
556
+ params['endDate'] = end_date.utc.iso8601 if end_date
557
+ params.merge!(parse_list_options(options))
558
+ @health_interface.setopts(options)
559
+ if options[:dry_run]
560
+ print_dry_run @health_interface.dry.list_alarms(params)
561
+ return 0
562
+ end
563
+ json_response = @health_interface.list_alarms(params)
564
+ render_result = render_with_format(json_response, options, 'alarms')
565
+ return 0 if render_result
566
+ alarms = json_response['alarms']
567
+ title = "Morpheus Health Alarms"
568
+ subtitles = []
569
+ # if params['category']
570
+ # subtitles << "Category: #{params['category']}"
571
+ # end
572
+ if params['status']
573
+ subtitles << "Status: #{params['status']}"
574
+ end
575
+ if params['alarmStatus'] == 'acknowledged'
576
+ subtitles << "(Acknowledged)"
577
+ end
578
+ if params['startDate']
579
+ subtitles << "Start Date: #{params['startDate']}"
580
+ end
581
+ if params['endDate']
582
+ subtitles << "End Date: #{params['endDate']}"
583
+ end
584
+ subtitles += parse_list_subtitles(options)
585
+ print_h1 title, subtitles
586
+ if alarms.empty?
587
+ print cyan,"No alarms found.",reset,"\n"
588
+ else
589
+ alarm_columns = [
590
+ {"ID" => lambda {|alarm| alarm['id'] } },
591
+ {"STATUS" => lambda {|alarm| format_health_status(alarm['status']) } },
592
+ {"RESOURCE" => lambda {|alarm| alarm['resourceName'] || alarm['refName'] } },
593
+ {"INFO" => lambda {|alarm| alarm['name'] } },
594
+ {"START DATE" => lambda {|alarm| format_local_dt(alarm['startDate']) } },
595
+ {"DURATION" => lambda {|alarm| format_duration(alarm['startDate'], alarm['acknoDate'] || Time.now) } },
596
+ ]
597
+ if options[:include_fields]
598
+ columns = options[:include_fields]
599
+ end
600
+ print as_pretty_table(alarms, alarm_columns, options)
601
+ print_results_pagination(json_response)
602
+ end
603
+ print reset,"\n"
604
+ return 0
605
+ rescue RestClient::Exception => e
606
+ print_rest_exception(e, options)
607
+ exit 1
608
+ end
609
+ end
610
+
611
+ def get_alarm(args)
612
+ options = {}
613
+ params = {}
614
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
615
+ opts.banner = subcommand_usage("[id]")
616
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
617
+ opts.footer = "Get details about a health alarm.\n[id] is required. Health Alarm ID"
618
+ end
619
+ optparse.parse!(args)
620
+
621
+ if args.count != 1
622
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
623
+ end
624
+
625
+ connect(options)
626
+
627
+ @health_interface.setopts(options)
628
+ if options[:dry_run]
629
+ print_dry_run @health_interface.dry.get_alarm(args[0], params)
630
+ return 0
631
+ end
632
+ json_response = @health_interface.get_alarm(args[0], params)
633
+ render_result = render_with_format(json_response, options, 'alarm')
634
+ return 0 if render_result
635
+ if json_response['alarm'].nil?
636
+ print_red_alert "Alarm not found by id #{args[0]}"
637
+ return 1
638
+ end
639
+ print_h1 "Alarm Details"
640
+ print cyan
641
+ alarm_columns = [
642
+ {"ID" => lambda {|alarm| alarm['id'] } },
643
+ {"Status" => lambda {|alarm| format_health_status(alarm['status']) } },
644
+ {"Resource" => lambda {|alarm| alarm['resourceName'] || alarm['refName'] } },
645
+ {"Info" => lambda {|alarm| alarm['name'] } },
646
+ {"Active" => lambda {|alarm| format_boolean(alarm['active']) } },
647
+ {"Start Date" => lambda {|alarm| format_local_dt(alarm['startDate']) } },
648
+ {"Duration" => lambda {|alarm| format_duration(alarm['startDate'], alarm['acknowledgedDate'] || Time.now) } },
649
+ {"Acknowledged Date" => lambda {|alarm| format_local_dt(alarm['acknowledgedDate']) } },
650
+ {"Acknowledged" => lambda {|alarm| format_boolean(alarm['acknowledged']) } }
651
+ ]
652
+ print_description_list(alarm_columns, json_response['alarm'])
653
+ print reset,"\n"
654
+ return 0
655
+
656
+ end
657
+
658
+ def acknowledge_alarms(args)
659
+ options = {}
660
+ params = {}
661
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
662
+ opts.banner = subcommand_usage("[alarm] [options]")
663
+ opts.on('-a', '--all', "Acknowledge all open alarms. This can be used instead of passing specific alarms.") do
664
+ params['all'] = true
665
+ end
666
+ build_common_options(opts, options, [:payload, :options, :json, :dry_run, :remote])
667
+ opts.footer = "Acknowledge health alarm(s).\n[alarm] is required. Alarm ID, supports multiple arguments."
668
+ end
669
+ optparse.parse!(args)
670
+
671
+ if params['all']
672
+ # updating all
673
+ if args.count > 0
674
+ raise_command_error "wrong number of arguments, --all option expects 0 and got (#{args.count}) #{args}\n#{optparse}"
675
+ end
676
+ else
677
+ # updating 1-N ids
678
+ if args.count < 0
679
+ raise_command_error "wrong number of arguments, expected 1-N and got (#{args.count}) #{args}\n#{optparse}"
680
+ end
681
+ params['ids'] = args.collect {|arg| arg }
682
+ end
683
+ connect(options)
684
+ begin
685
+ # validate ids
686
+ if params['ids']
687
+ parsed_id_list = []
688
+ params['ids'].each do |alarm_id|
689
+ alarm = find_health_alarm_by_name_or_id(alarm_id)
690
+ if alarm.nil?
691
+ # print_red_alert "Alarm not found by id #{args[0]}"
692
+ return 1
693
+ end
694
+ parsed_id_list << alarm['id']
695
+ end
696
+ params['ids'] = parsed_id_list.uniq
697
+ end
698
+
699
+ # construct payload
700
+ passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
701
+ payload = nil
702
+ if options[:payload]
703
+ payload = options[:payload]
704
+ payload.deep_merge!(passed_options) unless passed_options.empty?
705
+ else
706
+ payload = {}
707
+ # allow arbitrary -O options
708
+ payload.deep_merge!(passed_options) unless passed_options.empty?
709
+ end
710
+ id_list = params['ids'] || []
711
+ confirm_msg = params['all'] ? "Are you sure you want to acknowledge all open alarms?" : "Are you sure you want to acknowledge the #{id_list.size == 1 ? 'alarm' : 'alarms'} #{anded_list(id_list)}?"
712
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm(confirm_msg)
713
+ return 9, "aborted command"
714
+ end
715
+ @health_interface.setopts(options)
716
+ if options[:dry_run]
717
+ print_dry_run @health_interface.dry.acknowledge_alarms(params, payload)
718
+ return
719
+ end
720
+ json_response = @health_interface.acknowledge_alarms(params, payload)
721
+ render_result = render_with_format(json_response, options)
722
+ exit_code = 0 # json_response['success'] == true ? 0 : 1
723
+ return exit_code if render_result
724
+
725
+ if params['all']
726
+ print_green_success "Acknowledged all alarms"
727
+ else
728
+ print_green_success "Acknowledged #{id_list.size == 1 ? 'alarm' : 'alarms'} #{anded_list(id_list)}"
729
+ end
730
+ return exit_code
731
+ rescue RestClient::Exception => e
732
+ print_rest_exception(e, options)
733
+ exit 1
734
+ end
735
+ end
736
+
737
+ def unacknowledge_alarms(args)
738
+ options = {}
739
+ params = {acknowledged:false}
740
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
741
+ opts.banner = subcommand_usage("[alarm] [options]")
742
+ # opts.on('-a', '--all', "Acknowledge all open alarms. This can be used instead of passing specific alarms.") do
743
+ # params['all'] = true
744
+ # end
745
+ build_common_options(opts, options, [:payload, :options, :json, :dry_run, :remote])
746
+ opts.footer = "Unacknowledge health alarm(s).\n[alarm] is required. Alarm ID, supports multiple arguments."
747
+ end
748
+ optparse.parse!(args)
749
+
750
+ if params['all']
751
+ # updating all
752
+ if args.count > 0
753
+ raise_command_error "wrong number of arguments, --all option expects 0 and got (#{args.count}) #{args}\n#{optparse}"
754
+ end
755
+ else
756
+ # updating 1-N ids
757
+ if args.count < 0
758
+ raise_command_error "wrong number of arguments, expected 1-N and got (#{args.count}) #{args}\n#{optparse}"
759
+ end
760
+ params['ids'] = args.collect {|arg| arg }
761
+ end
762
+ connect(options)
763
+ begin
764
+ # validate ids
765
+ if params['ids']
766
+ parsed_id_list = []
767
+ params['ids'].each do |alarm_id|
768
+ alarm = find_health_alarm_by_name_or_id(alarm_id)
769
+ if alarm.nil?
770
+ # print_red_alert "Alarm not found by id #{args[0]}"
771
+ return 1
772
+ end
773
+ parsed_id_list << alarm['id']
774
+ end
775
+ params['ids'] = parsed_id_list.uniq
776
+ end
777
+
778
+ # construct payload
779
+ passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
780
+ payload = nil
781
+ if options[:payload]
782
+ payload = options[:payload]
783
+ payload.deep_merge!(passed_options) unless passed_options.empty?
784
+ else
785
+ payload = {}
786
+ # allow arbitrary -O options
787
+ payload.deep_merge!(passed_options) unless passed_options.empty?
788
+ end
789
+ id_list = params['ids'] || []
790
+ confirm_msg = params['all'] ? "Are you sure you want to unacknowledge all alarms?" : "Are you sure you want to unacknowledge the #{id_list.size == 1 ? 'alarm' : 'alarms'} #{anded_list(id_list)}?"
791
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm(confirm_msg)
792
+ return 9, "aborted command"
793
+ end
794
+ @health_interface.setopts(options)
795
+ if options[:dry_run]
796
+ print_dry_run @health_interface.dry.acknowledge_alarms(params, payload)
797
+ return
798
+ end
799
+ json_response = @health_interface.acknowledge_alarms(params, payload)
800
+ render_result = render_with_format(json_response, options)
801
+ exit_code = 0 # json_response['success'] == true ? 0 : 1
802
+ return exit_code if render_result
803
+
804
+ if params['all']
805
+ print_green_success "Acknowledged all alarms"
806
+ else
807
+ print_green_success "Acknowledged #{id_list.size == 1 ? 'alarm' : 'alarms'} #{anded_list(id_list)}"
808
+ end
809
+ return exit_code
810
+ rescue RestClient::Exception => e
811
+ print_rest_exception(e, options)
812
+ exit 1
813
+ end
814
+ end
815
+ private
816
+
817
+ def find_health_alarm_by_name_or_id(val)
818
+ if val.to_s =~ /\A\d{1,}\Z/
819
+ return find_health_alarm_by_id(val)
820
+ else
821
+ return find_health_alarm_by_name(val)
822
+ end
823
+ end
824
+
825
+ def find_health_alarm_by_id(id)
826
+ raise "#{self.class} has not defined @health_interface" if @health_interface.nil?
827
+ begin
828
+ json_response = @health_interface.get_alarm(id)
829
+ return json_response['alarm']
830
+ rescue RestClient::Exception => e
831
+ if e.response && e.response.code == 404
832
+ print_red_alert "Alarm not found by id #{id}"
833
+ else
834
+ raise e
835
+ end
836
+ end
837
+ end
838
+
839
+ def find_health_alarm_by_name(name)
840
+ raise "#{self.class} has not defined @health_interface" if @health_interface.nil?
841
+ alarms = @health_interface.list_alarms({name: name.to_s})['alarms']
842
+ if alarm.empty?
843
+ print_red_alert "Alarm not found by name #{name}"
844
+ return nil
845
+ elsif alarms.size > 1
846
+ print_red_alert "#{alarms.size} alarms found by name #{name}"
847
+ print as_pretty_table(alarms, [:id,:name], {color:red})
848
+ print reset,"\n"
849
+ return nil
850
+ else
851
+ return alarms[0]
852
+ end
853
+ end
854
+
855
+ def format_health_status(val, return_color=cyan)
856
+ out = ""
857
+ status_string = val.to_s.downcase
858
+ if(status_string)
859
+ if(status_string == 'ok' || status_string == 'running')
860
+ out << "#{green}#{status_string.upcase}#{return_color}"
861
+ elsif(status_string == 'error' || status_string == 'offline')
862
+ out << "#{red}#{status_string.upcase}#{return_color}"
863
+ elsif status_string == 'syncing'
864
+ out << "#{cyan}#{status_string.upcase}#{return_color}"
865
+ else
866
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
867
+ end
868
+ end
869
+ out
870
+ end
871
+
872
+ # this is for weird elastic status values that are actually colors
873
+ def format_index_health(val, return_color=cyan)
874
+ # hrmm
875
+ status_string = val.to_s.downcase # || 'green'
876
+ if status_string == 'warning' || status_string == 'yellow'
877
+ "#{yellow}WARNING#{cyan}"
878
+ elsif status_string == 'error' || status_string == 'red'
879
+ "#{red}ERROR#{cyan}"
880
+ elsif status_string == 'ok' || status_string == 'green'
881
+ "#{green}OK#{cyan}"
882
+ else
883
+ # hrmm
884
+ it['status']
885
+ end
886
+ end
887
+
888
+ def format_queue_status(val, return_color=cyan)
889
+ # hrmm
890
+ status_string = val.to_s.downcase # || 'ok'
891
+ if status_string == 'warning'
892
+ "#{yellow}WARNING#{cyan}"
893
+ elsif status_string == 'error'
894
+ "#{red}ERROR#{cyan}"
895
+ elsif status_string == 'ok'
896
+ "#{green}OK#{cyan}"
897
+ else
898
+ # hrmm
899
+ it['status']
900
+ end
901
+ end
902
+
903
+ end