diff --git a/app/models/manageiq/providers/redhat/infra_manager/api_integration.rb b/app/models/manageiq/providers/redhat/infra_manager/api_integration.rb index bf489e45cbe..27030654145 100644 --- a/app/models/manageiq/providers/redhat/infra_manager/api_integration.rb +++ b/app/models/manageiq/providers/redhat/infra_manager/api_integration.rb @@ -1,11 +1,24 @@ module ManageIQ::Providers::Redhat::InfraManager::ApiIntegration extend ActiveSupport::Concern + require 'ovirtsdk4' + + included do + process_api_features_support + end + + def supported_features + @supported_features ||= supported_api_versions.collect { |version| self.class.api_features[version] }.flatten.uniq + end + def connect(options = {}) raise "no credentials defined" if self.missing_credentials?(options[:auth_type]) - + version = options[:version] || 3 + unless options[:skip_supported_api_validation] || supports_the_api_version?(version) + raise "version #{version} of the api is not supported by the provider" + end # If there is API path stored in the endpoints table and use it: - path = default_endpoint.path + path = options[:path] || default_endpoint.path _log.info("Using stored API path '#{path}'.") unless path.blank? server = options[:ip] || address @@ -13,11 +26,10 @@ def connect(options = {}) username = options[:user] || authentication_userid(options[:auth_type]) password = options[:pass] || authentication_password(options[:auth_type]) service = options[:service] || "Service" - version = options[:version] || 3 # Create the underlying connection according to the version of the oVirt API requested by # the caller: - connect_method = version == 4 ? :raw_connect_v4 : :raw_connect_v3 + connect_method = "raw_connect_v#{version}".to_sym connection = self.class.public_send(connect_method, server, port, path, username, password, service) # Copy the API path to the endpoints table: @@ -30,6 +42,30 @@ def supports_port? true end + def supported_api_versions + supported_api_versions_from_cache + end + + def supported_api_versions_from_cache + Cacher.new(cache_key).fetch_fresh(last_refresh_date) { supported_api_verions_from_sdk } + end + + def cache_key + "REDHAT_EMS_CACHE_KEY_#{id}" + end + + def supported_api_verions_from_sdk + username = authentication_userid(:basic) + password = authentication_password(:basic) + probe_args = { :hostname => hostname, :port => port, :username => username, :password => password } + probe_results = OvirtSDK4::Probe.probe(probe_args) + probe_results.map(&:version) if probe_results + end + + def supports_the_api_version?(version) + supported_api_versions.include?(version) + end + def supported_auth_types %w(default metrics) end @@ -142,9 +178,9 @@ def verify_credentials(auth_type = nil, options = {}) def history_database_name @history_database_name ||= begin - version = version_3_0? ? '3_0' : '>3_0' - self.class.history_database_name_for(version) - end + version = version_3_0? ? '3_0' : '>3_0' + self.class.history_database_name_for(version) + end end def version_3_0? @@ -170,8 +206,31 @@ def with_disk_attachments_service(vm) end class_methods do + def api3_supported_features + [] + end + + def api4_supported_features + [:snapshots] + end + + def api_features + { 3 => api3_supported_features, 4 => api4_supported_features } + end + + def process_api_features_support + all_features = api_features.values.flatten.uniq + all_features.each do |feature| + supports feature do + unless supported_features.include?(feature) + unsupported_reason_add(feature, _("This feature is not supported by the api version of the provider")) + end + end + end + end + # Connect to the engine using version 4 of the API and the `ovirt-engine-sdk` gem. - def raw_connect_v4(server, port, path, username, password, service) + def raw_connect_v4(server, port, path, username, password, service, scheme = 'https') require 'ovirtsdk4' # Get the timeout from the configuration: @@ -179,7 +238,7 @@ def raw_connect_v4(server, port, path, username, password, service) # Create the connection: OvirtSDK4::Connection.new( - :url => "https://#{server}:#{port}#{path}", + :url => "#{scheme}://#{server}:#{port}#{path}", :username => username, :password => password, :timeout => timeout, @@ -233,4 +292,30 @@ def extract_ems_ref_id(href) href && href.split("/").last end end + + class Cacher + attr_reader :key + + def initialize(key) + @key = key + end + + def fetch_fresh(last_refresh_time) + force = stale_cache?(last_refresh_time) + res = Rails.cache.fetch(key, force: force) { build_entry { yield } } + res[:value] + end + + private + + def build_entry + {:created_at => Time.now.utc, :value => yield} + end + + def stale_cache?(last_refresh_time) + current_val = Rails.cache.read(key) + return true unless current_val && current_val[:created_at] && last_refresh_time + last_refresh_time > current_val[:created_at] + end + end end diff --git a/app/models/mixins/supports_feature_mixin.rb b/app/models/mixins/supports_feature_mixin.rb index 51c8bea75fa..aaf788f5312 100644 --- a/app/models/mixins/supports_feature_mixin.rb +++ b/app/models/mixins/supports_feature_mixin.rb @@ -81,6 +81,8 @@ module SupportsFeatureMixin :resize => 'Resizing', :retire => 'Retirement', :smartstate_analysis => 'Smartstate Analaysis', + :snapshots => 'Snapshots', + :terminate => 'Terminate a VM', }.freeze # Whenever this mixin is included we define all features as unsupported by default. diff --git a/spec/controllers/ems_infra_controller_spec.rb b/spec/controllers/ems_infra_controller_spec.rb index 099ca5685a3..efaa319c02b 100644 --- a/spec/controllers/ems_infra_controller_spec.rb +++ b/spec/controllers/ems_infra_controller_spec.rb @@ -631,6 +631,8 @@ allow(controller).to receive(:check_privileges).and_return(true) allow(controller).to receive(:assert_privileges).and_return(true) login_as FactoryGirl.create(:user, :features => "ems_infra_new") + allow_any_instance_of(ManageIQ::Providers::Redhat::InfraManager) + .to receive(:supported_api_versions).and_return([3, 4]) end render_views diff --git a/spec/models/manageiq/providers/redhat/infra_manager/provision/configuration/network_spec.rb b/spec/models/manageiq/providers/redhat/infra_manager/provision/configuration/network_spec.rb index 4f45e1ea3b6..77543b4cccc 100644 --- a/spec/models/manageiq/providers/redhat/infra_manager/provision/configuration/network_spec.rb +++ b/spec/models/manageiq/providers/redhat/infra_manager/provision/configuration/network_spec.rb @@ -31,6 +31,8 @@ allow(rhevm_vm).to receive_messages(:nics => [rhevm_nic1, rhevm_nic2]) allow(Ovirt::Cluster).to receive_messages(:find_by_href => rhevm_cluster) + allow_any_instance_of(ManageIQ::Providers::Redhat::InfraManager).to receive(:supported_api_versions) + .and_return([3, 4]) end context "#configure_network_adapters" do diff --git a/spec/models/manageiq/providers/redhat/infra_manager/refresher_3_0_spec.rb b/spec/models/manageiq/providers/redhat/infra_manager/refresher_3_0_spec.rb index 5996d91ee5e..20e0a89ba6f 100644 --- a/spec/models/manageiq/providers/redhat/infra_manager/refresher_3_0_spec.rb +++ b/spec/models/manageiq/providers/redhat/infra_manager/refresher_3_0_spec.rb @@ -3,6 +3,7 @@ guid, server, zone = EvmSpecHelper.create_guid_miq_server_zone @ems = FactoryGirl.create(:ems_redhat, :zone => zone, :hostname => "192.168.252.231", :ipaddress => "192.168.252.231", :port => 8443) @ems.update_authentication(:default => {:userid => "evm@manageiq.com", :password => "password"}) + allow(@ems).to receive(:supported_api_versions).and_return([3]) end it ".ems_type" do diff --git a/spec/models/manageiq/providers/redhat/infra_manager/refresher_3_1_spec.rb b/spec/models/manageiq/providers/redhat/infra_manager/refresher_3_1_spec.rb index 9e70723de67..19b2b526a22 100644 --- a/spec/models/manageiq/providers/redhat/infra_manager/refresher_3_1_spec.rb +++ b/spec/models/manageiq/providers/redhat/infra_manager/refresher_3_1_spec.rb @@ -3,6 +3,7 @@ guid, server, zone = EvmSpecHelper.create_guid_miq_server_zone @ems = FactoryGirl.create(:ems_redhat, :zone => zone, :hostname => "192.168.252.230", :ipaddress => "192.168.252.230", :port => 443) @ems.update_authentication(:default => {:userid => "evm@manageiq.com", :password => "password"}) + allow(@ems).to receive(:supported_api_versions).and_return([3]) end it ".ems_type" do diff --git a/spec/models/manageiq/providers/redhat/infra_manager/refresher_target_vm_spec.rb b/spec/models/manageiq/providers/redhat/infra_manager/refresher_target_vm_spec.rb index a93d8bf434c..d49244792b8 100644 --- a/spec/models/manageiq/providers/redhat/infra_manager/refresher_target_vm_spec.rb +++ b/spec/models/manageiq/providers/redhat/infra_manager/refresher_target_vm_spec.rb @@ -20,6 +20,7 @@ :ems_id => @ems.id, :uid_ems => "00000002-0002-0002-0002-0000000001e9_respool") @cluster.with_relationship_type("ems_metadata") { @cluster.add_child @rp } + allow(@ems).to receive(:supported_api_versions).and_return([3, 4]) end it "should refresh a vm" do diff --git a/spec/models/manageiq/providers/redhat/infra_manager_spec.rb b/spec/models/manageiq/providers/redhat/infra_manager_spec.rb index 002e72d03c4..77a3a98698d 100644 --- a/spec/models/manageiq/providers/redhat/infra_manager_spec.rb +++ b/spec/models/manageiq/providers/redhat/infra_manager_spec.rb @@ -19,7 +19,7 @@ it "rhevm_metrics_connect_options fetches configuration and allows overrides" do expect(ems.rhevm_metrics_connect_options[:host]).to eq("some.thing.tld") - expect(ems.rhevm_metrics_connect_options({:hostname => "different.tld"})[:host]) + expect(ems.rhevm_metrics_connect_options(:hostname => "different.tld")[:host]) .to eq("different.tld") end end @@ -78,4 +78,126 @@ expect(described_class.extract_ems_ref_id("/ovirt-engine/api/vms/123")).to eq("123") end end + + context "api versions" do + require 'ovirtsdk4' + let(:ems) { FactoryGirl.create(:ems_redhat_with_authentication) } + subject(:supported_api_versions) { ems.supported_api_versions } + context "#supported_api_versions" do + before(:each) do + Rails.cache.delete(ems.cache_key) + Rails.cache.write(ems.cache_key, cached_api_versions) + end + + context "when no cached supported_api_versions" do + let(:cached_api_versions) { nil } + it 'calls the OvirtSDK4::Probe.probe' do + expect(OvirtSDK4::Probe).to receive(:probe).and_return([]) + supported_api_versions + end + + it 'properly parses ProbeResults' do + allow(OvirtSDK4::Probe).to receive(:probe) + .and_return([OvirtSDK4::ProbeResult.new(:version => '3'), + OvirtSDK4::ProbeResult.new(:version => '4')]) + expect(supported_api_versions).to match_array(%w(3 4)) + end + end + + context "when cache of supported_api_versions is stale" do + let(:cached_api_versions) do + { + :created_at => Time.now.utc, + :value => [3] + } + end + + before(:each) do + allow(ems).to receive(:last_refresh_date) + .and_return(cached_api_versions[:created_at] + 1.day) + end + + it 'calls the OvirtSDK4::Probe.probe' do + expect(OvirtSDK4::Probe).to receive(:probe).and_return([]) + supported_api_versions + end + end + + context "when cache of supported_api_versions available" do + let(:cached_api_versions) do + { + :created_at => Time.now.utc, + :value => [3] + } + end + + before(:each) do + allow(ems).to receive(:last_refresh_date) + .and_return(cached_api_versions[:created_at] - 1.day) + end + + it 'returns from cache' do + expect(supported_api_versions).to match_array([3]) + end + end + end + + describe "#supports_the_api_version?" do + it "returns the supported api versions" do + allow(ems).to receive(:supported_api_versions).and_return([3]) + expect(ems.supports_the_api_version?(3)).to eq(true) + expect(ems.supports_the_api_version?(6)).to eq(false) + end + end + end + + context "supported features" do + let(:ems) { FactoryGirl.create(:ems_redhat) } + let(:supported_api_versions) { [3, 4] } + context "#process_api_features_support" do + before(:each) do + allow(SupportsFeatureMixin).to receive(:guard_queryable_feature).and_return(true) + allow(described_class).to receive(:api_features) + .and_return(3 => %w(feature1 feature3), 4 => %w(feature2 feature3)) + described_class.process_api_features_support + allow(ems).to receive(:supported_api_versions).and_return(supported_api_versions) + end + + context "no versions supported" do + let(:supported_api_versions) { [] } + it 'supports the right features' do + expect(ems.supports_feature1?).to be_falsey + expect(ems.supports_feature2?).to be_falsey + expect(ems.supports_feature3?).to be_falsey + end + end + + context "version 3 supported" do + let(:supported_api_versions) { [3] } + it 'supports the right features' do + expect(ems.supports_feature1?).to be_truthy + expect(ems.supports_feature2?).to be_falsey + expect(ems.supports_feature3?).to be_truthy + end + end + + context "version 4 supported" do + let(:supported_api_versions) { [4] } + it 'supports the right features' do + expect(ems.supports_feature1?).to be_falsey + expect(ems.supports_feature2?).to be_truthy + expect(ems.supports_feature3?).to be_truthy + end + end + + context "all versions supported" do + let(:supported_api_versions) { [3, 4] } + it 'supports the right features' do + expect(ems.supports_feature1?).to be_truthy + expect(ems.supports_feature2?).to be_truthy + expect(ems.supports_feature3?).to be_truthy + end + end + end + end end