Skip to content

Commit

Permalink
Merge pull request #635 from sapcc/avoid-backendquota-edit-war
Browse files Browse the repository at this point in the history
avoid backend_quota edit war for AZSeparatedResourceTopology
  • Loading branch information
majewsky authored Dec 17, 2024
2 parents 0a213a3 + bb68040 commit bee080e
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 26 deletions.
8 changes: 7 additions & 1 deletion internal/collector/scrape.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,9 @@ func (c *Collector) writeResourceScrapeResult(dbDomain db.Domain, dbProject db.P

resInfo := c.Cluster.InfoForResource(srv.Type, res.Name)
if resInfo.HasQuota {
res.BackendQuota = &backendQuota
if resInfo.Topology != liquid.AZSeparatedResourceTopology {
res.BackendQuota = &backendQuota
}
res.MinQuotaFromBackend = resourceData[res.Name].MinQuota
res.MaxQuotaFromBackend = resourceData[res.Name].MaxQuota
}
Expand Down Expand Up @@ -438,6 +440,10 @@ func (c *Collector) writeDummyResources(dbDomain db.Domain, dbProject db.Project
}
}

// FIXME: These dummy resources do not conform to `resInfo.Topology` and are never AZ-aware.
// I'm not fixing this right now because dummy resources are an extremely rare corner-case anyway.
// TODO: When we rework the DB schema next year, we should build it so that dummy resources can be avoided entirely.

// update scraped_at timestamp and reset stale flag to make sure that we do
// not scrape this service again immediately afterwards if there are other
// stale services to cover first
Expand Down
32 changes: 11 additions & 21 deletions internal/collector/scrape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,35 +622,29 @@ func Test_TopologyScrapes(t *testing.T) {
INSERT INTO project_az_resources (id, resource_id, az, usage, subresources, historical_usage, backend_quota) VALUES (7, 3, 'az-two', 2, '[{"index":2},{"index":3}]', '{"t":[%[1]d],"v":[2]}', 21);
INSERT INTO project_az_resources (id, resource_id, az, usage, physical_usage, historical_usage, backend_quota) VALUES (8, 4, 'az-one', 0, 0, '{"t":[%[3]d],"v":[0]}', 50);
INSERT INTO project_az_resources (id, resource_id, az, usage, physical_usage, historical_usage, backend_quota) VALUES (9, 4, 'az-two', 0, 0, '{"t":[%[3]d],"v":[0]}', 50);
INSERT INTO project_resources (id, service_id, name, quota, backend_quota) VALUES (1, 1, 'capacity', 0, 100);
INSERT INTO project_resources (id, service_id, name) VALUES (1, 1, 'capacity');
INSERT INTO project_resources (id, service_id, name) VALUES (2, 1, 'capacity_portion');
INSERT INTO project_resources (id, service_id, name, quota, backend_quota) VALUES (3, 1, 'things', 0, 42);
INSERT INTO project_resources (id, service_id, name, quota, backend_quota) VALUES (4, 2, 'capacity', 0, 100);
INSERT INTO project_resources (id, service_id, name) VALUES (3, 1, 'things');
INSERT INTO project_resources (id, service_id, name) VALUES (4, 2, 'capacity');
INSERT INTO project_resources (id, service_id, name) VALUES (5, 2, 'capacity_portion');
INSERT INTO project_resources (id, service_id, name, quota, backend_quota) VALUES (6, 2, 'things', 0, 42);
UPDATE project_services SET scraped_at = %[1]d, scrape_duration_secs = 5, serialized_metrics = '{"capacity_usage":0,"things_usage":4}', checked_at = %[1]d, next_scrape_at = %[2]d, quota_desynced_at = %[1]d WHERE id = 1 AND project_id = 1 AND type = 'unittest';
UPDATE project_services SET scraped_at = %[3]d, scrape_duration_secs = 5, serialized_metrics = '{"capacity_usage":0,"things_usage":4}', checked_at = %[3]d, next_scrape_at = %[4]d, quota_desynced_at = %[3]d WHERE id = 2 AND project_id = 2 AND type = 'unittest';
INSERT INTO project_resources (id, service_id, name) VALUES (6, 2, 'things');
UPDATE project_services SET scraped_at = %[1]d, scrape_duration_secs = 5, serialized_metrics = '{"capacity_usage":0,"things_usage":4}', checked_at = %[1]d, next_scrape_at = %[2]d WHERE id = 1 AND project_id = 1 AND type = 'unittest';
UPDATE project_services SET scraped_at = %[3]d, scrape_duration_secs = 5, serialized_metrics = '{"capacity_usage":0,"things_usage":4}', checked_at = %[3]d, next_scrape_at = %[4]d WHERE id = 2 AND project_id = 2 AND type = 'unittest';
`,
scrapedAt1.Unix(), scrapedAt1.Add(scrapeInterval).Unix(),
scrapedAt2.Unix(), scrapedAt2.Add(scrapeInterval).Unix(),
)

// set some quota acpq values.
// resource level (ACPQ always writes NULL on this level for AZSeparatedResourceTopology)
_, err := s.DB.Exec(`UPDATE project_resources SET quota = NULL WHERE name = $1`, "capacity")
_, err := s.DB.Exec(`UPDATE project_az_resources SET quota = $1 WHERE resource_id IN (1,4) and az != 'any'`, 20)
if err != nil {
t.Fatal(err)
}
_, err = s.DB.Exec(`UPDATE project_resources SET quota = NULL WHERE name = $1`, "things")
if err != nil {
t.Fatal(err)
}
// az level
_, err = s.DB.Exec(`UPDATE project_az_resources SET quota = $1 WHERE resource_id IN (1,4) and az != 'any'`, 20)
_, err = s.DB.Exec(`UPDATE project_az_resources SET quota = $1 WHERE resource_id IN (3,6) and az != 'any'`, 13)
if err != nil {
t.Fatal(err)
}
_, err = s.DB.Exec(`UPDATE project_az_resources SET quota = $1 WHERE resource_id IN (3,6) and az != 'any'`, 13)
_, err = s.DB.Exec(`UPDATE project_services SET quota_desynced_at = $1`, s.Clock.Now())
if err != nil {
t.Fatal(err)
}
Expand All @@ -668,10 +662,6 @@ func Test_TopologyScrapes(t *testing.T) {
UPDATE project_az_resources SET backend_quota = 13 WHERE id = 7 AND resource_id = 3 AND az = 'az-two';
UPDATE project_az_resources SET backend_quota = 20 WHERE id = 8 AND resource_id = 4 AND az = 'az-one';
UPDATE project_az_resources SET backend_quota = 20 WHERE id = 9 AND resource_id = 4 AND az = 'az-two';
UPDATE project_resources SET backend_quota = NULL WHERE id = 1 AND service_id = 1 AND name = 'capacity';
UPDATE project_resources SET backend_quota = NULL WHERE id = 3 AND service_id = 1 AND name = 'things';
UPDATE project_resources SET backend_quota = NULL WHERE id = 4 AND service_id = 2 AND name = 'capacity';
UPDATE project_resources SET backend_quota = NULL WHERE id = 6 AND service_id = 2 AND name = 'things';
UPDATE project_services SET quota_desynced_at = NULL, quota_sync_duration_secs = 5 WHERE id = 1 AND project_id = 1 AND type = 'unittest';
UPDATE project_services SET quota_desynced_at = NULL, quota_sync_duration_secs = 5 WHERE id = 2 AND project_id = 2 AND type = 'unittest';
`)
Expand All @@ -696,9 +686,7 @@ func Test_TopologyScrapes(t *testing.T) {
UPDATE project_az_resources SET backend_quota = NULL WHERE id = 7 AND resource_id = 3 AND az = 'az-two';
UPDATE project_az_resources SET backend_quota = 50 WHERE id = 8 AND resource_id = 4 AND az = 'az-one';
UPDATE project_az_resources SET backend_quota = 50 WHERE id = 9 AND resource_id = 4 AND az = 'az-two';
UPDATE project_resources SET quota = 0, backend_quota = 40 WHERE id = 1 AND service_id = 1 AND name = 'capacity';
UPDATE project_resources SET quota = 0, backend_quota = 26 WHERE id = 3 AND service_id = 1 AND name = 'things';
UPDATE project_resources SET quota = 0, backend_quota = 40 WHERE id = 4 AND service_id = 2 AND name = 'capacity';
UPDATE project_resources SET quota = 0, backend_quota = 26 WHERE id = 6 AND service_id = 2 AND name = 'things';
UPDATE project_services SET scraped_at = %[1]d, checked_at = %[1]d, next_scrape_at = %[2]d, quota_desynced_at = %[1]d WHERE id = 1 AND project_id = 1 AND type = 'unittest';
UPDATE project_services SET scraped_at = %[3]d, checked_at = %[3]d, next_scrape_at = %[4]d, quota_desynced_at = %[3]d WHERE id = 2 AND project_id = 2 AND type = 'unittest';
Expand All @@ -724,6 +712,8 @@ func Test_TopologyScrapes(t *testing.T) {
DELETE FROM project_az_resources WHERE id = 16 AND resource_id = 6 AND az = 'any';
UPDATE project_az_resources SET backend_quota = 21 WHERE id = 6 AND resource_id = 3 AND az = 'az-one';
UPDATE project_az_resources SET usage = 0, subresources = '', historical_usage = '{"t":[%[1]d,%[3]d],"v":[2,0]}' WHERE id = 7 AND resource_id = 3 AND az = 'az-two';
UPDATE project_resources SET quota = NULL, backend_quota = NULL WHERE id = 3 AND service_id = 1 AND name = 'things';
UPDATE project_resources SET quota = NULL, backend_quota = NULL WHERE id = 6 AND service_id = 2 AND name = 'things';
UPDATE project_services SET scraped_at = %[3]d, serialized_metrics = '{"capacity_usage":0,"things_usage":2}', checked_at = %[3]d, next_scrape_at = %[4]d WHERE id = 1 AND project_id = 1 AND type = 'unittest';
UPDATE project_services SET scraped_at = %[5]d, serialized_metrics = '{"capacity_usage":0,"things_usage":2}', checked_at = %[5]d, next_scrape_at = %[6]d WHERE id = 2 AND project_id = 2 AND type = 'unittest';
`,
Expand Down
4 changes: 2 additions & 2 deletions internal/datamodel/project_resource_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (u ProjectResourceUpdate) Run(dbi db.Interface, cluster *core.Cluster, now
result = append(result, res)

// check if we need to arrange for SetQuotaJob to look at this project service
if resInfo.HasQuota {
if resInfo.HasQuota && resInfo.Topology != liquid.AZSeparatedResourceTopology {
backendQuota := unwrapOrDefault(res.BackendQuota, -1)
quota := *res.Quota // definitely not nil, it was set above in validateResourceConstraints()
if backendQuota < 0 || uint64(backendQuota) != quota {
Expand Down Expand Up @@ -172,7 +172,7 @@ func unwrapOrDefault[T any](value *T, defaultValue T) T {

// Ensures that `res` conforms to various constraints and validation rules.
func validateResourceConstraints(res *db.ProjectResource, resInfo liquid.ResourceInfo) {
if !resInfo.HasQuota {
if !resInfo.HasQuota || resInfo.Topology == liquid.AZSeparatedResourceTopology {
// ensure that NoQuota resources do not contain any quota values
res.Quota = nil
res.BackendQuota = nil
Expand Down
8 changes: 6 additions & 2 deletions internal/test/plugins/quota_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,12 @@ func (p *GenericQuotaPlugin) Scrape(ctx context.Context, project core.KeystonePr
}

// populate azSeparatedQuota
for az, data := range copyOfVal.UsageData {
data.Quota = val.UsageData[az].Quota
topology := p.LiquidServiceInfo.Resources[key].Topology
if topology == liquid.AZSeparatedResourceTopology {
copyOfVal.Quota = 0
for az, data := range copyOfVal.UsageData {
data.Quota = val.UsageData[az].Quota
}
}

// test coverage for PhysicalUsage != Usage
Expand Down

0 comments on commit bee080e

Please sign in to comment.