class Morpheus::Cli::HealthCommand

Public Instance Methods

acknowledge_alarms(args) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 801
def acknowledge_alarms(args)
  options = {}
  params = {}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[alarm] [options]")
    opts.on('-a', '--all', "Acknowledge all open alarms. This can be used instead of passing specific alarms.") do
      params['all'] = true
    end
    build_common_options(opts, options, [:payload, :options, :json, :dry_run, :remote])
    opts.footer = "Acknowledge health alarm(s).\n[alarm] is required. Alarm ID, supports multiple arguments."
  end
  optparse.parse!(args)

  if params['all']
    # updating all
    if args.count > 0
      raise_command_error "wrong number of arguments, --all option expects 0 and got (#{args.count}) #{args}\n#{optparse}"
    end
  else
    # updating 1-N ids
    if args.count < 0
      raise_command_error "wrong number of arguments, expected 1-N and got (#{args.count}) #{args}\n#{optparse}"
    end
    params['ids'] = args.collect {|arg| arg }
  end
  connect(options)
  begin
    # validate ids
    if params['ids']
      parsed_id_list = []
      params['ids'].each do |alarm_id|
        alarm = find_health_alarm_by_name_or_id(alarm_id)
        if alarm.nil?
          # print_red_alert "Alarm not found by id #{args[0]}"
          return 1
        end
        parsed_id_list << alarm['id']
      end
      params['ids'] = parsed_id_list.uniq
    end

    # construct payload
    passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
    payload = nil
    if options[:payload]
      payload = options[:payload]
      payload.deep_merge!(passed_options) unless passed_options.empty?
    else
      payload = {}
      # allow arbitrary -O options
      payload.deep_merge!(passed_options) unless passed_options.empty?
    end
    id_list = params['ids'] || []
    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)}?"
    unless options[:yes] || Morpheus::Cli::OptionTypes.confirm(confirm_msg)
      return 9, "aborted command"
    end
    @health_interface.setopts(options)
    if options[:dry_run]
      print_dry_run @health_interface.dry.acknowledge_alarms(params, payload)
      return
    end
    json_response = @health_interface.acknowledge_alarms(params, payload)
    render_result = render_with_format(json_response, options)
    exit_code = 0 # json_response['success'] == true ? 0 : 1
    return exit_code if render_result

    if params['all']
      print_green_success "Acknowledged all alarms"
    else
      print_green_success "Acknowledged #{id_list.size == 1 ? 'alarm' : 'alarms'} #{anded_list(id_list)}"
    end
    return exit_code
  rescue RestClient::Exception => e
    print_rest_exception(e, options)
    exit 1
  end
end
alarms(args) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 668
def alarms(args)
  options = {}
  params = {}
  start_date, end_date = nil, nil
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage()
    opts.on('--category VALUE', String, "Filter by Alarm Category. datastore, computeZone, computeServer, etc.") do |val|
      params['alarmCategory'] = params['alarmCategory'] ? [params['alarmCategory'], val].flatten : val
    end
    opts.on('--status VALUE', String, "Filter by status. warning, error") do |val|
      params['status'] = params['status'] ? [params['status'], val].flatten : val
    end
    opts.on('--acknowledged', '--acknowledged', "Filter by acknowledged. By default only open alarms are returned.") do
      params['alarmStatus'] = 'acknowledged'
    end
    opts.on('--start TIMESTAMP','--start TIMESTAMP', "Start timestamp. Default is 24 hours ago.") do |val|
      start_date = parse_time(val) #.utc.iso8601
    end
    opts.on('--end TIMESTAMP','--end TIMESTAMP', "End timestamp. Default is now.") do |val|
      end_date = parse_time(val) #.utc.iso8601
    end
    build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
    opts.footer = "List health alarms."
  end
  optparse.parse!(args)
  if args.count != 0
    raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
  end
  connect(options)
  begin
    params['startDate'] = start_date.utc.iso8601 if start_date
    params['endDate'] = end_date.utc.iso8601 if end_date
    params.merge!(parse_list_options(options))
    @health_interface.setopts(options)
    if options[:dry_run]
      print_dry_run @health_interface.dry.list_alarms(params)
      return 0
    end
    json_response = @health_interface.list_alarms(params)
    render_result = render_with_format(json_response, options, 'alarms')
    return 0 if render_result
    alarms = json_response['alarms']
    title = "Morpheus Health Alarms"
    subtitles = []
    # if params['category']
    #   subtitles << "Category: #{params['category']}"
    # end
    if params['status']
      subtitles << "Status: #{params['status']}"
    end
    if params['alarmStatus'] == 'acknowledged'
      subtitles << "(Acknowledged)"
    end
    if params['startDate']
      subtitles << "Start Date: #{params['startDate']}"
    end
    if params['endDate']
      subtitles << "End Date: #{params['endDate']}"
    end
    subtitles += parse_list_subtitles(options)
    print_h1 title, subtitles
    if alarms.empty?
      print cyan,"No alarms found.",reset,"\n"
    else
      alarm_columns = [
        {"ID" => lambda {|alarm| alarm['id'] } },
        {"STATUS" => lambda {|alarm| format_health_status(alarm['status']) } },
        {"RESOURCE" => lambda {|alarm| alarm['resourceName'] || alarm['refName'] } },
        {"INFO" => lambda {|alarm| alarm['name'] } },
        {"START DATE" => lambda {|alarm| format_local_dt(alarm['startDate']) } },
        {"DURATION" => lambda {|alarm| format_duration(alarm['startDate'], alarm['acknoDate'] || Time.now) } },
      ]
      if options[:include_fields]
        columns = options[:include_fields]
      end
      print as_pretty_table(alarms, alarm_columns, options)
      print_results_pagination(json_response)
    end
    print reset,"\n"
    return 0
  rescue RestClient::Exception => e
    print_rest_exception(e, options)
    exit 1
  end
