app/models/service.rb
require 'ancestry'
class Service < ApplicationRecord
DEFAULT_PROCESS_DELAY_BETWEEN_GROUPS = 120
DEFAULT_POWER_STATE_DELAY = 60
DEFAULT_POWER_STATE_RETRIES = 3
ACTION_RESPONSE = {
"Power On" => :start,
"Power Off" => :stop,
"Shutdown" => :shutdown_guest,
"Suspend" => :suspend,
"Do Nothing" => nil
}.freeze
POWER_STATE_MAP = {
:start => "on",
:stop => "off",
:suspend => "off",
:shutdown_guest => "off"
}.freeze
has_ancestry :orphan_strategy => :destroy
belongs_to :service_template # Template this service was cloned from
belongs_to :tenant
has_many :dialogs, -> { distinct }, :through => :service_template
has_many :metric_rollups, :as => :resource
has_many :metrics, :as => :resource
has_many :vim_performance_states, :as => :resource
has_one :miq_request_task, :dependent => :nullify, :as => :destination
has_one :miq_request, :through => :miq_request_task
has_one :picture, :through => :service_template
virtual_belongs_to :parent_service
virtual_has_many :all_service_children
virtual_has_many :all_vms
virtual_has_many :direct_service_children
virtual_has_many :generic_objects
virtual_has_many :orchestration_stacks
virtual_has_many :power_states, :uses => :all_vms
virtual_has_many :vms
virtual_has_many :direct_vms
virtual_has_one :chargeback_report
virtual_has_one :configuration_script
virtual_has_one :custom_action_buttons
virtual_has_one :custom_actions
virtual_has_one :provision_dialog
virtual_has_one :reconfigure_dialog
virtual_has_one :user
before_create :update_attributes_from_dialog
delegate :provision_dialog, :to => :miq_request, :allow_nil => true
delegate :user, :to => :miq_request, :allow_nil => true
include SupportsFeatureMixin
include CiFeatureMixin
include CustomActionsMixin
include CustomAttributeMixin
include ExternalUrlMixin
include DeprecationMixin
include LifecycleMixin
include Metric::CiMixin
include NewWithTypeStiMixin
include OwnershipMixin
include ProcessTasksMixin
include ServiceMixin
include TenancyMixin
extend InterRegionApiMethodRelay
include Aggregation
include Operations
include ResourceLinking
include RetirementManagement
hide_attribute "display"
virtual_total :v_total_vms, :vms, :arel => aggregate_hardware_arel("v_total_vms", vms_tbl[:id].count, :skip_hardware => true)
virtual_column :has_parent, :type => :boolean
virtual_column :power_state, :type => :string
virtual_column :power_status, :type => :string
validates :name, :presence => true
default_value_for :visible, false
default_value_for :initiator, 'user'
default_value_for :lifecycle_state, 'unprovisioned'
default_value_for :retired, false
validates :visible, :inclusion => {:in => [true, false]}
validates :retired, :inclusion => {:in => [true, false]}
scope :displayed, -> { where(:visible => true) }
scope :retired, ->(bool = true) { where(:retired => bool) }
supports(:reconfigure) { _("Reconfigure unsupported") unless validate_reconfigure }
supports :retire
alias parent_service parent
alias_attribute :service, :parent
deprecate_attribute :display, :visible, :type => :boolean
virtual_belongs_to :service
def power_states
vms.map(&:power_state)
end
# renaming method from custom_actions_mixin
alias_method :custom_service_actions, :custom_actions
def custom_actions
service_template ? service_template.custom_actions(self) : custom_service_actions(self)
end
def custom_action_buttons
service_template ? service_template.custom_action_buttons(self) : generic_custom_buttons
end
def power_state
if options[:power_status] == "starting"
'on' if power_states_match?(:start)
elsif options[:power_status] == "stopping"
'off' if power_states_match?(:stop)
else
return 'on' if power_states_match?(:start)
'off' if power_states_match?(:stop)
end
end
def power_status
options[:power_status]
end
def self.active
where(:lifecycle_state => ["provisioning", "provisioned"], :retired => false)
end
def self.inactive
where(:lifecycle_state => ["unprovisioned", "error_in_provisioning"], :retired => false).or(retired)
end
def service_id
parent_id
end
virtual_attribute :service_id, :integer
# has_parent? is from the ancestry mixin
alias has_parent has_parent?
def request_class
ServiceReconfigureRequest
end
def request_type
'service_reconfigure'
end
def retireable?
return false unless provisioned?
# top level services do not have types; this method is used only in creating tasks for child services which always have types
# please see https://github.com/ManageIQ/manageiq/pull/17317#discussion_r186528878
parent.present? ? true : type.present?
end
def allow_retire_request_creation?
MiqRequest.with_type("ServiceRetireRequest").where(:approval_state => "pending_approval").find_each do |request|
if request.options.try(:[], :src_ids)&.include?(id)
next if request.request_state == "finished" || request.status == "Error"
_log.warn("MiqRequest with id:#{request.id} to retire Service name:'#{name}' id:#{id} already created but not approved yet")
return false
end
end
true
end
alias root_service root
alias services children
alias direct_service_children children
virtual_has_many :services
def indirect_service_children
descendants.where.not(children)
end
Vmdb::Deprecation.deprecate_methods(self, :indirect_service_children)
alias all_service_children descendants
def indirect_vms
MiqPreloader.preload_and_map(indirect_service_children, :direct_vms)
end
Vmdb::Deprecation.deprecate_methods(self, :indirect_vms)
def direct_vms
service_resources.where(:resource_type => 'VmOrTemplate').includes(:resource).collect(&:resource).compact
end
def all_vms
MiqPreloader.preload_and_map(subtree, :direct_vms)
end
def vms
all_vms
end
def last_index
@last_index ||= last_group_index
end
def start
raise_request_start_event
queue_group_action(:start, 0, 1, delay_for_action(0, :start))
end
def stop
raise_request_stop_event
queue_group_action(:stop, last_index, -1, delay_for_action(last_index, :stop))
end
def suspend
update_progress(:power_status => 'suspending')
queue_group_action(:suspend, last_index, -1, delay_for_action(last_index, :stop))
end
def shutdown_guest
queue_group_action(:shutdown_guest, last_index, -1, delay_for_action(last_index, :stop))
end
def power_states_match?(action)
all_states_match?(action) ? update_power_status(action) : false
end
def all_states_match?(action)
if composite?
power_states.uniq == map_power_states(action)
else
power_states[0] == POWER_STATE_MAP[action]
end
end
# @return true if this is a composite service
def composite?
children.present?
end
# @return true if this is a single service (not made up of multiple services)
def atomic?
!composite?
end
def orchestration_stacks
service_resources.where(:resource_type => 'OrchestrationStack').includes(:resource).collect(&:resource)
end
def generic_objects
service_resources.where(:resource_type => 'GenericObject').includes(:resource).collect(&:resource)
end
def group_resource_actions(action_name)
each_group_resource.collect(&action_name).uniq
end
def map_power_states(action)
action_name = "#{action}_action".to_sym
service_actions = group_resource_actions(action_name)
# We need to account for all nil :start_action or :stop_action attributes
# When all :start_actions are nil then return 'Power On' for the :start_action
# When all :stop_actions are nil then return 'Power Off' for the :stop_action
if service_actions.compact.empty?
action_index = Service::ACTION_RESPONSE.values.index(action)
mod_resources = service_actions.each_with_index do |sa, i|
sa.nil? ? service_actions[i] = Service::ACTION_RESPONSE.to_a[action_index][0] : sa
end
else
mod_resources = service_actions
end
# Map out the final power state we should have for the passed in action
mod_resources.map { |x| Service::ACTION_RESPONSE[x] }.map { |x| Service::POWER_STATE_MAP[x] }
end
def update_power_status(action)
expected_status = "#{action}_complete"
return true if options[:power_status] == expected_status
options[:power_status] = expected_status
update(:options => options)
end
private def update_progress(hash)
update(:options => options.merge(hash))
end
def process_group_action(action, group_idx, direction)
each_group_resource(group_idx) do |svc_rsc|
begin
rsc = svc_rsc.resource
rsc_action = service_action(action, svc_rsc)
rsc_name = "#{rsc.class.name}:#{rsc.id}" + (rsc.respond_to?(:name) ? ":#{rsc.name}" : "")
if rsc_action.nil?
_log.info("Not Processing action for Service:<#{name}:#{id}>, RSC:<#{rsc_name}}> in Group Idx:<#{group_idx}>")
elsif rsc.respond_to?(rsc_action)
_log.info("Processing action <#{rsc_action}> for Service:<#{name}:#{id}>, RSC:<#{rsc_name}}> in Group Idx:<#{group_idx}>")
rsc.send(rsc_action)
else
_log.info("Skipping action <#{rsc_action}> for Service:<#{name}:#{id}>, RSC:<#{rsc.class.name}:#{rsc.id}> in Group Idx:<#{group_idx}>")
end
rescue => err
_log.error("Error while processing Service:<#{name}> Group Idx:<#{group_idx}> Resource<#{rsc_name}>. Message:<#{err}>")
end
end
# Setup processing for the next group
next_grp_idx = next_group_index(group_idx, direction)
if next_grp_idx.nil?
raise_final_process_event(action)
else
queue_group_action(action, next_grp_idx, direction, delay_for_action(next_grp_idx, action))
end
end
def queue_group_action(action, group_idx, direction, deliver_delay)
nh = {
:class_name => self.class.name,
:instance_id => id,
:method_name => "process_group_action",
:args => [action, group_idx, direction]
}
nh[:deliver_on] = deliver_delay.seconds.from_now.utc if deliver_delay > 0
nh[:zone] = my_zone if my_zone
MiqQueue.put(nh)
true
end
def my_zone
# Verify the VM has a provider or my_zone will return the miq_server zone by default
vms.detect(&:ext_management_system).try(:my_zone)
end
def service_action(requested, service_resource)
method = "#{requested}_action"
response = service_resource.try(method)
response.nil? ? requested : ACTION_RESPONSE[response]
end
def validate_reconfigure
ra = reconfigure_resource_action
ra && ra.dialog_id && ra.fqname.present?
end
def reconfigure_resource_action
service_template.resource_actions.find_by(:action => 'Reconfigure') if service_template
end
def reconfigure_dialog
return nil unless supports?(:reconfigure)
resource_action = reconfigure_resource_action
options = {:target => self, :reconfigure => true}
workflow = ResourceActionWorkflow.new(self.options[:dialog], User.current_user, resource_action, options)
DialogSerializer.new.serialize(Array[workflow.dialog], true)
end
def raise_final_process_event(action)
case action.to_s
when "start" then raise_started_event
when "stop" then raise_stopped_event
end
end
def raise_request_start_event
update_progress(:power_status => 'starting')
MiqEvent.raise_evm_event(self, :request_service_start)
end
def raise_started_event
MiqEvent.raise_evm_event(self, :service_started)
end
def raise_request_stop_event
update_progress(:power_status => 'stopping')
MiqEvent.raise_evm_event(self, :request_service_stop)
end
def raise_stopped_event
MiqEvent.raise_evm_event(self, :service_stopped)
end
def raise_provisioned_event
MiqEvent.raise_evm_event(self, :service_provisioned)
end
def tenant_identity
user = evm_owner
user = User.super_admin.tap { |u| u.current_group = miq_group } if user.nil? || !user.miq_group_ids.include?(miq_group_id)
user
end
def chargeback_report
report_result = MiqReportResult.find_by(:name => chargeback_report_name)
if report_result.nil?
{:results => []}
else
{:results => report_result.result_set}
end
end
def self.queue_chargeback_reports(options = {})
Service.in_my_region.each do |s|
s.queue_chargeback_report_generation(options) unless s.vms.empty? || s.retired
end
end
def chargeback_report_name
"Chargeback-Vm-Monthly-#{name}-#{id}"
end
def generate_chargeback_report(options = {})
_log.info("Generation of chargeback report for service #{name} with #{id} started...")
MiqReportResult.where(:name => chargeback_report_name).destroy_all
report = MiqReport.new(chargeback_yaml)
options[:report_sync] = true
report.queue_generate_table(options)
_log.info("Report #{chargeback_report_name} generated")
end
def chargeback_yaml
yaml = YAML.load_file(Rails.root.join("product/chargeback/chargeback_vm_monthly.yaml"))
yaml["db_options"][:options][:service_id] = id
yaml["title"] = chargeback_report_name
yaml
end
def queue_chargeback_report_generation(options = {})
msg = "Generating chargeback report for `#{self.class.name}` with id #{id}"
task = MiqTask.create(
:name => msg,
:state => MiqTask::STATE_QUEUED,
:status => MiqTask::STATUS_OK,
:message => "Queueing: #{msg}"
)
cb = {
:class_name => task.class.to_s,
:instance_id => task.id,
:method_name => :queue_callback,
:args => ["Finished"]
}
MiqQueue.submit_job(
:service => "reporting",
:class_name => self.class.name,
:instance_id => id,
:task_id => task.id,
:miq_task_id => task.id,
:miq_callback => cb,
:method_name => "generate_chargeback_report",
:args => options
)
_log.info("Added to queue: #{msg}")
task
end
#
# Metric methods
#
PERF_ROLLUP_CHILDREN = [:vms]
def perf_rollup_parents(interval_name = nil)
[] unless interval_name == 'realtime'
end
def add_resource(rsc, options = {})
super.tap do |service_resource|
break if service_resource.nil?
# Create ancestry link between services
resource = service_resource.resource
resource.update(:parent => self) if resource.kind_of?(Service)
end
end
def enforce_single_service_parent?
true
end
def add_to_service(parent_service)
parent_service.add_resource!(self)
end
def remove_from_service(parent_service)
update(:parent => nil)
parent_service.remove_resource(self)
end
def configuration_script
end
def set_automate_timeout(timeout, action = nil)
options[automate_timeout_key(action)] = timeout
save!
end
private
def update_attributes_from_dialog
Service::DialogProperties.parse(options[:dialog], evm_owner).each { |key, value| self[key] = value }
end
def automate_timeout_key(action)
action.nil? ? :automate_timeout : "#{action.downcase}_automate_timeout".to_sym
end
end