Skip to content

Commit 15be92c

Browse files
committed
switch liquid designate to use prometheus queries for usage
1 parent 0ea5651 commit 15be92c

File tree

3 files changed

+54
-17
lines changed

3 files changed

+54
-17
lines changed

docs/liquids/designate.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ This liquid provides support for the DNS service Designate.
1313

1414
## Service-specific configuration
1515

16-
None.
16+
| Field | Type | Description |
17+
| ----- | ---- | ----------- |
18+
| `prometheus_config.api` | [`promquery.Config`](https://pkg.go.dev/github.com/sapcc/go-bits@v0.0.0-20260306103145-db6bddb66aec/promquery#Config) | Configuration for the Prometheus connection from which usage data is queried by the liquid. |
19+
| `prometheus_config.queries.zones` | [`text/template`](https://pkg.go.dev/text/template) compatible string | Prometheus query for scraping the number of zones per project. The template should contain a filter string `{{.ProjectUUID}}` to be filled with the UUID of the project to be queried for usages. |
20+
| `prometheus_config.queries.recordsets_per_zone` | [`text/template`](https://pkg.go.dev/text/template) compatible string | Prometheus query for scraping the maximum number of recordsets across all zones of this project. The template should contain a filter string `{{.ProjectUUID}}` to be filled with the UUID of the project to be queried for usages. |
1721

1822
## Resources
1923

internal/liquids/designate/liquid.go

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,31 @@
44
package designate
55

66
import (
7+
"bytes"
78
"context"
89
"errors"
10+
"fmt"
11+
"math"
912
"net/http"
13+
"text/template"
1014

1115
"github.com/gophercloud/gophercloud/v2"
1216
. "github.com/majewsky/gg/option"
1317
"github.com/sapcc/go-api-declarations/liquid"
18+
"github.com/sapcc/go-bits/promquery"
1419
"github.com/sapcc/go-bits/respondwith"
1520
)
1621

1722
// Logic implements the liquidapi.Logic interface for Designate.
1823
type Logic struct {
24+
// configuration
25+
PrometheusConfig struct {
26+
APIConfig promquery.Config `json:"api"`
27+
Queries struct {
28+
Zones string `json:"zones"`
29+
RecordsetsPerZone string `json:"recordsets_per_zone"`
30+
} `json:"queries"`
31+
} `json:"prometheus_config"`
1932
// connections
2033
DesignateV2 *Client `json:"-"`
2134
}
@@ -62,24 +75,43 @@ func (l *Logic) ScanUsage(ctx context.Context, projectUUID string, req liquid.Se
6275
return liquid.ServiceUsageReport{}, err
6376
}
6477

65-
// to query usage, start by listing all zones
66-
zoneIDs, err := l.DesignateV2.listZoneIDs(ctx, projectUUID)
67-
if err != nil {
68-
return liquid.ServiceUsageReport{}, err
69-
}
70-
71-
// query "recordsets per zone" usage by counting recordsets in each zone
72-
// individually (we could count all recordsets over the all project at once,
73-
// but that won't help since the quota applies per individual zone)
74-
maxRecordsetsPerZone := uint64(0)
75-
for _, zoneID := range zoneIDs {
76-
count, err := l.DesignateV2.countZoneRecordsets(ctx, projectUUID, zoneID)
78+
// note: The following data is available via designate API, but we transitioned to use
79+
// Prometheus queries because of the faster response times for more frequent scraping.
80+
client, err := l.PrometheusConfig.APIConfig.Connect()
81+
scrapeUsageMetric := func(query string) (uint64, error) {
82+
if query == "" {
83+
return 0, errors.New("config for this metric is missing")
84+
}
85+
tmpl, err := template.New("query").Parse(query)
7786
if err != nil {
78-
return liquid.ServiceUsageReport{}, err
87+
return 0, fmt.Errorf("error while parsing the template: %w", err)
88+
}
89+
data := map[string]any{
90+
"ProjectUUID": projectUUID,
7991
}
80-
if maxRecordsetsPerZone < count {
81-
maxRecordsetsPerZone = count
92+
var templated bytes.Buffer
93+
err = tmpl.Execute(&templated, data)
94+
if err != nil {
95+
return 0, fmt.Errorf("error while filling the template: %w", err)
8296
}
97+
defaultValue := float64(0)
98+
value, err := client.GetSingleValue(ctx, templated.String(), &defaultValue)
99+
if err != nil {
100+
return 0, fmt.Errorf("error while retrieving prometheus value: %w", err)
101+
}
102+
if value < 0 || math.IsNaN(value) || math.IsInf(value, 0) {
103+
return 0, fmt.Errorf("unexpected value: %f", value)
104+
}
105+
return uint64(math.Round(value)), nil
106+
}
107+
108+
zones, err := scrapeUsageMetric(l.PrometheusConfig.Queries.Zones)
109+
if err != nil {
110+
return liquid.ServiceUsageReport{}, fmt.Errorf("while scraping zones usage: %w", err)
111+
}
112+
maxRecordsetsPerZone, err := scrapeUsageMetric(l.PrometheusConfig.Queries.RecordsetsPerZone)
113+
if err != nil {
114+
return liquid.ServiceUsageReport{}, fmt.Errorf("while scraping recordsets per zone usage: %w", err)
83115
}
84116

85117
return liquid.ServiceUsageReport{
@@ -88,7 +120,7 @@ func (l *Logic) ScanUsage(ctx context.Context, projectUUID string, req liquid.Se
88120
"zones": {
89121
Quota: Some(quotas.Zones),
90122
PerAZ: liquid.InAnyAZ(liquid.AZResourceUsageReport{
91-
Usage: uint64(len(zoneIDs)),
123+
Usage: zones,
92124
}),
93125
},
94126
"recordsets_per_zone": {

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func main() {
8888
opts.ServiceInfoRefreshInterval = 0
8989
must.Succeed(liquidapi.Run(ctx, &cronus.Logic{}, opts))
9090
case "designate":
91+
opts.TakesConfiguration = true
9192
opts.ServiceInfoRefreshInterval = 0
9293
must.Succeed(liquidapi.Run(ctx, &designate.Logic{}, opts))
9394
case "ironic":

0 commit comments

Comments
 (0)