Project

General

Profile

Download (22.8 KB) Statistics
| Branch: | Tag: | Revision:
#
# Copyright 2013 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,
# including the implied warranties of MERCHANTABILITY,
# 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.

# rubocop:disable SymbolName
module Katello
class Api::V1::SystemsController < Api::V1::ApiController
respond_to :json

skip_before_filter :set_default_response_format, :only => :report

before_filter :find_default_organization_and_or_environment, :only => [:create, :index, :activate]
before_filter :find_optional_organization, :only => [:create, :hypervisors_update, :index, :activate, :report, :tasks]
before_filter :find_only_environment, :only => [:create]
before_filter :find_environment, :only => [:index, :report, :tasks]
before_filter :find_environment_and_content_view, :only => [:create]
before_filter :find_hypervisor_environment_and_content_view, :only => [:hypervisors_update]
before_filter :find_system, :only => [:destroy, :show, :update, :regenerate_identity_certificates,
:errata, :package_profile, :subscribe,
:unsubscribe, :subscriptions, :pools, :enabled_repos, :releases,
:add_system_groups, :remove_system_groups, :refresh_subscriptions, :checkin,
:subscription_status]
before_filter :find_content_view, :only => [:create, :update]

before_filter :authorize, :except => [:activate]

def organization_id_keys
[:organization_id, :owner]
end

# TODO: break up this method
# rubocop:disable MethodLength
def rules
index_systems = lambda { System.any_readable?(@organization) }
register_system = lambda { System.registerable?(@environment, @organization, @content_view) }
consumer_only = lambda { User.consumer? }
edit_system = lambda do
subscribable = @content_view ? @content_view.subscribable? : true
subscribable && (@system.editable? || User.consumer?)
end
read_system = lambda { @system.readable? || User.consumer? }
delete_system = lambda { @system.deletable? || User.consumer? }

{
:new => register_system,
:create => register_system,
:hypervisors_update => consumer_only,
:regenerate_identity_certificates => edit_system,
:update => edit_system,
:index => index_systems,
:show => read_system,
:subscription_status => read_system,
:destroy => delete_system,
:package_profile => read_system,
:errata => read_system,
:report => index_systems,
:subscribe => edit_system,
:unsubscribe => edit_system,
:subscriptions => read_system,
:pools => read_system,
:releases => read_system,
:activate => register_system,
:tasks => index_systems,
:task_show => read_system,
:enabled_repos => edit_system,
:add_system_groups => edit_system,
:remove_system_groups => edit_system,
:refresh_subscriptions => edit_system,
:checkin => edit_system
}
end

def_param_group :system do
param :facts, Hash, :desc => "Key-value hash of system-specific facts", :action_aware => true
param :installedProducts, Array, :desc => "List of products installed on the system", :action_aware => true
param :name, String, :desc => "Name of the system", :required => true, :action_aware => true
param :type, String, :desc => "Type of the system, it should always be 'system'", :required => true, :action_aware => true
param :serviceLevel, String, :allow_nil => true, :desc => "A service level for auto-healing process, e.g. SELF-SUPPORT", :action_aware => true
param :location, String, :desc => "Physical of the system"
param :content_view_id, :identifier
param :environment_id, :identifier
end

# this method is called from katello cli client and it does not work with activation keys
# for activation keys there is method activate (see custom routes)
api :POST, "/environments/:environment_id/systems", "Register a system in environment"
param_group :system
def create
@system = System.create!(system_params.merge(:environment => @environment,
:content_view => @content_view,
:serviceLevel => params[:service_level]))
respond_for_create
end

api :POST, "/hypervisors", "Update the hypervisors information for environment"
desc <<DESC
Takes a hash representing the mapping: host system having geust systems, e.g.:

{ "host-uuid": ["guest-uuid-1", "guest-uuid-2"] }

See virt-who tool for more details.
DESC
# TODO: refactor render
def hypervisors_update
cp_response, _ = System.register_hypervisors(@environment, @content_view, params.except(:controller, :action, :format))
render :json => cp_response
end

# used for registering with activation keys
api :POST, "/organizations/:organization_id/systems", "Register a system with activation key"
param :activation_keys, String, :required => true
param_group :system, :as => :create
def activate
# Activation keys are userless by definition so use the internal generic user
# Set it before calling find_activation_keys to allow communication with candlepin
User.current = User.hidden.first
activation_keys = find_activation_keys
ActiveRecord::Base.transaction do
# create new system entry
@system = System.new(system_params)

# register system - we apply ak in reverse order so when they conflict e.g. in environment, the first wins.
activation_keys.reverse_each { |ak| ak.apply_to_system(@system) }
@system.save!

