Skip to content

Commit

Permalink
Introduce Request and Task for firmware update
Browse files Browse the repository at this point in the history
We're introducing a new operation on physical server: firmware update.
We want to use state machine-driven Request for it because it's a long
running procedure and we want to be doing it on more than one server
with a single click (and then track its progress).

That being said - the PR contains a new Request class and a new
Task class (with internal state machine). The actual state machine
implementation is done in provider codebase - depending on class of
the selected physical server.

NOTE: PhysicalServerFirmwareUpdateRequest is implemented in a way that
each provider can decide whether to perform operation on one-by-one
server (SINGLE_TASK=false) or one-off (SINGLE_TASK=true). If one-off
option is chosen, then only a single task will be created for all
physical servers (options[:src_ids]) instead one task per server.

Signed-off-by: Miha Pleško <miha.plesko@xlab.si>
  • Loading branch information
miha-plesko committed May 30, 2019
1 parent a0be56d commit 8ee090d
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 16 deletions.
4 changes: 4 additions & 0 deletions app/models/manageiq/providers/physical_infra_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ def console_url
raise MiqException::Error, _("Console not supported")
end

def self.firmware_update_class
self::FirmwareUpdate
end

def self.display_name(number = 1)
n_('Physical Infrastructure Manager', 'Physical Infrastructure Managers', number)
end
Expand Down
5 changes: 5 additions & 0 deletions app/models/miq_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ class MiqRequest < ApplicationRecord
:PhysicalServerProvisionRequest => {
:provision_physical_server => N_("Physical Server Provision")
},
:PhysicalServerFirmwareUpdateRequest => {
:physical_server_firmware_update => N_("Physical Server Firmware Update")
},
:ServiceRetireRequest => {
:service_retire => N_("Service Retire")
},
Expand Down Expand Up @@ -456,6 +459,8 @@ def create_request_tasks
request_task_created = 0
requested_tasks.each do |idx|
req_task = create_request_task(idx)
next unless req_task

miq_request_tasks << req_task
req_task.deliver_to_automate
request_task_created += 1
Expand Down
43 changes: 43 additions & 0 deletions app/models/physical_server_firmware_update_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class PhysicalServerFirmwareUpdateRequest < MiqRequest
TASK_DESCRIPTION = 'Physical Server Firmware Update'.freeze
SOURCE_CLASS_NAME = 'PhysicalServer'.freeze

def description
'Physical Server Firmware Update'
end

def my_role(_action = nil)
'ems_operations'
end

def self.request_task_class
PhysicalServerFirmwareUpdateTask
end

def self.new_request_task(attribs)
source = source_physical_server(attribs[:source_id])
source.ext_management_system.class.firmware_update_class.new(attribs)
end

def create_request_task(idx)
return nil if single_task? && idx != options[:src_ids].first

super(idx)
end

def single_task?
@single_task ||= begin
source = self.class.source_physical_server(options[:src_ids].first)
source.ext_management_system.class.firmware_update_class::SINGLE_TASK
end
end

def self.source_physical_server(source_id)
PhysicalServer.find_by(:id => source_id).tap do |source|
raise MiqException::MiqProvisionError, "Unable to find source PhysicalServer with id [#{source_id}]" if source.nil?
if source.ext_management_system.nil?
raise MiqException::MiqProvisionError, "Source PhysicalServer with id [#{source_id}] has no EMS, unable to update firmware"
end
end
end
end
30 changes: 30 additions & 0 deletions app/models/physical_server_firmware_update_task.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class PhysicalServerFirmwareUpdateTask < MiqRequestTask
include_concern 'StateMachine'

validates :state, :inclusion => {
:in => %w[pending queued active firmware_updated finished],
:message => 'should be pending, queued, active, firmware_updated or finished'
}

AUTOMATE_DRIVES = false

def description
'Provision Physical Server'
end

def self.base_model
PhysicalServerProvisionTask
end

def do_request
signal :run_firmware_update
end

def self.request_class
PhysicalServerProvisionRequest
end

def self.display_name(number = 1)
n_('Firmware Update Task', 'Firmware Update Tasks', number)
end
end
36 changes: 36 additions & 0 deletions app/models/physical_server_firmware_update_task/state_machine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module PhysicalServerFirmwareUpdateTask::StateMachine
def run_firmware_update
raise MiqException::MiqFirmwareUpdateError, "Unable to find #{model_class} with id #{source_id.inspect}" if source.blank?

