Skip to content

[upload] Update upload subsystem for the new Red Hat upload API - #4448

Open
jcastill wants to merge 1 commit into
sosreport:mainfrom
jcastill:jcastillo-sos-new-upload
Open

[upload] Update upload subsystem for the new Red Hat upload API#4448
jcastill wants to merge 1 commit into
sosreport:mainfrom
jcastill:jcastillo-sos-new-upload

Conversation

@jcastill

@jcastill jcastill commented Aug 12, 2026

Copy link
Copy Markdown
Member

The Red Hat Customer Portal is migrating to a new upload API, so the upload target needs to be updated to use the new endpoints and flow.

Archives up to 5 GB are uploaded in a single HTTP PUT request, while archives exceeding that threshold are split into 128 MiB chunks and uploaded in parallel. As a consequence of the new upload limits, we don't perform fallback to SFTP depending on upload size, only when the upload fails or the user selects it explicitly.

The multipart flow handles session initialization, per-chunk retry, session expiration checks, and automatic abort on failure. A new --upload-threads option (1-16, default 4) controls the number of parallel upload threads per chunk used.


Please place an 'X' inside each '[]' to confirm you adhere to our Contributor Guidelines

  • Is the commit message split over multiple lines and hard-wrapped at 72 characters?
  • Is the subject and message clear and concise?
  • Does the subject start with [plugin_name] if submitting a plugin patch or a [section_name] if part of the core sosreport code?
  • Does the commit contain a Signed-off-by: First Lastname email@example.com?
  • Are any related Issues or existing PRs properly referenced via a Closes (Issue) or Resolved (PR) line?
  • Are all passwords or private data gathered by this PR obfuscated?

Comment thread sos/upload/targets/redhat.py Fixed
Comment thread sos/upload/targets/redhat.py Fixed
@packit-as-a-service

Copy link
Copy Markdown

Congratulations! One of the builds has completed. 🍾

You can install the built RPMs by following these steps:

  • sudo dnf install -y 'dnf*-command(copr)'
  • dnf copr enable packit/sosreport-sos-4448
  • And now you can install the packages.

Please note that the RPMs should be used only in a testing environment.

@jcastill jcastill added Kind/RedHat RedHat related item Kind/Upload upload component of sos labels Aug 12, 2026
@jcastill
jcastill force-pushed the jcastillo-sos-new-upload branch 2 times, most recently from 3c16dac to 24290ed Compare August 12, 2026 11:53
@jcastill

Copy link
Copy Markdown
Member Author

Some samples of the PR execution. Please excuse the wall of text, but I think it's important to show how the upload will look like, specially since the tests we can run are only in an stage environment and we have very limited options to test this and get stage accounts:

  • Upload of big file (more than 5GB) without verbosity:
$ sudo bin/sos upload --case-id 05118085 ../upload_files/testfile_5GB_plus1.bin --upload-target=redhat

sos upload (version 4.11.2)
This utility is used to upload files to a target location based either on a
command line option or detecting the local distribution.

The archive to be uploaded may contain data considered sensitive and its content
should be reviewed by the originating organization before being passed to any
third party.

No configuration changes will be made to the system running this utility.

Press ENTER to continue, or CTRL-C to quit

Attempting upload to Red Hat Customer Portal
Please visit the following URL to authenticate this device: https://sso.stage.redhat.com/device?user_code=BSLD-IPMP
Device authorized correctly. Uploading file to Red Hat Customer Portal
Upload will split the 5.0G report into multiple parts and send 4 chunk(s) in parallel
Starting multipart upload: 41 chunks with 4 threads.
Progress: 1/41 chunks uploaded
Progress: 2/41 chunks uploaded
[...]
Progress: 40/41 chunks uploaded
Progress: 41/41 chunks uploaded
Upload completed successfully.
File ../upload_files/testfile_5GB_plus1.bin uploaded successfully
  • Upload of file bigger than limit with verbosity:
$ sudo bin/sos upload --case-id 05118085 ../upload_files/testfile_5GB_plus1.bin --upload-target=redhat -vvv
[sudo] password for jcastill: 

sos upload (version 4.11.2)
This utility is used to upload files to a target location based either on a
command line option or detecting the local distribution.

The archive to be uploaded may contain data considered sensitive and its content
should be reviewed by the originating organization before being passed to any
third party.

No configuration changes will be made to the system running this utility.

Press ENTER to continue, or CTRL-C to quit

Size of archive is bigger than Red Hat Customer Portal limit for simple uploads of 5.0G. Using multi-part upload.

Attempting upload to Red Hat Customer Portal
Please visit the following URL to authenticate this device: https://sso.stage.redhat.com/device?user_code=ETRG-UWVB
The SSO authentication is successful
Device authorized correctly. Uploading file to Red Hat Customer Portal
Upload strategy: multipart
Upload will split the 5.0G report into multiple parts and send 4 chunk(s) in parallel
Multipart upload parameters: archive_size=5368709121, chunk_size=134217728, total_chunks=41
Requesting upload session: file=testfile_5GB_plus1.bin, size=5368709121, chunks=41
Upload session response: 200
Upload session initialized: attachmentId=aff911d44d674cc380f47ba4a0d-msn5dctb, strategy=MULTIPART, parts=41
Starting multipart upload: 41 chunks with 4 threads.
Part 2 uploaded successfully
Progress: 1/41 chunks uploaded
Part 3 uploaded successfully
Progress: 2/41 chunks uploaded
Part 1 uploaded successfully
Progress: 3/41 chunks uploaded
Part 4 uploaded successfully
Progress: 4/41 chunks uploaded
Part 5 uploaded successfully
Progress: 5/41 chunks uploaded
Part 6 uploaded successfully
Progress: 6/41 chunks uploaded
Part 8 uploaded successfully
Progress: 7/41 chunks uploaded
[...]
Part 39 uploaded successfully
Progress: 40/41 chunks uploaded
Part 40 uploaded successfully
Progress: 41/41 chunks uploaded
Completing upload: attachmentId=aff911d44d674cc380f47ba4a0d-msn5dctb, parts=41
Upload completed successfully.
File ../upload_files/testfile_5GB_plus1.bin uploaded successfully
  • Single HTTP upload (files less than 5Gb):
$ sudo bin/sos upload --case-id 05118085 ../upload_files/testfile_10MB.bin --upload-target=redhat 
[sudo] password for jcastill: 

sos upload (version 4.11.2)
This utility is used to upload files to a target location based either on a
command line option or detecting the local distribution.

The archive to be uploaded may contain data considered sensitive and its content
should be reviewed by the originating organization before being passed to any
third party.

No configuration changes will be made to the system running this utility.

Press ENTER to continue, or CTRL-C to quit

Attempting upload to Red Hat Customer Portal
Please visit the following URL to authenticate this device: https://sso.stage.redhat.com/device?user_code=QWAP-IWQT
Device authorized correctly. Uploading file to Red Hat Customer Portal
Uploading 10.0M report in a single request
Response status: 200
File ../upload_files/testfile_10MB.bin uploaded successfully
  • Single HTTP upload (files less than 5Gb) with verbosity:
sos upload (version 4.11.2)
This utility is used to upload files to a target location based either on a
command line option or detecting the local distribution.

The archive to be uploaded may contain data considered sensitive and its content
should be reviewed by the originating organization before being passed to any
third party.

No configuration changes will be made to the system running this utility.

Press ENTER to continue, or CTRL-C to quit

sudo bin/sos upload --case-id 05118085 ../upload_files/testfile_10MB.bin --upload-target=redhat -vvv
Attempting upload to Red Hat Customer Portal
Please visit the following URL to authenticate this device: https://sso.stage.redhat.com/device?user_code=YWQB-HPIB
The SSO authentication is successful
Device authorized correctly. Uploading file to Red Hat Customer Portal
Upload strategy: simple
Uploading 10.0M report in a single request
Simple upload: file=testfile_10MB.bin, size=10485760
Requesting upload session: file=testfile_10MB.bin, size=10485760, chunks=0
Upload session response: 200
Upload session initialized: attachmentId=a4b96d24deea4a31a0b64750643-msn7j8rn, strategy=SINGLE_PUT, parts=0
Uploading to URL: https://hydra-sct-attachments-stage.s3.us-west-2.amazonaws.com/***
Response status: 200
File ../upload_files/testfile_10MB.bin uploaded successfully

Comment thread sos/upload/targets/redhat.py Outdated
self.ui_log.error(
f"Presigned URL count mismatch: expected {total_chunks}, "
f"got {len(presigned_parts)}")
self._abort_upload(self._build_abort_data())

@pmoravec pmoravec Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You always call self._abort_upload(self._build_abort_data()) with the same argument. To pass less data, does it make sense to have the _abort_upload method without an argument, and have there directly

json=self._build_abort_data(),

instead of current

json=abort_data,

? (well, also debug message at the beginning would have to be updated accordingly).

Self devil's advocate: this approach is coherent with complete_upload method where the info about upload is specific in each case. So wither implementation (current or my proposal) are similarly "valid", in my eyes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point. I can change it now or post-release in September, to make sure I can test the change in prod. Which approach do you prefer?

