class ABS

Public Class Methods

all_job_resources_accounted_for(allocated_resources, hosts) click to toggle source
# File lib/vmfloaty/abs.rb, line 101
def self.all_job_resources_accounted_for(allocated_resources, hosts)
  allocated_host_list = allocated_resources.map { |ar| ar['hostname'] }
  (allocated_host_list - hosts).empty?
end
check_queue(conn, _job_id, req_obj, verbose) click to toggle source
# File lib/vmfloaty/abs.rb, line 333
def self.check_queue(conn, _job_id, req_obj, verbose)
  res = conn.post 'request', req_obj.to_json
  status = validate_queue_status_response(res.status, res.body, 'Check queue request', verbose)
  unless res.body.empty? || !valid_json?(res.body)
    res_body = JSON.parse(res.body)
    return res_body
  end
  res.body
end
delete(verbose, url, hosts, token, user) click to toggle source
# File lib/vmfloaty/abs.rb, line 106
def self.delete(verbose, url, hosts, token, user)
  # In ABS terms, this is a "returned" host.
  conn = Http.get_conn(verbose, supported_abs_url(url))
  conn.headers['X-AUTH-TOKEN'] = token if token

  FloatyLogger.info "Trying to delete hosts #{hosts}" if verbose
  requests = get_active_requests(verbose, url, user)

  jobs_to_delete = []

  ret_status = {}
  hosts.each do |host|
    ret_status[host] = {
      'ok' => false
    }
  end

  requests.each do |req_hash|
    next unless req_hash['state'] == 'allocated' || req_hash['state'] == 'filled'

    if hosts.include? req_hash['request']['job']['id']
      jobs_to_delete.push(req_hash)
      next
    end

    req_hash['allocated_resources'].each do |vm_name, _i|
      if hosts.include? vm_name['hostname']
        if all_job_resources_accounted_for(req_hash['allocated_resources'], hosts)
          ret_status[vm_name['hostname']] = {
            'ok' => true
          }
          jobs_to_delete.push(req_hash)
        else
          FloatyLogger.info "When using ABS you must delete all vms that you requested at the same time: Can't delete #{req_hash['request']['job']['id']}: #{hosts} does not include all of #{req_hash['allocated_resources']}"
        end
      end
    end
  end

  response_body = {}

  jobs_to_delete.each do |job|
    req_obj = {
      'job_id' => job['request']['job']['id'],
      'hosts' => job['allocated_resources']
    }

    FloatyLogger.info "Deleting #{req_obj}" if verbose

    return_result = conn.post 'return', req_obj.to_json
    req_obj['hosts'].each do |host|
      response_body[host['hostname']] = { 'ok' => true } if return_result.body == 'OK'
    end
  end

  response_body
end
disk(_verbose, _url, _hostname, _token, _disk) click to toggle source
# File lib/vmfloaty/abs.rb, line 380
def self.disk(_verbose, _url, _hostname, _token, _disk)
  raise NoMethodError, 'disk is not defined for ABS'
end
get_active_requests(verbose, url, user) click to toggle source
# File lib/vmfloaty/abs.rb, line 66
def self.get_active_requests(verbose, url, user)
  conn = Http.get_conn(verbose, supported_abs_url(url))
  res = conn.get 'status/queue'
  if valid_json?(res.body)
    requests = JSON.parse(res.body)
  else
    FloatyLogger.warn "Warning: couldn't parse body returned from abs/status/queue"
  end

  ret_val = []

  requests.each do |req|
    next if req == 'null'

    if valid_json?(req) # legacy ABS had another JSON string always-be-scheduling/pull/306
      req_hash = JSON.parse(req)
    elsif req.is_a?(Hash)
      req_hash = req
    else
      FloatyLogger.warn "Warning: couldn't parse request returned from abs/status/queue"
      next
    end

    begin
      next unless user == req_hash['request']['job']['user']

      ret_val.push(req_hash)
    rescue NoMethodError
      FloatyLogger.warn "Warning: couldn't parse user returned from abs/status/queue: "
    end
  end

  ret_val
end
list(verbose, url, os_filter = nil) click to toggle source

List available VMs in ABS

