-
Notifications
You must be signed in to change notification settings - Fork 2
/
nodeGroup.go
224 lines (198 loc) · 10.3 KB
/
nodeGroup.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package utils
import (
"context"
"fmt"
"reflect"
danav1 "github.com/dana-team/hns/api/v1"
"github.com/go-logr/logr"
"golang.org/x/exp/slices"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
danav1alpha1 "github.com/dana-team/hns-nqs-plugin/api/v1alpha1"
)
// CalculateNodeGroup calculates the resource list for a node group based on the provided nodes, NodeQuotaConfig, and node group name.
// It takes a context, a NodeList containing the nodes, the NodeQuotaConfig, and the node group name.
// It returns the calculated resource list (v1.ResourceList) for the node group.
func CalculateNodeGroup(nodes v1.NodeList, config danav1alpha1.NodeQuotaConfig, nodeGroup string) v1.ResourceList {
resourceMultiplier := getResourcesMultiplierByNodeGroup(config, nodeGroup)
nodeGroupResources := v1.ResourceList{}
for _, node := range nodes.Items {
resources := multiplyResourceList(node.Status.Allocatable, resourceMultiplier)
for resourceName, resourceQuantity := range resources {
addResourcesToList(&nodeGroupResources, resourceQuantity, string(resourceName))
}
}
return filterUncontrolledResources(nodeGroupResources, config.Spec.ControlledResources)
}
// getResourcesMultiplierByNodeGroup returns the resourcesMultiplier for the provided node group name.
func getResourcesMultiplierByNodeGroup(config danav1alpha1.NodeQuotaConfig, nodeGroup string) map[string]string {
var ResourceMultiplier map[string]string
for _, secondaryRoot := range config.Spec.Roots {
for _, resourceGroup := range secondaryRoot.SecondaryRoots {
if resourceGroup.Name == nodeGroup {
ResourceMultiplier = resourceGroup.ResourceMultiplier
}
}
}
return ResourceMultiplier
}
// getReservedResourcesByGroup retrieves the reserved resources for a specific node group from the NodeQuotaConfig.
// It takes the node group name and the NodeQuotaConfig.
// It returns the ReservedResources object (danav1alpha1.ReservedResources) for the node group.
func getReservedResourcesByGroup(group string, config danav1alpha1.NodeQuotaConfig) danav1alpha1.ReservedResources {
if !doesReservedResourceExist(config, group) {
return danav1alpha1.ReservedResources{}
}
for _, resource := range config.Status.ReservedResources {
if resource.NodeGroup == group {
return resource
}
}
return danav1alpha1.ReservedResources{}
}
// DeleteExpiredReservedResources removes the expired reserved resources from the NodeQuotaConfig.
// It takes the NodeQuotaConfig to modify and a logger for logging informational messages.
func DeleteExpiredReservedResources(config *danav1alpha1.NodeQuotaConfig, logger logr.Logger) {
var newReservedResources []danav1alpha1.ReservedResources
for _, resources := range config.Status.ReservedResources {
if isReservedResourceExpired(resources, *config) {
logger.Info(fmt.Sprintf("Removed ReservedResources from nodeGroup %s", resources.NodeGroup))
} else {
newReservedResources = append(newReservedResources, resources)
}
}
config.Status.ReservedResources = newReservedResources
}
// CalculateSecondaryNodeGroup calculates the resource list for a secondary node group based on the provided nodegroup and NodeQuotaConfig.
// It takes a context, a client for making API requests, a nodegroup to calculate resources for, and the NodeQuotaConfig.
// It returns an error (if any occurred) and the calculated resource list (v1.ResourceList).
func CalculateSecondaryNodeGroup(ctx context.Context, r client.Client, nodegroup danav1alpha1.NodeGroup, config *danav1alpha1.NodeQuotaConfig) (error, v1.ResourceList) {
logger, _ := logr.FromContext(ctx)
labelSelector := labels.SelectorFromSet(labels.Set(nodegroup.LabelSelector))
listOptions := &client.ListOptions{
LabelSelector: labelSelector,
}
nodeList := v1.NodeList{}
if err := r.List(ctx, &nodeList, listOptions); err != nil {
logger.Error(err, fmt.Sprintf("Error listing the nodes for the nodeGroup %s", nodegroup))
return err, v1.ResourceList{}
}
nodeResources := CalculateNodeGroup(nodeList, *config, nodegroup.Name)
return nil, nodeResources
}
// doesReservedResourceExist checks if a reserved resource exists in the NodeQuotaConfig for the given node group name.
// It takes the NodeQuotaConfig and the node group name to check.
// It returns a boolean value indicating whether the reserved resource exists or not.
func doesReservedResourceExist(config danav1alpha1.NodeQuotaConfig, nodeGroupName string) bool {
if len(config.Status.ReservedResources) == 0 {
return false
}
for _, reservedResources := range config.Status.ReservedResources {
if reservedResources.NodeGroup == nodeGroupName {
return true
}
}
return false
}
// UpdateRootSubnamespace updates the resourceQuota of the rootSubnamespace with the new quantity of resources.
func UpdateRootSubnamespace(ctx context.Context, rootResources v1.ResourceList, rootSubnamespace danav1alpha1.SubnamespacesRoots, logger logr.Logger, client client.Client) error {
rootRQ, err := GetRootQuota(client, ctx, rootSubnamespace.RootNamespace)
if err != nil {
logger.Error(err, fmt.Sprintf("Error getting the %s resourceQuota", rootSubnamespace.RootNamespace))
return err
}
rootRQ.Spec.Hard = patchResourcesToList(rootRQ.Spec.Hard, rootResources)
logger.Info(fmt.Sprintf("Updating RootSubnamespace %s with new resources", rootSubnamespace.RootNamespace))
if err := client.Update(ctx, &rootRQ); err != nil {
logger.Error(err, fmt.Sprintf("Error updating rootSubnamespace %s", rootSubnamespace.RootNamespace))
return err
}
return nil
}
// UpdateProcessedSecondaryRoots updates the secondaryRoots in the cluster with the new quantity of resources.
// It takes slice of Subnamespaces that was updated in memory and does API requests to commit the update.
func UpdateProcessedSecondaryRoots(ctx context.Context, processedSecondaryRoots []danav1.Subnamespace, logger logr.Logger, client client.Client) error {
for _, sns := range processedSecondaryRoots {
logger.Info(fmt.Sprintf("Updating secondaryRoot %s with new resources", sns.Name))
if err := client.Update(ctx, &sns); err != nil {
logger.Error(err, fmt.Sprintf("Error updating secondaryRoot %s", sns.Name))
return err
}
}
return nil
}
// isReservedResourceExpired checks if a reserved created more than X hours ago, defined by the user in the config CRD.
func isReservedResourceExpired(reservedResources danav1alpha1.ReservedResources, config danav1alpha1.NodeQuotaConfig) bool {
return hoursPassedSinceDate(reservedResources.Timestamp) >= config.Spec.ReservedHoursToLive
}
// setReservedToConfig sets the reserved resources for a node group in the NodeQuotaConfig.
// It takes the resource debt (v1.ResourceList) to set, the node group name, the NodeQuotaConfig to modify, and a logger for logging informational messages.
func setReservedToConfig(debt v1.ResourceList, nodeGroupName string, config *danav1alpha1.NodeQuotaConfig, logger logr.Logger) {
if doesReservedResourceExist(*config, nodeGroupName) {
reservedResources := getReservedResourcesByGroup(nodeGroupName, *config)
reservedResources.Resources = debt
removeReservedFromConfig(nodeGroupName, config)
config.Status.ReservedResources = append(config.Status.ReservedResources, reservedResources)
return
}
config.Status.ReservedResources = append(config.Status.ReservedResources, danav1alpha1.ReservedResources{
NodeGroup: nodeGroupName,
Resources: debt,
Timestamp: metav1.Now(),
})
logger.Info(fmt.Sprintf("Added ReservedResources to nodeGroup %s", nodeGroupName))
}
// removeReservedFromConfig removes the reserved resources for a node group from the NodeQuotaConfig.
// It takes the node group name and the NodeQuotaConfig to modify.
func removeReservedFromConfig(nodeGroupName string, config *danav1alpha1.NodeQuotaConfig) {
index := -1
for i, reservedResources := range config.Status.ReservedResources {
if reservedResources.NodeGroup == nodeGroupName {
index = i
}
}
if index == -1 {
return
}
config.Status.ReservedResources = slices.Delete(config.Status.ReservedResources, index, index+1)
}
// ProcessSecondaryRoot processes a secondary root node group and updates the corresponding Subnamespace object and add reserved resources to the config if needed.
// It takes a context, a client for making API requests, the secondary root node group, the NodeQuotaConfig,
// the root subnamespace, and a logger for logging informational messages.
// It returns an error (if any occurred) and the updated Subnamespace object (danav1.Subnamespace).
func ProcessSecondaryRoot(ctx context.Context, r client.Client, secondaryRoot danav1alpha1.NodeGroup, config *danav1alpha1.NodeQuotaConfig, rootSubnamespace string, logger logr.Logger) (danav1.Subnamespace, bool, error) {
sns := danav1.Subnamespace{}
if err := r.Get(ctx, types.NamespacedName{Namespace: rootSubnamespace, Name: secondaryRoot.Name}, &sns); err != nil {
logger.Error(err, fmt.Sprintf("Error getting the subnamespace %s", secondaryRoot.Name))
return sns, false, err
}
err, groupResources := CalculateSecondaryNodeGroup(ctx, r, secondaryRoot, config)
if err != nil {
return sns, false, err
}
groupReserved := getReservedResourcesByGroup(secondaryRoot.Name, *config)
filteredSNSResources := filterUncontrolledResources(sns.Spec.ResourceQuotaSpec.Hard, config.Spec.ControlledResources)
if isGreaterThan(filteredSNSResources, groupResources) {
// one or more nodes removed from cluster
debt := subtractTwoResourceList(sns.Spec.ResourceQuotaSpec.Hard, groupResources)
filteredDebt := filterUncontrolledResources(debt, config.Spec.ControlledResources)
if groupReserved.NodeGroup == "" || !isReservedResourceExpired(groupReserved, *config) {
setReservedToConfig(filteredDebt, secondaryRoot.Name, config, logger)
return sns, true, nil
}
} else {
// one or more nodes added to cluster
totalResources := MergeTwoResourceList(groupResources, groupReserved.Resources)
filteredTotalResources := filterUncontrolledResources(totalResources, config.Spec.ControlledResources)
if isGreaterThan(filteredTotalResources, filteredSNSResources) || isEqualTo(filteredTotalResources, filteredSNSResources) {
removeReservedFromConfig(secondaryRoot.Name, config)
}
}
if !reflect.DeepEqual(sns.Spec.ResourceQuotaSpec.Hard, groupResources) {
sns.Spec.ResourceQuotaSpec.Hard = patchResourcesToList(sns.Spec.ResourceQuotaSpec.Hard, groupResources)
}
return sns, false, err
}