Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Service resource linking. #16082

Merged
merged 2 commits into from
Oct 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/models/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class Service < ApplicationRecord

include_concern 'RetirementManagement'
include_concern 'Aggregation'
include_concern 'ResourceLinking'

virtual_column :has_parent, :type => :boolean
virtual_column :power_state, :type => :string
Expand Down
80 changes: 80 additions & 0 deletions app/models/service/linking_workflow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
class Service
class LinkingWorkflow < ManageIQ::Providers::EmsRefreshWorkflow
def load_transitions
self.state ||= 'initialize'

{
:initializing => {'initialize' => 'waiting_to_start'},
:start => {'waiting_to_start' => 'running'},
:refresh => {'running' => 'refreshing'},
:poll_refresh => {'refreshing' => 'refreshing'},
:post_refresh => {
'running' => 'post_refreshing',
'refreshing' => 'post_refreshing'
},
:finish => {'*' => 'finished'},
:abort_job => {'*' => 'aborting'},
:cancel => {'*' => 'canceling'},
:error => {'*' => '*'}
}
end

def run_native_op
_log.info("Enter")

unless linking_service
msg = "Job [%{id}] [%{name}] aborted: didn't find service ID: [%{service_id}] to link to" % {
:id => id, :name => name, :service_id => options[:service_id]
}
_log.error(msg)
signal(:abort, msg, 'error')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see :abort, but only :abort_job or :error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind. I saw it in the state_machine mixin.

end

unless target_entity
msg = "Job [%{id}] [%{name}] aborted: didn't find provider class: [%{target_class}] ID: [%{target_id}] to refresh" % {
:id => id, :name => name, :target_class => target_class, :target_id => target_id
}
_log.error(msg)
signal(:abort, msg, 'error')
end

if find_all_targets?
set_status("all VMs are found in DB")
signal(:post_refresh)
else
set_status("calling refresh")
queue_signal(:refresh)
end
end
alias_method :start, :run_native_op

def post_refresh
_log.info("Enter")

found_vms = linking_targets
not_found_vms = options[:uid_ems_array] - found_vms.pluck(:uid_ems)
_log.warn("VMs not found for linking to service ID [#{service.id}], name [#{service.name}]: #{not_found_vms}") unless not_found_vms.blank?

service = linking_service
found_vms.each { |vm| service.add_resource!(vm) }
signal(:finish, "linking VMs to service is complete", "ok")
rescue => err
_log.log_backtrace(err)
signal(:abort, err.message, "error")
end

private

def find_all_targets?
linking_targets.length == options[:uid_ems_array].length
end

def linking_targets
@linking_targets ||= VmOrTemplate.where(:uid_ems => options[:uid_ems_array], :ems_id => target_id)
end

def linking_service
@linking_service ||= Service.find_by(:id => options[:service_id])
end
end
end
28 changes: 28 additions & 0 deletions app/models/service/resource_linking.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Service::ResourceLinking
extend ActiveSupport::Concern

def add_provider_vms(provider, uid_ems_array)
vm_uid_array = Array(uid_ems_array).compact.uniq
raise _("no uid_ems_array defined for linking to service") if vm_uid_array.blank?

options = {
:target_class => provider.class.name,
:target_id => provider.id,
:uid_ems_array => vm_uid_array,
:name => "Linking VMs to service #{name} ID: [#{id}]",
:userid => evm_owner.userid,
:sync_key => guid,
:service_id => id,
:zone => my_zone
}

_log.info("NAME [#{options[:name]}] for user #{evm_owner.userid}")