Comment thread sos/upload/targets/redhat.py Outdated
"""Complete an upload session

:param complete_data: Information about the upload session
:returns: Response object onsuccess, None on failure

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: s/onsuccess/on success/.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... sorry. Will change this

Comment thread sos/upload/targets/redhat.py Outdated
return None

def _get_upload_session_details(self, archive, archive_size, total_chunks):
"""Get the details of the upload session lke attachmentId,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: s/lke/like/

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... I'm sorry

Comment thread sos/upload/targets/redhat.py
Comment thread sos/upload/targets/redhat.py Outdated
Comment on lines +199 to +202
# Lets reset session details before each attempt so that stale or
# expired presigned URLs from a previous call are not reused
# if this request fails and upload_archive retries.
self._upload_session_details = {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great explanation, thanks. I was about to ask why we cant "cache" the info.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Luckily we can test in stage and I could catch this sooner

Comment thread sos/upload/targets/redhat.py Outdated
Comment on lines +219 to +221
upload_url = (
f"{self.RH_API_HOST}"
f"{self.RH_HYDRA_ATTACHMENTS_PATH}/upload")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth be coherent with other requests.* invocations and have base = .. and upload_url = ..?

(maybe we can save the base into the class instance at init, as self.base, to prevent code duplication? it has pros and cons, though..)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what cons do you see? I only see pros tbh

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. right no cons seen here, so I am in favour of the self.base change :)

Comment thread sos/upload/targets/redhat.py
Comment thread sos/upload/targets/redhat.py
Comment thread man/en/sos-report.1
Number of threads to use for multipart uploads. When an archive exceeds a
certain size, defined in the targets, it is uploaded in parallel chunks
using this many threads. Default is 4.
.TP

