[PATCH conductor 01/15] Add base classes and modules to support provider selection algorithms

Jozef Zigmund jzigmund at redhat.com
Thu Aug 2 02:17:21 UTC 2012


On Sun, 2012-07-22 at 12:16 +0200, ifarkas at redhat.com wrote:
> From: Imre Farkas <ifarkas at redhat.com>
> 
> ---
>  src/lib/provider_selection.rb                    |    4 +
>  src/lib/provider_selection/base.rb               |   92 ++++++++++++++++++++++
>  src/lib/provider_selection/chainable_strategy.rb |   16 ++++
>  src/lib/provider_selection/match.rb              |   15 ++++
>  src/lib/provider_selection/priority_group.rb     |   57 ++++++++++++++
>  src/lib/provider_selection/rank.rb               |   19 +++++
>  src/lib/provider_selection/strategy.rb           |   25 ++++++
>  7 files changed, 228 insertions(+)
>  create mode 100644 src/lib/provider_selection.rb
>  create mode 100644 src/lib/provider_selection/base.rb
>  create mode 100644 src/lib/provider_selection/chainable_strategy.rb
>  create mode 100644 src/lib/provider_selection/match.rb
>  create mode 100644 src/lib/provider_selection/priority_group.rb
>  create mode 100644 src/lib/provider_selection/rank.rb
>  create mode 100644 src/lib/provider_selection/strategy.rb
> 
> diff --git a/src/lib/provider_selection.rb b/src/lib/provider_selection.rb
> new file mode 100644
> index 0000000..eff3c25
> --- /dev/null
> +++ b/src/lib/provider_selection.rb
> @@ -0,0 +1,4 @@
> +# Require files in lib dir
> +['match', 'priority_group', 'rank', 'strategy', 'base'].each do |file_name|
> +  require File.join(File.dirname(__FILE__), 'provider_selection', file_name)
> +end
> diff --git a/src/lib/provider_selection/base.rb b/src/lib/provider_selection/base.rb
> new file mode 100644
> index 0000000..d2525fb
> --- /dev/null
> +++ b/src/lib/provider_selection/base.rb
> @@ -0,0 +1,92 @@
> +module ProviderSelection
> +
> +  class Base
> +
> +    @strategies = []
> +    @view_paths = []
> +
> +    class << self
> +      attr_reader :strategies
> +      attr_reader :view_paths
> +
> +      def register_strategy(name, lib_path, options = {})
> +        require lib_path

^ this can be potentially security hole, where the loaded lib can get
access to the whole environment. For now, put here TODO with notice of security issue

