From: Imre Farkas <ifarkas(a)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.#{(a)name}.name")
+ end
+
+ end
+
+end
\ No newline at end of file
--
1.7.11.2