From a770596e5379e32ea5ea4f4a2cdf1e0198b26427 Mon Sep 17 00:00:00 2001 From: Jesus Bermudez Velazquez Date: Thu, 29 Aug 2024 13:48:02 +0100 Subject: [PATCH 1/3] Propagate the upgrate call to SCC Proxy the zypper migration upgrade call to SCC when the system is BYOS --- .../v3/systems/products_controller_spec.rb | 195 ++++++++++++++---- engines/scc_proxy/lib/scc_proxy/engine.rb | 67 ++++-- 2 files changed, 208 insertions(+), 54 deletions(-) diff --git a/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb b/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb index a2679292c..53b172b71 100644 --- a/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb +++ b/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb @@ -551,65 +551,176 @@ describe '#upgrade' do subject { response } - let(:system) { FactoryBot.create(:system) } + let(:instance_data) { 'dummy_instance_data' } let(:request) { put url, headers: headers, params: payload } - let!(:old_product) { FactoryBot.create(:product, :with_mirrored_repositories, :activated, system: system) } - let(:payload) do - { - identifier: new_product.identifier, - version: new_product.version, - arch: new_product.arch - } - end - before { request } + context 'when system is byos' do + let(:system) { FactoryBot.create(:system, :byos, :with_system_information, instance_data: instance_data) } + let!(:old_product) { FactoryBot.create(:product, :with_mirrored_repositories, :activated, system: system) } + let(:payload) do + { + identifier: new_product.identifier, + version: new_product.version, + arch: new_product.arch + } + end + let(:scc_systems_products_url) { 'https://scc.suse.com/connect/systems/products' } + let(:scc_headers) do + { + 'Accept' => 'application/json,application/vnd.scc.suse.com.v4+json', + 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', + 'Authorization' => headers['HTTP_AUTHORIZATION'], + 'Content-Type' => 'application/json', + 'User-Agent' => 'Ruby' + } + end + + context 'when SCC upgrade success' do + before do + # pp headers + stub_request(:put, scc_systems_products_url) + .with({ headers: scc_headers, body: payload.merge({ byos_mode: 'byos' }) }) + .and_return(status: 200, body: '', headers: {}) + request + end + + context "when migration target base product doesn't have an activated successor/predecessor" do + let(:new_product) { FactoryBot.create(:product, :with_mirrored_repositories) } + + it 'HTTP response code is 422' do + expect(response).to have_http_status(422) + end + + it 'renders an error' do + data = JSON.parse(response.body) + expect(data['error']).to eq('Migration target not allowed on this instance type') + end + end + + context 'when migration target base product has the same identifier' do + let(:new_product) do + FactoryBot.create( + :product, :with_mirrored_repositories, identifier: old_product.identifier, + version: '999', predecessors: [ old_product ] + ) + end - context "when migration target base product doesn't have an activated successor/predecessor" do - let(:new_product) { FactoryBot.create(:product, :with_mirrored_repositories) } + it 'HTTP response code is 201' do + expect(response).to have_http_status(201) + end - it 'HTTP response code is 422' do - expect(response).to have_http_status(422) + it "doesn't render an error" do + data = JSON.parse(response.body) + expect(data).not_to have_key('error') + end + end end - it 'renders an error' do - data = JSON.parse(response.body) - expect(data['error']).to eq('Migration target not allowed on this instance type') + context 'when SCC upgrade fails' do + before do + stub_request(:put, scc_systems_products_url) + .with({ headers: scc_headers, body: payload.merge({ byos_mode: 'byos' }) }) + .and_return( + status: 401, + body: { error: 'error_message' }.to_json, + headers: {} + ) + request + end + + context "when migration target base product doesn't have an activated successor/predecessor" do + let(:new_product) { FactoryBot.create(:product, :with_mirrored_repositories) } + + it 'HTTP response code is 422' do + expect(response).to have_http_status(422) + end + + it 'renders an error' do + data = JSON.parse(response.body) + expect(data['error']).to eq('Migration target not allowed on this instance type') + end + end + + context 'when migration target base product has the same identifier' do + let(:new_product) do + FactoryBot.create( + :product, :with_mirrored_repositories, identifier: old_product.identifier, + version: '999', predecessors: [ old_product ] + ) + end + + it 'HTTP response code is 422' do + expect(response).to have_http_status(422) + end + + it 'renders an error' do + data = JSON.parse(response.body) + expect(data).to have_key('error') + end + end end end - context 'when migration target base product has a different identifier' do - let(:new_product) do - FactoryBot.create( - :product, :with_mirrored_repositories, - identifier: old_product.identifier + '-foo', predecessors: [ old_product ] - ) + context 'when system is payg' do + let(:system) { FactoryBot.create(:system, :payg, :with_system_information, instance_data: instance_data) } + let!(:old_product) { FactoryBot.create(:product, :with_mirrored_repositories, :activated, system: system) } + let(:payload) do + { + identifier: new_product.identifier, + version: new_product.version, + arch: new_product.arch + } end - it 'HTTP response code is 422' do - expect(response).to have_http_status(422) - end + before { request } - it 'renders an error' do - data = JSON.parse(response.body) - expect(data['error']).to eq('Migration target not allowed on this instance type') - end - end + context "when migration target base product doesn't have an activated successor/predecessor" do + let(:new_product) { FactoryBot.create(:product, :with_mirrored_repositories) } - context 'when migration target base product has the same identifier' do - let(:new_product) do - FactoryBot.create( - :product, :with_mirrored_repositories, identifier: old_product.identifier, - version: '999', predecessors: [ old_product ] - ) + it 'HTTP response code is 422' do + expect(response).to have_http_status(422) + end + + it 'renders an error' do + data = JSON.parse(response.body) + expect(data['error']).to eq('Migration target not allowed on this instance type') + end end - it 'HTTP response code is 201' do - expect(response).to have_http_status(201) + context 'when migration target base product has a different identifier' do + let(:new_product) do + FactoryBot.create( + :product, :with_mirrored_repositories, + identifier: old_product.identifier + '-foo', predecessors: [ old_product ] + ) + end + + it 'HTTP response code is 422' do + expect(response).to have_http_status(422) + end + + it 'renders an error' do + data = JSON.parse(response.body) + expect(data['error']).to eq('Migration target not allowed on this instance type') + end end - it "doesn't render an error" do - data = JSON.parse(response.body) - expect(data).not_to have_key('error') + context 'when migration target base product has the same identifier' do + let(:new_product) do + FactoryBot.create( + :product, :with_mirrored_repositories, identifier: old_product.identifier, + version: '999', predecessors: [ old_product ] + ) + end + + it 'HTTP response code is 201' do + expect(response).to have_http_status(201) + end + + it "doesn't render an error" do + data = JSON.parse(response.body) + expect(data).not_to have_key('error') + end end end end diff --git a/engines/scc_proxy/lib/scc_proxy/engine.rb b/engines/scc_proxy/lib/scc_proxy/engine.rb index f551e4500..9946111a2 100644 --- a/engines/scc_proxy/lib/scc_proxy/engine.rb +++ b/engines/scc_proxy/lib/scc_proxy/engine.rb @@ -2,7 +2,7 @@ require 'net/http' ANNOUNCE_URL = 'https://scc.suse.com/connect/subscriptions/systems'.freeze -ACTIVATE_PRODUCT_URL = 'https://scc.suse.com/connect/systems/products'.freeze +SYSTEM_PRODUCTS_URL = 'https://scc.suse.com/connect/systems/products'.freeze SYSTEMS_ACTIVATIONS_URL = 'https://scc.suse.com/connect/systems/activations'.freeze DEREGISTER_SYSTEM_URL = 'https://scc.suse.com/connect/systems'.freeze DEREGISTER_PRODUCT_URL = 'https://scc.suse.com/connect/systems/products'.freeze @@ -117,6 +117,17 @@ def prepare_scc_request(uri_path, product, auth, params, mode) scc_request end + def prepare_scc_update_request(uri_path, product, auth, mode) + scc_request = Net::HTTP::Put.new(uri_path, headers(auth, nil)) + scc_request.body = { + identifier: product.identifier, + version: product.version, + arch: product.arch, + byos_mode: mode + }.to_json + scc_request + end + def announce_system_scc(auth, params) uri = URI.parse(ANNOUNCE_URL) http = Net::HTTP.new(uri.host, uri.port) @@ -129,7 +140,7 @@ def announce_system_scc(auth, params) end def scc_activate_product(product, auth, params, mode) - uri = URI.parse(ACTIVATE_PRODUCT_URL) + uri = URI.parse(SYSTEM_PRODUCTS_URL) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true scc_request = prepare_scc_request(uri.path, product, auth, params, mode) @@ -254,6 +265,20 @@ def scc_check_subscription_expiration(headers, login, system_token, logger, mode SccProxy.activations_fail_state(scc_systems_activations, headers, product) end + + def scc_upgrade(auth, product, system_login, mode, logger) + uri = URI.parse(SYSTEM_PRODUCTS_URL) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + scc_request = prepare_scc_update_request(uri.path, product, auth, mode) + response = http.request(scc_request) + unless response.code_type == Net::HTTPOK + logger.info "Could not upgrade the system (#{system_login}), error: #{response.message} #{response.code}" + response.message = SccProxy.parse_error(response.message) if response.message.include? 'json' + raise ActionController::TranslatedError.new(response.body) + end + response + end end # rubocop:disable Metrics/ClassLength @@ -316,12 +341,12 @@ def has_no_regcode?(auth_header) Api::Connect::V3::Systems::ProductsController.class_eval do before_action :scc_activate_product, only: %i[activate] + before_action :scc_upgrade, only: %i[upgrade], if: -> { @system.byos? } protected # rubocop:disable Metrics/PerceivedComplexity # rubocop:disable Metrics/CyclomaticComplexity - # rubocop:disable Metrics/AbcSize def scc_activate_product logger.info "Activating product #{@product.product_string} to SCC" auth = nil @@ -337,12 +362,13 @@ def scc_activate_product if @system.payg? && base_prod.present? raise 'Incompatible extension product' unless @product.arch == base_prod.arch && @product.version == base_prod.version - params['hostname'] = @system.hostname - params['proxy_byos_mode'] = mode - params['scc_login'] = @system.login - params['scc_password'] = @system.password - params['hwinfo'] = JSON.parse(@system.system_information) - params['instance_data'] = @system.instance_data + update_params_system_info mode + # params['hostname'] = @system.hostname + # params['proxy_byos_mode'] = mode + # params['scc_login'] = @system.login + # params['scc_password'] = @system.password + # params['hwinfo'] = JSON.parse(@system.system_information) + # params['instance_data'] = @system.instance_data announce_auth = "Token token=#{params[:token]}" response = SccProxy.announce_system_scc(announce_auth, params) @@ -375,6 +401,8 @@ def scc_activate_product end logger.info 'No token provided' if params[:token].blank? end + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity def deregister_hybrid(auth) response = SccProxy.deregister_system_scc(auth, @system.system_token) @@ -386,10 +414,25 @@ def deregister_hybrid(auth) end logger.info 'System successfully deregistered from SCC' end + + def scc_upgrade + logger.info "Upgrading system to product #{@product.product_string} to SCC" + auth = nil + auth = request.headers['HTTP_AUTHORIZATION'] if request.headers.include?('HTTP_AUTHORIZATION') + mode = 'byos' if @system.byos? + SccProxy.scc_upgrade(auth, @product, @system.login, mode, logger) + logger.info "System #{@system.login} successfully upgraded with SCC" + end + + def update_params_system_info(mode) + params['hostname'] = @system.hostname + params['proxy_byos_mode'] = mode + params['scc_login'] = @system.login + params['scc_password'] = @system.password + params['hwinfo'] = JSON.parse(@system.system_information) + params['instance_data'] = @system.instance_data + end end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/CyclomaticComplexity - # rubocop:enable Metrics/PerceivedComplexity Api::Connect::V4::Systems::ProductsController.class_eval do before_action :scc_deactivate_product, only: %i[destroy] From 7e139ad975aacefda82d0cc3a97ae250b875240c Mon Sep 17 00:00:00 2001 From: Jesus Bermudez Velazquez Date: Fri, 30 Aug 2024 10:59:51 +0100 Subject: [PATCH 2/3] Update the http status code to 201 created --- .../requests/api/connect/v3/systems/products_controller_spec.rb | 2 +- engines/scc_proxy/lib/scc_proxy/engine.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb b/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb index 53b172b71..3a002f4d1 100644 --- a/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb +++ b/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb @@ -580,7 +580,7 @@ # pp headers stub_request(:put, scc_systems_products_url) .with({ headers: scc_headers, body: payload.merge({ byos_mode: 'byos' }) }) - .and_return(status: 200, body: '', headers: {}) + .and_return(status: 201, body: '', headers: {}) request end diff --git a/engines/scc_proxy/lib/scc_proxy/engine.rb b/engines/scc_proxy/lib/scc_proxy/engine.rb index 9946111a2..554aea8e8 100644 --- a/engines/scc_proxy/lib/scc_proxy/engine.rb +++ b/engines/scc_proxy/lib/scc_proxy/engine.rb @@ -272,7 +272,7 @@ def scc_upgrade(auth, product, system_login, mode, logger) http.use_ssl = true scc_request = prepare_scc_update_request(uri.path, product, auth, mode) response = http.request(scc_request) - unless response.code_type == Net::HTTPOK + unless response.code_type == Net::HTTPCreated logger.info "Could not upgrade the system (#{system_login}), error: #{response.message} #{response.code}" response.message = SccProxy.parse_error(response.message) if response.message.include? 'json' raise ActionController::TranslatedError.new(response.body) From f429365a215255e45a42e744e7cf8451acd4ac5c Mon Sep 17 00:00:00 2001 From: Jesus Bermudez Velazquez Date: Fri, 30 Aug 2024 11:01:05 +0100 Subject: [PATCH 3/3] rename method from prepare_scc_update_request to prepare_scc_upgrade_request --- engines/scc_proxy/lib/scc_proxy/engine.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/engines/scc_proxy/lib/scc_proxy/engine.rb b/engines/scc_proxy/lib/scc_proxy/engine.rb index 554aea8e8..52532ad7b 100644 --- a/engines/scc_proxy/lib/scc_proxy/engine.rb +++ b/engines/scc_proxy/lib/scc_proxy/engine.rb @@ -117,7 +117,7 @@ def prepare_scc_request(uri_path, product, auth, params, mode) scc_request end - def prepare_scc_update_request(uri_path, product, auth, mode) + def prepare_scc_upgrade_request(uri_path, product, auth, mode) scc_request = Net::HTTP::Put.new(uri_path, headers(auth, nil)) scc_request.body = { identifier: product.identifier, @@ -270,7 +270,7 @@ def scc_upgrade(auth, product, system_login, mode, logger) uri = URI.parse(SYSTEM_PRODUCTS_URL) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true - scc_request = prepare_scc_update_request(uri.path, product, auth, mode) + scc_request = prepare_scc_upgrade_request(uri.path, product, auth, mode) response = http.request(scc_request) unless response.code_type == Net::HTTPCreated logger.info "Could not upgrade the system (#{system_login}), error: #{response.message} #{response.code}" @@ -363,12 +363,6 @@ def scc_activate_product raise 'Incompatible extension product' unless @product.arch == base_prod.arch && @product.version == base_prod.version update_params_system_info mode - # params['hostname'] = @system.hostname - # params['proxy_byos_mode'] = mode - # params['scc_login'] = @system.login - # params['scc_password'] = @system.password - # params['hwinfo'] = JSON.parse(@system.system_information) - # params['instance_data'] = @system.instance_data announce_auth = "Token token=#{params[:token]}" response = SccProxy.announce_system_scc(announce_auth, params)