# File lib/vmfloaty/abs.rb, line 165
def self.list(verbose, url, os_filter = nil)
  conn = Http.get_conn(verbose, supported_abs_url(url))

  os_list = []

  res = conn.get 'status/platforms/vmpooler'
  if valid_json?(res.body)
    res_body = JSON.parse(res.body)
    if res_body.key?('vmpooler_platforms')
      os_list << '*** VMPOOLER Pools ***'
      os_list += if res_body['vmpooler_platforms'].is_a?(String)
                   JSON.parse(res_body['vmpooler_platforms']) # legacy ABS had another JSON string always-be-scheduling/pull/306
                 else
                   res_body['vmpooler_platforms']
                 end
    end
  end

  res = conn.get 'status/platforms/ondemand_vmpooler'
  if valid_json?(res.body)
    res_body = JSON.parse(res.body)
    if res_body.key?('ondemand_vmpooler_platforms') && res_body['ondemand_vmpooler_platforms'] != '[]'
      os_list << ''
      os_list << '*** VMPOOLER ONDEMAND Pools ***'
      if res_body['ondemand_vmpooler_platforms'].is_a?(String)
        os_list += JSON.parse(res_body['ondemand_vmpooler_platforms']) # legacy ABS had another JSON string always-be-scheduling/pull/306
      else
        os_list += res_body['ondemand_vmpooler_platforms']
      end
    end
  end

  res = conn.get 'status/platforms/nspooler'
  if valid_json?(res.body)
    res_body = JSON.parse(res.body)
    if res_body.key?('nspooler_platforms')
      os_list << ''
      os_list << '*** NSPOOLER Pools ***'
      os_list += if res_body['nspooler_platforms'].is_a?(String)
                   JSON.parse(res_body['nspooler_platforms']) # legacy ABS had another JSON string always-be-scheduling/pull/306
                 else
                   res_body['nspooler_platforms']
                 end
    end
  end

  res = conn.get 'status/platforms/aws'
  if valid_json?(res.body)
    res_body = JSON.parse(res.body)
    if res_body.key?('aws_platforms')
      os_list << ''
      os_list << '*** AWS Pools ***'
      os_list += if res_body['aws_platforms'].is_a?(String)
                   JSON.parse(res_body['aws_platforms']) # legacy ABS had another JSON string always-be-scheduling/pull/306
                 else
                   res_body['aws_platforms']
                 end
    end
  end

  os_list.delete 'ok'

  os_filter ? os_list.select { |i| i[/#{os_filter}/] } : os_list
end
list_active(verbose, url, _token, user) click to toggle source
# File lib/vmfloaty/abs.rb, line 53
def self.list_active(verbose, url, _token, user)
  hosts = []
  get_active_requests(verbose, url, user).each do |req_hash|
    next unless req_hash.key?('allocated_resources')

    req_hash['allocated_resources'].each do |onehost|
      hosts.push(onehost['hostname'])
    end
  end

  hosts
end
list_active_job_ids(verbose, url, user) click to toggle source
# File lib/vmfloaty/abs.rb, line 42
def self.list_active_job_ids(verbose, url, user)
  all_job_ids = []
  @active_hostnames = {}
  get_active_requests(verbose, url, user).each do |req_hash|
    @active_hostnames[req_hash['request']['job']['id']] = req_hash # full hash saved for later retrieval
    all_job_ids.push(req_hash['request']['job']['id'])
  end

  all_job_ids
end
modify(_verbose, _url, _hostname, _token, _modify_hash) click to toggle source
# File lib/vmfloaty/abs.rb, line 376
def self.modify(_verbose, _url, _hostname, _token, _modify_hash)
  raise NoMethodError, 'modify is not defined for ABS'
end
query(verbose, url, job_id) click to toggle source
# File lib/vmfloaty/abs.rb, line 359
def self.query(verbose, url, job_id)
  # return saved hostnames from the last time list_active was run
  # preventing having to query the API again.
  # This works as long as query is called after list_active
  return @active_hostnames if @active_hostnames && !@active_hostnames.empty?

  # If using the cli query job_id
  conn = Http.get_conn(verbose, supported_abs_url(url))
  queue_info_res = conn.get "status/queue/info/#{job_id}"
  if valid_json?(queue_info_res.body)
    queue_info = JSON.parse(queue_info_res.body)
  else
    FloatyLogger.warn "Could not parse the status/queue/info/#{job_id}"
  end
  queue_info
end
retrieve(verbose, os_types, token, url, user, config, _ondemand = nil, continue = nil) click to toggle source

Retrieve an OS from ABS.

# File lib/vmfloaty/abs.rb, line 231
def self.retrieve(verbose, os_types, token, url, user, config, _ondemand = nil, continue = nil)
  #
  # Contents of post must be like:
  #
  # {
  #   "resources": {
  #     "centos-7-i386": 1,
  #     "ubuntu-1404-x86_64": 2
  #   },
  #   "job": {
  #     "id": "12345",
  #     "tags": {
  #       "user": "username",
  #     }
  #   }
  # }

  conn = Http.get_conn(verbose, supported_abs_url(url))
  conn.headers['X-AUTH-TOKEN'] = token if token

  saved_job_id = if continue.nil?
                   "#{user}-#{DateTime.now.strftime('%Q')}"
                 else
                   continue
                 end

  req_obj = {
    resources: os_types,
    job: {
      id: saved_job_id,
      tags: {
        user: user
      }
    }
  }

  if config['vmpooler_fallback'] # optional and not available as cli flag
    vmpooler_config = Utils.get_vmpooler_service_config(config['vmpooler_fallback'])
    # request with this token, on behalf of this user
    req_obj[:vm_token] = vmpooler_config['token']
  end

  if config['priority']
    req_obj[:priority] = case config['priority']
                         when 'high'
                           1
                         when 'medium'
                           2
                         when 'low'
                           3
                         else
                           config['priority'].to_i
                         end
  end

  FloatyLogger.info "Posting to ABS #{req_obj.to_json}" if verbose

  # os_string = os_type.map { |os, num| Array(os) * num }.flatten.join('+')
  # raise MissingParamError, 'No operating systems provided to obtain.' if os_string.empty?
  FloatyLogger.info "Requesting VMs with job_id: #{saved_job_id} Will retry for up to an hour."
  res = conn.post 'request', req_obj.to_json

  retries = 360

  status = validate_queue_status_response(res.status, res.body, 'Initial request', verbose)

  begin
    (1..retries).each do |i|
      res_body = check_queue(conn, saved_job_id, req_obj, verbose)
      return translated(res_body, saved_job_id) if res_body.is_a?(Array) # when we get a response with hostnames

      sleep_seconds = 10 if i >= 10
      sleep_seconds = i if i < 10
      FloatyLogger.info "Waiting #{sleep_seconds}s (x#{i}) #{res_body.strip}"

      sleep(sleep_seconds)
    end
  rescue SystemExit, Interrupt
    FloatyLogger.info "\n\nFloaty interrupted, you can resume polling with\n1) `floaty get [same arguments] and adding the flag --continue #{saved_job_id}` or query the state of the queue via\n2) `floaty query #{saved_job_id}` or delete it via\n3) `floaty delete #{saved_job_id}`"
    exit 1
  end
  nil
end
revert(_verbose, _url, _hostname, _token, _snapshot_sha) click to toggle source
# File lib/vmfloaty/abs.rb, line 384
def self.revert(_verbose, _url, _hostname, _token, _snapshot_sha)
  raise NoMethodError, 'revert is not defined for ABS'
end
snapshot(_verbose, _url, _hostname, _token) click to toggle source
# File lib/vmfloaty/abs.rb, line 343
def self.snapshot(_verbose, _url, _hostname, _token)
  raise NoMethodError, "Can't snapshot with ABS, use '--service vmpooler' (even for vms checked out with ABS)"
end
status(verbose, url) click to toggle source
# File lib/vmfloaty/abs.rb, line 347
def self.status(verbose, url)
  conn = Http.get_conn(verbose, supported_abs_url(url))

  res = conn.get 'status'

  res.body == 'OK'
end
summary(_verbose, _url) click to toggle source
# File lib/vmfloaty/abs.rb, line 355
def self.summary(_verbose, _url)
  raise NoMethodError, 'summary is not defined for ABS'
end
supported_abs_url(url) click to toggle source

when missing, adds the required api/v2 in the url

# File lib/vmfloaty/abs.rb, line 413
def self.supported_abs_url(url)
  expected_ending = 'api/v2'
  unless url.include?(expected_ending)
    # add a slash if missing
    expected_ending = "/#{expected_ending}" if url[-1] != '/'
    url = "#{url}#{expected_ending}"
  end
  url
end
translated(res_body, job_id) click to toggle source

We should fix the ABS API to be more like the vmpooler or nspooler api, but for now

# File lib/vmfloaty/abs.rb, line 318
def self.translated(res_body, job_id)
  vmpooler_formatted_body = { 'job_id' => job_id }

  res_body.each do |host|
    if vmpooler_formatted_body[host['type']] && vmpooler_formatted_body[host['type']]['hostname'].instance_of?(Array)
      vmpooler_formatted_body[host['type']]['hostname'] << host['hostname']
    else
      vmpooler_formatted_body[host['type']] = { 'hostname' => [host['hostname']] }
    end
  end
  vmpooler_formatted_body['ok'] = true

  vmpooler_formatted_body
end
valid_json?(json) click to toggle source
# File lib/vmfloaty/abs.rb, line 405
def self.valid_json?(json)
  JSON.parse(json)
  true
rescue TypeError, JSON::ParserError => e
  false
end
validate_queue_status_response(status_code, body, request_name, verbose) click to toggle source

Validate the http code returned during a queue status request.

Return a success message that can be displayed if the status code is success, otherwise raise an error.

# File lib/vmfloaty/abs.rb, line 392
def self.validate_queue_status_response(status_code, body, request_name, verbose)
  case status_code
  when 200
    "#{request_name} returned success (Code 200)" if verbose
  when 202
    "#{request_name} returned accepted, processing (Code 202)" if verbose
  when 401
    raise AuthError, "HTTP #{status_code}: The token provided could not authenticate.\n#{body}"
  else
    raise "HTTP #{status_code}: #{request_name} request to ABS failed!\n#{body}"
  end
end