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

ifarkas at redhat.com ifarkas at redhat.com
Tue Aug 14 13:39:37 UTC 2012


From: Imre Farkas <ifarkas at redhat.com>

---
 src/lib/provider_selection.rb                    |  20 ++++
 src/lib/provider_selection/base.rb               | 117 +++++++++++++++++++++++
 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, 391 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..b7a4300
--- /dev/null
+++ b/src/lib/provider_selection/base.rb
@@ -0,0 +1,117 @@
+#
+#   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.each do |provider_account|
+        # 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 = []
+      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..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
+
+    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
-- 
1.7.11.2




More information about the aeolus-devel mailing list