diff --git a/app/controllers/api/instances_controller.rb b/app/controllers/api/instances_controller.rb index d2f94f3df9..0c4a199d25 100644 --- a/app/controllers/api/instances_controller.rb +++ b/app/controllers/api/instances_controller.rb @@ -4,6 +4,7 @@ class InstancesController < BaseController include Subcollections::LoadBalancers include Subcollections::SecurityGroups include Subcollections::Snapshots + extend Api::Mixins::CentralAdmin DEFAULT_ROLE = 'ems_operations'.freeze @@ -29,6 +30,7 @@ def stop_resource(type, id = nil, _data = nil) result end end + central_admin :stop_resource, :stop def start_resource(type, id = nil, _data = nil) raise BadRequestError, "Must specify an id for starting a #{type} resource" unless id @@ -42,6 +44,7 @@ def start_resource(type, id = nil, _data = nil) result end end + central_admin :start_resource, :start def pause_resource(type, id = nil, _data = nil) raise BadRequestError, "Must specify an id for pausing a #{type} resource" unless id @@ -68,6 +71,7 @@ def suspend_resource(type, id = nil, _data = nil) result end end + central_admin :suspend_resource, :suspend def shelve_resource(type, id = nil, _data = nil) raise BadRequestError, "Must specify an id for shelving a #{type} resource" unless id @@ -94,6 +98,7 @@ def reboot_guest_resource(type, id = nil, _data = nil) result end end + central_admin :reboot_guest_resource, :reboot_guest def reset_resource(type, id = nil, _data = nil) raise BadRequestError, "Must specify an id for resetting a #{type} resource" unless id @@ -107,6 +112,7 @@ def reset_resource(type, id = nil, _data = nil) result end end + central_admin :reset_resource, :reset private diff --git a/app/controllers/api/mixins/central_admin.rb b/app/controllers/api/mixins/central_admin.rb new file mode 100644 index 0000000000..1820038b97 --- /dev/null +++ b/app/controllers/api/mixins/central_admin.rb @@ -0,0 +1,38 @@ +module Api + module Mixins + module CentralAdmin + def self.extended(klass) + unless klass.const_defined?("MethodRelay") + method_relay = klass.const_set("MethodRelay", Module.new) + klass.prepend(method_relay) + end + end + + def central_admin(method, action = method) + const_get("MethodRelay").class_eval do + define_method(method) do |*meth_args, &meth_block| + api_args = yield(*meth_args) if block_given? + + type, id, _rest = meth_args + + if ApplicationRecord.id_in_current_region?(id.to_i) + super(*meth_args, &meth_block) + else + region_number = ApplicationRecord.id_to_region(id) + Api::Mixins::CentralAdmin.inter_region_call(region_number, @req.subject.to_sym, action, api_args, id).tap do |response| + add_href_to_result(response, type, id) + add_task_to_result(response, response["task_href"].split("api/tasks/").last) if response["task_href"] + end + end + end + end + end + + def self.inter_region_call(region, collection, action, api_args, id) + InterRegionApiMethodRelay.exec_api_call(region, collection, action, api_args, id) + rescue InterRegionApiMethodRelay::InterRegionApiMethodRelayError => error + {"success" => false, "message" => error.message} + end + end + end +end diff --git a/app/controllers/api/vms_controller.rb b/app/controllers/api/vms_controller.rb index 1c5991cbaf..ffe50a170d 100644 --- a/app/controllers/api/vms_controller.rb +++ b/app/controllers/api/vms_controller.rb @@ -9,6 +9,7 @@ class VmsController < BaseController include Subcollections::Software include Subcollections::Snapshots include Subcollections::MetricRollups + extend Api::Mixins::CentralAdmin VALID_EDIT_ATTRS = %w(description child_resources parent_resource).freeze RELATIONSHIP_COLLECTIONS = %w(vms templates).freeze @@ -26,6 +27,7 @@ def start_resource(type, id = nil, _data = nil) result end end + central_admin :start_resource, :start def stop_resource(type, id = nil, _data = nil) raise BadRequestError, "Must specify an id for stopping a #{type} resource" unless id @@ -39,6 +41,7 @@ def stop_resource(type, id = nil, _data = nil) result end end + central_admin :stop_resource, :stop def suspend_resource(type, id = nil, _data = nil) raise BadRequestError, "Must specify an id for suspending a #{type} resource" unless id @@ -52,6 +55,7 @@ def suspend_resource(type, id = nil, _data = nil) result end end + central_admin :suspend_resource, :suspend def pause_resource(type, id = nil, _data = nil) raise BadRequestError, "Must specify an id for pausing a #{type} resource" unless id @@ -188,6 +192,7 @@ def reset_resource(type, id = nil, _data = nil) result end end + central_admin :reset_resource, :reset def reboot_guest_resource(type, id = nil, _data = nil) raise BadRequestError, "Must specify an id for rebooting a #{type} resource" unless id @@ -201,6 +206,7 @@ def reboot_guest_resource(type, id = nil, _data = nil) result end end + central_admin :reboot_guest_resource, :reboot_guest def shutdown_guest_resource(type, id = nil, _data = nil) raise BadRequestError, "Must specify an id for shutting down a #{type} resource" unless id @@ -214,6 +220,7 @@ def shutdown_guest_resource(type, id = nil, _data = nil) result end end + central_admin :shutdown_guest_resource, :shutdown_guest def refresh_resource(type, id = nil, _data = nil) raise BadRequestError, "Must specify an id for refreshing a #{type} resource" unless id diff --git a/spec/requests/instances_spec.rb b/spec/requests/instances_spec.rb index ab4a0eff8e..69603d46fd 100644 --- a/spec/requests/instances_spec.rb +++ b/spec/requests/instances_spec.rb @@ -17,7 +17,7 @@ def update_raw_power_state(state, *instances) let(:instance_url) { api_instance_url(nil, instance) } let(:instance1_url) { api_instance_url(nil, instance1) } let(:instance2_url) { api_instance_url(nil, instance2) } - let(:invalid_instance_url) { api_instance_url(nil, 999_999) } + let(:invalid_instance_url) { api_instance_url(nil, ApplicationRecord.id_in_region(999_999, ApplicationRecord.my_region_number)) } let(:instances_list) { [instance1_url, instance2_url] } let(:instance_guid) { instance.guid } @@ -843,4 +843,14 @@ def update_raw_power_state(state, *instances) expect(response.parsed_body).to include(expected) end end + + describe "/api/instances central admin" do + let(:resource_type) { "instance" } + + include_examples "resource power operations", :vm_amazon, :reboot_guest + include_examples "resource power operations", :vm_amazon, :reset + include_examples "resource power operations", :vm_amazon, :start + include_examples "resource power operations", :vm_amazon, :stop + include_examples "resource power operations", :vm_amazon, :suspend + end end diff --git a/spec/requests/vms_spec.rb b/spec/requests/vms_spec.rb index f8b0816244..5f39cd758f 100644 --- a/spec/requests/vms_spec.rb +++ b/spec/requests/vms_spec.rb @@ -22,7 +22,7 @@ let(:vm_guid) { vm.guid } let(:vm_url) { api_vm_url(nil, vm) } - let(:invalid_vm_url) { api_vm_url(nil, 999_999) } + let(:invalid_vm_url) { api_vm_url(nil, ApplicationRecord.id_in_region(999_999, ApplicationRecord.my_region_number)) } def update_raw_power_state(state, *vms) vms.each { |vm| vm.update_attributes!(:raw_power_state => state) } @@ -1819,4 +1819,15 @@ def update_raw_power_state(state, *vms) expect(response).to have_http_status(:forbidden) end end + + describe "/api/vms central admin" do + let(:resource_type) { "vm" } + + include_examples "resource power operations", :vm_vmware, :reboot_guest + include_examples "resource power operations", :vm_vmware, :reset + include_examples "resource power operations", :vm_vmware, :shutdown_guest + include_examples "resource power operations", :vm_vmware, :start + include_examples "resource power operations", :vm_vmware, :stop + include_examples "resource power operations", :vm_vmware, :suspend + end end diff --git a/spec/support/shared_examples/resource_power_operations.rb b/spec/support/shared_examples/resource_power_operations.rb new file mode 100644 index 0000000000..96ab64af68 --- /dev/null +++ b/spec/support/shared_examples/resource_power_operations.rb @@ -0,0 +1,20 @@ +shared_examples "resource power operations" do |factory, operation| + let!(:resource) { FactoryGirl.create(factory, :id => ApplicationRecord.id_in_region(1, region_remote.region)) } + let(:api_client_collection) { double("/api/#{resource_type.pluralize}") } + let(:api_client_connection) { double("ApiClient", :instances => api_client_collection) } + let(:api_resource) { double(resource_type) } + let(:operation) { operation } + let(:region_remote) { FactoryGirl.create(:miq_region) } + let(:url) { send("api_#{resource_type}_url", nil, resource) } + + it operation.to_s do + api_basic_authorize(action_identifier(resource_type.pluralize.to_sym, operation)) + + expect(api_client_connection).to receive(resource_type.pluralize).and_return(api_client_collection) + expect(InterRegionApiMethodRelay).to receive(:api_client_connection_for_region).with(region_remote.region).and_return(api_client_connection) + expect(api_client_collection).to receive(:find).with(resource.id).and_return(api_resource) + expect(api_resource).to receive(operation) + + post(url, :params => gen_request(operation)) + end +end