Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Resource performance fix #79

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
5 changes: 5 additions & 0 deletions lib/manageiq/api/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
require "manageiq/api/client/client"
require "manageiq/api/client/mixins/action_mixin"
require "manageiq/api/client/mixins/custom_inspect_mixin"
require "manageiq/api/client/mixins/resource_action_mixin"
require "manageiq/api/client/mixins/collection_action_mixin"
require "manageiq/api/client/mixins/queryable_mixin"

require "manageiq/api/client/api"
require "manageiq/api/client/action"
Expand All @@ -25,4 +28,6 @@
require "manageiq/api/client/product_info"
require "manageiq/api/client/resource"
require "manageiq/api/client/server_info"
require "manageiq/api/client/subcollection"
require "manageiq/api/client/subresource"
require "manageiq/api/client/version"
2 changes: 1 addition & 1 deletion lib/manageiq/api/client/authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Authentication
}.freeze

CUSTOM_INSPECT_EXCLUSIONS = [:@password].freeze
include CustomInspectMixin
include ManageIQ::API::CustomInspectMixin

def initialize(options = {})
@user, @password = fetch_credentials(options)
Expand Down
60 changes: 3 additions & 57 deletions lib/manageiq/api/client/collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ module ManageIQ
module API
class Client
class Collection
include ActionMixin
include ManageIQ::API::CollectionActionMixin
include Enumerable
include QueryRelation::Queryable
include ManageIQ::API::QueryableMixin

CUSTOM_INSPECT_EXCLUSIONS = [:@client].freeze
include CustomInspectMixin

ACTIONS_RETURNING_RESOURCES = %w(create query).freeze
include ManageIQ::API::CustomInspectMixin

attr_reader :client

Expand All @@ -25,47 +23,6 @@ def initialize(client, collection_spec)
clear_actions
end

def each(&block)
all.each(&block)
end

# find(#) returns the object
# find([#]) returns an array of the object
# find(#, #, ...) or find([#, #, ...]) returns an array of the objects
def find(*args)
request_array = args.size == 1 && args[0].kind_of?(Array)
args = args.flatten
case args.size
when 0
raise "Couldn't find resource without an 'id'"
when 1
res = limit(1).where(:id => args[0]).to_a
raise "Couldn't find resource with 'id' #{args}" if res.blank?
request_array ? res : res.first
else
raise "Multiple resource find is not supported" unless respond_to?(:query)
query(args.collect { |id| { "id" => id } })
end
end

def find_by(args)
limit(1).where(args).first
end

def pluck(*attrs)
select(*attrs).to_a.pluck(*attrs)
end

def self.subclass(name)
name = name.camelize

if const_defined?(name, false)
const_get(name, false)
else
const_set(name, Class.new(self))
end
end

def get(options = {})
options[:expand] = (String(options[:expand]).split(",") | %w(resources)).join(",")
options[:filter] = Array(options[:filter]) if options[:filter].is_a?(String)
Expand All @@ -77,17 +34,6 @@ def get(options = {})
end
end

def search(mode, options)
options[:limit] = 1 if mode == :first
result = get(parameters_from_query_relation(options))
case mode
when :first then result.first
when :last then result.last
when :all then result
else raise "Invalid mode #{mode} specified for search"
end
end

def options
@collection_options ||= CollectionOptions.new(client.options(name))
end
Expand Down
5 changes: 3 additions & 2 deletions lib/manageiq/api/client/collection_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ class CollectionOptions
attr_reader :attributes
attr_reader :virtual_attributes
attr_reader :relationships
attr_reader :subcollections
attr_reader :data

def initialize(options = {})
@attributes, @virtual_attributes, @relationships, @data =
options.values_at("attributes", "virtual_attributes", "relationships", "data")
@attributes, @virtual_attributes, @relationships, @subcollections, @data =
options.values_at("attributes", "virtual_attributes", "relationships", "subcollections", "data")
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/manageiq/api/client/mixins/action_mixin.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module ActionMixin
module ManageIQ::API::ActionMixin
extend ActiveSupport::Concern

private
Expand Down
72 changes: 72 additions & 0 deletions lib/manageiq/api/client/mixins/collection_action_mixin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
module ManageIQ::API::CollectionActionMixin
include ManageIQ::API::ActionMixin

ACTIONS_RETURNING_RESOURCES = %w(create query).freeze

def each(&block)
all.each(&block)
end

def self.included(base)
base.extend ClassMethods
end

module ClassMethods
def subclass(name)
name = name.camelize

if const_defined?(name, false)
const_get(name, false)
else
const_set(name, Class.new(self))
end
end
end

private

def exec_action(name, *args, &block)
action = find_action(name)
body = action_body(action.name, *args, &block)
bulk_request = body.key?("resources")
res = client.send(action.method, URI(action.href)) { body }
if ACTIONS_RETURNING_RESOURCES.include?(action.name) && res.key?("results")
klass = ManageIQ::API::Client::Resource.subclass(self.name)
res = results_to_objects(res["results"], klass)
res = res[0] if !bulk_request && res.size == 1
else
res = res["results"].collect { |result| action_result(result) }
end
res
end

