[PATCH conductor 01/22] Base classes and modules to support provider selection algorithms

Jozef Zigmund jzigmund at redhat.com
Mon Aug 13 18:27:14 UTC 2012


On Thu, 2012-08-02 at 14:24 +0200, ifarkas at redhat.com wrote:
> From: Imre Farkas <ifarkas at redhat.com>
> 
> ---
>  src/lib/provider_selection.rb                    |  20 ++++
>  src/lib/provider_selection/base.rb               | 121 +++++++++++++++++++++++
>  src/lib/provider_selection/chainable_strategy.rb |  44 +++++++++
>  src/lib/provider_selection/match.rb              |  50 ++++++++++
>  src/lib/provider_selection/priority_group.rb     |  71 +++++++++++++
>  src/lib/provider_selection/rank.rb               |  54 ++++++++++
>  src/lib/provider_selection/strategy.rb           |  35 +++++++
>  7 files changed, 395 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..375c9dc
> --- /dev/null
> +++ b/src/lib/provider_selection.rb
> @@ -0,0 +1,20 @@
> +#
> +#   Copyright 2012 Red Hat, Inc.
> +#
> +#   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 files in lib dir
> +['match', 'priority_group', 'rank', 'strategy', 'base', 'chainable_strategy'].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..84a42fa
> --- /dev/null
> +++ b/src/lib/provider_selection/base.rb
> @@ -0,0 +1,121 @@
> +#
> +#   Copyright 2012 Red Hat, Inc.
> +#
> +#   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.
> +#
> +
> +module ProviderSelection
> +
> +  class Base
> +
> +    @strategies = []
> +    @view_paths = []
> +
> +    class << self
> +      attr_reader :strategies
> +      attr_reader :view_paths
> +
> +      def register_strategy(name, lib_path, options = {})
> +        # TODO: this require call should be revised in the future considering security aspects
> +        require lib_path
> +        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.to_s }
> +      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
> +      pool = @instances.first.pool
> +      rank = Rank.new(pool)
> +
> +      provider_accounts = find_common_provider_accounts.map do |provider_account|
> +        provider_account if provider_account.quota.can_start?(@instances)
> +      end.uniq.compact
> +
> +      # Adding a priority of 100000 to the default priority group which contains
> +      # all the available provider accounts.
> +      # The user defined priority groups has a score between -100 and +100.
> +      default_priority_group = PriorityGroup.new(100000)
> +      rank.default_priority_group = default_priority_group
> +
> +      provider_accounts.uniq.each do |provider_account|

^^ there is not needed to call uniq as it's called upper(line 59)

> +        # Rescale to provider account priority to the [-100, 100] interval
> +        score = provider_account.priority
> +        if score.present? && score > Match::UPPER_LIMIT
> +          score = Match::UPPER_LIMIT
> +        elsif score.present? && score < Match::LOWER_LIMIT
> +          score = Match::LOWER_LIMIT
> +        end
> +
> +        default_priority_group.matches << Match.new(provider_account, score)
> +      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 = self.class.find_strategy_by_name(name)
> +      @strategy_chain = @strategy.klass.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)

^^ this caused that result for 1st instance has every prov. account
twice

> +        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..9f3b974
> --- /dev/null
> +++ b/src/lib/provider_selection/chainable_strategy.rb
> @@ -0,0 +1,44 @@
> +#
> +#   Copyright 2012 Red Hat, Inc.
> +#
> +#   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.
> +#
> +
> +module ProviderSelection
> +
> +  module ChainableStrategy
> +
> +    module InstanceMethods
> +
> +      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
> +
> +    module ClassMethods
> +
> +      def properties
> +        @properties || {}
> +      end
> +
> +    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..485bc3a
> --- /dev/null
> +++ b/src/lib/provider_selection/match.rb
> @@ -0,0 +1,50 @@
> +#
> +#   Copyright 2012 Red Hat, Inc.
> +#
> +#   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.
> +#
> +
> +module ProviderSelection
> +
> +  class Match
> +
> +    UPPER_LIMIT = 100
> +    LOWER_LIMIT = -100
> +
> +    attr_reader :score
> +    attr_accessor :provider_account
> +
> +    def initialize(provider_account, score = nil)
> +      @provider_account = provider_account
> +      @score = score
> +    end
> +
> +    def score=(val)
> +      raise ArgumentError.new("score cannot be higher than #{UPPER_LIMIT}") if val > UPPER_LIMIT
> +      raise ArgumentError.new("score cannot be lower than #{LOWER_LIMIT}") if val < LOWER_LIMIT
> +
> +      @score = val
> +    end
> +
> +    # nil values should value more then any other value
> +    def calculated_score
> +      if @score.nil?
> +        UPPER_LIMIT + 1
> +      else
> +        @score
> +      end
> +    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..c2a0c0d
> --- /dev/null
> +++ b/src/lib/provider_selection/priority_group.rb
> @@ -0,0 +1,71 @@
> +#
> +#   Copyright 2012 Red Hat, Inc.
> +#
> +#   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.
> +#
> +
> +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_matches)
> +      priority_group = PriorityGroup.new(obj.score)
> +
> +      possible_provider_accounts = obj.all_provider_accounts
> +      allowed_provider_accounts = allowed_matches.map do |match|
> +        match.provider_account
> +      end.uniq
> +
> +      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
> +
> +    def get_random_match
> +      match_sum_score = @matches.sum do |match|
> +        (Match::UPPER_LIMIT + 1) - match.calculated_score
> +      end
> +
> +      # In order to work with 'nil' scores adding the size of the @matches array
> +      match_sum_score += @matches.length
> +      random_value = rand(match_sum_score)
> +
> +      sum = match_sum_score
> +      matches.each do |match|
> +        difference = (Match::UPPER_LIMIT + 1) - match.calculated_score
> +        sum -= (difference + 1)
> +        if random_value >= sum
> +          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..7479ddf
> --- /dev/null
> +++ b/src/lib/provider_selection/rank.rb
> @@ -0,0 +1,54 @@
> +#
> +#   Copyright 2012 Red Hat, Inc.
> +#
> +#   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.
> +#
> +
> +module ProviderSelection
> +
> +  class Rank
> +
> +    attr_accessor :priority_groups
> +    attr_reader :pool
> +
> +    def initialize(pool)
> +      @priority_groups = []
> +      @pool = pool
> +    end
> +
> +    def ordered_priority_groups
> +      @priority_groups.sort do |a, b|
> +        if a.score.nil? && b.score.nil?
> +          0
> +        elsif a.score.nil?
> +          1
> +        elsif b.score.nil?
> +          -1
> +        else
> +          a.score <=> b.score
> +        end
> +      end
> +    end
> +
> +    def default_priority_group
> +      @default_priority_group
> +    end
^^ can be attr_reader :default_priority_group
> +
> +    def default_priority_group=(val)
> +      @default_priority_group = val
> +      priority_groups << val
> +    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..2706f92
> --- /dev/null
> +++ b/src/lib/provider_selection/strategy.rb
> @@ -0,0 +1,35 @@
> +#
> +#   Copyright 2012 Red Hat, Inc.
> +#
> +#   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.
> +#
> +
> +module ProviderSelection
> +
> +  class Strategy
> +
> +    attr_reader :name
> +    attr_reader :klass
> +
> +    def initialize(name, klass)
> +      @name = name
> +      @klass = klass
> +    end
> +
> +    def translated_name
> +      I18n.t("strategies.#{@name}.name")
> +    end
> +
> +  end
> +
> +end
> \ No newline at end of file






More information about the aeolus-devel mailing list