• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sapcc / limes / 14831633718

05 May 2025 07:48AM UTC coverage: 78.206% (+1.0%) from 77.223%
14831633718

Pull #708

github

Varsius
replace resource data types by liquid reports
Pull Request #708: replace resource data types by liquid reports

130 of 163 new or added lines in 6 files covered. (79.75%)

3 existing lines in 2 files now uncovered.

6355 of 8126 relevant lines covered (78.21%)

46.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.0
/internal/plugins/liquid.go
1
/*******************************************************************************
2
*
3
* Copyright 2024 SAP SE
4
*
5
* Licensed under the Apache License, Version 2.0 (the "License");
6
* you may not use this file except in compliance with the License.
7
* You should have received a copy of the License along with this
8
* program. If not, you may obtain a copy of the License at
9
*
10
*     http://www.apache.org/licenses/LICENSE-2.0
11
*
12
* Unless required by applicable law or agreed to in writing, software
13
* distributed under the License is distributed on an "AS IS" BASIS,
14
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
* See the License for the specific language governing permissions and
16
* limitations under the License.
17
*
18
*******************************************************************************/
19

20
package plugins
21

22
import (
23
        "context"
24
        "encoding/json"
25
        "fmt"
26
        "math/big"
27

28
        "github.com/gophercloud/gophercloud/v2"
29
        . "github.com/majewsky/gg/option"
30
        "github.com/sapcc/go-api-declarations/limes"
31
        "github.com/sapcc/go-api-declarations/liquid"
32
        "github.com/sapcc/go-bits/liquidapi"
33
        "github.com/sapcc/go-bits/logg"
34

35
        "github.com/sapcc/limes/internal/core"
36
        "github.com/sapcc/limes/internal/db"
37
)
38

39
type LiquidQuotaPlugin struct {
40
        // configuration
41
        Area              string         `yaml:"area"`
42
        LiquidServiceType string         `yaml:"liquid_service_type"`
43
        ServiceType       db.ServiceType `yaml:"-"`
44

45
        // state
46
        LiquidServiceInfo liquid.ServiceInfo `yaml:"-"`
47
        LiquidClient      core.LiquidClient  `yaml:"-"`
48
}
49

50
func init() {
3✔
51
        core.QuotaPluginRegistry.Add(func() core.QuotaPlugin { return &LiquidQuotaPlugin{} })
106✔
52
}
53

54
// PluginTypeID implements the core.QuotaPlugin interface.
55
func (p *LiquidQuotaPlugin) PluginTypeID() string {
3✔
56
        return "liquid"
3✔
57
}
3✔
58

59
// Init implements the core.QuotaPlugin interface.
60
func (p *LiquidQuotaPlugin) Init(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, serviceType db.ServiceType) (err error) {
100✔
61
        p.ServiceType = serviceType
100✔
62
        if p.LiquidServiceType == "" {
100✔
63
                p.LiquidServiceType = "liquid-" + string(p.ServiceType)
×
64
        }
×
65

66
        p.LiquidClient, err = core.NewLiquidClient(client, eo, liquidapi.ClientOpts{ServiceType: p.LiquidServiceType})
100✔
67
        if err != nil {
100✔
68
                return err
×
69
        }
×
70

71
        p.LiquidServiceInfo, err = p.LiquidClient.GetInfo(ctx)
100✔
72
        if err != nil {
100✔
73
                return err
×
74
        }
×
75
        err = liquid.ValidateServiceInfo(p.LiquidServiceInfo)
100✔
76
        return err
100✔
77
}
78

79
// ServiceInfo implements the core.QuotaPlugin interface.
80
func (p *LiquidQuotaPlugin) ServiceInfo() liquid.ServiceInfo {
1,930✔
81
        return p.LiquidServiceInfo
1,930✔
82
}
1,930✔
83

84
// Resources implements the core.QuotaPlugin interface.
85
func (p *LiquidQuotaPlugin) Resources() map[liquid.ResourceName]liquid.ResourceInfo {
×
86
        return p.LiquidServiceInfo.Resources
×
87
}
×
88