@pmoravec pmoravec Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth explaining here the:

  • boundaries (1-17)
  • effect (higher value means faster upload, but consumes more bandwidth and more memory (128MB per thread - but dont we copy the data in memory 2 times? isnt it 256ish MB? I would need to experiment but dont have means for it..)

(then we can drop the mention of memory footprint from cmdline help, maybe?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, good point. I can add a vague note about the memory usage, and can try to create a simple python script to test memory usage. But I want to make sure that the users know the trade offs

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about something like this?

  .B \--upload-threads THREADS
  Number of threads to use for multipart uploads (1\-16, default: 4).
  When an archive exceeds a certain size, defined in the targets, it is
  uploaded in parallel chunks using this many threads. Higher values
  speed up the upload but consume more bandwidth and memory, as each
  thread holds a 128 MiB chunk in memory.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm personally of the opinion this shouldn't be user-controllable. On the security side of things there's definitely room for abuse here (though, admittedly, that's a very small concern here today given the infrastructure RH in particular would have in place, more of a concern for if/when multipart gets exposed to more generic targets), but also I don't think users really gain a net-benefit from having this control.

Uploads perceived as "slow" likely wouldn't be realistically helped all that much by more threads, as they'd be more directly limited/influenced by the end user's bandwidth.

Comment thread man/en/sos-upload.1
and use it for uploads.

.TP
.B \--upload-threads THREADS

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

)
response = self.simple_http_upload(archive=files, verify=verify)
if response is None:
raise Exception(

@pmoravec pmoravec Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we raise some less generic exception class? Or pass the response details in the exception text?

Comment thread sos/upload/targets/redhat.py Outdated
headers={'Content-Type':
'application/octet-stream'},
verify=verify,
timeout=TIMEOUT_DEFAULT)

@pmoravec pmoravec Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uploading a 4.9GB file can timeout on slow connections (5 GB / 300s = 17 MB/s minimum). Worth using similar formula like on another place (max(TIMEOUT_DEFAULT, archive_size // (512 * 1024)))?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. I'll reword the TODO as well because I think it's worth keeping an eye on the timeout here.
A quick note, I asked if we can use multipart for less than 5Gb or reduce the limit size and was told that no, so we are stuck here with a big request

Comment thread sos/upload/targets/redhat.py Outdated
The complete/abort lifecycle only applies to multipart
uploads for files exceeding _max_size_request.

:param archive: Dict containing the file tuple under the 'file' key

@pmoravec pmoravec Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why dict and not the value itself (or rather it's [1] file_obj directly)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a leftover of the old way. I'll rework this

Comment thread sos/upload/targets/redhat.py Outdated
def multipart_upload(self, archive):
"""Manage the multipart upload for files exceeding _max_size_request.

:param archive: Dict containing the file tuple under the 'file' key

@pmoravec pmoravec Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why dict and not the value itself (or rather it's [1] file_obj directly)?

Comment thread sos/upload/targets/redhat.py Outdated
self._upload_thread_count = thread_count

# Calculate number of chunks for the multipart upload
chunk_size = 128 * 1024 * 1024 # 128 MiB

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought: worth moving this constatnt close to _max_size_request declaration?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will make it more readable.

Comment thread sos/upload/targets/redhat.py Outdated
# Calculate number of chunks for the multipart upload
chunk_size = 128 * 1024 * 1024 # 128 MiB
archive_size = os.path.getsize(file_path)
total_chunks = (archive_size + chunk_size - 1) // chunk_size

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The + chunk_size - 1 part means you round-divide to up, not down (like // does), right? Maybe worth commenting / explaining it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of rounding up is to make sure we cover partial chunks, but yes I can add a note explaining this

Comment thread sos/upload/targets/redhat.py Outdated
f"Unexpected error while uploading chunk: {e}")
failed_chunks.append(chunk_info)

if expiration and datetime.now(timezone.utc) >= expiration:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a race condition:

  • some chunk gets uploaded just before expiration, still on time,
  • we get successful result,
  • then the expiration time happens
  • then we evaluate this check and claim the upload failed

(if some chunk was still pending, that is correct behaviour. if it was the latest chunk we wrongly claim it failed)

Not sure how better test this, though.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to test this either, it's quite a corner case, but not the kind of bug we'd like to wait and see because we are talking about very big file attachments, so I'll see if I can add a check and make it more robust

Comment thread sos/upload/targets/redhat.py
Comment thread sos/upload/targets/redhat.py
@pmoravec

pmoravec commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

I am mostly OK with the PR, thanks for the lengthy work.

Many comments I raised are rather subjective opinion or "what about" ideas of improvement - I dont insist on most of them.

I feel there are a few places here and there, where the code might get improved to be more robust, but I dont want to be too paranoic and polute the PR by my fears. Rather the daily use will reveal them, if there is any real wekaness.

@jcastill

Copy link
Copy Markdown
Member Author

I am mostly OK with the PR, thanks for the lengthy work.

Many comments I raised are rather subjective opinion or "what about" ideas of improvement - I dont insist on most of them.

I feel there are a few places here and there, where the code might get improved to be more robust, but I dont want to be too paranoic and polute the PR by my fears. Rather the daily use will reveal them, if there is any real wekaness.

I welcome the paranoia because that keeps me on my toes, and well, that's what reviews are for. My only concern is that I want something that works when the release day comes, and then after a couple of weeks of testing and verifying that works we can fix some of your concerns. Does that make sense?

Comment thread sos/upload/targets/redhat.py Outdated
@jcastill
jcastill force-pushed the jcastillo-sos-new-upload branch from 24290ed to bbd05a4 Compare August 14, 2026 13:37
The Red Hat Customer Portal is migrating to a new upload API, so the
upload target needs to be updated to use the new endpoints and flow.

Archives up to 5 GB are uploaded in a single HTTP PUT request, while
archives exceeding that threshold are split into 128 MiB chunks and
uploaded in parallel. As a consequence of the new upload limits,
we don't perform fallback to SFTP depending on upload size, only
when the upload fails or the user selects it explicitly.

The multipart flow handles session initialization, per-chunk retry,
session expiration checks, and automatic abort on failure.
A new --upload-threads option (1-16, default 4) controls the
number of parallel upload threads per chunk used.

Related: RHEL-182746

Signed-off-by: Jose Castillo <jcastillo@redhat.com>
@jcastill
jcastill force-pushed the jcastillo-sos-new-upload branch from bbd05a4 to 9803b00 Compare August 14, 2026 13:46

@pmoravec pmoravec left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. We might rename the get session details method and raise less generic exception than Exception, but that is minor stuff.

@TurboTurtle TurboTurtle left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment on the new option.

Comment thread man/en/sos-report.1
Number of threads to use for multipart uploads. When an archive exceeds a
certain size, defined in the targets, it is uploaded in parallel chunks
using this many threads. Default is 4.
.TP

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm personally of the opinion this shouldn't be user-controllable. On the security side of things there's definitely room for abuse here (though, admittedly, that's a very small concern here today given the infrastructure RH in particular would have in place, more of a concern for if/when multipart gets exposed to more generic targets), but also I don't think users really gain a net-benefit from having this control.

Uploads perceived as "slow" likely wouldn't be realistically helped all that much by more threads, as they'd be more directly limited/influenced by the end user's bandwidth.

@pmoravec

Copy link
Copy Markdown
Contributor

One comment on the new option.

Im dont have strong preference in having this option-able. I think the option can help in some specific scenarios (big bandwidth and impatient customer => increase the threads, or low bandwidth => decrease them), but I agree the option would be rather misused than properly used. So no prob making the number as hard coded.

Is that the only feedback / is the PR good otherwise? (we would like to get it merged soon, cc @TurboTurtle and/or @bmr-cymru or @arif-ali ).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Kind/RedHat RedHat related item Kind/Upload upload component of sos

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants