class Utils

Public Class Methods

format_host_output(hosts) click to toggle source
# File lib/vmfloaty/utils.rb, line 77
def self.format_host_output(hosts)
  hosts.flat_map do |os, names|
    # Assume hosts are stored in Arrays and ignore everything else
    names.map { |name| "- #{name} (#{os})" } if names.is_a? Array
  end.join("\n")
end
generate_os_hash(os_args) click to toggle source
# File lib/vmfloaty/utils.rb, line 84
def self.generate_os_hash(os_args)
  # expects args to look like:
  # ["centos", "debian=5", "windows=1"]

  # Build vm hash where
  #
  #  [operating_system_type1 -> total,
  #   operating_system_type2 -> total,
  #   ...]
  os_types = {}
  os_args.each do |arg|
    os_arr = arg.split('=')
    os_types[os_arr[0]] = os_arr.size == 1 ? 1 : os_arr[1].to_i
  end
  os_types
end
get_host_data(verbose, service, hostnames = []) { |host_data result| ... } click to toggle source
# File lib/vmfloaty/utils.rb, line 178
def self.get_host_data(verbose, service, hostnames = [])
  result = {}
  hostnames = [hostnames] unless hostnames.is_a? Array
  hostnames.each do |hostname|
    response = service.query(verbose, hostname)
    host_data = response[hostname]
    if block_given?
      yield host_data result
    else
      case service.type
      when 'ABS'
        # For ABS, 'hostname' variable is the jobID
        result[hostname] = host_data if host_data['state'] == 'allocated' || host_data['state'] == 'filled'
      when 'Pooler'
        result[hostname] = host_data
      when 'NonstandardPooler'
        result[hostname] = host_data
      else
        raise "Invalid service type #{service.type}"
      end
    end
  rescue StandardError => e
    FloatyLogger.error("Something went wrong while trying to gather information on #{hostname}:")
    FloatyLogger.error(e)
  end
  result
end
get_service_config(config, options) click to toggle source
# File lib/vmfloaty/utils.rb, line 274
def self.get_service_config(config, options)
  # The top-level url, user, and token values in the config file are treated as defaults
  service_config = {
    'url' => config['url'],
    'user' => config['user'],
    'token' => config['token'],
    'vmpooler_fallback' => config['vmpooler_fallback'],
    'type' => config['type'] || 'vmpooler'
  }

  if config['services']
    if options.service.nil?
      # If the user did not specify a service name at the command line, but configured services do exist,
      # use the first configured service in the list by default.
      _, values = config['services'].first
      service_config.merge! values
    else
      # If the user provided a service name at the command line, use that service if posible, or fail
      unless config['services'][options.service]
        raise ArgumentError,
              "Could not find a configured service named '#{options.service}' in ~/.vmfloaty.yml"
      end

      # If the service is configured but some values are missing, use the top-level defaults to fill them in
      service_config.merge! config['services'][options.service]
    end
  # No config file but service is declared on command line
  elsif !config['services'] && options.service
    service_config['type'] = options.service
  end

  # Prioritize an explicitly specified url, user, or token if the user provided one
  service_config['priority'] = options.priority unless options.priority.nil?
  service_config['url'] = options.url unless options.url.nil?
  service_config['token'] = options.token unless options.token.nil?
  service_config['user'] = options.user unless options.user.nil?

  service_config
end
get_service_object(type = '') click to toggle source
# File lib/vmfloaty/utils.rb, line 259
def self.get_service_object(type = '')
  abs_strings = %w[abs alwaysbescheduling always_be_scheduling]
  nspooler_strings = %w[ns nspooler nonstandard nonstandard_pooler]
  vmpooler_strings = %w[vmpooler]
  if abs_strings.include? type.downcase
    ABS
  elsif nspooler_strings.include? type.downcase
    NonstandardPooler
  elsif vmpooler_strings.include? type.downcase
    Pooler
  else
    Pooler
  end
end
get_vmpooler_service_config(vmpooler_fallback) click to toggle source

This method gets the vmpooler service configured in ~/.vmfloaty

