diff --git a/app/models/miq_request.rb b/app/models/miq_request.rb index 03f57cf437ba..1710d034a2ca 100644 --- a/app/models/miq_request.rb +++ b/app/models/miq_request.rb @@ -1,9 +1,14 @@ class MiqRequest < ApplicationRecord extend InterRegionApiMethodRelay - ACTIVE_STATES = %w(active queued) + ACTIVE_STATES = %w(active queued).freeze REQUEST_UNIQUE_KEYS = %w(id state status created_on updated_on type).freeze + CANCEL_STATE_REQUESTED = "cancel_requested".freeze + CANCEL_STATE_PROCESSING = "canceling".freeze + CANCEL_STATE_FINISHED = "canceled".freeze + CANCEL_STATES = [CANCEL_STATE_REQUESTED, CANCEL_STATE_PROCESSING, CANCEL_STATE_FINISHED].freeze + belongs_to :source, :polymorphic => true belongs_to :destination, :polymorphic => true belongs_to :requester, :class_name => "User" @@ -14,7 +19,7 @@ class MiqRequest < ApplicationRecord alias_attribute :state, :request_state - serialize :options, Hash + serialize :options, Hash default_value_for(:message) { |r| "#{r.class::TASK_DESCRIPTION} - Request Created" } default_value_for :options, {} @@ -23,8 +28,14 @@ class MiqRequest < ApplicationRecord default_value_for :status, 'Ok' default_value_for :process, true + include ReservedMixin + reserve_attribute :cancel_state, :string + validates_inclusion_of :approval_state, :in => %w(pending_approval approved denied), :message => "should be 'pending_approval', 'approved' or 'denied'" validates_inclusion_of :status, :in => %w(Ok Warn Error Timeout Denied) + validates :cancel_state, :inclusion => { :in => CANCEL_STATES, + :allow_nil => true, + :message => "should be one of #{CANCEL_STATES.join(", ")}" } validate :validate_class, :validate_request_type @@ -54,17 +65,18 @@ class MiqRequest < ApplicationRecord scope :created_recently, ->(days_ago) { where("miq_requests.created_on > ?", days_ago.days.ago) } scope :with_approval_state, ->(state) { where(:approval_state => state) } + scope :with_cancel_state, ->(state) { where(:cancel_state => state) } scope :with_type, ->(type) { where(:type => type) } scope :with_request_type, ->(type) { where(:request_type => type) } scope :with_requester, ->(id) { where(:requester_id => User.with_same_userid(id).collect(&:id)) } MODEL_REQUEST_TYPES = { - :Automate => { + :Automate => { :AutomationRequest => { :automation => N_("Automation") } }, - :Service => { + :Service => { :MiqProvisionConfiguredSystemRequest => { :provision_via_foreman => N_("%{config_mgr_type} Provision") % {:config_mgr_type => ui_lookup(:ui_title => 'foreman')} }, @@ -199,8 +211,7 @@ def build_request_event(event_name) ) {'EventStream::event_stream' => event_obj.id, - :event_stream_id => event_obj.id - } + :event_stream_id => event_obj.id} end def call_automate_event(event_name, synchronous: false) @@ -432,6 +443,11 @@ def execute end def create_request_tasks + if cancel_requested? + do_cancel + return + end + # Quota denial will result in automate_event_failed? being true return if automate_event_failed?("request_starting") @@ -589,12 +605,30 @@ def cancel raise _("Cancel operation is not supported for #{self.class.name}") end + def cancel_requested? + cancel_state == CANCEL_STATE_REQUESTED + end + def canceling? - false + cancel_state == CANCEL_STATE_PROCESSING + end + + def canceled? + cancel_state == CANCEL_STATE_FINISHED end private + def do_cancel + update_attributes(:cancel_state => CANCEL_STATE_PROCESSING) + cancel_cleanup + update_attributes(:cancel_state => CANCEL_STATE_FINISHED, :request_state => "finished", :status => "Error", :message => "Request is canceled by user.") + _log.info("Request #{description} is canceled by user.") + end + + def cancel_cleanup + end + def clean_up_keys_for_request_task req_task_attributes = attributes.dup (req_task_attributes.keys - MiqRequestTask.column_names + REQUEST_UNIQUE_KEYS).each { |key| req_task_attributes.delete(key) } diff --git a/app/models/miq_request_task.rb b/app/models/miq_request_task.rb index d09f0747c47a..d4455f9e4fc7 100644 --- a/app/models/miq_request_task.rb +++ b/app/models/miq_request_task.rb @@ -24,6 +24,17 @@ class MiqRequestTask < ApplicationRecord include MiqRequestMixin include TenancyMixin + include ReservedMixin + reserve_attribute :cancel_state, :string + + CANCEL_STATE_REQUESTED = "cancel_requested".freeze + CANCEL_STATE_PROCESSING = "canceling".freeze + CANCEL_STATE_FINISHED = "canceled".freeze + CANCEL_STATES = [CANCEL_STATE_REQUESTED, CANCEL_STATE_PROCESSING, CANCEL_STATE_FINISHED].freeze + + validates :cancel_state, :inclusion => { :in => CANCEL_STATES, + :allow_nil => true, + :message => "should be one of #{CANCEL_STATES.join(", ")}" } def approved? if miq_request.class.name.include?('Template') && miq_request_task @@ -208,8 +219,16 @@ def cancel raise _("Cancel operation is not supported for #{self.class.name}") end + def cancel_requested? + cancel_state == MiqRequestTask::CANCEL_STATE_REQUESTED + end + def canceling? - false + cancel_state == MiqRequestTask::CANCEL_STATE_PROCESSING + end + + def canceled? + cancel_state == MiqRequestTask::CANCEL_STATE_FINISHED end private diff --git a/app/models/service_template_provision_request.rb b/app/models/service_template_provision_request.rb index 87fedbf22b61..7ccf8f062665 100644 --- a/app/models/service_template_provision_request.rb +++ b/app/models/service_template_provision_request.rb @@ -25,6 +25,11 @@ class ServiceTemplateProvisionRequest < MiqRequest include MiqProvisionQuotaMixin def process_service_order + if cancel_requested? + do_cancel + return + end + case options[:cart_state] when ServiceOrder::STATE_ORDERED ServiceOrder.order_immediately(self, requester) diff --git a/app/models/service_template_transformation_plan_request.rb b/app/models/service_template_transformation_plan_request.rb index 2026f136a4ac..7e18a48142ea 100644 --- a/app/models/service_template_transformation_plan_request.rb +++ b/app/models/service_template_transformation_plan_request.rb @@ -25,12 +25,7 @@ def approve_vm(vm_id) end def cancel - options['cancel_requested'] = true - save! + update_attributes(:cancel_state => MiqRequest::CANCEL_STATE_REQUESTED) miq_request_tasks.each(&:cancel) end - - def canceling? - options['cancel_requested'] - end end diff --git a/app/models/service_template_transformation_plan_task.rb b/app/models/service_template_transformation_plan_task.rb index ba3db4794723..3f8396921fdc 100644 --- a/app/models/service_template_transformation_plan_task.rb +++ b/app/models/service_template_transformation_plan_task.rb @@ -103,12 +103,7 @@ def transformation_log_queue(userid = nil) end def cancel - options['cancel_requested'] = true - save! - end - - def canceling? - options['cancel_requested'] + update_attributes(:cancel_state => MiqRequestTask::CANCEL_STATE_REQUESTED) end private diff --git a/spec/models/service_template_transformation_plan_request_spec.rb b/spec/models/service_template_transformation_plan_request_spec.rb index 69887b9b657b..663d137358c5 100644 --- a/spec/models/service_template_transformation_plan_request_spec.rb +++ b/spec/models/service_template_transformation_plan_request_spec.rb @@ -38,4 +38,24 @@ expect(ServiceResource.find_by(:resource => vms[0]).status).to eq(ServiceResource::STATUS_APPROVED) end end + + context "gets cancel request" do + it "gets cancel_requested request" do + request.cancel + expect(request.cancel_state).to eq(MiqRequest::CANCEL_STATE_REQUESTED) + expect(request.cancel_requested?).to be_truthy + expect(request.canceling?).to be_falsey + expect(request.canceled?).to be_falsey + end + + it "calls do_cancel" do + request.cancel + + request.send(:do_cancel) + expect(request.cancel_state).to eq(MiqRequest::CANCEL_STATE_FINISHED) + expect(request.request_state).to eq('finished') + expect(request.status).to eq('Error') + expect(request.message).to eq('Request is canceled by user.') + end + end end diff --git a/spec/models/service_template_transformation_plan_task_spec.rb b/spec/models/service_template_transformation_plan_task_spec.rb index 12130dcdf10d..dad1727fe007 100644 --- a/spec/models/service_template_transformation_plan_task_spec.rb +++ b/spec/models/service_template_transformation_plan_task_spec.rb @@ -196,5 +196,13 @@ expect(vm).to be_is_tagged_with("migrated", :ns => "/managed", :cat => "transformation_status") end end + + describe '#cancel' do + it 'catches cancel state' do + task.cancel + expect(task.cancel_state).to eq(MiqRequestTask::CANCEL_STATE_REQUESTED) + expect(task.cancel_requested?).to be_truthy + end + end end end