89
// Scrape implements the core.QuotaPlugin interface.
90
func (p *LiquidQuotaPlugin) Scrape(ctx context.Context, project core.KeystoneProject, allAZs []limes.AvailabilityZone) (result liquid.ServiceUsageReport, err error) {
33✔
91
        // shortcut for liquids that only have rates and no resources
33✔
92
        if len(p.LiquidServiceInfo.Resources) == 0 && len(p.LiquidServiceInfo.UsageMetricFamilies) == 0 {
34✔
93
                return liquid.ServiceUsageReport{}, nil
1✔
94
        }
1✔
95

96
        req, err := p.BuildServiceUsageRequest(project, allAZs)
32✔
97
        if err != nil {
32✔
NEW
98
                return liquid.ServiceUsageReport{}, err
×
99
        }
×
100

101
        result, err = p.LiquidClient.GetUsageReport(ctx, project.UUID, req)
32✔
102
        if err != nil {
38✔
103
                return liquid.ServiceUsageReport{}, err
6✔
104
        }
6✔
105
        if result.InfoVersion != p.LiquidServiceInfo.Version {
26✔
106
                logg.Fatal("ServiceInfo version for %s changed from %d to %d; restarting now to reload ServiceInfo...",
×
NEW
107
                        p.LiquidServiceType, p.LiquidServiceInfo.Version, result.InfoVersion)
×
108
        }
×
109
        err = liquid.ValidateServiceInfo(p.LiquidServiceInfo)
26✔
110
        if err != nil {
27✔
111
                return liquid.ServiceUsageReport{}, err
1✔
112
        }
1✔
113
        err = liquid.ValidateUsageReport(result, req, p.LiquidServiceInfo)
25✔
114
        if err != nil {
27✔
115
                return liquid.ServiceUsageReport{}, err
2✔
116
        }
2✔
117

118
        return result, nil
23✔
119
}
120

121
func (p *LiquidQuotaPlugin) BuildServiceUsageRequest(project core.KeystoneProject, allAZs []limes.AvailabilityZone) (liquid.ServiceUsageRequest, error) {
33✔
122
        req := liquid.ServiceUsageRequest{AllAZs: allAZs}
33✔
123
        if p.LiquidServiceInfo.UsageReportNeedsProjectMetadata {
34✔
124
                req.ProjectMetadata = Some(project.ForLiquid())
1✔
125
        }
1✔
126
        return req, nil
33✔
127
}
128

129
// SetQuota implements the core.QuotaPlugin interface.
130
func (p *LiquidQuotaPlugin) SetQuota(ctx context.Context, project core.KeystoneProject, quotaReq map[liquid.ResourceName]liquid.ResourceQuotaRequest) error {
6✔
131
        req := liquid.ServiceQuotaRequest{Resources: quotaReq}
6✔
132
        if p.LiquidServiceInfo.QuotaUpdateNeedsProjectMetadata {
6✔
133
                req.ProjectMetadata = Some(project.ForLiquid())
×
134
        }
×
135

136
        return p.LiquidClient.PutQuota(ctx, project.UUID, req)
6✔
137
}
138

139
// Rates implements the core.QuotaPlugin interface.
140
func (p *LiquidQuotaPlugin) Rates() map[liquid.RateName]liquid.RateInfo {
×
141
        return p.LiquidServiceInfo.Rates
×
142
}
×
143

144
// ScrapeRates implements the core.QuotaPlugin interface.
145
func (p *LiquidQuotaPlugin) ScrapeRates(ctx context.Context, project core.KeystoneProject, allAZs []limes.AvailabilityZone, prevSerializedState string) (result map[liquid.RateName]*big.Int, serializedState string, err error) {
6✔
146
        // shortcut for liquids that do not have rates
6✔
147
        if len(p.LiquidServiceInfo.Rates) == 0 {
7✔
148
                return nil, "", nil
1✔
149
        }
1✔
150

151
        req := liquid.ServiceUsageRequest{
5✔
152
                AllAZs:          allAZs,
5✔
153
                SerializedState: json.RawMessage(prevSerializedState),
5✔
154
        }
5✔
155
        if p.LiquidServiceInfo.UsageReportNeedsProjectMetadata {
5✔
156
                req.ProjectMetadata = Some(project.ForLiquid())
×
157
        }
×
158

159
        resp, err := p.LiquidClient.GetUsageReport(ctx, project.UUID, req)
5✔
160
        if err != nil {
6✔
161
                return nil, "", err
1✔
162
        }
1✔
163
        if resp.InfoVersion != p.LiquidServiceInfo.Version {
4✔
164
                logg.Fatal("ServiceInfo version for %s changed from %d to %d; restarting now to reload ServiceInfo...",
×
165
                        p.LiquidServiceType, p.LiquidServiceInfo.Version, resp.InfoVersion)
×
166
        }
×
167

168
        result = make(map[liquid.RateName]*big.Int)
4✔
169
        for rateName := range p.LiquidServiceInfo.Rates {
12✔
170
                rateReport := resp.Rates[rateName]
8✔
171
                if rateReport == nil {
8✔
172
                        return nil, "", fmt.Errorf("missing report for rate %q", rateName)
×
173
                }
×
174

175
                // TODO: add AZ-awareness for rate usage in Limes
176
                // (until this is done, we take the sum over all AZs here)
177
                result[rateName] = &big.Int{}
8✔
178
                for _, azReport := range rateReport.PerAZ {
16✔
179
                        if usage, ok := azReport.Usage.Unpack(); ok {
16✔
180
                                var x big.Int
8✔
181
                                result[rateName] = x.Add(result[rateName], usage)
8✔
182
                        }
8✔
183
                }
184
        }
185

186
        return result, string(resp.SerializedState), nil
4✔
187
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc