Add new capacity resource#710
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces an optional “bytes/capacity” quota alongside the existing manifest-count quota, wiring it through the quota APIs and enforcing it during blob upload initiation when enabled via configuration.
Changes:
- Add a new
bytesquota field to the quota model and database schema (migration 054). - Extend the Quotas APIs (Keppel-native + LIQUID) to expose/accept the optional
bytesquota and report bytes usage when enabled. - Enforce bytes quota in the registry upload start endpoint when
TrackBytesQuotais enabled; add test coverage for the “bytes quota provided but feature disabled” error case.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/test/setup.go | Adds a test setup option to enable bytes quota tracking in config. |
| internal/processor/quotas.go | Adds bytes quota fields to request/response and introduces bytes-usage/quota logic. |
| internal/models/quotas.go | Extends Quotas DB/JSON model with a Bytes field. |
| internal/keppel/database.go | Adds DB migration to introduce quotas.bytes column. |
| internal/keppel/config.go | Adds TrackBytesQuota config flag sourced from KEPPEL_TRACK_BYTES_QUOTA. |
| internal/api/registry/uploads.go | Enforces bytes quota (when enabled) before starting a blob upload. |
| internal/api/keppel/quotas.go | Threads request context into quota processor calls. |
| internal/api/keppel/quotas_test.go | Adds tests for rejecting bytes quota updates when the feature is disabled. |
| internal/api/keppel/liquid.go | Adds LIQUID “capacity” resource and maps it to the bytes quota field. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
6914805 to
5ba472c
Compare
5ba472c to
10245f0
Compare
wagnerd3
left a comment
There was a problem hiding this comment.
The point about the naming applies in many cases, I stopped commenting now. Though I understand that my POV is very billing-focused, I can see that a keppel-developer maybe understands the bytes naming better? Probably 2nd opinion from @majewsky would help.
| type Quotas struct { | ||
| AuthTenantID string `db:"auth_tenant_id" json:"-"` | ||
| Bytes uint64 `db:"bytes" json:"bytes,omitempty"` | ||
| ManifestCount uint64 `db:"manifests" json:"manifests"` | ||
| } |
There was a problem hiding this comment.
Can we get rid of these json:"..." tags? As in, does it break any tests if we do? I'm assuming that this used to be relevant because those records were directly rendered on the quota GET endpoint, but now there is the indirection of processor.QuotaResponse.
There was a problem hiding this comment.
The AuditQuotas type is still using this type and it breaks test if removed.
--- FAIL: TestQuotasAPIWithBytes (0.18s)
quotas_test.go:358: value mismatch at /events/0/target/attachments/0/content: expected "{\"manifests\":0}", but got "{\"AuthTenantID\":\"tenant1\",\"Bytes\":0,\"ManifestCount\":0}"
quotas_test.go:358: value mismatch at /events/0/target/attachments/1/content: expected "{\"bytes\":5000000,\"manifests\":50}", but got "{\"AuthTenantID\":\"tenant1\",\"Bytes\":5000000,\"ManifestCount\":50}"
quotas_test.go:400: value mismatch at /events/0/target/attachments/0/content: expected "{\"bytes\":5000000,\"manifests\":50}", but got "{\"AuthTenantID\":\"tenant1\",\"Bytes\":5000000,\"ManifestCount\":50}"
quotas_test.go:400: value mismatch at /events/0/target/attachments/1/content: expected "{\"bytes\":50000000,\"manifests\":100}", but got "{\"AuthTenantID\":\"tenant1\",\"Bytes\":50000000,\"ManifestCount\":100}"
2026/05/08 17:25:38 REQUEST: - - "GET https://trivy.example.org/trivy?format=spdx-json&image=registry.example.org%2Ftest1%2Ffoo%40sha256%3Ae3c1e46560a7ce30e3d107791e1f60a588eda9554564a5d17aa365e53dd6ae58 HTTP/1.1" 200 24970 "-" "-" 0.002s
--- FAIL: TestQuotasAPI (0.11s)
quotas_test.go:138: value mismatch at /events/0/target/attachments/0/content: expected "{\"manifests\":0}", but got "{\"AuthTenantID\":\"tenant1\",\"Bytes\":0,\"ManifestCount\":0}"
quotas_test.go:138: value mismatch at /events/0/target/attachments/1/content: expected "{\"manifests\":50}", but got "{\"AuthTenantID\":\"tenant1\",\"Bytes\":0,\"ManifestCount\":50}"
quotas_test.go:179: value mismatch at /events/0/target/attachments/0/content: expected "{\"manifests\":50}", but got "{\"AuthTenantID\":\"tenant1\",\"Bytes\":0,\"ManifestCount\":50}"
quotas_test.go:179: value mismatch at /events/0/target/attachments/1/content: expected "{\"manifests\":100}", but got "{\"AuthTenantID\":\"tenant1\",\"Bytes\":0,\"ManifestCount\":100}"
FAIL
See https://github.wdf.sap.corp/sap-cloud-infrastructure/storage-resource-services/issues/657 for the naming. I probably mixed some cases up though 😅 |
10245f0 to
45de8a3
Compare
45de8a3 to
2c1dbe3
Compare
| var liquidInfoVersion int64 = time.Now().Unix() | ||
|
|
There was a problem hiding this comment.
Oh yeah, that is indeed a problem. We will have to think about this. The obvious solution would be to deploy the LIQUID API as a standalone process that does not allow for horizontal scaling, but maybe we can come up with something that does not increase deployment complexity.
| if a.cfg.TrackBytesQuota { | ||
| bytesUsage, err := a.sd.UsedBytes(r.Context(), account.AuthTenantID) | ||
| if respondWithError(w, r, err) { | ||
| return | ||
| } | ||
| if bytesUsage >= quotas.Bytes { | ||
| msg := fmt.Sprintf("bytes quota exceeded (quota = %d, usage = %d)", quotas.Bytes, bytesUsage) | ||
| keppel.ErrDenied.With(msg).WithStatus(http.StatusConflict).WriteAsRegistryV2ResponseTo(w, r) | ||
| return | ||
| } |
There was a problem hiding this comment.
@majewsky how do we want to do the activation? If we enable the feature, limes will not instantly be able to set it everywhere. Do we do the migration via a SQL query?
There was a problem hiding this comment.
OpenStack often uses a default of -1 to mean unlimited, and then in practice Limes overrides that as soon as it assigns quota. This would allow for a non-disruptive rollout.
Since I don't want to go into the negative-numbers territory, we could default to math.MaxUint64 (or maybe math.MaxInt64 to not overflow in case of int64 conversions).
2c1dbe3 to
5ddfd6c
Compare
Merging this branch changes the coverage (2 decrease, 2 increase)
Coverage by fileChanged files (no unit tests)
Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code. Changed unit test files
|
No description provided.