end
connect(opts) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 9
def connect(opts)
  @api_client = establish_remote_appliance_connection(opts)
  @health_interface = @api_client.health
end
export_logs(args) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 576
def export_logs(args)
  params = {}
  options = {}
  outfile = nil
  do_overwrite = false
  do_mkdir = false
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[file]")
    opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
      params['level'] = params['level'] ? [params['level'], val].flatten : [val]
    end
    opts.on('--start TIMESTAMP','--start TIMESTAMP', "Start date timestamp in standard iso8601 format. Default is 24 hours ago.") do |val|
      params['startDate'] = val # parse_time(val).utc.iso8601
    end
    opts.on('--end TIMESTAMP','--end TIMESTAMP', "End date timestamp in standard iso8601 format. Default is now.") do |val|
      params['endDate'] = val # parse_time(val).utc.iso8601
    end
    opts.on( '-f', '--force', "Overwrite existing [file] if it exists." ) do
      do_overwrite = true
      # do_mkdir = true
    end
    opts.on( '-p', '--mkdir', "Create missing directories for [file] if they do not exist." ) do
      do_mkdir = true
    end
    build_common_options(opts, options, [:list, :query, :dry_run, :remote])
    opts.footer = "Export morpheus appliance log." + "\n" +
                  "[file] is required. This is local destination for the downloaded file. Example: morpheus.log"
  end
  optparse.parse!(args)
  verify_args!(args:args, optparse:optparse, count: 1)
  connect(options)
  params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
  params.merge!(parse_list_options(options))

  outfile = args[0]
  outfile = File.expand_path(outfile)
  
  if Dir.exist?(outfile)
    raise_command_error("[file] is invalid. It is the name of an existing directory: #{outfile}", args, optparse)
  end
  destination_dir = File.dirname(outfile)
  if !Dir.exist?(destination_dir)
    if do_mkdir
      print cyan,"Creating local directory #{destination_dir}",reset,"\n"
      FileUtils.mkdir_p(destination_dir)
    else
      raise_command_error("[file] is invalid. Directory not found: #{destination_dir}", args, optparse)
    end
  end
  if File.exist?(outfile)
    if do_overwrite
      # uhh need to be careful wih the passed filepath here..
      # don't delete, just overwrite.
      # File.delete(outfile)
    else
      raise_command_error("[file] is invalid. File already exists: #{outfile}\nUse -f to overwrite the existing file.", args, optparse)
    end
  end

  @health_interface.setopts(options)
  if options[:dry_run]
    print_dry_run @health_interface.dry.export_logs(outfile, params)
    return 0
  end
  if !options[:quiet]
    print cyan + "Downloading file #{outfile} ... "
  end
  http_response, bad_body = @health_interface.export_logs(outfile, params)
  # FileUtils.chmod(0600, outfile)
  success = http_response.code.to_i == 200
  if success
    if !options[:quiet]
      print green + "SUCCESS" + reset + " (" + format_bytes(http_response["Content-Length"].to_i).to_s + ")" + "\n"
    end
    # todo: parse default outfile from http_response["Content-Type"]
    return 0
  else
    if !options[:quiet]
      print red + "ERROR" + reset + " HTTP #{http_response.code}" + "\n"
    end
    # F it, just remove a bad result
    if File.exist?(outfile) && File.file?(outfile)
      Morpheus::Logging::DarkPrinter.puts "Deleting bad file download: #{outfile}" if Morpheus::Logging.debug?
      File.delete(outfile)
    end
    if options[:debug]
      puts_error http_response.inspect
    end
    return 1, "download failed"
  end
