Skip to content

Commit ce8668f

Browse files
committed
Don't store external tree labels explicitly
While preparing to add managed labels, I found it was weird to have an explicit set of external tree labels when these can easily be derived from the existing list of labels. This change removes the explicit external tree labels and instead recomputes them on-demand, which should be rare (external namespaces are neither used nor synced frequently). Tested: existing unit/integ tests (no change in functionality)
1 parent c3013a0 commit ce8668f

File tree

5 files changed

+42
-36
lines changed

5 files changed

+42
-36
lines changed

internal/forest/namespace.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package forest
22

33
import (
44
"reflect"
5+
"strconv"
6+
"strings"
57

68
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
79
"k8s.io/apimachinery/pkg/labels"
@@ -26,7 +28,8 @@ type Namespace struct {
2628
exists bool
2729
allowCascadingDeletion bool
2830

29-
// labels store the original namespaces' labels
31+
// labels store the original namespaces' labels, and are used for object propagation exceptions
32+
// and to store the tree labels of external namespaces.
3033
labels map[string]string
3134

3235
// sourceObjects store the objects created by users, identified by GVK and name.
@@ -44,15 +47,10 @@ type Namespace struct {
4447
// Anchors store a list of anchors in the namespace.
4548
Anchors []string
4649

47-
// Manager stores the manager of the namespace. The default value
48-
// "hnc.x-k8s.io" means it's managed by HNC.
50+
// Manager stores the manager of the namespace. The default value of "hnc.x-k8s.io" means it's
51+
// managed by HNC. Any other value means that the namespace is an "external" namespace, whose
52+
// metadata (e.g. labels) are set outside of HNC.
4953
Manager string
50-
51-
// ExternalTreeLabels stores external tree labels if this namespace is an external namespace.
52-
// It will be empty if the namespace is not external. External namespace will at least have one
53-
// tree label of itself. The key is the tree label without ".tree.hnc.x-k8s.io/depth" suffix.
54-
// The value is the depth.
55-
ExternalTreeLabels map[string]int
5654
}
5755

5856
// Name returns the name of the namespace, of "<none>" if the namespace is nil.
@@ -90,6 +88,19 @@ func (ns *Namespace) UnsetExists() bool {
9088
return changed
9189
}
9290

91+
// GetTreeLabels returns all the tree labels with the values converted into integers for easier
92+
// manipulation.
93+
func (ns *Namespace) GetTreeLabels() map[string]int {
94+
r := map[string]int{}
95+
for k, v := range ns.labels {
96+
if !strings.Contains(k, api.LabelTreeDepthSuffix) {
97+
continue
98+
}
99+
r[k], _ = strconv.Atoi(v)
100+
}
101+
return r
102+
}
103+
93104
func (ns *Namespace) GetLabels() labels.Set {
94105
return labels.Set(ns.labels)
95106
}
@@ -176,5 +187,5 @@ func (ns *Namespace) HasAnchor(n string) bool {
176187

177188
// IsExternal returns true if the namespace is not managed by HNC.
178189
func (ns *Namespace) IsExternal() bool {
179-
return len(ns.ExternalTreeLabels) > 0
190+
return ns.Manager != "" && ns.Manager != api.MetaGroup
180191
}

internal/hierarchyconfig/reconciler.go

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -331,15 +331,14 @@ func (r *Reconciler) syncWithForest(log logr.Logger, nsInst *corev1.Namespace, i
331331
// syncExternalNamespace sets external tree labels to the namespace in the forest
332332
// if the namespace is an external namespace not managed by HNC.
333333
func (r *Reconciler) syncExternalNamespace(log logr.Logger, nsInst *corev1.Namespace, ns *forest.Namespace) {
334-
ns.Manager = nsInst.Annotations[api.AnnotationManagedBy]
335-
if ns.Manager == "" || ns.Manager == api.MetaGroup {
334+
mgr := nsInst.Annotations[api.AnnotationManagedBy]
335+
if mgr == "" || mgr == api.MetaGroup {
336336
// If the internal namespace is just converted from an external namespace,
337337
// enqueue the descendants to remove the propagated external tree labels.
338338
if ns.IsExternal() {
339339
r.enqueueAffected(log, "subtree root converts from external to internal", ns.DescendantNames()...)
340340
}
341341
ns.Manager = api.MetaGroup
342-
ns.ExternalTreeLabels = nil
343342
return
344343
}
345344

@@ -348,18 +347,7 @@ func (r *Reconciler) syncExternalNamespace(log logr.Logger, nsInst *corev1.Names
348347
if !ns.IsExternal() {
349348
r.enqueueAffected(log, "subtree root converts from internal to external", ns.DescendantNames()...)
350349
}
351-
352-
// Get tree labels and set them in the forest. If there's no tree labels on the
353-
// namespace, set only one tree label of itself.
354-
etls := make(map[string]int)
355-
for tl, d := range nsInst.Labels {
356-
enm := strings.TrimSuffix(tl, api.LabelTreeDepthSuffix)
357-
if enm != tl {
358-
etls[enm], _ = strconv.Atoi(d)
359-
}
360-
}
361-
etls[ns.Name()] = 0
362-
ns.ExternalTreeLabels = etls
350+
ns.Manager = mgr
363351
}
364352

365353
// syncSubnamespaceParent sets the parent to the owner and updates the SubnamespaceAnchorMissing
@@ -504,6 +492,9 @@ func (r *Reconciler) syncAnchors(log logr.Logger, ns *forest.Namespace, anms []s
504492
func (r *Reconciler) syncTreeLabels(log logr.Logger, nsInst *corev1.Namespace, ns *forest.Namespace) bool {
505493
if ns.IsExternal() {
506494
metadata.SetLabel(nsInst, nsInst.Name+api.LabelTreeDepthSuffix, "0")
495+
496+
// Set the labels so we can retrieve the external tree labels in the future if needed
497+
ns.SetLabels(nsInst.Labels)
507498
return false
508499
}
509500

@@ -530,9 +521,8 @@ func (r *Reconciler) syncTreeLabels(log logr.Logger, nsInst *corev1.Namespace, n
530521
// Note it's impossible to have an external namespace as a non-root, which is
531522
// enforced by both admission controllers and the reconciler here.
532523
if curNS.IsExternal() {
533-
for k, v := range curNS.ExternalTreeLabels {
534-
l = k + api.LabelTreeDepthSuffix
535-
metadata.SetLabel(nsInst, l, strconv.Itoa(depth+v))
524+
for k, v := range curNS.GetTreeLabels() {
525+
metadata.SetLabel(nsInst, k, strconv.Itoa(depth+v))
536526
}
537527
break
538528
}
@@ -543,7 +533,7 @@ func (r *Reconciler) syncTreeLabels(log logr.Logger, nsInst *corev1.Namespace, n
543533
// Update the labels in the forest so that we can quickly access the labels and
544534
// compare if they match the given selector
545535
if ns.SetLabels(nsInst.Labels) {
546-
log.Info("Namespace tree labels have been updated.")
536+
log.Info("Namespace managed and tree labels have been updated")
547537
return true
548538
}
549539
return false

internal/hierarchyconfig/validator_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ func TestChangeParentOnManagedBy(t *testing.T) {
7070
l := zap.New()
7171

7272
// Make c and d external namespaces
73-
f.Get("c").ExternalTreeLabels = map[string]int{"c" + api.LabelTreeDepthSuffix: 0}
74-
f.Get("d").ExternalTreeLabels = map[string]int{"d" + api.LabelTreeDepthSuffix: 0}
73+
f.Get("c").Manager = "external-tool"
74+
f.Get("d").Manager = "external-tool"
7575

7676
// These cases test changing parent for internal or external namespaces, described
7777
// in the table at https://bit.ly/hnc-external-hierarchy#heading=h.z9mkbslfq41g

internal/namespace/validator.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,12 @@ func (v *Validator) illegalIncludedNamespaceLabel(req *nsRequest) admission.Resp
149149
// namespace name cannot be updated.
150150
func (v *Validator) nameExistsInExternalHierarchy(req *nsRequest) admission.Response {
151151
for _, nm := range v.Forest.GetNamespaceNames() {
152-
if _, ok := v.Forest.Get(nm).ExternalTreeLabels[req.ns.Name]; ok {
152+
ns := v.Forest.Get(nm)
153+
if !ns.IsExternal() {
154+
continue
155+
}
156+
externalTreeLabels := ns.GetTreeLabels()
157+
if _, ok := externalTreeLabels[req.ns.Name]; ok {
153158
msg := fmt.Sprintf("The namespace name %q is reserved by the external hierarchy manager %q.", req.ns.Name, v.Forest.Get(nm).Manager)
154159
return webhooks.Deny(metav1.StatusReasonAlreadyExists, msg)
155160
}

internal/namespace/validator_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,10 @@ func TestCreateNamespace(t *testing.T) {
124124
f := foresttest.Create("-")
125125
vns := &Validator{Forest: f}
126126
a := f.Get("a")
127-
a.ExternalTreeLabels = map[string]int{
128-
nm: 1,
129-
a.Name(): 0,
130-
}
127+
a.SetLabels(map[string]string{
128+
nm + api.LabelTreeDepthSuffix: "1",
129+
a.Name() + api.LabelTreeDepthSuffix: "0",
130+
})
131131

132132
// Requested namespace uses "exhier" as name.
133133
ns := &corev1.Namespace{}

0 commit comments

Comments
 (0)