> +        new_klass = "provider_selection/strategies/#{name.to_s}".classify.constantize
> +        @strategies << Strategy.new(name, new_klass)
> +      end
> +
> +      def find_strategy_by_name(name)
> +        @strategies.detect{ |strategy| strategy.name == name }
> +      end
> +
> +      def add_view_path(view_path)
> +        @view_paths << view_path
> +      end
> +    end
> +
> +    attr_reader :instances
> +
> +    def initialize(instances)
> +      @instances = instances
> +      @strategy_chain = self
> +    end
> +
> +    def calculate
> +      rank = Rank.new(@instances)
> +
> +      provider_accounts = find_common_provider_accounts.map do |provider_account|
> +        provider_account if provider_account.quota.can_start?(@instances)
> +      end.uniq.compact
> +
> +      default_priority_group = PriorityGroup.new
> +      rank.priority_groups << default_priority_group
> +
> +      provider_accounts.uniq.each do |provider_account|
> +        default_priority_group.matches << Match.new(provider_account)
> +      end
> +
> +      rank
> +    end
> +
> +    def next_match
> +      rank = @strategy_chain.calculate
> +
> +      rank.ordered_priority_groups.each do |priority_group|
> +        random_match = priority_group.get_random_match
> +        return random_match if random_match.present?
> +      end
> +
> +      nil
> +    end
> +
> +    def chain_strategy(name, options = {})
> +      @strategy_chain = "provider_selection/strategies/#{name.to_s}".classify.constantize.new(@strategy_chain, options)
> +    end
> +
> +    private
> +
> +    def find_common_provider_accounts
> +      instance_matches_grouped_by_instances = @instances.map do |instance|
> +        matches, errors = instance.matches
> +        matches
> +      end
> +
> +      result = []
> +      if instance_matches_grouped_by_instances.any?
> +          result += instance_matches_grouped_by_instances.first.map(&:provider_account)
> +      end
> +
> +      instance_matches_grouped_by_instances.each_with_index do |instance_matches, index|
> +        if index == 0
> +          result += instance_matches.map(&:provider_account)
> +        else
> +          result &= instance_matches.map(&:provider_account)
> +        end
> +      end
> +
> +      result
> +    end
> +
> +  end
> +
> +end
> diff --git a/src/lib/provider_selection/chainable_strategy.rb b/src/lib/provider_selection/chainable_strategy.rb
> new file mode 100644
> index 0000000..12377de
> --- /dev/null
> +++ b/src/lib/provider_selection/chainable_strategy.rb
> @@ -0,0 +1,16 @@
> +module ProviderSelection
> +
> +  module ChainableStrategy
> +
> +    def initialize(strategies, options)
> +      @strategies = strategies
> +      @options = options
> +    end
> +
> +    def method_missing(method, *args)
> +      args.empty? ? @strategies.send(method) : @strategies.send(method, args)
> +    end
> +
> +  end
> +
> +end
> diff --git a/src/lib/provider_selection/match.rb b/src/lib/provider_selection/match.rb
> new file mode 100644
> index 0000000..5b4c5ed
> --- /dev/null
> +++ b/src/lib/provider_selection/match.rb
> @@ -0,0 +1,15 @@
> +module ProviderSelection
> +
> +  class Match
> +
> +    attr_accessor :score
> +    attr_accessor :provider_account
> +
> +    def initialize(provider_account, score = nil)
> +      @provider_account = provider_account
> +      @score = score
> +    end
> +
> +  end
> +
> +end
> \ No newline at end of file
> diff --git a/src/lib/provider_selection/priority_group.rb b/src/lib/provider_selection/priority_group.rb
> new file mode 100644
> index 0000000..c884ca2
> --- /dev/null
> +++ b/src/lib/provider_selection/priority_group.rb
> @@ -0,0 +1,57 @@
> +module ProviderSelection
> +
> +  class PriorityGroup
> +
> +    attr_accessor :matches
> +    attr_accessor :score
> +
> +    def initialize(score = nil)
> +      @matches = []
> +      @score = score
> +    end
> +
> +    def self.create_from_active_record(obj, allowed_provider_accounts)
> +      return nil if allowed_provider_accounts.nil? || allowed_provider_accounts.empty?
> +
> +      priority_group = PriorityGroup.new(obj.score)
> +
> +      possible_provider_accounts = []
> +      obj.provider_priority_group_elements.each do |element|
> +        if element.value.is_a?(Provider)
> +          possible_provider_accounts += element.value.provider_accounts
> +        elsif element.value.is_a?(ProviderAccount)
> +          possible_provider_accounts << element.value
> +        end
> +      end
> +
> +      possible_provider_accounts &= allowed_provider_accounts
> +
> +      return nil if possible_provider_accounts.empty?
> +
> +      possible_provider_accounts.each do |provider_account|
> +        priority_group.matches << Match.new(provider_account)
> +      end
> +
> +      priority_group
> +    end
> +
> +    # TODO: extend the implementation to be able to work with negative scores
> +    def get_random_match
> +      # In order to work with 0 scores adding the size of the @matches array
> +      match_sum_score = @matches.sum{ |match| match.score.to_i } + @matches.length
> +      random_value = rand(match_sum_score)
> +
> +      sum = 0
> +      matches.each_with_index do |match, index|
> +        sum += match.score.to_i
> +        if random_value < sum + index + 1
> +          return match
> +        end
> +      end
> +
> +      nil
> +    end
> +
> +  end
> +
> +end
> diff --git a/src/lib/provider_selection/rank.rb b/src/lib/provider_selection/rank.rb
> new file mode 100644
> index 0000000..643dd71
> --- /dev/null
> +++ b/src/lib/provider_selection/rank.rb
> @@ -0,0 +1,19 @@
> +module ProviderSelection
> +
> +  class Rank
> +
> +    attr_accessor :priority_groups
> +    attr_reader :instances
> +
> +    def initialize(instances)
> +      @priority_groups = []
> +      @instances = instances
> +    end
> +
> +    def ordered_priority_groups
> +      @priority_groups.sort_by{ |priority_group| priority_group.score.to_i }.reverse
> +    end
> +
> +  end
> +
> +end
> \ No newline at end of file
> diff --git a/src/lib/provider_selection/strategy.rb b/src/lib/provider_selection/strategy.rb
> new file mode 100644
> index 0000000..4e0fa6a
> --- /dev/null
> +++ b/src/lib/provider_selection/strategy.rb
> @@ -0,0 +1,25 @@
> +module ProviderSelection
> +
> +  class Strategy
> +
> +    attr_reader :name
> +    attr_reader :klass
> +    attr_reader :include_in_listings
> +
> +    def initialize(name, klass)
> +      @name = name
> +      @klass = klass
> +      @include_in_listing = if klass.respond_to?('include_in_listings?')
> +        klass.include_in_listings?
> +      else
> +        true
> +      end
> +    end
> +
> +    def translated_name
> +      I18n.t("strategies.#{@name}.name")
> +    end
> +
> +  end
> +
> +end
> \ No newline at end of file

Please consider using 'require' in a method. Also I'm
not sure, but I think that RH Copyright's text should be applied on
those lib files.

-- 
Jozef Zigmund







More information about the aeolus-devel mailing list