From eac5d1f70c44fc8a74494a1584434b11ed43b7e0 Mon Sep 17 00:00:00 2001 From: Lucy Fu Date: Thu, 5 Oct 2017 15:29:19 -0400 Subject: [PATCH 1/2] Add Service::LinkingWorkflow. --- app/models/service/linking_workflow.rb | 80 +++++++++++ spec/models/service/linking_workflow_spec.rb | 135 +++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 app/models/service/linking_workflow.rb create mode 100644 spec/models/service/linking_workflow_spec.rb diff --git a/app/models/service/linking_workflow.rb b/app/models/service/linking_workflow.rb new file mode 100644 index 00000000000..9bd5b8347b5 --- /dev/null +++ b/app/models/service/linking_workflow.rb @@ -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') + 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 diff --git a/spec/models/service/linking_workflow_spec.rb b/spec/models/service/linking_workflow_spec.rb new file mode 100644 index 00000000000..228796c7d09 --- /dev/null +++ b/spec/models/service/linking_workflow_spec.rb @@ -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) } + + 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 From 4146352ad9549aed7ca539cd859b51b748f8a187 Mon Sep 17 00:00:00 2001 From: Lucy Fu Date: Mon, 2 Oct 2017 09:16:10 -0400 Subject: [PATCH 2/2] Add Service::ResourceLinking. --- app/models/service.rb | 1 + app/models/service/resource_linking.rb | 28 ++++++++++++++++++++ spec/models/service/resource_linking_spec.rb | 19 +++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 app/models/service/resource_linking.rb create mode 100644 spec/models/service/resource_linking_spec.rb diff --git a/app/models/service.rb b/app/models/service.rb index ed73661711c..37d76e0d7e7 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -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 diff --git a/app/models/service/resource_linking.rb b/app/models/service/resource_linking.rb new file mode 100644 index 00000000000..4544c78b2aa --- /dev/null +++ b/app/models/service/resource_linking.rb @@ -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 diff --git a/spec/models/service/resource_linking_spec.rb b/spec/models/service/resource_linking_spec.rb new file mode 100644 index 00000000000..eb208ade25b --- /dev/null +++ b/spec/models/service/resource_linking_spec.rb @@ -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