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.

Signed-off-by: Miha Pleško <miha.plesko@xlab.si>
  • Loading branch information
miha-plesko committed Jul 11, 2019
1 parent e93bf1d commit 3fd59c8
Show file tree
Hide file tree
Showing 12 changed files with 302 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::FirmwareUpdateTask
end

def self.display_name(number = 1)
n_('Physical Infrastructure Manager', 'Physical Infrastructure Managers', number)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class ManageIQ::Providers::PhysicalInfraManager::FirmwareUpdateTask < PhysicalServerFirmwareUpdateTask
end
3 changes: 3 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
50 changes: 50 additions & 0 deletions app/models/physical_server_firmware_update_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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)
affected_ems(attribs).class.firmware_update_class.new(attribs)
end

def requested_task_idx
[-1] # we are only using one task per request not matter how many servers are affected
end

def self.affected_physical_servers(attribs)
ids = attribs.dig('options', :src_ids)
raise MiqException::MiqFirmwareUpdateError, 'At least one PhysicalServer is required' if ids&.empty?

PhysicalServer.where(:id => ids).tap do |servers|
unless servers.size == ids.size
raise MiqException::MiqFirmwareUpdateError, 'At least one PhysicalServer is missing'
end
unless servers.map(&:ems_id).uniq.size == 1
raise MiqException::MiqFirmwareUpdateError, 'All PhysicalServers need to belong to same EMS'
end
end
end

def self.affected_ems(attribs)
affected_physical_servers(attribs).first.ext_management_system
end

def affected_physical_servers
@affected_physical_servers ||= self.class.affected_physical_servers(attributes)
end

def affected_ems
@affected_ems ||= self.class.affected_ems(attributes)
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
'Physical Server Firmware Update'
end

def self.base_model
PhysicalServerFirmwareUpdateTask
end

def do_request
signal :run_firmware_update
end

def self.request_class
PhysicalServerFirmwareUpdateRequest
end

def self.display_name(number = 1)
n_('Firmware Update Task', 'Firmware Update Tasks', number)
end
end
34 changes: 34 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,34 @@
module PhysicalServerFirmwareUpdateTask::StateMachine
def run_firmware_update
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.
# Affected servers can be accessed via miq_request.affected_physical_servers
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 => 'finished', :message => msg('firmware update completed'))
signal :finish
end

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

def msg(txt)
"Updating firmware for PhysicalServer(s): #{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
93 changes: 93 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,93 @@
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

it '#requested_task_idx' do
expect(subject.requested_task_idx).to eq([-1])
end

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

let(:ems) { FactoryBot.create(:ems_physical_infra) }
let(:task) { double('TASK') }

it 'returns subclassed task' do
expect(described_class).to receive(:affected_ems).and_return(ems)
expect(task).to receive(:new).with('ATTRS')
described_class.new_request_task('ATTRS')
end
end

describe '.affected_physical_servers' do
let(:attrs) { { 'options' => {:src_ids => src_ids} } }
let(:server1) { FactoryBot.create(:physical_server, :ems_id => 1) }
let(:server2) { FactoryBot.create(:physical_server, :ems_id => 2) }
let(:server3) { FactoryBot.create(:physical_server, :ems_id => 2) }

context 'when no src_ids are given' do
let(:src_ids) { [] }
it 'handled error is raised' do
expect { described_class.affected_physical_servers(attrs) }.to raise_error(MiqException::MiqFirmwareUpdateError)
end
end

context 'when invalid src_ids are given' do
let(:src_ids) { ['invalid'] }
it 'handled error is raised' do
expect { described_class.affected_physical_servers(attrs) }.to raise_error(MiqException::MiqFirmwareUpdateError)
end
end

context 'when servers belong to different ems' do
let(:src_ids) { [server1.id, server2.id] }
it 'handled error is raised' do
expect { described_class.affected_physical_servers(attrs) }.to raise_error(MiqException::MiqFirmwareUpdateError)
end
end

context 'when all okay' do
let(:src_ids) { [server2.id, server3.id] }
it 'server list is returned' do
expect(described_class.affected_physical_servers(attrs)).to eq([server2, server3])
end
end
end

it '#affected_physical_servers' do
expect(described_class).to receive(:affected_physical_servers).and_return('RES')
expect(subject.affected_physical_servers).to eq('RES')
end

describe '.affected_ems' do
let(:ems) { FactoryBot.create(:ems_physical_infra) }
let(:server) { FactoryBot.create(:physical_server, :ems_id => ems.id) }

it 'when all okay' do
expect(described_class).to receive(:affected_physical_servers).and_return([server])
expect(described_class.affected_ems(nil)).to eq(ems)
end
end

it '#affected_ems' do
expect(described_class).to receive(:affected_ems).and_return('RES')
expect(subject.affected_ems).to eq('RES')
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
describe PhysicalServerFirmwareUpdateTask do
let(:server) { FactoryBot.create(:physical_server) }
let(:src_ids) { [server.id] }

subject { described_class.new(:options => { :src_ids => src_ids }) }

describe '#run_firmware_update' do
context 'when ok' do
it do
expect(subject).to receive(:signal).with(:start_firmware_update)
subject.run_firmware_update
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(: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(:status => 'Ok') }
it do
expect(log).to receive(:info).with(satisfy { |msg| msg.include?('... Complete') })
subject.finish
end
end
end
end
18 changes: 18 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,18 @@
describe PhysicalServerFirmwareUpdateTask do
it '#description' do
expect(subject.description).to eq('Physical Server Firmware Update')
end

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

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

it '#do_request' do
expect(subject).to receive(:signal).with(:run_firmware_update)
subject.do_request
end
end

0 comments on commit 3fd59c8

Please sign in to comment.