Service::LinkingWorkflow.create_job(options).tap do |job|
job.signal(:start)
end
rescue => err
_log.log_backtrace(err)
raise
end
end
135 changes: 135 additions & 0 deletions spec/models/service/linking_workflow_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
describe Service::LinkingWorkflow do
let(:service) { FactoryGirl.create(:service) }
let(:provider) { FactoryGirl.create(:ems_vmware) }
let(:uid_ems_array) { ["423c9963-378c-813f-1dbd-630e464d59d4", "423cf3e2-e319-3953-993f-fd8513db951d"] }
let(:options) do
{
:target_class => provider.class.name,
:target_id => provider.id,
:uid_ems_array => uid_ems_array,
:service_id => service.id
}
end
let(:job) { described_class.create_job(options) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the job be automatically deleted after test?


context 'run_native_op' do
subject { job.run_native_op }

it 'raises an error if service is not found' do
options[:service_id] = 999
expect(job).to receive(:signal).with(:abort, "Job [#{job.id}] [#{job.name}] aborted: didn't find service ID: [999] to link to", "error")
subject
end

it 'raises an error if provider is not found' do
options[:target_id] = 999
msg = "Job [#{job.id}] [#{job.name}] aborted: didn't find provider class: [#{provider.class.name}] ID: [999] to refresh"
expect(job).to receive(:signal).with(:abort, msg, "error")
subject
end

it 'calls refresh if not all VMs found in DB' do
expect(job).to receive(:queue_signal).with(:refresh)
subject
end

it 'calls post_refresh if all VMs found in DB' do
uid_ems_array.each { |uid| FactoryGirl.create(:vm_vmware, :uid_ems => uid, :ems_id => provider.id) }
expect(job).to receive(:signal).with(:post_refresh)
subject
end
end

context 'post_refresh' do
subject { job.post_refresh }

it 'links found VMs to service' do
uid_ems_array.each { |uid| FactoryGirl.create(:vm_vmware, :uid_ems => uid, :ems_id => provider.id) }
subject
expect(service.vms.count).to eq(2)
end
end

context 'state transitions' do
%w(start refresh poll_refresh post_refresh finish abort_job cancel error).each do |signal|
shared_examples_for "allows #{signal} signal" do
it signal.to_s do
expect(job).to receive(signal.to_sym)
job.signal(signal.to_sym)
end
end
end

%w(start refresh poll_refresh post_refresh).each do |signal|
shared_examples_for "doesn't allow #{signal} signal" do
it signal.to_s do
expect { job.signal(signal.to_sym) }.to raise_error(RuntimeError, /#{signal} is not permitted at state #{job.state}/)
end
end
end

context 'waiting_to_start' do
before do
job.state = 'waiting_to_start'
end

it_behaves_like 'allows start signal'
it_behaves_like 'allows finish signal'
it_behaves_like 'allows abort_job signal'
it_behaves_like 'allows cancel signal'
it_behaves_like 'allows error signal'

it_behaves_like 'doesn\'t allow refresh signal'
it_behaves_like 'doesn\'t allow poll_refresh signal'
it_behaves_like 'doesn\'t allow post_refresh signal'
end

context 'running' do
before do
job.state = 'running'
end

it_behaves_like 'allows refresh signal'
it_behaves_like 'allows post_refresh signal'
it_behaves_like 'allows finish signal'
it_behaves_like 'allows abort_job signal'
it_behaves_like 'allows cancel signal'
it_behaves_like 'allows error signal'

it_behaves_like 'doesn\'t allow start signal'
it_behaves_like 'doesn\'t allow poll_refresh signal'
end

context 'refreshing' do
before do
job.state = 'refreshing'
end

it_behaves_like 'allows poll_refresh signal'
it_behaves_like 'allows post_refresh signal'
it_behaves_like 'allows finish signal'
it_behaves_like 'allows abort_job signal'
it_behaves_like 'allows cancel signal'
it_behaves_like 'allows error signal'

it_behaves_like 'doesn\'t allow start signal'
it_behaves_like 'doesn\'t allow refresh signal'
end

context 'post_refreshing' do
before do
job.state = 'post_refreshing'
end

it_behaves_like 'allows finish signal'
it_behaves_like 'allows abort_job signal'
it_behaves_like 'allows cancel signal'
it_behaves_like 'allows error signal'

it_behaves_like 'doesn\'t allow start signal'
it_behaves_like 'doesn\'t allow refresh signal'
it_behaves_like 'doesn\'t allow poll_refresh signal'
it_behaves_like 'doesn\'t allow post_refresh signal'
end
end
end
19 changes: 19 additions & 0 deletions spec/models/service/resource_linking_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
describe Service do
describe '#add_provider_vms' do
let(:service) { FactoryGirl.create(:service, :evm_owner => FactoryGirl.create(:user)) }
let(:provider) { FactoryGirl.create(:ems_vmware) }
let(:uid_ems_array) { ["423c9963-378c-813f-1dbd-630e464d59d4", "423cf3e2-e319-3953-993f-fd8513db951d"] }

it 'raises an error if uid_ems_array is not passed in' do
expect { service.add_provider_vms(provider, []) }.to raise_error(RuntimeError, "no uid_ems_array defined for linking to service")
end

it 'creates a Service::LinkingWorkflow job' do
expect(Service::LinkingWorkflow).to receive(:create_job) do |args|
expect(args).to match(hash_including(:target_class => provider.class.name, :target_id => provider.id))
expect(args).to match(hash_including(:uid_ems_array => array_including(uid_ems_array)))
end.and_return(double(:signal => :start))
service.add_provider_vms(provider, uid_ems_array)
end
end
end