/
createvalidator.go
178 lines (146 loc) · 6.71 KB
/
createvalidator.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
package subnamespace
import (
"fmt"
"net/http"
"regexp"
"github.com/dana-team/hns/internal/objectcontext"
"github.com/dana-team/hns/internal/quota"
"github.com/dana-team/hns/internal/subnamespace/resourcepool"
"github.com/dana-team/hns/internal/subnamespace/snsutils"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
// handleCreate implements the non-boilerplate logic of the validator, allowing it to be more easily unit
// tested (i.e. without constructing a full admission.Request).
func (v *SubnamespaceValidator) handleCreate(snsObject *objectcontext.ObjectContext) admission.Response {
if response := v.validateSubnamespaceName(snsObject); !response.Allowed {
return response
}
if response := v.validateUniqueSNSName(snsObject); !response.Allowed {
return response
}
isSNSResourcePool, err := resourcepool.IsSNSResourcePool(snsObject.Object)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if response := v.validateResourcePoolOnly(isSNSResourcePool); !response.Allowed {
return response
}
// validate that the new parent doesn't already have too many subnamespaces in its branch
// the maximum number a subnamespace can have in its branch is called by the MaxSNS flag
if response := v.validateKeyCountInDB(snsObject); !response.Allowed {
return response
}
if response := v.validateSNSUnderRP(snsObject, isSNSResourcePool); !response.Allowed {
return response
}
if rsp := ValidateResourceQuotaParams(snsObject, isSNSResourcePool); !rsp.Allowed {
return rsp
}
if rsp := v.validateEnoughResourcesInParentSNS(snsObject); !rsp.Allowed {
return rsp
}
return admission.Allowed("")
}
// validateSubnamespaceName validate name for subnamespace according to RFC 1123, to match namespace name validation.
func (v *SubnamespaceValidator) validateSubnamespaceName(snsObject *objectcontext.ObjectContext) admission.Response {
snsName := snsObject.Name()
if len(snsName) > 63 {
message := fmt.Sprintf("Invalid value: %s: the subnamespace name should be at most 63 characters", snsName)
return admission.Denied(message)
}
if match, _ := regexp.MatchString("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", snsName); !match {
message := fmt.Sprintf("Invalid value: %s: a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc',", snsName)
return admission.Denied(message)
}
return admission.Allowed("")
}
// validateUniqueSNSName validates that a namespace with the given subnamespace name doesn't already exist.
func (v *SubnamespaceValidator) validateUniqueSNSName(snsObject *objectcontext.ObjectContext) admission.Response {
logger := log.FromContext(snsObject.Ctx)
snsName := snsObject.Name()
exists, err := snsutils.DoesSNSNamespaceExist(snsObject)
if err != nil {
logger.Error(err, "failed to check if namespace exists", "subnamespace", snsName)
return admission.Denied(err.Error())
}
if exists {
message := fmt.Sprintf("it's forbidden to create a subnamespace with a name that already exists. A subnamespace "+
"name must be unique across the cluster, and a namespace of name %q already exists; change "+
"the subnamespace name and try again", snsName)
return admission.Denied(message)
}
return admission.Allowed("")
}
// validateResourcePoolOnly validates whether only ResourcePools can be created
// in accordance to a set environment variable
func (v *SubnamespaceValidator) validateResourcePoolOnly(isSNSResourcePool bool) admission.Response {
if !isSNSResourcePool && v.OnlyRP {
message := "it's forbidden to create a subnamespace which is not a ResourcePool"
return admission.Denied(message)
}
return admission.Allowed("")
}
// validateKeyCountInDB validates that creating a new subnamespace under a given parent
// will not cause the new parent to exceed the maximum limit of namespaces in its hierarchy.
func (v *SubnamespaceValidator) validateKeyCountInDB(snsObject *objectcontext.ObjectContext) admission.Response {
parentSNSName := snsObject.Object.GetNamespace()
key := v.NamespaceDB.Key(parentSNSName)
if key != "" {
if v.NamespaceDB.KeyCount(key) >= v.MaxSNS {
message := fmt.Sprintf("it's forbidden to create more than '%v' namespaces under hierarchy %q", v.MaxSNS, key)
return admission.Denied(message)
}
}
return admission.Allowed("")
}
// validateSNSUnderRP validates if a subnamespace tries to be created under a ResourcePool.
func (v *SubnamespaceValidator) validateSNSUnderRP(snsObject *objectcontext.ObjectContext, isSNSResourcePool bool) admission.Response {
snsParentNSName := snsObject.Object.GetNamespace()
snsParentNS, err := objectcontext.New(snsObject.Ctx, snsObject.Client, types.NamespacedName{Name: snsParentNSName}, &corev1.Namespace{})
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
isParentNSResourcePool, err := resourcepool.IsNSResourcePool(snsParentNS)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if !isSNSResourcePool && isParentNSResourcePool {
message := fmt.Sprintf("it's forbidden to create a regular subnamespace under a ResourcePool. Only a ResourcePool SNS can be "+
"created under a ResourcePool. %q is part of a ResourcePool", snsParentNSName)
return admission.Denied(message)
}
return admission.Allowed("")
}
// validateEnoughResourcesInParentSNS validates that there are enough resources available in a parent subnamespace
// to create a new subnamespace with certain resources under it.
func (v *SubnamespaceValidator) validateEnoughResourcesInParentSNS(snsObject *objectcontext.ObjectContext) admission.Response {
logger := log.FromContext(snsObject.Ctx)
snsName := snsObject.Name()
snsParentName := snsObject.Namespace()
parentQuotaObject, err := quota.SubnamespaceParentObject(snsObject)
if err != nil {
logger.Error(err, "unable to get parent quota object")
return admission.Denied(err.Error())
}
quotaParent := quota.GetQuotaObjectSpec(parentQuotaObject.Object).Hard
quotaSNS := quota.SubnamespaceSpec(snsObject.Object).Hard
siblingsResources := quota.GetQuotaObjectsListResources(quota.SubnamespaceSiblingObjects(snsObject))
for resourceName := range quotaParent {
var (
siblings, _ = siblingsResources[resourceName]
parent, _ = quotaParent[resourceName]
request, _ = quotaSNS[resourceName]
)
parent.Sub(siblings)
parent.Sub(request)
if parent.Value() < 0 {
message := fmt.Sprintf("it's forbidden to create subnamespace %q under %q when there are are not "+
"enough resources of type %q in %q", snsName, snsParentName, resourceName.String(), snsParentName)
return admission.Denied(message)
}
}
return admission.Allowed("")
}