# File lib/vmfloaty/utils.rb, line 315
def self.get_vmpooler_service_config(vmpooler_fallback)
  config = Conf.read_config
  # The top-level url, user, and token values in the config file are treated as defaults
  service_config = {
    'url' => config['url'],
    'user' => config['user'],
    'token' => config['token'],
    'type' => 'vmpooler'
  }

  # at a minimum, the url needs to be configured
  if config['services'] && config['services'][vmpooler_fallback] && config['services'][vmpooler_fallback]['url']
    # If the service is configured but some values are missing, use the top-level defaults to fill them in
    service_config.merge! config['services'][vmpooler_fallback]
  elsif vmpooler_fallback.nil?
    raise ArgumentError,
          "The abs service should have a key named 'vmpooler_fallback' in ~/.vmfloaty.yml with a value that points to a vmpooler service name use this format:\nservices:\n  myabs:\n    url: 'http://abs.com'\n    user: 'superman'\n    token: 'kryptonite'\n    vmpooler_fallback: 'myvmpooler'\n  myvmpooler:\n    url: 'http://vmpooler.com'\n    user: 'superman'\n    token: 'kryptonite'"
  else
    raise ArgumentError,
          "Could not find a configured service named '#{vmpooler_fallback}' in ~/.vmfloaty.yml use this format:\nservices:\n  #{vmpooler_fallback}:\n    url: 'http://vmpooler.com'\n    user: 'superman'\n    token: 'kryptonite'"
  end

  service_config
end
pretty_print_hosts(verbose, service, hostnames = [], print_to_stderr = false, indent = 0) click to toggle source
# File lib/vmfloaty/utils.rb, line 124
def self.pretty_print_hosts(verbose, service, hostnames = [], print_to_stderr = false, indent = 0)
  output_target = print_to_stderr ? $stderr : $stdout

  fetched_data = get_host_data(verbose, service, hostnames)
  fetched_data.each do |hostname, host_data|
    case service.type
    when 'ABS'
      # For ABS, 'hostname' variable is the jobID
      #
      # Create a vmpooler service to query each hostname there so as to get the metadata too

      output_target.puts "- [JobID:#{host_data['request']['job']['id']}] <#{host_data['state']}>"
      host_data['allocated_resources'].each do |allocated_resources, _i|
        if (allocated_resources['engine'] == 'vmpooler' || allocated_resources['engine'] == 'ondemand') && service.config['vmpooler_fallback']
          vmpooler_service = service.clone
          vmpooler_service.silent = true
          vmpooler_service.maybe_use_vmpooler
          pretty_print_hosts(verbose, vmpooler_service, allocated_resources['hostname'].split('.')[0],
                             print_to_stderr, indent + 2)
        else
          # TODO: we could add more specific metadata for the other services, nspooler and aws
          output_target.puts "  - #{allocated_resources['hostname']} (#{allocated_resources['type']})"
        end
      end
    when 'Pooler'
      tag_pairs = []
      tag_pairs = host_data['tags'].map { |key, value| "#{key}: #{value}" } unless host_data['tags'].nil?
      duration = "#{host_data['running']}/#{host_data['lifetime']} hours"
      metadata = [host_data['state'], host_data['template'], duration, *tag_pairs]
      # For backwards compatibility with vmpooler api v1
      message =
        if host_data['domain']
          "- #{hostname}.#{host_data['domain']} (#{metadata.join(', ')})".gsub(/^/, ' ' * indent)
        else
          "- #{host_data['fqdn']} (#{metadata.join(', ')})".gsub(/^/, ' ' * indent)
        end

      if host_data['state'] && host_data['state'] == 'destroyed'
        output_target.puts "- DESTROYED #{hostname}.#{host_data['domain']}".gsub(/^/, ' ' * indent)
      else
        output_target.puts message
      end
    when 'NonstandardPooler'
      line = "- #{host_data['fqdn']} (#{host_data['os_triple']}"
      line += ", #{host_data['hours_left_on_reservation']}h remaining"
      line += ", reason: #{host_data['reserved_for_reason']}" unless  host_data['reserved_for_reason'].nil? || host_data['reserved_for_reason'].empty?
      line += ')'
      output_target.puts line
    else
      raise "Invalid service type #{service.type}"
    end
  end