def results_to_objects(results, klass)
results.collect do |resource_hash|
if ManageIQ::API::Client::ActionResult.an_action_result?(resource_hash)
ManageIQ::API::Client::ActionResult.new(resource_hash)
else
klass.new(self, resource_hash)
end
end
end

def action_body(action_name, *args, &block)
args = args.flatten
args = args.first if args.size == 1 && args.first.kind_of?(Hash)
args = {} if args.blank?
block_data = block ? block.call : {}
body = { "action" => action_name }
if block_data.present?
if block_data.kind_of?(Array)
body["resources"] = block_data.collect { |resource| resource.merge(args) }
elsif args.present? && args.kind_of?(Array)
body["resources"] = args.collect { |resource| resource.merge(block_data) }
else
body["resource"] = args.dup.merge!(block_data)
end
elsif args.present?
body[args.kind_of?(Array) ? "resources" : "resource"] = args
end
body
end
end
2 changes: 1 addition & 1 deletion lib/manageiq/api/client/mixins/custom_inspect_mixin.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module CustomInspectMixin
module ManageIQ::API::CustomInspectMixin
extend ActiveSupport::Concern

def inspect
Expand Down
95 changes: 95 additions & 0 deletions lib/manageiq/api/client/mixins/queryable_mixin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
module ManageIQ::API::QueryableMixin
include QueryRelation::Queryable

# find(#) returns the object
# find([#]) returns an array of the object
# find(#, #, ...) or find([#, #, ...]) returns an array of the objects
def find(*args)
request_array = args.size == 1 && args[0].kind_of?(Array)
args = args.flatten
case args.size
when 0
raise "Couldn't find resource without an 'id'"
when 1
res = limit(1).where(:id => args[0]).to_a
raise "Couldn't find resource with 'id' #{args}" if res.blank?
request_array ? res : res.first
else
raise "Multiple resource find is not supported" unless respond_to?(:query)
query(args.collect { |id| { "id" => id } })
end
end

def find_by(args)
limit(1).where(args).first
end

def pluck(*attrs)
select(*attrs).to_a.pluck(*attrs)
end

def search(mode, options)
options[:limit] = 1 if mode == :first
result = get(parameters_from_query_relation(options))
case mode
when :first then result.first
when :last then result.last
when :all then result
else raise "Invalid mode #{mode} specified for search"
end
end

private

def parameters_from_query_relation(options)
api_params = {}
[:offset, :limit].each { |opt| api_params[opt] = options[opt] if options[opt] }
api_params[:attributes] = options[:select].join(",") if options[:select].present?
if options[:where]
api_params[:filter] ||= []
api_params[:filter] += filters_from_query_relation("=", options[:where])
end
if options[:not]
api_params[:filter] ||= []
api_params[:filter] += filters_from_query_relation("!=", options[:not])
end
if options[:order]
order_parameters_from_query_relation(options[:order]).each { |param, value| api_params[param] = value }
end
api_params
end

def filters_from_query_relation(condition, option)
filters = []
option.each do |attr, values|
Array(values).each do |value|
value = "'#{value}'" if value.kind_of?(String) && !value.match(/^(NULL|nil)$/i)
filters << "#{attr}#{condition}#{value}"
end
end
filters
end

def order_parameters_from_query_relation(option)
query_relation_option =
if option.kind_of?(Array)
option.each_with_object({}) { |name, hash| hash[name] = "asc" }
else
option.dup
end

res_sort_by = []
res_sort_order = []
query_relation_option.each do |sort_attr, sort_order|
res_sort_by << sort_attr
sort_order =
case sort_order
when /^asc/i then "asc"
when /^desc/i then "desc"
else raise "Invalid sort order #{sort_order} specified for attribute #{sort_attr}"
end
res_sort_order << sort_order
end
{ :sort_by => res_sort_by.join(","), :sort_order => res_sort_order.join(",") }
end
end
25 changes: 25 additions & 0 deletions lib/manageiq/api/client/mixins/resource_action_mixin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module ManageIQ::API::ResourceActionMixin
include ManageIQ::API::ActionMixin

private

def exec_action(name, args = nil, &block)
args ||= {}
raise "Action #{name} parameters must be a hash" if !args.kind_of?(Hash)
action = find_action(name)
res = client.send(action.method, URI(action.href)) do
body = { "action" => action.name }
resource = args.dup
resource.merge!(block.call) if block
resource.present? ? body.merge("resource" => resource) : body
end
action_result(res)
end

def reload_actions
return unless attributes.key?("href")
resource_hash = client.get(attributes["href"])
@attributes = resource_hash.except("actions")
fetch_actions(resource_hash)
end
end
Loading