Skip to content

tests: add /debug endpoint to fakestore to allow interrupting downloads - #16881

Merged
bboozzoo merged 7 commits into
canonical:masterfrom
ZeyadYasser:fakestore-debug-kill-request-action
Apr 13, 2026
Merged

tests: add /debug endpoint to fakestore to allow interrupting downloads#16881
bboozzoo merged 7 commits into
canonical:masterfrom
ZeyadYasser:fakestore-debug-kill-request-action

Conversation

@ZeyadYasser

@ZeyadYasser ZeyadYasser commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

This PR adds a new /debug endpoint to the fakestore, it adds one debug action kill-request which allows injecting connection dropping after specified number of bytes on a per-path granularity.

This is needed to allow testing resuming of partial downloads which is implemented in #16841

JIRA: SNAPDENG-36634

Signed-off-by: Zeyad Gouda <zeyad.gouda@canonical.com>
@ZeyadYasser ZeyadYasser added this to the 2.76 milestone Apr 8, 2026
@github-actions

github-actions Bot commented Apr 8, 2026

Copy link
Copy Markdown

Fri Apr 10 21:16:12 UTC 2026
The following results are from: https://github.com/canonical/snapd/actions/runs/24251689441

Failures:

Skipped tests from snapd-testing-skip

If you wish to have any of the below tests run in your PR, in your PR description, add 'unskip:' followed by a copy-and-pasted list (without variants) of the below tests you wish to run (unskip plus test list must be valid yaml)

  • garden:ubuntu-25.10-64:tests/main/apparmor-prompting-support
  • openstack-arm:ubuntu-24.04-arm-64:tests/main/i18n
  • openstack-arm:ubuntu-core-24-arm-64:tests/core/create-user
  • openstack-arm:ubuntu-core-24-arm-64:tests/core/create-user-2
  • openstack-arm:ubuntu-core-24-arm-64:tests/core/remove-user
  • openstack-arm:ubuntu-core-24-arm-64:tests/main/i18n
  • openstack:debian-sid-64:tests/main/degraded
  • openstack:debian-sid-64:tests/main/interfaces-network-status-classic
  • openstack:opensuse-16.0-64:tests/main/cgroup-devices-v2
  • openstack:opensuse-tumbleweed-64:tests/main/cgroup-devices-v2
  • openstack:opensuse-tumbleweed-selinux-64:tests/main/cgroup-devices-v2
  • openstack:opensuse-tumbleweed-selinux-64:tests/main/selinux-classic-confinement
  • openstack:ubuntu-24.04-64:tests/main/i18n
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-flag-restart
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:download_file_conflict
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:download_file_defaults
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:download_file_safer
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:read_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:read_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:timespan_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:timespan_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_allow_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_deny_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-prompt-restoration
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-snapd-startup
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-support
  • openstack:ubuntu-25.10-64:tests/main/interfaces-requests-activates-handlers
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-flag-restart
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:download_file_conflict
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:download_file_defaults
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:download_file_safer
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:read_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:read_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:timespan_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:timespan_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_allow_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_deny_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-prompt-restoration
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-snapd-startup
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-support
  • openstack:ubuntu-26.04-64:tests/main/i18n
  • openstack:ubuntu-26.04-64:tests/main/interfaces-requests-activates-handlers
  • openstack:ubuntu-core-18-64:tests/core/create-user
  • openstack:ubuntu-core-18-64:tests/core/create-user-2
  • openstack:ubuntu-core-18-64:tests/core/remove-user
  • openstack:ubuntu-core-20-64:tests/core/create-user
  • openstack:ubuntu-core-20-64:tests/core/create-user-2
  • openstack:ubuntu-core-20-64:tests/core/remove-user
  • openstack:ubuntu-core-22-64:tests/core/create-user
  • openstack:ubuntu-core-22-64:tests/core/create-user-2
  • openstack:ubuntu-core-22-64:tests/core/remove-user
  • openstack:ubuntu-core-24-64:tests/core/create-user
  • openstack:ubuntu-core-24-64:tests/core/create-user-2
  • openstack:ubuntu-core-24-64:tests/core/remove-user
  • openstack:ubuntu-core-26-64:tests/core/create-user
  • openstack:ubuntu-core-26-64:tests/core/create-user-2
  • openstack:ubuntu-core-26-64:tests/core/remove-user

@bboozzoo bboozzoo 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

mux.Handle("/download/", http.StripPrefix("/download/", http.FileServer(http.Dir(topDir))))

fileServer := http.StripPrefix("/download/", http.FileServer(http.Dir(topDir)))
mux.Handle("/download/", logRangeHeader(store.applyKillAfter(fileServer.ServeHTTP)))

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.

note to self, we could switch to gorilla mux to at least get the default logger for every request

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.