end
get(args) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 18
def get(args)
  options = {}
  params = {}
  live_health = false
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[-a] [options]")
    opts.on('-a', '--all', "Display all details: CPU, Memory, Database, etc." ) do
      options[:details] = true
      options[:show_cpu] = true
      options[:show_threads] = true
      options[:show_memory] = true
      options[:show_database] = true
      options[:show_elastic] = true
      options[:show_queue] = true
    end
    opts.on('--details', '--details', "Display all details: CPU, Memory, Database, etc." ) do
      options[:details] = true
      options[:show_cpu] = true
      options[:show_memory] = true
      options[:show_database] = true
      options[:show_elastic] = true
      options[:show_queue] = true
    end
    opts.add_hidden_option('--details') # prefer -a, --all
    opts.on('--cpu', "Display CPU details" ) do
      options[:show_cpu] = true
    end
    opts.on('--threads', "Display Thread details" ) do
      options[:show_threads] = true
    end
    opts.on('--memory', "Display Memory details" ) do
      options[:show_memory] = true
    end
    opts.on('--database', "Display Database details" ) do
      options[:show_database] = true
    end
    opts.on('--elastic', "Display Elasticsearch details" ) do
      options[:show_elastic] = true
    end
    opts.on('--queue', "Display Queue (Rabbit) details" ) do
      options[:show_queue] = true
    end
    opts.on('--queues', "Display Queue (Rabbit) details" ) do
      options[:show_queue] = true
    end
    opts.on('--rabbit', "Display Queue (Rabbit) details" ) do
      options[:show_queue] = true
    end
    opts.add_hidden_option('--queues')
    opts.add_hidden_option('--rabbit')
    opts.on('--live', "Fetch Live Health Data. By default the last cached health data is returned. This also retrieves all elastic indices." ) do
      live_health = true
    end
    build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
    opts.footer = "Get appliance health information." + "\n" +
                  "By default, only the health status and levels are displayed." + "\n" +
                  "Display more details with the options --cpu, --database, --memory, etc." + "\n" +
                  "Display all details with the -a option."
  end
  optparse.parse!(args)

  if args.count != 0
    raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
  end

  connect(options)
  begin
    @health_interface.setopts(options)
    if options[:dry_run]
      print_dry_run(live_health ? @health_interface.dry.live(params) : @health_interface.dry.get(params))
      return 0
    end
    json_response = live_health ? @health_interface.live(params) : @health_interface.get(params)
    render_result = render_with_format(json_response, options, 'health')
    exit_code = json_response['success'] == true ? 0 : 1
    return exit_code if render_result

    health = json_response['health']
    subtitles = []
    if options[:details]
      subtitles << "Details"
    end
    if live_health
      subtitles << "(Live)"
    end
    print_h1 "Morpheus Health", subtitles, options
    # thin print below here
    options.merge!({thin:true})
    
    if health.nil?
      print yellow,"No health data returned.",reset,"\n"
      return 1
    end
    if health['elastic'] && health['elastic']['noticeMessage'].to_s != ""
      print cyan,health['elastic']['noticeMessage'],reset,"\n"
      print "\n"
    end

    #print_h2 "Health Summary", options
    print cyan
    health_summary_columns = {
      "Overall" => lambda {|it| format_health_status(it['cpu']['status']) rescue '' },
      "CPU" => lambda {|it| format_health_status(it['cpu']['status']) rescue '' },
      "Memory" => lambda {|it| format_health_status(it['memory']['status']) rescue '' },
      "Database" => lambda {|it| format_health_status(it['database']['status']) rescue '' },
      "Elastic" => lambda {|it| format_health_status(it['elastic']['status']) rescue '' },
      "Queue" => lambda {|it| format_health_status(it['rabbit']['status']) rescue '' },
    }
    print as_pretty_table(health, health_summary_columns, options)
    print "\n"
    
    # flash warnings
    if health['cpu'] && health['cpu']['status'] != 'ok' && health['cpu']['statusMessage']
      status_color = health['cpu']['status'] == 'error' ? red : yellow
      print status_color,health['cpu']['statusMessage'],reset,"\n"
    end
    if health['memory'] && health['memory']['status'] != 'ok' && health['memory']['statusMessage']
      status_color = health['memory']['status'] == 'error' ? red : yellow
      print status_color,health['memory']['statusMessage'],reset,"\n"
    end
    if health['database'] && health['database']['status'] != 'ok' && health['database']['statusMessage']
      status_color = health['database']['status'] == 'error' ? red : yellow
      print status_color,health['database']['statusMessage'],reset,"\n"
    end
    # if health['elastic'] && health['elastic']['noticeMessage'].to_s != ""
    #   print cyan,health['elastic']['noticeMessage'],reset,"\n"
    # end
    if health['elastic'] && health['elastic']['status'] != 'ok' && health['elastic']['statusMessage']
      status_color = health['elastic']['status'] == 'error' ? red : yellow
      print status_color,health['elastic']['statusMessage'],reset,"\n"
    end
    if health['rabbit'] && health['rabbit']['status'] != 'ok' && health['rabbit']['statusMessage']
      status_color = health['rabbit']['status'] == 'error' ? red : yellow
      print status_color,health['rabbit']['statusMessage'],reset,"\n"
    end

    print_h2 "Health Levels", options
    print cyan
    health_levels_columns = {
      "Morpheus CPU" => lambda {|it| format_percent(it['cpu']['cpuLoad'].to_f, 0) rescue '' },
      "System CPU" => lambda {|it| format_percent(it['cpu']['cpuTotalLoad'].to_f, 0) rescue '' },
      "Morpheus Memory" => lambda {|it| format_percent(it['memory']['memoryPercent'].to_f * 100, 0) rescue '' },
      "System Memory" => lambda {|it| format_percent(it['memory']['systemMemoryPercent'].to_f * 100, 0) rescue '' },
      "Used Swap" => lambda {|it| format_percent(it['memory']['swapPercent'].to_f * 100, 0) rescue '' },
    }
    print as_pretty_table(health, health_levels_columns, options)
    # print "\n"

    if options[:show_cpu]
      # CPU
      if health['cpu'].nil?
        print yellow,"No cpu health information returned.",reset,"\n"
      else
        print_h2 "CPU", options
        print cyan
        cpu_columns = {
          "Processor Count" => lambda {|it| it['processorCount'] rescue '' },
          "Process Time" => lambda {|it| format_human_duration(it['processTime'].to_f / 1000) rescue '' },
          "Morpheus CPU" => lambda {|it| (it['cpuLoad'].to_f.round(2).to_s + '%') rescue '' },
          "System CPU" => lambda {|it| (it['cpuTotalLoad'].to_f.round(2).to_s + '%') rescue '' },
          "System Load" => lambda {|it| (it['systemLoad'].to_f.round(3)) rescue '' },
        }
        #print as_pretty_table(health['cpu'], cpu_columns, options)
        print_description_list(cpu_columns, health['cpu'], options)
      end
    end

    # Threads ()
    if options[:show_threads]
      print_h2 "Threads", options
      if health['threads'].nil?
        print yellow,"No thread information returned.",reset,"\n\n"
      else
        print cyan

        thread_summary_columns = {
          "Thread Count" => lambda {|it| it['totalThreads'].size rescue '' },
          "Busy Threads" => lambda {|it| it['busyThreads'].size rescue '' },
          "Running Threads" => lambda {|it| it['runningThreads'].size rescue '' },
          "Blocked Threads" => lambda {|it| it['blockedThreads'].size rescue '' },
        }
        print_description_list(thread_summary_columns, health['threads'], options)


        thread_columns = [
          {"Name".upcase => lambda {|it| it['name']} },
          {"Status".upcase => lambda {|it| 
            # hrmm
            status_string = (it['status'] || it['state']).to_s.downcase
            status_color = cyan
            # if status_string.include?('waiting')
            #   status_color = yellow
            # end
            "#{status_color}#{status_string.upcase}#{cyan}"
          } },
          # {"CPU Time" => lambda {|it| it['cpuTime'].to_s } },
          # {"CPU Time" => lambda {|it| format_human_duration(it['cpuTime'].to_f / 1000) rescue '' } },
          {"CPU Percent" => lambda {|it| it['cpuPercent'].to_i.to_s + '%' } }
        ]

        if health['threads']['busyThreads'] && health['threads']['busyThreads'].size > 0
          print_h2 "Busy Threads"
          print cyan
          print as_pretty_table(health['threads']['busyThreads'], thread_columns, options)
        end

        if health['threads']['runningThreads'] && health['threads']['runningThreads'].size > 0
          print_h2 "Running Threads"
          print cyan
          print as_pretty_table(health['threads']['runningThreads'], thread_columns, options)
        end

        if health['threads']['blockedThreads'] && health['threads']['blockedThreads'].size > 0
          print_h2 "Blocked Threads"
          print cyan
          print as_pretty_table(health['threads']['blockedThreads'], thread_columns, options)
        end
      end
    end

    # Memory
    if options[:show_memory]
      if health['memory'].nil?
        print yellow,"No memory health information returned.",reset,"\n"
      else
        print_h2 "Memory", options
        print cyan
        memory_columns = {
          "Morpheus Memory" => lambda {|it| format_bytes_short(it['totalMemory']) rescue '' },
          "Morpheus Used Memory" => lambda {|it| format_bytes_short(it['usedMemory']) rescue '' },
          "Morpheus Free Memory" => lambda {|it| format_bytes_short(it['freeMemory']) rescue '' },
          "Morpheus Memory Usage" => lambda {|it| format_percent(it['memoryPercent'].to_f * 100) rescue '' },
          "System Memory" => lambda {|it| format_bytes_short(it['systemMemory']) rescue '' },
          "System Used Memory" => lambda {|it| format_bytes_short(it['committedMemory']) rescue '' },
          "System Free Memory" => lambda {|it| format_bytes_short(it['systemFreeMemory']) rescue '' },
          "System Memory Usage" => lambda {|it| format_percent(it['systemMemoryPercent'].to_f * 100) rescue '' },
          "System Swap" => lambda {|it| format_bytes_short(it['systemSwap']) rescue '' },
          "Free Swap" => lambda {|it| format_bytes_short(it['systemFreeSwap']) rescue '' },
          #"Used Swap" => lambda {|it| format_percent(it['swapPercent'].to_f * 100) rescue '' }
          # "System Load" => lambda {|it| (it['systemLoad'].to_f(3)) rescue '' },
        }
        #print as_pretty_table(health['memory'], memory_columns, options)
        print_description_list(memory_columns, health['memory'], options)
      end
    end

    # Database (mysql)
    if options[:show_database]
      if health['database'].nil?
        print yellow,"No database health information returned.",reset,"\n"
      else
        print_h2 "Database", options
        print cyan
        database_columns = {
          "Lifetime Connections" => lambda {|it| it['stats']['Connections'] rescue '' },
          "Aborted Connections" => lambda {|it| it['stats']['Aborted_connects'] rescue '' },
          "Max Used Connections" => lambda {|it| it['stats']['Max_used_connections'] rescue '' },
          "Max Connections" => lambda {|it| it['maxConnections'] rescue '' },
          "Threads Running" => lambda {|it| it['stats']['Threads_running'] rescue '' },
          "Threads Connected" => lambda {|it| it['stats']['Threads_connected'] rescue '' },
          "Slow Queries" => lambda {|it| it['stats']['Slow_queries'] rescue '' },
          "Temp Tables" => lambda {|it| it['stats']['Created_tmp_disk_tables'] rescue '' },
          "Handler Read First" => lambda {|it| it['stats']['Handler_read_first'] rescue '' },
          "Buffer Pool Free" => lambda {|it| it['stats']['Innodb_buffer_pool_wait_free'] rescue '' },
          "Open Tables" => lambda {|it| it['stats']['Open_tables'] rescue '' },
          "Table Scans" => lambda {|it| it['stats']['Select_scan'] rescue '' },
          "Full Joins" => lambda {|it| it['stats']['Select_full_join'] rescue '' },
          "Key Read Requests" => lambda {|it| it['stats']['Key_read_requests'] rescue '' },
          "Key Reads" => lambda {|it| it['stats']['Key_reads'] rescue '' },
          "Engine Waits" => lambda {|it| it['stats']['Innodb_log_waits'] rescue '' },
          "Lock Waits" => lambda {|it| it['stats']['Table_locks_waited'] rescue '' },
          "Handler Read Rnd" => lambda {|it| it['stats']['Handler_read_rnd'] rescue '' },
          "Engine IO Writes" => lambda {|it| it['stats']['Innodb_data_writes'] rescue '' },
          "Engine IO Reads" => lambda {|it| it['stats']['Innodb_data_reads'] rescue '' },
          "Engine IO Double Writes" => lambda {|it| it['stats']['Innodb_dblwr_writes'] rescue '' },
          "Engine Log Writes" => lambda {|it| it['stats']['Innodb_log_writes'] rescue '' },
          "Engine Memory" => lambda {|it| format_bytes_short(it['innodbStats']['largeMemory']) rescue '' },
          "Dictionary Memory" => lambda {|it| format_bytes_short(it['innodbStats']['dictionaryMemory']) rescue '' },
          "Buffer Pool Size" => lambda {|it| it['innodbStats']['bufferPoolSize'] rescue '' },
          "Free Buffers" => lambda {|it| it['innodbStats']['freeBuffers'] rescue '' },
          "Database Pages" => lambda {|it| it['innodbStats']['databasePages'] rescue '' },
          "Old Pages" => lambda {|it| it['innodbStats']['oldPages'] rescue '' },
          "Dirty Page Percent" => lambda {|it| format_percent(it['innodbStats']['dirtyPagePercent'] ? it['innodbStats']['dirtyPagePercent'] : '') rescue '' },
          "Max Dirty Pages" => lambda {|it| format_percent(it['innodbStats']['maxDirtyPagePercent'].to_f) rescue '' },
          "Pending Reads" => lambda {|it| format_number(it['innodbStats']['pendingReads']) rescue '' },
          "Insert Rate" => lambda {|it| format_rate(it['innodbStats']['insertsPerSecond'].to_f) rescue '' },
          "Update Rate" => lambda {|it| format_rate(it['innodbStats']['updatesPerSecond'].to_f) rescue '' },
          "Delete Rate" => lambda {|it| format_rate(it['innodbStats']['deletesPerSecond'].to_f) rescue '' },
          "Read Rate" => lambda {|it| format_rate(it['innodbStats']['readsPerSecond']) rescue '' },
          "Buffer Hit Rate" => lambda {|it| format_percent(it['innodbStats']['bufferHitRate'].to_f * 100) rescue '' },
          "Read Write Ratio" => lambda {|it| 
            rw_ratio = ""
            begin
              total_writes = (it['stats']['Com_update'].to_i) + (it['stats']['Com_insert'].to_i) + (it['stats']['Com_delete'].to_f)
              total_reads = (it['stats']['Com_select'].to_i)
              if total_writes > 0
                rw_ratio = (total_reads.to_f / total_writes.to_f).round(2).to_s
              end
            rescue => ex
              puts ex
            end
            rw_ratio
          },
          "Uptime" => lambda {|it| (it['stats']['Uptime'] ? format_human_duration(it['stats']['Uptime'].to_i) : '') rescue '' },
        }
        
        print_description_list(database_columns, health['database'], options)
        #print as_pretty_table(health['database'], database_columns, options)

      end
    end

    # Elasticsearch
    if options[:show_elastic]
      if health['elastic'].nil?
        print yellow,"No elastic health information returned.",reset,"\n\n"
      else
        print_h2 "Elastic", options
        print cyan

        elastic_columns = {
          "Status" => 'status',
          # "Status" => lambda {|it| format_health_status(it['status']) rescue '' },
          # "Status" => lambda {|it|
          #   begin
          #     if it['statusMessage'].to_s != ""
          #       format_health_status(it['status']).to_s + " - " + it['statusMessage']
          #     else
          #       format_health_status(it['status'])
          #     end
          #   rescue => ex
          #     ''
          #   end
          # },
          "Cluster" => lambda {|it| it['stats']['clusterName'] rescue '' },
          "Node Count" => lambda {|it| it['stats']['nodeTotal'] rescue '' },
          "Data Nodes" => lambda {|it| it['stats']['nodeData'] rescue '' },
          "Shards" => lambda {|it| it['stats']['shards'] rescue '' },
          "Primary Shards" => lambda {|it| it['stats']['primary'] rescue '' },
          "Relocating Shards" => lambda {|it| it['stats']['relocating'] rescue '' },
          "Initializing" => lambda {|it| it['stats']['initializing'] rescue '' },
          "Unassigned" => lambda {|it| it['stats']['unassigned'] rescue '' },
          "Pending Tasks" => lambda {|it| it['stats']['pendingTasks'] rescue '' },
          "Active Shards" => lambda {|it| it['stats']['activePercent'] rescue '' },
        }
        
        print_description_list(elastic_columns, health['elastic'], options)
        #print as_pretty_table(health['elastic'], elastic_columns, options)

        elastic_nodes_columns = [
          {"NODE" => lambda {|it| it['name'] } },
          {"MASTER" => lambda {|it| it['master'] == '*' } },
          {"LOCATION" => lambda {|it| it['ip'] } },
          {"RAM" => lambda {|it| it['ramPercent'] } },
          {"HEAP" => lambda {|it| it['heapPercent'] } },
          {"CPU USAGE" => lambda {|it| it['cpuCount'] } },
          {"1M LOAD" => lambda {|it| it['loadOne'] } },
          {"5M LOAD" => lambda {|it| it['loadFive'] } },
          {"15M LOAD" => lambda {|it| it['loadFifteen'] } }
        ]

        print_h2 "Elastic Nodes"
        if health['elastic']['nodes'].nil? || health['elastic']['nodes'].empty?
          print yellow,"No nodes found.",reset,"\n\n"
        else
          print as_pretty_table(health['elastic']['nodes'], elastic_nodes_columns, options)
        end
        
        elastic_indices_columns = [
          {"Health".upcase => lambda {|it| format_index_health(it['health']) } },
          {"Index".upcase => lambda {|it| it['index']} },
          {"Status".upcase => lambda {|it| it['status'] } },
          {"Primary".upcase => lambda {|it| it['primary'] } },
          {"Replicas".upcase => lambda {|it| it['replicas'] } },
          {"Doc Count".upcase => lambda {|it| format_number(it['count']) } },
          {"Primary Size".upcase => lambda {|it| it['primarySize'] } },
          {"Total Size".upcase => lambda {|it| it['totalSize'] } },
        ]

        # when the api returns indices, it will include badIndices, so don't show both.
        if health['elastic']['indices'] && health['elastic']['indices'].size > 0
          print_h2 "Elastic Indices"
          if health['elastic']['indices'].nil? || health['elastic']['indices'].empty?
            print yellow,"No indices found.",reset,"\n\n"
          else
            print cyan
            print as_pretty_table(health['elastic']['indices'], elastic_indices_columns, options)
          end
        else
          print_h2 "Bad Elastic Indices"
          if health['elastic']['badIndices'].nil? || health['elastic']['badIndices'].empty?
            # print cyan,"No bad indices found.",reset,"\n\n"
          else
            print cyan
            print as_pretty_table(health['elastic']['badIndices'], elastic_indices_columns, options)
          end
        end


      end
    end

    # Queues (rabbit)
    if options[:show_queue]
      print_h2 "Queue (Rabbit)", options
      if health['rabbit'].nil?
        print yellow,"No rabbit queue health information returned.",reset,"\n\n"
      else
        print cyan

        rabbit_summary_columns = {
          "Status" => lambda {|it| 
            begin
              if it['statusMessage'].to_s != ""
                format_health_status(it['status']).to_s + " - " + it['statusMessage'] 
              else
                format_health_status(it['status'])
              end
            rescue => ex
              ''
            end
          },
          "Queues" => lambda {|it| it['queues'].size rescue '' },
          "Busy Queues" => lambda {|it| it['busyQueues'].size rescue '' },
          "Error Queues" => lambda {|it| it['errorQueues'].size rescue '' }
        }
        
        print_description_list(rabbit_summary_columns, health['rabbit'], options)
        #print as_pretty_table(health['rabbit'], rabbit_summary_columns, options)

        print_h2 "Queues"
        queue_columns = [
          {"Status".upcase => lambda {|it| 
            # hrmm
            status_string = it['status'].to_s.downcase # || 'green'
            if status_string == 'warning'
              "#{yellow}WARNING#{cyan}"
            elsif status_string == 'error'
              "#{red}ERROR#{cyan}"
            elsif status_string == 'ok'
              "#{green}OK#{cyan}"
            else
              # hrmm
              it['status']
            end
          } },
          {"Name".upcase => lambda {|it| it['name']} },
          {"Count".upcase => lambda {|it| format_number(it['count']) } }
        ]
        
        if health['rabbit'].nil? || health['rabbit']['queues'].nil? || health['rabbit']['queues'].empty?
          print yellow,"No queues found.",reset,"\n\n"
        else
          print cyan
          print as_pretty_table(health['rabbit']['queues'], queue_columns, options)
        end

        if health['rabbit'].nil? || health['rabbit']['busyQueues'].nil? || health['rabbit']['busyQueues'].empty?
          # print cyan,"No busy queues found.",reset,"\n"
        else
          print_h2 "Busy Queues"
          print cyan
          print as_pretty_table(health['rabbit']['busyQueues'], queue_columns, options)
        end

        if health['rabbit'].nil? || health['rabbit']['errorQueues'].nil? || health['rabbit']['errorQueues'].empty?
          # print cyan,"No error queues found.",reset,"\n"
        else
          print_h2 "Error Queues"
          print cyan
          print as_pretty_table(health['rabbit']['errorQueues'], queue_columns, options)
        end

      end

    end


    print "\n"
    return 0
  rescue RestClient::Exception => e
    print_rest_exception(e, options)
    exit 1
  end
end
get_alarm(args) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 754
def get_alarm(args)
  options = {}
  params = {}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[id]")
    build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
    opts.footer = "Get details about a health alarm.\n[id] is required. Health Alarm ID"
  end
  optparse.parse!(args)

  if args.count != 1
    raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
  end

  connect(options)
  
  @health_interface.setopts(options)
  if options[:dry_run]
    print_dry_run @health_interface.dry.get_alarm(args[0], params)
    return 0
  end
  json_response = @health_interface.get_alarm(args[0], params)
  render_result = render_with_format(json_response, options, 'alarm')
  return 0 if render_result
  if json_response['alarm'].nil?
    print_red_alert "Alarm not found by id #{args[0]}"
    return 1
  end
  print_h1 "Alarm Details"
  print cyan
  alarm_columns = [
    {"ID" => lambda {|alarm| alarm['id'] } },
    {"Status" => lambda {|alarm| format_health_status(alarm['status']) } },
    {"Resource" => lambda {|alarm| alarm['resourceName'] || alarm['refName'] } },
    {"Info" => lambda {|alarm| alarm['name'] } },
    {"Active" => lambda {|alarm| format_boolean(alarm['active']) } },
    {"Start Date" => lambda {|alarm| format_local_dt(alarm['startDate']) } },
    {"Duration" => lambda {|alarm| format_duration(alarm['startDate'], alarm['acknowledgedDate'] || Time.now) } },
    {"Acknowledged Date" => lambda {|alarm| format_local_dt(alarm['acknowledgedDate']) } },
    {"Acknowledged" => lambda {|alarm| format_boolean(alarm['acknowledged']) } }
  ]
  print_description_list(alarm_columns, json_response['alarm'])
  print reset,"\n"
  return 0

end
handle(args) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 14
def handle(args)
  handle_subcommand(args)
end
logs(args) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 504
def logs(args)
  options = {}
  params = {}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage()
    opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
      params['level'] = params['level'] ? [params['level'], val].flatten : [val]
    end
    opts.on('--start TIMESTAMP','--start TIMESTAMP', "Start date timestamp in standard iso8601 format. Default is 24 hours ago.") do |val|
      params['startDate'] = val # parse_time(val).utc.iso8601
    end
    opts.on('--end TIMESTAMP','--end TIMESTAMP', "End date timestamp in standard iso8601 format. Default is now.") do |val|
      params['endDate'] = val # parse_time(val).utc.iso8601
    end
    opts.on('-t', '--table', "Format output as a table.") do
      options[:table] = true
    end
    opts.on('-a', '--all', "Display all details: entire message." ) do
      options[:details] = true
    end
    build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
    opts.footer = "List health logs. These are the logs of the morpheus appliance itself."
  end
  optparse.parse!(args)
  if args.count > 0
    options[:phrase] = args.join(" ")
  end
  connect(options)
  begin
    # params['startDate'] = start_date.utc.iso8601 if start_date
    # params['endDate'] = end_date.utc.iso8601 if end_date
    # params['startMs'] = (start_date.to_i * 1000) if start_date
    # params['endMs'] = (end_date.to_i * 1000) if end_date
    params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
    params.merge!(parse_list_options(options))
    @health_interface.setopts(options)
    if options[:dry_run]
      print_dry_run @health_interface.dry.logs(params)
      return 0
    end
    json_response = @health_interface.logs(params)
    render_result = json_response['logs'] ? render_with_format(json_response, options, 'logs') : render_with_format(json_response, options, 'data')
    return 0 if render_result
    logs = json_response['data'] || json_response['logs']
    title = "Morpheus Health Logs"
    subtitles = []
    if params['level']
      subtitles << "Level: #{[params['level']].flatten.join(',')}"
    end
    if params['startDate']
      subtitles << "Start: #{params['startDate']}"
    end
    if params['endDate']
      subtitles << "End: #{params['endDate']}"
    end
    subtitles += parse_list_subtitles(options)
    print_h1 title, subtitles
    
    if logs.empty?
      print "#{cyan}No logs found.#{reset}\n"
    else
      print format_log_records(logs, options, false)
      print_results_pagination(json_response)
    end
    print reset,"\n"
    return 0
  rescue RestClient::Exception => e
    print_rest_exception(e, options)
    exit 1
  end
end
unacknowledge_alarms(args) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 880
def unacknowledge_alarms(args)
  options = {}
  params = {acknowledged:false}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[alarm] [options]")
    # opts.on('-a', '--all', "Acknowledge all open alarms. This can be used instead of passing specific alarms.") do
    #   params['all'] = true
    # end
    build_common_options(opts, options, [:payload, :options, :json, :dry_run, :remote])
    opts.footer = "Unacknowledge health alarm(s).\n[alarm] is required. Alarm ID, supports multiple arguments."
  end
  optparse.parse!(args)

  if params['all']
    # updating all
    if args.count > 0
      raise_command_error "wrong number of arguments, --all option expects 0 and got (#{args.count}) #{args}\n#{optparse}"
    end
  else
    # updating 1-N ids
    if args.count < 0
      raise_command_error "wrong number of arguments, expected 1-N and got (#{args.count}) #{args}\n#{optparse}"
    end
    params['ids'] = args.collect {|arg| arg }
  end
  connect(options)
  begin
    # validate ids
    if params['ids']
      parsed_id_list = []
      params['ids'].each do |alarm_id|
        alarm = find_health_alarm_by_name_or_id(alarm_id)
        if alarm.nil?
          # print_red_alert "Alarm not found by id #{args[0]}"
          return 1
        end
        parsed_id_list << alarm['id']
      end
      params['ids'] = parsed_id_list.uniq
    end

    # construct payload
    passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
    payload = nil
    if options[:payload]
      payload = options[:payload]
      payload.deep_merge!(passed_options) unless passed_options.empty?
    else
      payload = {}
      # allow arbitrary -O options
      payload.deep_merge!(passed_options) unless passed_options.empty?
    end
    id_list = params['ids'] || []
    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)}?"
    unless options[:yes] || Morpheus::Cli::OptionTypes.confirm(confirm_msg)
      return 9, "aborted command"
    end
    @health_interface.setopts(options)
    if options[:dry_run]
      print_dry_run @health_interface.dry.acknowledge_alarms(params, payload)
      return
    end
    json_response = @health_interface.acknowledge_alarms(params, payload)
    render_result = render_with_format(json_response, options)
    exit_code = 0 # json_response['success'] == true ? 0 : 1
    return exit_code if render_result

    if params['all']
      print_green_success "Acknowledged all alarms"
    else
      print_green_success "Acknowledged #{id_list.size == 1 ? 'alarm' : 'alarms'} #{anded_list(id_list)}"
    end
    return exit_code
  rescue RestClient::Exception => e
    print_rest_exception(e, options)
    exit 1
  end
end

Private Instance Methods

find_health_alarm_by_id(id) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 968
def find_health_alarm_by_id(id)
  raise "#{self.class} has not defined @health_interface" if @health_interface.nil?
  begin
    json_response = @health_interface.get_alarm(id)
    return json_response['alarm']
  rescue RestClient::Exception => e
    if e.response && e.response.code == 404
      print_red_alert "Alarm not found by id #{id}"
    else
      raise e
    end
  end
end
find_health_alarm_by_name(name) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 982
def find_health_alarm_by_name(name)
  raise "#{self.class} has not defined @health_interface" if @health_interface.nil?
  alarms = @health_interface.list_alarms({name: name.to_s})['alarms']
  if alarm.empty?
    print_red_alert "Alarm not found by name #{name}"
    return nil
  elsif alarms.size > 1
    print_red_alert "#{alarms.size} alarms found by name #{name}"
    print as_pretty_table(alarms, [:id,:name], {color:red})
    print reset,"\n"
    return nil
  else
    return alarms[0]
  end
end
find_health_alarm_by_name_or_id(val) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 960
def find_health_alarm_by_name_or_id(val)
  if val.to_s =~ /\A\d{1,}\Z/
    return find_health_alarm_by_id(val)
  else
    return find_health_alarm_by_name(val)
  end
