crowbar/crowbar-core

View on GitHub
bin/crowbar_node_state

Summary

Maintainability
Test Coverage
#!/usr/bin/env ruby
#
# Copyright 2011-2013, Dell
# Copyright 2013-2014, SUSE LINUX Products GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require "rubygems"
require "net/http"
require "net/http/digest_auth"
require "uri"
require "inifile"
require "json"
require "getoptlong"

@debug = false
@hostname = ENV["CROWBAR_IP"]
@hostname = "127.0.0.1" unless @hostname
@port = 80
@headers = {
  "Accept" => "application/json",
  "Content-Type" => "application/json"
}
@data = ""
@allow_zero_args = false
@timeout = 500
@barclamp="machines"
@noready = false

#
# Parsing options can be added by adding to this list before calling opt_parse
#
@options = [
    [["--help", "-h", GetoptLong::NO_ARGUMENT], "--help or -h - help"],
    [["--username", "-U", GetoptLong::REQUIRED_ARGUMENT], "--username <username> or -U <username>  - specifies the username to use"],
    [["--password", "-P", GetoptLong::REQUIRED_ARGUMENT], "--password <password> or -P <password>  - specifies the password to use"],
    [["--hostname", "-n", GetoptLong::REQUIRED_ARGUMENT], "--hostname <name or ip> or -n <name or ip>  - specifies the destination server"],
    [["--port", "-p", GetoptLong::REQUIRED_ARGUMENT], "--port <port> or -p <port> - specifies the destination server port"],
    [["--debug", "-d", GetoptLong::NO_ARGUMENT], "--debug or -d - turns on debugging information"],
    [["--timeout", GetoptLong::REQUIRED_ARGUMENT], "--timeout <seconds> - timeout in seconds for read http reads"],
    [["--no-ready", GetoptLong::NO_ARGUMENT], "--no-ready - ignore ready nodes"]
]

@commands = {
  "status" => ["status", "status - show status of nodes"]
}

def print_commands(cmds, spacer = "  ")
  cmds.each do |key, command|
    puts "#{spacer}#{command[1]}"
    print_commands(command[2], "  #{spacer}") if command[0] =~ /run_sub_command\(/
  end
end

def usage (rc)
  puts "Usage: crowbar node_state  [options] <subcommands>"
  @options.each do |options|
    puts "  #{options[1]}"
  end
  print_commands(@commands.sort)
  exit rc
end

def help
  usage 0
end

def debug(msg)
  puts msg if @debug
end

def authenticate(req, uri, data = nil, limit = 10)
  uri.user = @username
  uri.password = @password

  h = Net::HTTP.new uri.host, uri.port
  if uri.instance_of? URI::HTTPS
    h.verify_mode = OpenSSL::SSL::VERIFY_NONE
    h.use_ssl = true
  end
  h.read_timeout = @timeout

  r = req.new uri.request_uri, @headers
  r.body = data if data

  res = h.request r

  debug "(r) hostname: #{uri.host}:#{uri.port}"
  debug "(r) request: #{uri.path}"
  debug "(r) method: #{req::METHOD}"
  debug "(r) return code: #{res.code}"
  debug "(r) return body: #{res.body}"
  res.each_header do |h, v|
    debug "(r) return #{h}: #{v}"
  end

  if res.header["location"]
    uri = URI.parse(res.header["location"])
    return authenticate(req, uri, data, limit)
  end

  if res["www-authenticate"]
    digest = Net::HTTP::DigestAuth.new
    auth = digest.auth_header uri, res["www-authenticate"], req::METHOD

    r = req.new uri.request_uri, @headers
    r.body = data if data
    r.add_field "Authorization", auth

    res = h.request r

    debug "(a) hostname: #{uri.host}:#{uri.port}"
    debug "(a) request: #{uri.path}"
    debug "(a) method: #{req::METHOD}"
    debug "(a) return code: #{res.code}"
    debug "(a) return body: #{res.body}"
    res.each_header do |h, v|
      debug "(a) return #{h}: #{v}"
    end
  end

  res
end

def get_json(path)
  uri = URI.parse("http://#{@hostname}:#{@port}/nodes#{path}")
  res = authenticate(Net::HTTP::Get,uri)

  puts "DEBUG: (g) hostname: #{uri.host}:#{uri.port}" if @debug
  puts "DEBUG: (g) request: #{uri.path}" if @debug
  puts "DEBUG: (g) return code: #{res.code}" if @debug
  puts "DEBUG: (g) return body: #{res.body}" if @debug

  return [res.body, res.code.to_i] if res.code.to_i != 200

  struct = JSON.parse(res.body)

  puts "DEBUG: (g) JSON parse structure = #{struct.inspect}" if @debug

  return [struct, 200]
end

def status()
  struct = get_json("/status")

  if struct[1] == 200
    string = ""
    struct[0]["nodes"].keys.sort.each do |n|
      status = struct[0]["nodes"][n]["status"]
      string += "#{n}   #{status}\n" unless @noready and status == "Ready"
    end
    ["#{string}", 0]
  elsif struct[1] == 404
    ["No current configuration for status", 1]
  else
    ["Failed to talk to service show: #{struct[1]}: #{struct[0]}", 1]
  end
end

def get_user_password
  @username = ENV["CROWBAR_USERNAME"]
  @password = ENV["CROWBAR_PASSWORD"]

  return unless @username.nil?

  [
    File.join(ENV["HOME"], ".crowbarrc"),
    File.join("/etc", "crowbarrc")
  ].each do |file|
    next unless File.exist?(file)
    begin
      site = ENV["CROWBAR_ALIAS"] || "default"
      config = IniFile.load(file).to_h[site]
      @username = config["username"]
      @password = config["password"]
    rescue IniFile::Error
      STDERR.puts "Could not parse config file \"#{file}\""
    end
  end
end

def opt_parse()
  get_user_password

  sub_options = @options.map { |x| x[0] }
  lsub_options = @options.map { |x| [x[0][0], x[2]] }
  opts = GetoptLong.new(*sub_options)

  opts.each do |opt, arg|
    case opt
      when "--help"
        usage 0
      when "--debug"
        @debug = true
      when "--no-ready"
        @noready = true
      when "--hostname"
        @hostname = arg
      when "--username"
        @username = arg
      when "--password"
        @password = arg
      when "--port"
        @port = arg.to_i
      when "--data"
        @data = arg
      when "--timeout"
        @timeout = arg
      when "--file"
        data = File.read(arg)
      else
        found = false
        lsub_options.each do |x|
          next if x[0] != opt
          eval x[1]
          found = true
        end
        usage -1 unless found
    end
  end

  if ARGV.length == 0 and !@allow_zero_args
    usage -1
  end

  if @username.nil? or @password.nil?
    STDERR.puts "Incomplete credentials, will not be able to authenticate!"
    STDERR.puts "Please create a crowbarrc configuration file, " \
        "set CROWBAR_USERNAME and CROWBAR_PASSWORD, or use -U and -P"
    exit 1
  end
end

def run_sub_command(cmds, subcmd)
  cmd = cmds[subcmd]
  usage -2 if cmd.nil?
  eval cmd[0]
end

def run_command()
  run_sub_command(@commands, ARGV.shift)
end

def main()
  opt_parse
  res = run_command
  puts res[0]
  exit res[1]
end

main