done in #16883

@bboozzoo bboozzoo added the Simple 😃 A small PR which can be reviewed quickly label Apr 9, 2026
@bboozzoo
bboozzoo requested review from maykathm and miguelpires April 9, 2026 09:18
@codecov

codecov Bot commented Apr 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.42857% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.59%. Comparing base (3027222) to head (ee7319d).
⚠️ Report is 17 commits behind head on master.

Files with missing lines Patch % Lines
tests/lib/fakestore/store/store.go 81.42% 19 Missing and 7 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #16881      +/-   ##
==========================================
+ Coverage   77.58%   77.59%   +0.01%     
==========================================
  Files        1369     1368       -1     
  Lines      189490   189768     +278     
  Branches     2446     2446              
==========================================
+ Hits       147008   147244     +236     
- Misses      33601    33639      +38     
- Partials     8881     8885       +4     
Flag Coverage Δ
unittests 77.59% <81.42%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bboozzoo bboozzoo added the Auto rerun spread Auto reruns spread up to 4 times in non-draft PRs w/ >=1 approval and <20 fails in any fund. system label Apr 9, 2026

@maykathm maykathm 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.

Looks great, thanks!

@miguelpires miguelpires 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.

question

Comment thread tests/lib/fakestore/store/store.go Outdated

// endpoint -> quota value, note this is stateful, i.e. the quota is counted
// for all requests to a given endpoint and after exceeding it, all
// subsequent requests will fail until it is reset though a request

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.

Suggested change
// subsequent requests will fail until it is reset though a request
// subsequent requests will fail until it is reset through a request

Comment thread tests/lib/fakestore/store/store.go Outdated
Comment on lines +376 to +387
if kaw.killAfter >= 0 {
kaw.killAfter -= int64(len(p))
}

if kaw.killAfter < 0 {
// hijack the connection to force a hard drop
hj, ok := kaw.ResponseWriter.(http.Hijacker)
if ok {
conn, _, _ := hj.Hijack()
conn.Close() // hard close the TCP connection
}
return 0, fmt.Errorf("connection killed")

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.

Maybe it doesn't matter for the test you want to write, but this doesn't strictly kill the connection after writing up to a threshold, it kills it if the write would exceed the limit. Maybe that's exactly what you need?

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.

Fair point. I think we can close the connection after exactly exceeding the limit. I'll push a patch

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.

done

@bboozzoo
bboozzoo requested a review from miguelpires April 10, 2026 07:06
Comment thread tests/lib/fakestore/store/store.go Outdated
}

func (kaw *killAfterWriter) Write(p []byte) (int, error) {
if kaw.killAfter < 0 {

@miguelpires miguelpires Apr 10, 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.

I think we'll never fall into this case? For instance, if the buffer has 4 bytes and the killAfter is at 2, we'll write 2 bytes and set the killAfter to 0 and close the connection. Subsequent calls won't fall into this and will call Write() with an empty buffer

Suggested change
if kaw.killAfter < 0 {
if kaw.killAfter <= 0 {

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.

Ah good point. The net effect should be identical, a Write() with 0 bytes of data is essentially a noop but shouldKill would be set to true anyway and we'd hit the later branch. Anyways, I pushed a tweak and checked the coverage to confirm we're hitting the early exit branch now.

@bboozzoo
bboozzoo requested a review from miguelpires April 10, 2026 10:36

@miguelpires miguelpires 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.

thanks!

Comment thread tests/lib/fakestore/store/store.go Outdated

kaw := &killAfterWriter{
ResponseWriter: w,
killAfter: killAfter,

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.

hah, unit tests found a funny race with how the counter is used. AFAIU what happens is that within Write() we may update the internal value of kaw.killAfter and close the connection. The value which is stored in the Store and used to gate a subsequent connection is only updated in a defer function further below which uses kaw.KillAfter as input. So it is entirely possible that in unit tests, after the connection is closed for the first tine, the test can proceed and perform another connection which will be handled before the defer code triggered by previous call had a chance to execute. This will cause next call to observe a value in Store that hasn't yet been updated. In unit tests is is manifested by this failed check:

store_test.go:1354:
  // Connection forcefully closed mid-transfer, exactly killAfter bytes received
  c.Check(int64(len(got)), Equals, int64(0))
  ... obtained int64 = 512
  ... expected int64 = 0

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.

pushed a fix

Fix a race in consumign and trackign the left quota.

Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
@bboozzoo
bboozzoo merged commit 82e3264 into canonical:master Apr 13, 2026
385 of 396 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Auto rerun spread Auto reruns spread up to 4 times in non-draft PRs w/ >=1 approval and <20 fails in any fund. system Simple 😃 A small PR which can be reviewed quickly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants