Skip to content
Open
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
34 changes: 23 additions & 11 deletions app/controllers/api/connect/v3/subscriptions/systems_controller.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
class Api::Connect::V3::Subscriptions::SystemsController < Api::Connect::BaseController

def announce_system
# Construct the system creation parameters
create_params = {
hostname: params[:hostname],
system_information: info_params(:hwinfo)[:hwinfo].to_json
}
@system = System.create!(hostname: params[:hostname], system_information: info_params(:hwinfo)[:hwinfo].to_json)

# Check if any profiles have been provided
process_system_profiles(create_params)
# Extract any complete profiles, setting the profile response header
# if invalid or incomplete profiles are detected.
profile_params = {}
extract_complete_profiles(profile_params)

@system = System.create!(**create_params)
# Update with any provided profiles, just setting the response header
# if and logging a message if any errors occur.
update_system_profiles(profile_params)

logger.info("System '#{@system.hostname}' announced")
respond_with(@system, serializer: ::V3::SystemSerializer, location: nil)
end

private

def process_system_profiles(create_params)
def update_system_profiles(profile_params)
# do nothing if profile_params is empty
return unless profile_params.any?

@system.update!(**profile_params)
rescue StandardError => e
# If any errors occur while updating the profiles, log a warning
# and set the response header but do not fail the request.
logger.warn("System '#{@system.hostname}' profile update failed: #{e.message}")
response.headers['X-System-Profiles-Action'] = 'clear-cache'
end

def extract_complete_profiles(profile_params)
if params.key?(:system_profiles)
profiles = info_params(:system_profiles)[:system_profiles]

Expand All @@ -35,11 +47,11 @@ def process_system_profiles(create_params)
response.headers['X-System-Profiles-Action'] = 'clear-cache'
end

# Include the complete profiles in create_params only if
# Include the complete profiles in profile_params only if
# complete profiles were actually provided
if complete.any?
logger.debug("valid complete profiles detected: #{complete.keys}")
create_params[:complete_profiles] = complete
profile_params[:complete_profiles] = complete
end
end
end
Expand Down
68 changes: 39 additions & 29 deletions app/controllers/api/connect/v3/systems/systems_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,17 @@ def update
@system.hostname = params[:hostname]

# If a system_profiles param has been provided, process
# the provided profiles
# the provided profiles; if an error occurs log it and
# set the response header to tell the client to send full
# profiles next time, and continue on with the request
# handling
if params.key?(:system_profiles)
profiles = info_params(:system_profiles)[:system_profiles]

# Partition profiles into three categories, namely complete,
# incomplete (missing the data field), and invalid (missing
# the identifier field)
complete, incomplete, invalid = Profile.filter_profiles(profiles.to_h)

# Further refine the incomplete profiles to identify any that
# are known, and retrieve complete versions of them
known_incomplete = Profile.identify_known_profiles(incomplete)

# Determine the unknown profiles in incomplete group, if any
unknown_incomplete_types = incomplete.keys - known_incomplete.keys

# If any of the provided profiles is invalid or if any of the
# incomplete profiles aren't known, set the response header
if invalid.any? || unknown_incomplete_types.any?
logger.debug("problematic invalid (missing identifier field) profiles detected: #{invalid.keys}") if invalid.any?
logger.debug("problematic unrecognised incomplete (missing data field) profiles detected: #{unknown_incomplete_types}") if unknown_incomplete_types.any?
begin
process_system_profiles(info_params(:system_profiles)[:system_profiles])
rescue StandardError => e
logger.warn("System profiles updates failed, continuing without them: #{e.message}")
response.headers['X-System-Profiles-Action'] = 'clear-cache'
end

# Aggregate the provided complete profiles with the retrieved
# complete profiles associated with known incompletes, updating
# the system if applicable.
aggregated_completes = complete.merge(known_incomplete)
if aggregated_completes.any?
logger.debug("valid aggregated complete profiles detected: #{aggregated_completes.keys}")
@system.update(complete_profiles: aggregated_completes)
end
end

# Since the payload is handled by rails all values are converted to string
Expand All @@ -73,6 +52,37 @@ def deregister

private

def process_system_profiles(profiles)
# Partition profiles into three categories, namely complete,
# incomplete (missing the data field), and invalid (missing
# the identifier field)
complete, incomplete, invalid = Profile.filter_profiles(profiles.to_h)

# Further refine the incomplete profiles to identify any that
# are known, and retrieve complete versions of them
known_incomplete = Profile.identify_known_profiles(incomplete)

# Determine the unknown profiles in incomplete group, if any
unknown_incomplete_types = incomplete.keys - known_incomplete.keys

# If any of the provided profiles is invalid or if any of the
# incomplete profiles aren't known, set the response header
if invalid.any? || unknown_incomplete_types.any?
logger.debug("problematic invalid (missing identifier field) profiles detected: #{invalid.keys}") if invalid.any?
logger.debug("problematic unrecognised incomplete (missing data field) profiles detected: #{unknown_incomplete_types}") if unknown_incomplete_types.any?
response.headers['X-System-Profiles-Action'] = 'clear-cache'
end

# Aggregate the provided complete profiles with the retrieved
# complete profiles associated with known incompletes, updating
# the system if applicable.
aggregated_completes = complete.merge(known_incomplete)
if aggregated_completes.any?
logger.debug("valid aggregated complete profiles detected: #{aggregated_completes.keys}")
@system.update(complete_profiles: aggregated_completes)
end
end

def info_params(key)
# Allow all attributes without validating the key structure
# This is fine since the systems are only internal and RMT users
Expand Down
4 changes: 3 additions & 1 deletion engines/scc_proxy/lib/scc_proxy/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -314,13 +314,14 @@ def announce_system
# it is a unique value per instance across all CSPs
login: instance_identifier
}
profile_params = {}
if has_no_regcode?(auth_header)
# NON BYOS case
# no token sent to check with SCC
system_values[:proxy_byos_mode] = :payg

# Check if any profiles have been provided
process_system_profiles(system_values)
extract_complete_profiles(profile_params)
else
request.request_parameters['proxy_byos_mode'] = 'byos'
scc_response, scc_response_headers = SccProxy.announce_system_scc(
Expand All @@ -337,6 +338,7 @@ def announce_system
end
@system, error = create_system(system_values, logger)
if @system.present?
update_system_profiles(profile_params)
logger.info("System '#{@system.hostname}' announced")
respond_with(@system, serializer: ::V3::SystemSerializer, location: nil)
else
Expand Down
6 changes: 6 additions & 0 deletions package/obs/rmt-server.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Tue Aug 11 21:54:10 UTC 2026 - Fergal Mc Carthy <fmccarthy@suse.com>

- Version 3.2 (pre)
* Avoid profiles related registration and keepalive failures (jsc#SCC-822)

-------------------------------------------------------------------
Tue Aug 11 08:35:19 UTC 2026 - Jesús Bermúdez Velázquez <jesus.bv@suse.com>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,48 @@
).to match(profile_set_mixed_complete)
end
end

context 'with system_profiles parameters and a profile creation error' do
before do
allow(Profile).to receive(:ensure_profile_exists).and_raise(StandardError.new('DB Error'))
end

it 'creates system but fails profile update, setting header to clear-cache' do
post url, params: { system_profiles: profile_set_all, hostname: 'testhost' }.to_json, headers: headers

expect(response).to be_successful
expect(response).to have_http_status(:created)
expect(response.headers['X-System-Profiles-Action']).to eq('clear-cache')

system = System.find_by(login: json_response[:login])

expect(system.profiles.count).to eq(0)
end
end

context 'with system_profiles parameters and a profile creation error on retry' do
before do
allow(Profile).to receive(:ensure_profile_exists).and_raise(StandardError.new('DB Error'))
allow(System).to receive(:create!).and_raise(StandardError.new('Another DB Error'))
end

it 're-raises when system creation fails' do
expect do
post url, params: { system_profiles: profile_set_all, hostname: 'testhost' }.to_json, headers: headers
end.to raise_error(StandardError, 'Another DB Error')
end
end

context 'with system_profiles parameters and a creation error when no profiles are present' do
before do
allow(System).to receive(:create!).and_raise(StandardError.new('Unique constraint violation'))
end

it 're-raises the error when no profiles were present' do
expect do
post url, params: { hostname: 'testhost' }.to_json, headers: headers
end.to raise_error(StandardError, 'Unique constraint violation')
end
end
end
end
19 changes: 19 additions & 0 deletions spec/requests/api/connect/v3/systems/systems_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,25 @@
expect(response.headers).not_to include('System-Token')
end
end

context 'with profiles and a profile creation error' do
let(:profiles) { profile_set_all }
let(:payload) { { hostname: 'test', hwinfo: hwinfo, system_profiles: profiles } }

before do
allow(Profile).to receive(:ensure_profile_exists).and_raise(StandardError.new('DB Error'))
end

it 'continues request processing without profiles on error' do
update_action

expect(response).to be_successful
expect(response).to have_http_status(:no_content)
expect(response.headers['X-System-Profiles-Action']).to eq('clear-cache')

expect(system.profiles.count).to eq(0)
end
end
end

describe '#deregister' do
Expand Down
Loading