app/models/katello/activation_key.rb in katello-1.5.0 vs app/models/katello/activation_key.rb in katello-2.2.2

- old
+ new

@@ -1,7 +1,7 @@ # -# Copyright 2013 Red Hat, Inc. +# Copyright 2014 Red Hat, Inc. # # This software is licensed to you under the GNU General Public # License as published by the Free Software Foundation; either version # 2 of the License (GPLv2) or (at your option) any later version. # There is NO WARRANTY for this software, express or implied, @@ -9,162 +9,232 @@ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should # have received a copy of GPLv2 along with this software; if not, see # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. module Katello -class ActivationKey < ActiveRecord::Base - self.include_root_in_json = false + class ActivationKey < Katello::Model + self.include_root_in_json = false - include Glue::ElasticSearch::ActivationKey if Katello.config.use_elasticsearch - include Authorization::ActivationKey + include Glue::Candlepin::ActivationKey if Katello.config.use_cp + include Glue::ElasticSearch::ActivationKey if Katello.config.use_elasticsearch + include Glue if Katello.config.use_cp + include Katello::Authorization::ActivationKey + include ForemanTasks::Concerns::ActionSubject - belongs_to :organization, :inverse_of => :activation_keys - belongs_to :environment, :class_name => "KTEnvironment", :inverse_of => :activation_keys - belongs_to :user, :inverse_of => :activation_keys, :class_name => "::User" - belongs_to :content_view, :inverse_of => :activation_keys + belongs_to :organization, :inverse_of => :activation_keys + belongs_to :environment, :class_name => "KTEnvironment", :inverse_of => :activation_keys + belongs_to :user, :inverse_of => :activation_keys, :class_name => "::User" + belongs_to :content_view, :inverse_of => :activation_keys - has_many :key_pools, :class_name => "Katello::KeyPool", :dependent => :destroy - has_many :pools, :through => :key_pools + has_many :key_host_collections, :class_name => "Katello::KeyHostCollection", :dependent => :destroy + has_many :host_collections, :through => :key_host_collections - has_many :key_system_groups, :class_name => "Katello::KeySystemGroup", :dependent => :destroy - has_many :system_groups, :through => :key_system_groups + has_many :system_activation_keys, :class_name => "Katello::SystemActivationKey", :dependent => :destroy + has_many :systems, :through => :system_activation_keys - has_many :system_activation_keys, :class_name => "Katello::SystemActivationKey", :dependent => :destroy - has_many :systems, :through => :system_activation_keys + before_validation :set_default_content_view, :unless => :persisted? - after_find :validate_pools + validates_lengths_from_database + validates_with Validators::KatelloNameFormatValidator, :attributes => :name + validates :name, :presence => true + validates :name, :uniqueness => {:scope => :organization_id} + validate :environment_exists + validates :max_content_hosts, :numericality => {:less_than => 2**31, :allow_nil => true} + validates_each :max_content_hosts do |record, attr, value| + if record.unlimited_content_hosts + unless value.nil? + record.errors[attr] << _("cannot be set because unlimited content hosts is set") + end + else + if value.nil? + record.errors[attr] << _("cannot be nil") + elsif value <= 0 + record.errors[attr] << _("cannot be less than one") + elsif value < record.systems.length + # we don't let users to set usage limit lower than current in-use + record.errors[attr] << _("cannot be lower than current usage count (%s)" % record.systems.length) + end + end + end + validates_with Validators::ContentViewEnvironmentValidator - before_validation :set_default_content_view, :unless => :persisted? - validates_with Validators::KatelloNameFormatValidator, :attributes => :name - validates :name, :presence => true - validates :name, :uniqueness => {:scope => :organization_id} - validates_with Validators::KatelloDescriptionFormatValidator, :attributes => :description - validates :environment, :presence => true - validate :environment_exists - validates :content_view, :presence => true, :allow_blank => false - validates_each :usage_limit do |record, attr, value| - if !value.nil? && (value < -1 || value == 0 || (value != -1 && value < record.usage_count)) - # we don't let users to set usage limit lower than current usage - record.errors[attr] << _("must be higher than current usage (%s) or unlimited" % record.usage_count) + scope :in_environment, lambda { |env| where(:environment_id => env) } + + scoped_search :on => :name, :complete_value => true + scoped_search :on => :organization_id, :complete_value => true + + def environment_exists + if environment_id && environment.nil? + errors.add(:environment, _("ID: %s doesn't exist ") % environment_id) + elsif !environment.nil? && environment.organization != self.organization + errors.add(:environment, _("name: %s doesn't exist ") % environment.name) + end end - end - validates_with Validators::ContentViewEnvironmentValidator - def environment_exists - if environment.nil? - errors.add(:environment, _("ID: %s doesn't exist ") % environment_id) - elsif environment.organization != self.organization - errors.add(:environment, _("name: %s doesn't exist ") % environment.name) + def usage_count + system_activation_keys.count end - end - def usage_count - system_activation_keys.count - end + def related_resources + self.organization + end - # sets up system when registering with this activation key - must be executed in a transaction - def apply_to_system(system) - if !usage_limit.nil? && usage_limit != -1 && usage_count >= usage_limit - fail Errors::UsageLimitExhaustedException, _("Usage limit (%{limit}) exhausted for activation key '%{name}'") % {:limit => usage_limit, :name => name} + def available_releases + if self.environment + self.environment.available_releases + else + self.organization.library.available_releases + end end - system.environment_id = self.environment_id if self.environment_id - system.content_view_id = self.content_view_id if self.content_view_id - system.system_activation_keys.build(:activation_key => self) - end - def calculate_consumption(product, pools, allocate) - pools = pools.sort_by { |pool| [pool.start_date, pool.cp_id] } - consumption = {} + # For efficiency, sometimes the candlepin pool objects have already been fetched so allow + # them to be passed in directly. By default, a call to candlepin will be made + def subscriptions(cp_pools = nil) + cp_pools ||= self.get_key_pools - if product.provider.redhat_provider? - pools.each do |pool| - consumption[pool] ||= 0 - consumption[pool] += 1 + pools = cp_pools.collect { |cp_pool| Pool.find_pool(cp_pool['id'], cp_pool) } + + subscriptions = pools.collect do |pool| + product = Product.where(:cp_id => pool.product_id).first + next if product.nil? + pool.provider_id = product.provider_id + pool end - else - consumption[pools.first] = 1 + subscriptions.compact end - return consumption - end - # subscribe to each product according the entitlements remaining - # TODO: break up method - # rubocop:disable MethodLength - def subscribe_system(system) - already_subscribed = [] - begin - # sanity check before we start subscribing - self.pools.each do |pool| - fail _("Pool %s has no product associated") % pool.cp_id unless pool.product_id - fail _("Unable to determine quantity for pool %s") % pool.cp_id unless pool.quantity + def available_subscriptions + all_pools = self.get_pools + key_pool_ids = self.get_key_pools.collect { |pool| pool[:id] } + pools = all_pools.reject { |pool| key_pool_ids.include? pool[:id] } + self.subscriptions(pools) + end + + def products + all_products = [] + + cp_pools = self.get_key_pools + if cp_pools + pools = cp_pools.collect { |cp_pool| Pool.find_pool(cp_pool['id'], cp_pool) } + product_ids = pools.map(&:product_id) + marketing_products = MarketingProduct.includes(:engineering_products, :marketing_engineering_products). + where(:cp_id => product_ids) + products = Product.where(:cp_id => product_ids).where('type != ?', "Katello::MarketingProduct") + + marketing_products.each do |product| + all_products += product.engineering_products + end + + all_products += products end - allocate = system.sockets.to_i - Rails.logger.debug "Number of sockets for registration: #{allocate}" - fail _("Number of sockets must be higher than 0 for system %s") % system.name if allocate <= 0 + all_products + end - # we sort just to make the order deterministig. - self.pools.group_by(&:product_id).sort_by(&:first).each do |product_id, pools| - product = Product.find_by_cp_id(product_id, self.organization) - consumption = calculate_consumption(product, pools, allocate) + def available_content + self.products.map(&:available_content).flatten + end - Rails.logger.debug "Autosubscribing pools: #{consumption.map { |pool, amount| "#{pool.cp_id} => #{amount}"}.join(", ")}" - consumption.each do |pool, amount| - Rails.logger.debug "Subscribing #{system.name} to product: #{product_id}, consuming pool #{pool.cp_id} of amount: #{amount}" - if entitlements_array = system.subscribe(pool.cp_id, amount) - # store for possible rollback - entitlements_array.each do |ent| - already_subscribed << ent['id'] + # sets up system when registering with this activation key - must be executed in a transaction + def apply_to_system(system) + if !max_content_hosts.nil? && !self.unlimited_content_hosts && usage_count >= max_content_hosts + fail Errors::MaxContentHostsReachedException, _("Max Content Hosts (%{limit}) reached for activation key '%{name}'") % {:limit => max_content_hosts, :name => name} + end + system.environment_id = self.environment_id if self.environment_id + system.content_view_id = self.content_view_id if self.content_view_id + system.system_activation_keys.build(:activation_key => self) + end + + def calculate_consumption(product, pools, _allocate) + pools = pools.sort_by { |pool| [pool.start_date, pool.cp_id] } + consumption = {} + + if product.provider.redhat_provider? + pools.each do |pool| + consumption[pool] ||= 0 + consumption[pool] += 1 + end + else + consumption[pools.first] = 1 + end + return consumption + end + + # subscribe to each product according the entitlements remaining + # TODO: break up method + # rubocop:disable MethodLength + def subscribe_system(system) + already_subscribed = [] + begin + # sanity check before we start subscribing + self.pools.each do |pool| + fail _("Pool %s has no product associated") % pool.cp_id unless pool.product_id + fail _("Unable to determine quantity for pool %s") % pool.cp_id unless pool.quantity + end + + allocate = system.sockets.to_i + Rails.logger.debug "Number of sockets for registration: #{allocate}" + fail _("Number of sockets must be higher than 0 for system %s") % system.name if allocate <= 0 + + # we sort just to make the order deterministig. + self.pools.group_by(&:product_id).sort_by(&:first).each do |product_id, pools| + product = Product.find_by_cp_id(product_id, self.organization) + consumption = calculate_consumption(product, pools, allocate) + + Rails.logger.debug "Autosubscribing pools: #{consumption.map { |pool, amount| "#{pool.cp_id} => #{amount}" }.join(", ")}" + consumption.each do |pool, amount| + Rails.logger.debug "Subscribing #{system.name} to product: #{product_id}, consuming pool #{pool.cp_id} of amount: #{amount}" + if entitlements_array = system.subscribe(pool.cp_id, amount) + # store for possible rollback + entitlements_array.each do |ent| + already_subscribed << ent['id'] + end end end end - end - rescue => e - Rails.logger.error "Autosubscribtion failed, rolling back: #{already_subscribed.inspect}" - already_subscribed.each do |entitlement_id| - begin - Rails.logger.debug "Rolling back: #{entitlement_id}" - system.unsubscribe(entitlement_id) - rescue => re - Rails.logger.fatal "Rollback failed, skipping: #{re.message}" + rescue => e + Rails.logger.error "Autosubscription failed, rolling back: #{already_subscribed.inspect}" + already_subscribed.each do |entitlement_id| + begin + Rails.logger.debug "Rolling back: #{entitlement_id}" + system.unsubscribe(entitlement_id) + rescue => re + Rails.logger.fatal "Rollback failed, skipping: #{re.message}" + end end + raise e end - raise e end - end - def as_json(*args) - ret = super(*args) - ret[:pools] = pools.map do |pool| - pool.as_json + def copy(new_name) + new_key = ActivationKey.new + new_key.name = new_name + new_key.attributes = self.attributes.slice("description", "environment_id", "organization_id", "content_view_id", "max_content_hosts", "unlimited_content_hosts") + new_key.host_collection_ids = self.host_collection_ids + new_key end - ret[:usage_count] = usage_count - ret[:editable] = ActivationKey.readable?(organization) - ret - end - private + def subscribe_to_pool(pool_id, quantity = 1) + self.subscribe(pool_id, quantity) + rescue RestClient::ResourceNotFound, RestClient::BadRequest => e + raise JSON.parse(e.response)['displayMessage'] + end - def set_default_content_view - self.content_view = self.environment.try(:default_content_view) unless self.content_view - end + def unsubscribe_from_pool(pool_id) + self.unsubscribe(pool_id) + rescue RestClient::ResourceNotFound, RestClient::BadRequest => e + raise JSON.parse(e.response)['displayMessage'] + end - # Fetch each of the pools from candlepin, removing any that no longer - # exist (eg. from loss of a Virtual Guest pool) - def validate_pools - obsolete_pools = [] - self.pools.each do |pool| - begin - Resources::Candlepin::Pool.find(pool.cp_id) - rescue RestClient::ResourceNotFound - obsolete_pools << pool + private + + def set_default_content_view + if self.environment && self.content_view.nil? + self.content_view = self.environment.try(:default_content_view) end end - updated_pools = self.pools - obsolete_pools - if self.pools != updated_pools - self.pools = updated_pools - self.save! + + def self.humanize_class_name(_name = nil) + _("Activation Keys") end end - -end end