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

sapcc / limes / 14335672306

08 Apr 2025 02:07PM UTC coverage: 79.464% (+0.07%) from 79.394%
14335672306

Pull #692

github

majewsky
add documentation for commitment_behavior_for_resource
Pull Request #692: allow domain-specific commitment configuration

147 of 164 new or added lines in 11 files covered. (89.63%)

6079 of 7650 relevant lines covered (79.46%)

55.98 hits per line

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

81.69
/internal/core/commitment_behavior.go
1
/*******************************************************************************
2
*
3
* Copyright 2025 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 core
21

22
import (
23
        "slices"
24
        "time"
25

26
        . "github.com/majewsky/gg/option"
27
        "github.com/sapcc/go-api-declarations/limes"
28
        limesresources "github.com/sapcc/go-api-declarations/limes/resources"
29
        "github.com/sapcc/go-bits/errext"
30
        "github.com/sapcc/go-bits/regexpext"
31
)
32

33
// CommitmentBehavior describes how commitments work for a single resource.
34
//
35
// It appears in type ServiceConfiguration.
36
type CommitmentBehavior struct {
37
        // This ConfigSet is keyed on domain name, because commitment durations
38
        // (and thus committability) are allowed to differ per domain.
39
        //
40
        // If Durations.Pick() returns an empty slice, then commitments are entirely forbidden for that resource in the given domain.
41
        Durations regexpext.ConfigSet[string, []limesresources.CommitmentDuration] `yaml:"durations_per_domain"`
42

43
        MinConfirmDate Option[time.Time]                `yaml:"min_confirm_date"`
44
        UntilPercent   Option[float64]                  `yaml:"until_percent"`
45
        ConversionRule Option[CommitmentConversionRule] `yaml:"conversion_rule"`
46
}
47

48
// Validate returns a list of all errors in this behavior configuration.
49
//
50
// The `path` argument denotes the location of this behavior in the
51
// configuration file, and will be used when generating error messages.
52
func (b CommitmentBehavior) Validate(path string) (errs errext.ErrorSet) {
50✔
53
        if percent, ok := b.UntilPercent.Unpack(); ok {
50✔
NEW
54
                if percent < 0 {
×
NEW
55
                        errs.Addf("invalid value: %s.until_percent may not be smaller than 0", path)
×
NEW
56
                }
×
NEW
57
                if percent > 100 {
×
NEW
58
                        errs.Addf("invalid value: %s.until_percent may not be bigger than 100", path)
×
NEW
59
                }
×
60
        }
61

62
        return errs
50✔
63
}
64

65
// ScopedCommitmentBehavior is a CommitmentBehavior that applies only to a certain scope (usually a specific domain).
66
// It is created through the For... methods on type CommitmentBehavior.
67
type ScopedCommitmentBehavior struct {
68
        Durations      []limesresources.CommitmentDuration
69
        MinConfirmDate Option[time.Time]
70
        UntilPercent   Option[float64]
71
        ConversionRule Option[CommitmentConversionRule]
72
}
73

74
// ForDomain resolves Durations.Pick() using the provided domain name.
75
func (b CommitmentBehavior) ForDomain(domainName string) ScopedCommitmentBehavior {
676✔
76
        return ScopedCommitmentBehavior{
676✔
77
                Durations:      b.Durations.Pick(domainName).UnwrapOr(nil),
676✔
78
                MinConfirmDate: b.MinConfirmDate,
676✔
79
                UntilPercent:   b.UntilPercent,
676✔
80
                ConversionRule: b.ConversionRule,
676✔
81
        }
676✔
82
}
676✔
83

84
// ForCluster merges the commitment behaviors for all domains together, thus reporting
85
// all durations that are allowed on at least one domain in no guaranteed order.
86
func (b CommitmentBehavior) ForCluster() ScopedCommitmentBehavior {
158✔
87
        // merge all `b.Durations[].Value` together
158✔
88
        var allDurations []limesresources.CommitmentDuration
158✔
89
        for _, entry := range b.Durations {
234✔
90
                if len(allDurations) == 0 {
152✔
91
                        // optimization: avoid the loop below if possible
76✔
92
                        allDurations = slices.Clone(entry.Value)
76✔
93
                } else {
76✔
NEW
94
                        // merge without duplicates
×
NEW
95
                        for _, duration := range entry.Value {
×
NEW
96
                                if !slices.Contains(allDurations, duration) {
×
NEW
97
                                        allDurations = append(allDurations, duration)
×
NEW
98
                                }
×
99
                        }
100
                }
101
        }
102

103
        return ScopedCommitmentBehavior{
158✔
104
                Durations:      allDurations,
158✔
105
                MinConfirmDate: b.MinConfirmDate,
158✔
106
                UntilPercent:   b.UntilPercent,
158✔
107
                ConversionRule: b.ConversionRule,
158✔
108
        }
158✔
109
}
110

111
// CanConfirmCommitmentsAt evaluates the MinConfirmDate field.
112
func (b ScopedCommitmentBehavior) CanConfirmCommitmentsAt(t time.Time) bool {
27✔
113
        return b.MinConfirmDate.IsNoneOr(func(minConfirmDate time.Time) bool { return minConfirmDate.Before(t) })
34✔
114
}
115

116
// ForAPI converts this behavior into its API representation.
117
func (b ScopedCommitmentBehavior) ForAPI(now time.Time) Option[limesresources.CommitmentConfiguration] {
678✔
118
        if len(b.Durations) == 0 {
1,168✔
119
                return None[limesresources.CommitmentConfiguration]()
490✔
120
        }
490✔
121
        result := limesresources.CommitmentConfiguration{
188✔
122
                Durations: b.Durations,
188✔
123
        }
188✔
124
        if date, ok := b.MinConfirmDate.Unpack(); ok && date.After(now) {
235✔
125
                result.MinConfirmBy = &limes.UnixEncodedTime{Time: date}
47✔
126
        }
47✔
127
        return Some(result)
188✔
128
}
129

130
// CommitmentConversionRule describes how commitments for a resource may be converted
131
// into commitments for other resources with the same rule identifier.
132
type CommitmentConversionRule struct {
133
        Identifier string `yaml:"identifier"`
134
        Weight     uint64 `yaml:"weight"`
135
}
136

137
// CommitmentConversionRate describes the rate for converting commitments between two compatible resources.
138
type CommitmentConversionRate struct {
139
        FromAmount uint64
140
        ToAmount   uint64
141
}
142

143
// GetConversionRateTo checks whether this resource can be converted into the given resource.
144
// If so, the conversion rate is returned.
145
func (b ScopedCommitmentBehavior) GetConversionRateTo(other ScopedCommitmentBehavior) Option[CommitmentConversionRate] {
16✔
146
        sourceRule, ok := b.ConversionRule.Unpack()
16✔
147
        if !ok {
16✔
NEW
148
                return None[CommitmentConversionRate]()
×
NEW
149
        }
×
150
        targetRule, ok := other.ConversionRule.Unpack()
16✔
151
        if !ok {
20✔
152
                return None[CommitmentConversionRate]()
4✔
153
        }
4✔
154
        if sourceRule.Identifier != targetRule.Identifier {
14✔
155
                return None[CommitmentConversionRate]()
2✔
156
        }
2✔
157

158
        divisor := getGreatestCommonDivisor(sourceRule.Weight, targetRule.Weight)
10✔
159
        return Some(CommitmentConversionRate{
10✔
160
                FromAmount: targetRule.Weight / divisor,
10✔
161
                ToAmount:   sourceRule.Weight / divisor,
10✔
162
        })
10✔
163
}
164

165
func getGreatestCommonDivisor(a, b uint64) uint64 {
35✔
166
        if b == 0 {
45✔
167
                return a
10✔
168
        }
10✔
169
        return getGreatestCommonDivisor(b, a%b)
25✔
170
}
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

© 2026 Coveralls, Inc