# subscribe system - if anything goes wrong subscriptions are deleted in Candlepin and exception is rethrown
activation_keys.each do |ak|
ak.subscribe_system(@system)
ak.system_groups.each do |group|
group.system_ids = (group.system_ids + [@system.id]).uniq
group.save!
end
end

respond_for_create
end
end

api :PUT, "/systems/:id", "Update system information"
param_group :system
def update
attrs = params.clone
slice_attrs = [:name, :description, :location,
:facts, :guestIds, :installedProducts,
:releaseVer, :serviceLevel, :lastCheckin
]
unless User.consumer?
slice_attrs = slice_attrs + [:environment_id, :content_view_id]
attrs[:content_view_id] = nil if attrs[:content_view_id] == false
attrs[:content_view_id] = params[:content_view][:id] if params[:content_view]
attrs[:environment_id] = params[:environment][:id] if params[:environment]
end

attrs[:installedProducts] = [] if attrs.key?(:installedProducts) && attrs[:installedProducts].nil?

@system.update_attributes!(attrs.slice(*slice_attrs))

respond
end

api :PUT, "/systems/:id/checkin", "Update system check-in time"
param :date, String, :desc => "check-in time"
def checkin
@system.checkin(params[:date])
respond_for_show
end

api :GET, "/environments/:environment_id/systems", "List systems in environment"
api :GET, "/organizations/:organization_id/systems", "List systems in organization"
param :name, String, :desc => "Filter systems by name"
param :pool_id, String, :desc => "Filter systems by subscribed pool"
param :search, String, :desc => "Filter systems by advanced search query"
param :uuid, String, :desc => "Filter systems by uuid"
def index
query_string = params[:name] ? "name:#{params[:name]}" : params[:search]
filters = []

if params[:env_id]
find_environment
filters << {:terms => {:environment_id => [params[:env_id]] }}
else
filters << readable_filters
end

filters << {:terms => {:uuid => System.all_by_pool_uuid(params['pool_id']) }} if params['pool_id']
filters << {:terms => {:uuid => [params['uuid']] }} if params['uuid']

options = {
:filters => filters,
:load_records? => true
}
options[:sort_by] = params[:sort_by] if params[:sort_by]
options[:sort_order] = params[:sort_order] if params[:sort_order]

if params[:paged]
options[:page_size] = params[:page_size] || current_user.page_size
end

items = Glue::ElasticSearch::Items.new(System)
systems, total_count = items.retrieve(query_string, params[:offset], options)
System.prepopulate!(systems)

if params[:paged]
systems = {
:results => systems,
:subtotal => total_count,
:total => items.total_items
}
end

respond(:collection => systems)
end

api :GET, "/systems/:id", "Show a system"
param :id, String, :desc => "UUID of the system", :required => true
def show
respond
end

api :DELETE, "/systems/:id", "Unregister a system"
param :id, String, :desc => "UUID of the system", :required => true
def destroy
@system.destroy
respond :message => _("Deleted system '%s'") % params[:id], :status => 204
end

api :GET, "/systems/:id/subscription_status", "Show status of subscriptions on the system"
param :id, String, :desc => "UUID of the system", :required => true
def subscription_status
respond_for_index :collection => @system.compliance
end

api :GET, "/systems/:id/pools", "List pools a system is subscribed to"
param :id, String, :desc => "UUID of the system", :required => true
def pools
match_system = params.key?(:match_system) ? params[:match_system].to_bool : false
match_installed = params.key?(:match_installed) ? params[:match_installed].to_bool : false
no_overlap = params.key?(:no_overlap) ? params[:no_overlap].to_bool : false

cp_pools = @system.filtered_pools(match_system, match_installed, no_overlap)

respond_for_index :collection => { :pools => cp_pools }
end

api :GET, "/systems/:id/releases", "Show releases available for the system"
param :id, String, :desc => "UUID of the system", :required => true
desc <<-DESC
A hint for choosing the right value for the releaseVer param
DESC
def releases
respond_for_index :collection => { :releases => @system.available_releases }
end

api :GET, "/systems/:id/packages", "List packages installed on the system"
param :id, String, :desc => "UUID of the system", :required => true
def package_profile
respond_for_index :collection => @system.simple_packages.sort { |a, b| a.name.downcase <=> b.name.downcase }
end

api :GET, "/systems/:id/errata", "List errata available for the system"
param :id, String, :desc => "UUID of the system", :required => true
def errata
respond_for_index :collection => @system.errata
end

# TODO: break this mehtod up
api :GET, "/environments/:environment_id/systems/report", "Get system reports for the environment"
api :GET, "/organizations/:organization_id/systems/report", "Get system reports for the organization"
def report # rubocop:disable MethodLength
data = @environment.nil? ? @organization.systems.readable(@organization) : @environment.systems.readable(@organization)