end
format_health_status(val, return_color=cyan) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 998
def format_health_status(val, return_color=cyan)
  out = ""
  status_string = val.to_s.downcase
  if(status_string)
    if(status_string == 'ok' || status_string == 'running')
      out << "#{green}#{status_string.upcase}#{return_color}"
    elsif(status_string == 'error' || status_string == 'offline')
      out << "#{red}#{status_string.upcase}#{return_color}"
    elsif status_string == 'syncing'
      out << "#{cyan}#{status_string.upcase}#{return_color}"
    else
      out << "#{yellow}#{status_string.upcase}#{return_color}"
    end
  end
  out
end
format_index_health(val, return_color=cyan) click to toggle source

this is for weird elastic status values that are actually colors

# File lib/morpheus/cli/commands/health_command.rb, line 1016
def format_index_health(val, return_color=cyan)
  # hrmm
  status_string = val.to_s.downcase # || 'green'
  if status_string == 'warning' || status_string == 'yellow'
    "#{yellow}WARNING#{cyan}"
  elsif status_string == 'error' || status_string == 'red'
    "#{red}ERROR#{cyan}"
  elsif status_string == 'ok' || status_string == 'green'
    "#{green}OK#{cyan}"
  else
    # hrmm
    it['status']
  end
end
format_queue_status(val, return_color=cyan) click to toggle source
# File lib/morpheus/cli/commands/health_command.rb, line 1031
def format_queue_status(val, return_color=cyan)
  # hrmm
  status_string = val.to_s.downcase # || 'ok'
  if status_string == 'warning'
    "#{yellow}WARNING#{cyan}"
  elsif status_string == 'error'
    "#{red}ERROR#{cyan}"
  elsif status_string == 'ok'
    "#{green}OK#{cyan}"
  else
    # hrmm
    it['status']
  end
end