end
pretty_print_status(verbose, service) click to toggle source
# File lib/vmfloaty/utils.rb, line 206
def self.pretty_print_status(verbose, service)
  status_response = service.status(verbose)

  case service.type
  when 'Pooler'
    message = status_response['status']['message']
    pools = status_response['pools']
    pools.select! { |_, pool| pool['ready'] < pool['max'] } unless verbose

    width = pools.keys.map(&:length).max
    pools.each do |name, pool|
      max = pool['max']
      ready = pool['ready']
      pending = pool['pending']
      missing = max - ready - pending
      char = 'o'
      puts "#{name.ljust(width)} #{(char * ready)}#{(char * pending)}#{(char * missing)}"
    rescue StandardError => e
      FloatyLogger.error "#{name.ljust(width)} #{e}"
    end
    puts message
  when 'NonstandardPooler'
    pools = status_response
    pools.delete 'ok'
    pools.select! { |_, pool| pool['available_hosts'] < pool['total_hosts'] } unless verbose

    width = pools.keys.map(&:length).max
    pools.each do |name, pool|
      max = pool['total_hosts']
      ready = pool['available_hosts']
      pending = pool['pending'] || 0 # not available for nspooler
      missing = max - ready - pending
      char = 'o'
      puts "#{name.ljust(width)} #{(char * ready)}#{(char * pending)}#{(char * missing)}"
    rescue StandardError => e
      FloatyLogger.error "#{name.ljust(width)} #{e}"
    end
  when 'ABS'
    FloatyLogger.error 'ABS Not OK' unless status_response
    puts 'ABS is OK' if status_response
  else
    raise "Invalid service type #{service.type}"
  end
end
print_fqdn_for_host(service, hostname, host_data) click to toggle source
standardize_hostnames(response_body) click to toggle source

TODO: Takes the json response body from an HTTP GET request and “pretty prints” it

# File lib/vmfloaty/utils.rb, line 11
def self.standardize_hostnames(response_body)
  # vmpooler api v1 response body example when `floaty get` arguments are `ubuntu-1610-x86_64=2 centos-7-x86_64`:
  # {
  #   "ok": true,
  #   "domain": "delivery.mycompany.net",
  #   "ubuntu-1610-x86_64": {
  #     "hostname": ["gdoy8q3nckuob0i", "ctnktsd0u11p9tm"]
  #   },
  #   "centos-7-x86_64": {
  #     "hostname": "dlgietfmgeegry2"
  #   }
  # }

  # vmpooler api v2 response body example when `floaty get` arguments are `ubuntu-1610-x86_64=2 centos-7-x86_64`:
  # {
  #   "ok": true,
  #   "ubuntu-1610-x86_64": {
  #     "hostname": ["gdoy8q3nckuob0i.pooler.example.com", "ctnktsd0u11p9tm.pooler.example.com"]
  #   },
  #   "centos-7-x86_64": {
  #     "hostname": "dlgietfmgeegry2.pooler.example.com"
  #   }
  # }

  # nonstandard pooler response body example when `floaty get` arguments are `solaris-11-sparc=2 ubuntu-16.04-power8`:
  # {
  #   "ok": true,
  #   "solaris-10-sparc": {
  #     "hostname": ["sol10-10.delivery.mycompany.net", "sol10-11.delivery.mycompany.net"]
  #   },
  #   "ubuntu-16.04-power8": {
  #     "hostname": "power8-ubuntu1604-6.delivery.mycompany.net"
  #   }
  # }

  # abs pooler response body example when `floaty get` arguments are :
  # {
  #   "hostname"=>"thin-soutane.delivery.puppetlabs.net",
  #   "type"=>"centos-7.2-tmpfs-x86_64",
  #   "engine"=>"vmpooler"
  # }

  unless response_body.delete('ok')
    raise ArgumentError,
          "Bad GET response passed to format_hosts: #{response_body.to_json}"
  end

  # vmpooler reports the domain separately from the hostname
  domain = response_body.delete('domain')

  result = {}

  # ABS has a job_id associated with hosts so pass that along
  abs_job_id = response_body.delete('job_id')
  result['job_id'] = abs_job_id unless abs_job_id.nil?

  filtered_response_body = response_body.reject { |key, _| %w[request_id ready].include?(key) }
  filtered_response_body.each do |os, value|
    hostnames = Array(value['hostname'])
    hostnames.map! { |host| "#{host}.#{domain}" } if domain
    result[os] = hostnames
  end

  result
end
strip_heredoc(str) click to toggle source

Adapted from ActiveSupport

# File lib/vmfloaty/utils.rb, line 252
def self.strip_heredoc(str)
  min_indent = str.scan(/^[ \t]*(?=\S)/).min
  min_indent_size = min_indent.nil? ? 0 : min_indent.size

  str.gsub(/^[ \t]{#{min_indent_size}}/, '')
end