data = data.flatten.map do |r|
r.reportable_data(
:only => [:uuid, :name, :location, :created_at, :updated_at],
:methods => [:environment, :organization, :compliance_color, :compliant_until, :custom_info]
)
end

system_report = Util::ReportTable.new(
:data => data,
:column_names => %w(name uuid location organization environment created_at updated_at
compliance_color compliant_until custom_info),
:transforms => (lambda do |r|
r.organization = r.organization.name
r.environment = r.environment.name
r.created_at = r.created_at.to_s
r.updated_at = r.updated_at.to_s
r.compliant_until = r.compliant_until.to_s
r.custom_info = r.custom_info.collect { |info| info.to_s }.join(", ")
end)
)
respond_to do |format|
format.text { render :text => system_report.as(:text) }
format.csv { render :text => system_report.as(:csv) }
end
end

api :GET, "/organizations/:organization_id/systems/tasks", "List async tasks for the system"
param :system_name, String, :desc => "Name of the system"
param :system_uuid, String, :desc => "UUID of the system"
def tasks
if params[:system_name]
@tasks = System.where(:name => params[:system_name]).first.tasks
elsif params[:system_uuid]
@tasks = System.where(:uuid => params[:system_uuid]).first.tasks
end

respond_for_index :collection => @tasks
end

api :PUT, "/systems/:id/enabled_repos", "Update the information about enabled repositories"
desc <<-DESC
Used by katello-agent to keep the information about enabled repositories up to date.
This information is then used for computing the errata available for the system.
DESC
param :enabled_repos, Hash, :required => true do
param :repos, Array, :required => true do
param :baseurl, Array, :description => "List of enabled repo urls for the repo (Only first is used.)", :required => false
end
end
def enabled_repos
repos_params = params['enabled_repos'] rescue raise(HttpErrors::BadRequest, _("Expected attribute is missing:") + " enabled_repos")
repos_params = repos_params['repos'] || []

unknown_paths = []
repos = []
repos_params.each do |repo|
if !repo['baseurl'].blank?
path = URI(repo['baseurl'].first).path
possible_repos = Repository.where(:relative_path => path.gsub('/pulp/repos/', ''))
if possible_repos.empty?
unknown_paths << path
logger.warn("System #{@system.name} (#{@system.id}) requested binding to unknown repo #{path}")
else
repos << possible_repos.first
logger.warn("System #{@system.name} (#{@system.id}) requested binding to path #{path} matching" +
"#{possible_repos.size} repositories.") if possible_repos.size > 1
end
else
logger.warn("System #{@system.name} (#{@system.id}) attempted to bind to unspecific repo (#{repo}).")
end
end

pulp_ids = repos.collect{|r| r.pulp_id}
processed_ids, error_ids = @system.enable_yum_repos(pulp_ids)

result = {}
result[:processed_ids] = processed_ids
result[:error_ids] = error_ids
result[:unknown_labels] = unknown_paths
if error_ids.present? || unknown_paths.present?
result[:result] = "error"
else
result[:result] = "ok"
end

respond_for_show :resource => result
end

api :POST, "/systems/:id/system_groups", "Add a system to groups"
param :system, Hash, :required => true do
param :system_group_ids, Array, :desc => "List of group ids to add the system to", :required => true
end
def add_system_groups
ids = params[:system][:system_group_ids]
@system.system_group_ids = (@system.system_group_ids + ids).uniq
@system.save!
respond_for_create
end

api :DELETE, "/systems/:id/system_groups", "Remove a system from groups"
param :system, Hash, :required => true do
param :system_group_ids, Array, :desc => "List of group ids to add the system to", :required => true
end
def remove_system_groups
ids = params[:system][:system_group_ids].map(&:to_i)
@system.system_group_ids = (@system.system_group_ids - ids).uniq
@system.save!
respond_for_show
end

api :PUT, "/systems/:id/refresh_subscriptions", "Trigger a refresh of subscriptions, auto-attaching if enabled"
param :id, String, :desc => "UUID of the system", :required => true
def refresh_subscriptions
@system.refresh_subscriptions
respond_for_show
end

protected

def find_only_environment
if !@environment && @organization && !params.key?(:environment_id)
if @organization.environments.empty?
fail HttpErrors::BadRequest, _("Organization %{org} has the '%{env}' environment only. Please create an environment for system registration.") %
{ :org => @organization.name, :env => "Library" }
end