dump_obj(options, "MIQ(#{self.class.name}##{__method__}) options: ", $log, :info)
signal :start_firmware_update
end

def start_firmware_update
# Implement firmware update in subclass, user-defined values are stored in options field.
raise NotImplementedError, 'Must be implemented in subclass and signal :done_firmware_update when done'
end

def done_firmware_update
update_and_notify_parent(:message => msg('done updating firmware'))
signal :mark_as_completed
end

def mark_as_completed
update_and_notify_parent(:state => 'firmware_updated', :message => msg('firmware update completed'))
MiqEvent.raise_evm_event(source, 'generic_task_finish', :message => "Done updating firmware on PhysicalServer")
signal :finish
end

def finish
if status != 'Error'
_log.info("Executing provision task: [#{description}]... Complete")
else
_log.info("Executing provision task: [#{description}]... Errored")
end
end

def msg(txt)
"Updating firmware on PhysicalServer id=#{source.id}, name=#{source.name}, ems_ref=#{source.ems_ref}: #{txt}"
end
end
3 changes: 2 additions & 1 deletion spec/factories/miq_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
factory :miq_provision_request, :class => "MiqProvisionRequest" do
source { create(:miq_template) }
end
factory :physical_server_provision_request, :class => "PhysicalServerProvisionRequest"
factory :physical_server_provision_request, :class => "PhysicalServerProvisionRequest"
factory :physical_server_firmware_update_request, :class => "PhysicalServerFirmwareUpdateRequest"

factory :service_template_transformation_plan_request, :class => "ServiceTemplateTransformationPlanRequest" do
source { create(:service_template_transformation_plan) }
Expand Down
3 changes: 2 additions & 1 deletion spec/factories/miq_request_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
factory :miq_provision_openstack, :parent => :miq_provision_cloud, :class => "ManageIQ::Providers::Openstack::CloudManager::Provision"

# Physical Infrastructure
factory :physical_server_provision_task, :parent => :miq_provision, :class => "PhysicalServerProvisionTask"
factory :physical_server_provision_task, :parent => :miq_provision, :class => "PhysicalServerProvisionTask"
factory :physical_server_firmware_update_task, :parent => :miq_provision, :class => "PhysicalServerFirmwareUpdateTask"

# Automate
factory :automation_task, :parent => :miq_request_task, :class => "AutomationTask"
Expand Down
29 changes: 15 additions & 14 deletions spec/models/miq_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
context "CONSTANTS" do
it "REQUEST_TYPES" do
expected_request_types = {
:MiqProvisionRequest => {:template => "VM Provision", :clone_to_vm => "VM Clone", :clone_to_template => "VM Publish"},
:MiqProvisionRequestTemplate => {:template => "VM Provision Template"},
:MiqProvisionConfiguredSystemRequest => {:provision_via_foreman => "#{ui_lookup(:ui_title => 'foreman')} Provision"},
:VmReconfigureRequest => {:vm_reconfigure => "VM Reconfigure"},
:VmCloudReconfigureRequest => {:vm_cloud_reconfigure => "VM Cloud Reconfigure"},
:VmMigrateRequest => {:vm_migrate => "VM Migrate"},
:VmRetireRequest => {:vm_retire => "VM Retire"},
:ServiceRetireRequest => {:service_retire => "Service Retire"},
:OrchestrationStackRetireRequest => {:orchestration_stack_retire => "Orchestration Stack Retire"},
:AutomationRequest => {:automation => "Automation"},
:ServiceTemplateProvisionRequest => {:clone_to_service => "Service Provision"},
:ServiceReconfigureRequest => {:service_reconfigure => "Service Reconfigure"},
:PhysicalServerProvisionRequest => {:provision_physical_server => "Physical Server Provision"},
:ServiceTemplateTransformationPlanRequest => {:transformation_plan => "Transformation Plan"}
:MiqProvisionRequest => {:template => "VM Provision", :clone_to_vm => "VM Clone", :clone_to_template => "VM Publish"},
:MiqProvisionRequestTemplate => {:template => "VM Provision Template"},
:MiqProvisionConfiguredSystemRequest => {:provision_via_foreman => "#{ui_lookup(:ui_title => 'foreman')} Provision"},
:VmReconfigureRequest => {:vm_reconfigure => "VM Reconfigure"},
:VmCloudReconfigureRequest => {:vm_cloud_reconfigure => "VM Cloud Reconfigure"},
:VmMigrateRequest => {:vm_migrate => "VM Migrate"},
:VmRetireRequest => {:vm_retire => "VM Retire"},
:ServiceRetireRequest => {:service_retire => "Service Retire"},
:OrchestrationStackRetireRequest => {:orchestration_stack_retire => "Orchestration Stack Retire"},
:AutomationRequest => {:automation => "Automation"},
:ServiceTemplateProvisionRequest => {:clone_to_service => "Service Provision"},
:ServiceReconfigureRequest => {:service_reconfigure => "Service Reconfigure"},
:PhysicalServerProvisionRequest => {:provision_physical_server => "Physical Server Provision"},
:PhysicalServerFirmwareUpdateRequest => {:physical_server_firmware_update => "Physical Server Firmware Update"},
:ServiceTemplateTransformationPlanRequest => {:transformation_plan => "Transformation Plan"}
}

expect(described_class::REQUEST_TYPES).to eq(expected_request_types)
Expand Down
51 changes: 51 additions & 0 deletions spec/models/physical_server_firmware_update_request_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
describe PhysicalServerFirmwareUpdateRequest do
it '.TASK_DESCRIPTION' do
expect(described_class::TASK_DESCRIPTION).to eq('Physical Server Firmware Update')
end

it '.SOURCE_CLASS_NAME' do
expect(described_class::SOURCE_CLASS_NAME).to eq('PhysicalServer')
end

it '.request_task_class' do
expect(described_class.request_task_class).to eq(PhysicalServerFirmwareUpdateTask)
end

it '#description' do
expect(subject.description).to eq('Physical Server Firmware Update')
end

it '#my_role' do
expect(subject.my_role).to eq('ems_operations')
end

describe '.new_request_task' do
before do
allow(ems.class).to receive(:firmware_update_class).and_return(task)
end

let(:server) { FactoryBot.create(:physical_server, :ext_management_system => ems) }
let(:ems) { FactoryBot.create(:ems_physical_infra) }
let(:task) { double('TASK') }

context 'when source is ok' do
it do
expect(task).to receive(:new).with(:source_id => server.id)
described_class.new_request_task(:source_id => server.id)
end
end

context 'when source is missing' do
it do
expect { described_class.new_request_task(:source_id => 'missing') }.to raise_error(MiqException::MiqProvisionError)
end
end

context 'when source is lacking EMS' do
before { server.update!(:ext_management_system => nil) }
it do
expect { described_class.new_request_task(:source_id => server.id) }.to raise_error(MiqException::MiqProvisionError)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
describe PhysicalServerFirmwareUpdateTask do
let(:server) { FactoryBot.create(:physical_server) }

subject { described_class.new(:source => server) }

describe '#run_provision' do
context 'when missing source' do
let(:server) { nil }
it do
expect { subject.run_provision }.to raise_error(MiqException::MiqProvisionError)
end
end

context 'when ok' do
it do
expect(subject).to receive(:signal).with(:start_firmware_update)
subject.run_provision
end
end
end

it '#done_firmware_update' do
expect(subject).to receive(:signal).with(:mark_as_completed)
expect(subject).to receive(:update_and_notify_parent)
subject.done_firmware_update
end

it '#mark_as_completed' do
expect(subject).to receive(:signal).with(:finish)
expect(subject).to receive(:update_and_notify_parent)
subject.mark_as_completed
end

describe '#finish' do
before { allow(subject).to receive(:_log).and_return(log) }

let(:log) { double('LOG') }

context 'when task has errored' do
before { subject.update_attribute(:status, 'Error') }
it do
expect(log).to receive(:info).with(satisfy { |msg| msg.include?('Errored') })
subject.finish
end
end

context 'when task has completed' do
before { subject.update_attribute(:status, 'Ok') }
it do
expect(log).to receive(:info).with(satisfy { |msg| msg.include?('... Complete') })
subject.finish
end
end
end
end
13 changes: 13 additions & 0 deletions spec/models/physical_server_firmware_update_task_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
describe PhysicalServerFirmwareUpdateTask do
it '#description' do
expect(subject.description).to eq('Physical Server Firmware Update')
end

it '#model_class' do
expect(subject.model_class).to eq(PhysicalServer)
end

it '.request_class' do
expect(described_class.request_class).to eq(PhysicalServerFirmwareUpdateRequest)
end
end

0 comments on commit 8ee090d

Please sign in to comment.