Skip to content

Commit 606e642

Browse files
authored
Add resource calculator support for kubedb ops requests (#266)
Signed-off-by: Imtiaz Uddin <[email protected]>
1 parent ac73d91 commit 606e642

File tree

108 files changed

+1838
-2264
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+1838
-2264
lines changed

go.mod

+6-6
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ require (
4646
kmodules.xyz/custom-resources v0.25.2
4747
kmodules.xyz/go-containerregistry v0.0.11
4848
kmodules.xyz/monitoring-agent-api v0.25.4
49-
kmodules.xyz/resource-metadata v0.17.26-0.20231009112556-f90c55e7954d
50-
kmodules.xyz/resource-metrics v0.25.3
49+
kmodules.xyz/resource-metadata v0.17.26
50+
kmodules.xyz/resource-metrics v0.25.5
5151
kmodules.xyz/sets v0.25.0
5252
kubeops.dev/scanner v0.0.15
5353
kubepack.dev/lib-helm v0.7.3
@@ -196,11 +196,11 @@ require (
196196
go.uber.org/atomic v1.10.0 // indirect
197197
go.uber.org/multierr v1.8.0 // indirect
198198
go.uber.org/zap v1.23.0 // indirect
199-
golang.org/x/crypto v0.13.0 // indirect
200-
golang.org/x/net v0.15.0 // indirect
199+
golang.org/x/crypto v0.14.0 // indirect
200+
golang.org/x/net v0.17.0 // indirect
201201
golang.org/x/oauth2 v0.7.0 // indirect
202-
golang.org/x/sys v0.12.0 // indirect
203-
golang.org/x/term v0.12.0 // indirect
202+
golang.org/x/sys v0.13.0 // indirect
203+
golang.org/x/term v0.13.0 // indirect
204204
golang.org/x/text v0.13.0 // indirect
205205
golang.org/x/time v0.3.0 // indirect
206206
golang.org/x/tools v0.7.0 // indirect

go.sum

+12-12
Original file line numberDiff line numberDiff line change
@@ -1395,8 +1395,8 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0
13951395
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
13961396
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
13971397
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
1398-
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
1399-
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
1398+
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
1399+
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
14001400
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
14011401
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
14021402
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1506,8 +1506,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
15061506
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
15071507
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
15081508
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
1509-
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
1510-
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
1509+
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
1510+
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
15111511
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
15121512
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
15131513
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1673,15 +1673,15 @@ golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBc
16731673
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
16741674
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
16751675
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1676-
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
1677-
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1676+
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
1677+
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
16781678
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
16791679
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
16801680
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
16811681
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
16821682
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
1683-
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
1684-
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
1683+
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
1684+
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
16851685
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
16861686
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
16871687
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -2141,10 +2141,10 @@ kmodules.xyz/monitoring-agent-api v0.25.4 h1:OwkvtV23QhUy3f7o9lxPWaAW3jiJMGAMPPR
21412141
kmodules.xyz/monitoring-agent-api v0.25.4/go.mod h1:3LhrLDGQKQXhxYcjA/WNaO4HPpopYQzOutsEp2i3008=
21422142
kmodules.xyz/offshoot-api v0.25.4 h1:IjJNvkphcdYUG8XO/pBwXpuP8W+jxAWJZ3yH8vgI/as=
21432143
kmodules.xyz/offshoot-api v0.25.4/go.mod h1:PUk4EuJFhhyQykCflHj7EgXcljGIqs9vi0IN0RpxtY4=
2144-
kmodules.xyz/resource-metadata v0.17.26-0.20231009112556-f90c55e7954d h1:E0SkGh/coySTCdes4ZLw9kBT+RaVMjNhzm6mycxhK7g=
2145-
kmodules.xyz/resource-metadata v0.17.26-0.20231009112556-f90c55e7954d/go.mod h1:tyLxzAVkhlL3/jFdcQcX1RZ8i8h9bs+AJur5RcuaW40=
2146-
kmodules.xyz/resource-metrics v0.25.3 h1:g9EjNfYRrUSnbA4r+bUQefQ5Ban6I6rpKjnB3ER+Yew=
2147-
kmodules.xyz/resource-metrics v0.25.3/go.mod h1:H7YLdUQJXUSzf5cNI4IYWU4Wsmrua/jpw7gqDnE3BwM=
2144+
kmodules.xyz/resource-metadata v0.17.26 h1:4wHVycXqnBIy+Tabo4CF1N1SQAQvHkEHFhNKw7FwJV8=
2145+
kmodules.xyz/resource-metadata v0.17.26/go.mod h1:J1Pcm+DebjntCzzHw+e3ZKsO7pdAD7ZvriVY/+BMciU=
2146+
kmodules.xyz/resource-metrics v0.25.5 h1:hQu6r2VtkmeiO7tpR5uYDWj7u7mKloaGZ1j8dH30b24=
2147+
kmodules.xyz/resource-metrics v0.25.5/go.mod h1:y7pDmTWuVLNGSjwckKCwJFhCgi5fhbwS7PAcH2rmGcY=
21482148
kmodules.xyz/sets v0.25.0 h1:belY/3trp/M/CKc1TEteA40jb2uCIdwKHhjpvrIxG+8=
21492149
kmodules.xyz/sets v0.25.0/go.mod h1:ICWmplgCsxniWkImSBGlJrLT9npqOXKhX/Bwbc7bmEw=
21502150
kubeops.dev/scanner v0.0.15 h1:u9hM2096LEa+BPlI/p7tcqvOv7A9fAFEw8AOEGHP5GY=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Copyright AppsCode Inc. and Contributors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package resourcecalculator
18+
19+
import (
20+
"context"
21+
"errors"
22+
"strings"
23+
24+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
25+
"k8s.io/apimachinery/pkg/runtime/schema"
26+
"k8s.io/apimachinery/pkg/types"
27+
opsv1alpha1 "kmodules.xyz/resource-metrics/ops.kubedb.com/v1alpha1"
28+
"sigs.k8s.io/controller-runtime/pkg/client"
29+
)
30+
31+
// ReferencedObjInfo indicates the information about the referenced database
32+
// object by the kubedb OpsRequest object
33+
type ReferencedObjInfo struct {
34+
group string
35+
version string
36+
kind string
37+
name string
38+
namespace string
39+
}
40+
41+
const (
42+
DBGroup = "kubedb.com"
43+
DBVersion = "v1alpha2"
44+
)
45+
46+
// wrapReferencedDBResourceWithOpsReqObject get the DB resource reference by the OpsRequest
47+
// object and wrap the DB object within it as 'referencedDB'
48+
func wrapReferencedDBResourceWithOpsReqObject(kc client.Client, u *unstructured.Unstructured) error {
49+
opsReqObj := u.UnstructuredContent()
50+
opsPathMapper, err := opsv1alpha1.LoadOpsPathMapper(u.UnstructuredContent())
51+
if err != nil {
52+
return err
53+
}
54+
refObjPath := opsPathMapper.GetReferencedDbObjectPath()
55+
refObjNamePath := make([]string, len(refObjPath))
56+
copy(refObjNamePath, refObjPath)
57+
refObjNamePath[len(refObjNamePath)-1] = "name"
58+
59+
refObjInfo, err := getOpsRequestReferencedDbObjectInfo(u, refObjNamePath)
60+
if err != nil {
61+
return err
62+
}
63+
refDb, err := getReferencedDBResource(kc, refObjInfo)
64+
if err != nil {
65+
return err
66+
}
67+
err = unstructured.SetNestedMap(opsReqObj, refDb.UnstructuredContent(), refObjPath...)
68+
if err != nil {
69+
return err
70+
}
71+
u.Object = opsReqObj
72+
73+
return nil
74+
}
75+
76+
// getOpsRequestReferencedDbObjectInfo extracts the referenced database information from OpsRequest object
77+
func getOpsRequestReferencedDbObjectInfo(u *unstructured.Unstructured, refObjNamePath []string) (*ReferencedObjInfo, error) {
78+
refDbName, ok, err := unstructured.NestedString(u.UnstructuredContent(), refObjNamePath...)
79+
if err != nil {
80+
return nil, err
81+
}
82+
if !ok {
83+
return nil, errors.New("referenced database name not found")
84+
}
85+
ns := u.GetNamespace()
86+
kind := strings.TrimSuffix(u.GetKind(), "OpsRequest")
87+
88+
return &ReferencedObjInfo{
89+
group: DBGroup,
90+
version: DBVersion,
91+
kind: kind,
92+
name: refDbName,
93+
namespace: ns,
94+
}, nil
95+
}
96+
97+
// getReferencedDBResource get the database object referenced by the OpsRequest object and returns it
98+
func getReferencedDBResource(kc client.Client, ri *ReferencedObjInfo) (*unstructured.Unstructured, error) {
99+
dbRes := &unstructured.Unstructured{}
100+
dbRes.SetGroupVersionKind(schema.GroupVersionKind{
101+
Group: ri.group,
102+
Version: ri.version,
103+
Kind: ri.kind,
104+
})
105+
106+
err := kc.Get(context.TODO(), types.NamespacedName{Name: ri.name, Namespace: ri.namespace}, dbRes)
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
return dbRes, nil
112+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
Copyright AppsCode Inc. and Contributors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package resourcecalculator
18+
19+
import (
20+
"context"
21+
"errors"
22+
"fmt"
23+
"strings"
24+
25+
core "k8s.io/api/core/v1"
26+
apierrors "k8s.io/apimachinery/pkg/api/errors"
27+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28+
"k8s.io/apimachinery/pkg/runtime/schema"
29+
clustermeta "kmodules.xyz/client-go/cluster"
30+
"kmodules.xyz/resource-metadata/apis/management/v1alpha1"
31+
rsapi "kmodules.xyz/resource-metadata/apis/meta/v1alpha1"
32+
"kmodules.xyz/resource-metrics/api"
33+
opsv1alpha1 "kmodules.xyz/resource-metrics/ops.kubedb.com/v1alpha1"
34+
"sigs.k8s.io/controller-runtime/pkg/client"
35+
)
36+
37+
func quota(obj map[string]interface{}, pq *v1alpha1.ProjectQuota) (*rsapi.QuotaDecision, error) {
38+
qd := &rsapi.QuotaDecision{
39+
Decision: rsapi.DecisionAllow,
40+
Violations: make([]string, 0),
41+
}
42+
if pq == nil {
43+
qd.Decision = rsapi.DecisionNoOpinion
44+
return qd, nil
45+
}
46+
47+
gvk := getGVK(obj)
48+
if gvk.Group == "ops.kubedb.com" {
49+
opsPathMapper, err := opsv1alpha1.LoadOpsPathMapper(obj)
50+
if err != nil {
51+
return nil, err
52+
}
53+
dbObj, err := extractReferencedObject(obj, opsPathMapper.GetReferencedDbObjectPath()...)
54+
if err != nil {
55+
return nil, err
56+
}
57+
if err := deductRefDbObjResourceUsageFromProjectQuota(dbObj, pq); err != nil {
58+
return nil, err
59+
}
60+
gvk = getGVK(dbObj)
61+
}
62+
63+
c, err := api.Load(obj)
64+
if err != nil {
65+
return nil, err
66+
}
67+
dbRequests, err := c.AppResourceRequests(obj)
68+
if err != nil {
69+
return nil, err
70+
}
71+
dbLimits, err := c.AppResourceLimits(obj)
72+
if err != nil {
73+
return nil, err
74+
}
75+
dbDemand := mergeRequestsLimits(dbRequests, dbLimits)
76+
77+
for _, quota := range pq.Status.Quotas {
78+
if quota.Result != v1alpha1.ResultSuccess {
79+
continue
80+
}
81+
if quota.Group == gvk.Group {
82+
if quota.Kind != "" && quota.Kind != gvk.Kind {
83+
continue
84+
}
85+
newUsed := api.AddResourceList(quota.Used, dbDemand)
86+
for rk, newUsed := range newUsed {
87+
hard, found := quota.Hard[rk]
88+
if !found {
89+
continue
90+
}
91+
if newUsed.Cmp(hard) > 0 {
92+
dd := dbDemand[rk]
93+
du := quota.Used[rk]
94+
dh := quota.Hard[rk]
95+
96+
qd.Decision = rsapi.DecisionDeny
97+
qd.Violations = append(qd.Violations,
98+
fmt.Sprintf("Project quota exceeded. Requested: %s=%s, Used: %s=%s, Limited: %s=%s", rk, dd.String(), rk, du.String(), rk, dh.String()))
99+
}
100+
}
101+
}
102+
}
103+
104+
return qd, nil
105+
}
106+
107+
func deductRefDbObjResourceUsageFromProjectQuota(dbObj map[string]interface{}, pq *v1alpha1.ProjectQuota) error {
108+
c, err := api.Load(dbObj)
109+
if err != nil {
110+
return err
111+
}
112+
dbRequests, err := c.AppResourceRequests(dbObj)
113+
if err != nil {
114+
return err
115+
}
116+
dbLimits, err := c.AppResourceLimits(dbObj)
117+
if err != nil {
118+
return err
119+
}
120+
dbDemand := mergeRequestsLimits(dbRequests, dbLimits)
121+
122+
gvk := getGVK(dbObj)
123+
for i, quota := range pq.Status.Quotas {
124+
if quota.Result != v1alpha1.ResultSuccess {
125+
continue
126+
}
127+
if quota.Group == gvk.Group {
128+
if quota.Kind != "" && quota.Kind != gvk.Kind {
129+
continue
130+
}
131+
quota.Used = api.SubtractResourceList(quota.Used, dbDemand)
132+
}
133+
pq.Status.Quotas[i].Used = quota.Used
134+
}
135+
136+
return nil
137+
}
138+
139+
func mergeRequestsLimits(requests, limits core.ResourceList) core.ResourceList {
140+
rl := make(core.ResourceList)
141+
for k, r := range requests {
142+
_, _, found := strings.Cut(k.String(), ".")
143+
if !found {
144+
rl["requests."+k] = r
145+
} else {
146+
rl[k] = r
147+
}
148+
}
149+
for k, l := range limits {
150+
_, _, found := strings.Cut(k.String(), ".")
151+
if !found {
152+
rl["limits."+k] = l
153+
} else {
154+
rl[k] = l
155+
}
156+
}
157+
158+
return rl
159+
}
160+
161+
func getProjectQuota(kc client.Client, ns string) (*v1alpha1.ProjectQuota, error) {
162+
projectId, _, err := clustermeta.GetProjectId(kc, ns)
163+
if err != nil {
164+
return nil, err
165+
}
166+
var pj v1alpha1.ProjectQuota
167+
err = kc.Get(context.TODO(), client.ObjectKey{Name: projectId}, &pj)
168+
if err != nil {
169+
if apierrors.IsNotFound(err) {
170+
return nil, nil
171+
}
172+
return nil, err
173+
}
174+
return &pj, nil
175+
}
176+
177+
func getGVK(obj map[string]interface{}) schema.GroupVersionKind {
178+
var unObj unstructured.Unstructured
179+
unObj.SetUnstructuredContent(obj)
180+
181+
return unObj.GroupVersionKind()
182+
}
183+
184+
func extractReferencedObject(opsObj map[string]interface{}, refDbPath ...string) (map[string]interface{}, error) {
185+
if len(refDbPath) == 0 {
186+
refDbPath = []string{"spec", "databaseRef", "referencedDB"}
187+
}
188+
dbObj, found, _ := unstructured.NestedMap(opsObj, refDbPath...)
189+
if !found {
190+
return nil, errors.New("referenced db object not found")
191+
}
192+
193+
return dbObj, nil
194+
}

0 commit comments

Comments
 (0)