# Some subscription-managers will call /users/$user/owners to retrieve the orgs that a user belongs to.
# Then, If there is just one org, that will be passed to the POST /api/consumers as the owner. To handle
# this scenario, if the org passed in matches the user's default org, use the default env. If not use
# the single env of the org or throw an error if more than one.
#
if @organization.environments.size > 1
if current_user.default_environment && current_user.default_environment.organization == @organization
@environment = current_user.default_environment
else
fail HttpErrors::BadRequest, _("Organization %s has more than one environment. Please specify target environment for system registration.") % @organization.name
end
else
if @environment = @organization.environments.first
return
end
end
end
end

def find_hypervisor_environment_and_content_view
cve = get_content_view_environment_by_label(params[:env])
@environment = cve.environment
@content_view = cve.content_view
end

def get_content_view_environment_by_label(label)
get_content_view_environment("label", label)
end

def get_content_view_environment(key, value)
cve = nil
if value
cve = ContentViewEnvironment.where(key => value).first
fail HttpErrors::NotFound, _("Couldn't find environment '%s'") % value unless cve
if @organization.nil? || !@organization.readable?
unless cve.content_view.readable? || User.consumer?
fail Errors::SecurityViolation, _("Could not access content view in environment '%s'") % value
end
end
end
cve
end

def get_content_view_environment_by_cp_id(id)
get_content_view_environment("cp_id", id)
end

def find_environment
return unless params.key?(:environment_id)

@environment = KTEnvironment.find(params[:environment_id])
fail HttpErrors::NotFound, _("Couldn't find environment '%s'") % params[:environment_id] if @environment.nil?
@organization = @environment.organization
@environment
end

def find_environment_and_content_view
# There are some scenarios (primarily create) where a system may be
# created using the content_view_environment.cp_id which is the
# equivalent of "environment_id"-"content_view_id".
return unless params.key?(:environment_id)

if params[:environment_id].is_a? String
if !params.key?(:content_view_id)
cve = get_content_view_environment_by_cp_id(params[:environment_id])
@environment = cve.environment
@organization = @environment.organization
@content_view = cve.content_view
else
# assumption here is :content_view_id is passed as a separate attrib
@environment = KTEnvironment.find(params[:environment_id])
@organization = @environment.organization
fail HttpErrors::NotFound, _("Couldn't find environment '%s'") % params[:environment_id] if @environment.nil?
end
return @environment, @content_view
else
find_environment
end
end

def find_default_organization_and_or_environment
# This has to grab the first default org associated with this user AND
# the environment that goes with him.
return if params.key?(:organization_id) || params.key?(:owner) || params.key?(:environment_id)

#At this point we know that they didn't supply an org or environment, so we can look up the default
@environment = current_user.default_environment
if @environment
@organization = @environment.organization
else
fail HttpErrors::NotFound, _("You have not set a default organization and environment on the user %s.") % current_user.login
end
end

def find_system
@system = System.first(:conditions => { :uuid => params[:id] })
if @system.nil?
Resources::Candlepin::Consumer.get params[:id] # check with candlepin if system is Gone, raises RestClient::Gone
fail HttpErrors::NotFound, _("Couldn't find system '%s'") % params[:id]
end
@system
end

def find_activation_keys
if ak_names = params[:activation_keys]
ak_names = ak_names.split(",")
activation_keys = ak_names.map do |ak_name|
activation_key = @organization.activation_keys.find_by_name(ak_name)
fail HttpErrors::NotFound, _("Couldn't find activation key '%s'") % ak_name unless activation_key
activation_key
end
else
activation_keys = []
end
if activation_keys.empty?
fail HttpErrors::BadRequest, _("At least one activation key must be provided")
end
activation_keys
end

def readable_filters
{:terms => {:environment_id => KTEnvironment.systems_readable(@organization).collect { |item| item.id } }}
end

def find_content_view
if (content_view_id = (params[:content_view_id] || params[:system].try(:[], :content_view_id)))
setup_content_view(content_view_id)
end
end

def setup_content_view(cv_id)
return if @content_view
organization = @organization
organization ||= @system.organization if @system
organization ||= @environment.organization if @environment
if cv_id && organization
@content_view = ContentView.readable(organization).find_by_id(cv_id)
fail HttpErrors::NotFound, _("Couldn't find content view '%s'") % cv_id if @content_view.nil?
else
@content_view = nil
end
end

# to make sure that the changes are reflected to elasticsearch immediately
# otherwise the index action doesn't have to know about the changes
def refresh_index
System.index.refresh if Katello.config.use_elasticsearch
end

def system_params
system_params = params.slice(:name, :owner, :facts, :installedProducts)

if params.key?(:cp_type)
system_params[:cp_type] = params[:cp_type]
elsif params.key?(:type)
system_params[:cp_type] = params[:type]
end

system_params
end

end
end
(41-41/44)