[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