diff --git a/incubator/hnc/api/v1alpha1/hierarchical_namespace.go b/incubator/hnc/api/v1alpha1/hierarchical_namespace.go deleted file mode 100644 index b570c08a8..000000000 --- a/incubator/hnc/api/v1alpha1/hierarchical_namespace.go +++ /dev/null @@ -1,79 +0,0 @@ -/* - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Constant for the hierarchicalnamespaces resource type. -const HierarchicalNamespaces string = "hierarchicalnamespaces" - -// HNSState describes the state of a hierarchical namespace. The state could be -// "missing", "ok", "conflict" or "forbidden". The definitions will be described below. -type HNSState string - -// HNSStates, which are documented in the comment to HierarchicalNamespaceStatus.State. -const ( - Missing HNSState = "missing" - Ok HNSState = "ok" - Conflict HNSState = "conflict" - Forbidden HNSState = "forbidden" -) - -// HierarchicalNamespaceStatus defines the observed state of HierarchicalNamespace. -type HierarchicalNamespaceStatus struct { - // Describes the state of a hierarchical namespace. - // - // Currently, the supported values are: - // - // - "missing": the child namespace has not been created yet. This should be the default - // state when the HNS is just created. - // - // - "ok": the child namespace exists. - // - // - "conflict": a namespace of the same name already exists. The admission controller - // will attempt to prevent this. - // - // - "forbidden": the HNS was created in a namespace that doesn't allow children, such - // as kube-system or hnc-system. The admission controller will attempt to prevent this. - State HNSState `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:resource:path=hierarchicalnamespaces,shortName=hns,scope=Namespaced - -// HierarchicalNamespace is the Schema for the self-service namespace API. -// See details at http://bit.ly/hnc-self-serve-ux. -type HierarchicalNamespace struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Status HierarchicalNamespaceStatus `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// HierarchicalNamespaceList contains a list of HierarchicalNamespace. -type HierarchicalNamespaceList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []HierarchicalNamespace `json:"items"` -} - -func init() { - SchemeBuilder.Register(&HierarchicalNamespace{}, &HierarchicalNamespaceList{}) -} diff --git a/incubator/hnc/api/v1alpha1/hierarchy_types.go b/incubator/hnc/api/v1alpha1/hierarchy_types.go index 8b96797d7..6c0167df2 100644 --- a/incubator/hnc/api/v1alpha1/hierarchy_types.go +++ b/incubator/hnc/api/v1alpha1/hierarchy_types.go @@ -21,8 +21,8 @@ import ( // Constants for types and well-known names const ( - Singleton = "hierarchy" - HierarchyConfigurations = "hierarchyconfigurations" + Singleton = "hierarchy" + Resource = "hierarchyconfigurations" ) // Constants for labels and annotations diff --git a/incubator/hnc/api/v1alpha1/zz_generated.deepcopy.go b/incubator/hnc/api/v1alpha1/zz_generated.deepcopy.go index f759f5889..80273d8af 100644 --- a/incubator/hnc/api/v1alpha1/zz_generated.deepcopy.go +++ b/incubator/hnc/api/v1alpha1/zz_generated.deepcopy.go @@ -159,79 +159,6 @@ func (in *HNCConfigurationStatus) DeepCopy() *HNCConfigurationStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HierarchicalNamespace) DeepCopyInto(out *HierarchicalNamespace) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HierarchicalNamespace. -func (in *HierarchicalNamespace) DeepCopy() *HierarchicalNamespace { - if in == nil { - return nil - } - out := new(HierarchicalNamespace) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HierarchicalNamespace) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HierarchicalNamespaceList) DeepCopyInto(out *HierarchicalNamespaceList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]HierarchicalNamespace, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HierarchicalNamespaceList. -func (in *HierarchicalNamespaceList) DeepCopy() *HierarchicalNamespaceList { - if in == nil { - return nil - } - out := new(HierarchicalNamespaceList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HierarchicalNamespaceList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HierarchicalNamespaceStatus) DeepCopyInto(out *HierarchicalNamespaceStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HierarchicalNamespaceStatus. -func (in *HierarchicalNamespaceStatus) DeepCopy() *HierarchicalNamespaceStatus { - if in == nil { - return nil - } - out := new(HierarchicalNamespaceStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HierarchyConfiguration) DeepCopyInto(out *HierarchyConfiguration) { *out = *in diff --git a/incubator/hnc/config/crd/bases/hnc.x-k8s.io_hierarchicalnamespaces.yaml b/incubator/hnc/config/crd/bases/hnc.x-k8s.io_hierarchicalnamespaces.yaml deleted file mode 100644 index c7e7aef82..000000000 --- a/incubator/hnc/config/crd/bases/hnc.x-k8s.io_hierarchicalnamespaces.yaml +++ /dev/null @@ -1,62 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.4 - creationTimestamp: null - name: hierarchicalnamespaces.hnc.x-k8s.io -spec: - group: hnc.x-k8s.io - names: - kind: HierarchicalNamespace - listKind: HierarchicalNamespaceList - plural: hierarchicalnamespaces - shortNames: - - hns - singular: hierarchicalnamespace - scope: Namespaced - validation: - openAPIV3Schema: - description: HierarchicalNamespace is the Schema for the self-service namespace - API. See details at http://bit.ly/hnc-self-serve-ux. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - type: string - metadata: - type: object - status: - description: HierarchicalNamespaceStatus defines the observed state of HierarchicalNamespace. - properties: - status: - description: "Describes the state of a hierarchical namespace. \n Currently, - the supported values are: \n - \"missing\": the child namespace has - not been created yet. This should be the default state when the HNS - is just created. \n - \"ok\": the child namespace exists. \n - \"conflict\": - a namespace of the same name already exists. The admission controller - will attempt to prevent this. \n - \"forbidden\": the HNS was created - in a namespace that doesn't allow children, such as kube-system or - hnc-system. The admission controller will attempt to prevent this." - type: string - type: object - type: object - version: v1alpha1 - versions: - - name: v1alpha1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/incubator/hnc/config/crd/kustomization.yaml b/incubator/hnc/config/crd/kustomization.yaml index c20e6efd3..4a3014ad2 100644 --- a/incubator/hnc/config/crd/kustomization.yaml +++ b/incubator/hnc/config/crd/kustomization.yaml @@ -4,7 +4,6 @@ resources: - bases/hnc.x-k8s.io_hierarchyconfigurations.yaml - bases/hnc.x-k8s.io_hncconfigurations.yaml -- bases/hnc.x-k8s.io_hierarchicalnamespaces.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/incubator/hnc/config/samples/hnc_v1_hns.yaml b/incubator/hnc/config/samples/hnc_v1_hns.yaml deleted file mode 100644 index c5915c25d..000000000 --- a/incubator/hnc/config/samples/hnc_v1_hns.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: hnc.x-k8s.io/v1alpha1 -kind: HierarchicalNamespace -metadata: - name: hns-sample -spec: diff --git a/incubator/hnc/pkg/kubectl/create2.go b/incubator/hnc/pkg/kubectl/create2.go deleted file mode 100644 index a2bd3fbf6..000000000 --- a/incubator/hnc/pkg/kubectl/create2.go +++ /dev/null @@ -1,46 +0,0 @@ -/* - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package kubectl - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" -) - -var create2Cmd = &cobra.Command{ - Use: "create2 -n parent child", - Short: "Creates a subnamespace under the given parent.", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - parent, _ := cmd.Flags().GetString("namespace") - if parent == "" { - fmt.Println("Error: parent must be set via --namespace or -n") - os.Exit(1) - } - // Create the hns instance, the custom resource representing the self-serve subnamespace. - // TODO: ensure the specified child doesn't already exist and the parent is allowed - // to create self-serve subnamespaces through the admission controller. See issue - // https://github.com/kubernetes-sigs/multi-tenancy/issues/458 - client.createHierarchicalNamespace(parent, args[0]) - }, -} - -func newCreate2Cmd() *cobra.Command { - create2Cmd.Flags().StringP("namespace", "n", "", "The parent namespace for the new self-serve subnamespace") - return create2Cmd -} diff --git a/incubator/hnc/pkg/kubectl/root.go b/incubator/hnc/pkg/kubectl/root.go index 7a8554065..ccb672b27 100644 --- a/incubator/hnc/pkg/kubectl/root.go +++ b/incubator/hnc/pkg/kubectl/root.go @@ -43,7 +43,6 @@ type realClient struct{} type Client interface { getHierarchy(nnm string) *api.HierarchyConfiguration updateHierarchy(hier *api.HierarchyConfiguration, reason string) - createHierarchicalNamespace(nnm string, hnnm string) } func init() { @@ -88,7 +87,6 @@ func init() { rootCmd.AddCommand(newDescribeCmd()) rootCmd.AddCommand(newTreeCmd()) rootCmd.AddCommand(newCreateCmd()) - rootCmd.AddCommand(newCreate2Cmd()) } func Execute() { @@ -113,7 +111,7 @@ func (cl *realClient) getHierarchy(nnm string) *api.HierarchyConfiguration { hier := &api.HierarchyConfiguration{} hier.Name = api.Singleton hier.Namespace = nnm - err := hncClient.Get().Resource(api.HierarchyConfigurations).Namespace(nnm).Name(api.Singleton).Do().Into(hier) + err := hncClient.Get().Resource(api.Resource).Namespace(nnm).Name(api.Singleton).Do().Into(hier) if err != nil && !errors.IsNotFound(err) { fmt.Printf("Error reading hierarchy for %s: %s\n", nnm, err) os.Exit(1) @@ -125,9 +123,9 @@ func (cl *realClient) updateHierarchy(hier *api.HierarchyConfiguration, reason s nnm := hier.Namespace var err error if hier.CreationTimestamp.IsZero() { - err = hncClient.Post().Resource(api.HierarchyConfigurations).Namespace(nnm).Name(api.Singleton).Body(hier).Do().Error() + err = hncClient.Post().Resource(api.Resource).Namespace(nnm).Name(api.Singleton).Body(hier).Do().Error() } else { - err = hncClient.Put().Resource(api.HierarchyConfigurations).Namespace(nnm).Name(api.Singleton).Body(hier).Do().Error() + err = hncClient.Put().Resource(api.Resource).Namespace(nnm).Name(api.Singleton).Body(hier).Do().Error() } if err != nil { fmt.Printf("\nCould not %s.\nReason: %s\n", reason, err) @@ -135,18 +133,6 @@ func (cl *realClient) updateHierarchy(hier *api.HierarchyConfiguration, reason s } } -func (cl *realClient) createHierarchicalNamespace(nnm string, hnnm string) { - hns := &api.HierarchicalNamespace{} - hns.Name = hnnm - hns.Namespace = nnm - err := hncClient.Post().Resource(api.HierarchicalNamespaces).Namespace(nnm).Name(hnnm).Body(hns).Do().Error() - if err != nil { - fmt.Printf("\nCould not create hierarchicalnamespace instance.\nReason: %s\n", err) - os.Exit(1) - } - fmt.Printf("Successfully created \"%s\" hierarchicalnamespace instance in \"%s\" namespace\n", hnnm, nnm) -} - func childNamespaceExists(hier *api.HierarchyConfiguration, cn string) bool { for _, n := range hier.Spec.RequiredChildren { if cn == n { diff --git a/incubator/hnc/pkg/reconcilers/hierarchical_namespace.go b/incubator/hnc/pkg/reconcilers/hierarchical_namespace.go deleted file mode 100644 index 350ad2f95..000000000 --- a/incubator/hnc/pkg/reconcilers/hierarchical_namespace.go +++ /dev/null @@ -1,51 +0,0 @@ -/* - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package reconcilers - -import ( - "github.com/go-logr/logr" - api "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/api/v1alpha1" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// HierarchicalNamespaceReconciler reconciles HierarchicalNamespace CRs to make sure -// all the hierarchical namespaces are properly maintained. -type HierarchicalNamespaceReconciler struct { - client.Client - Log logr.Logger -} - -// Reconcile sets up some basic variables and then calls the business logic. -// It currently only generates some logs for testing. -func (r *HierarchicalNamespaceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { - // TODO report error state if the webhook is bypassed - see issue - // https://github.com/kubernetes-sigs/multi-tenancy/issues/459 - if ex[req.Namespace] { - return ctrl.Result{}, nil - } - - log := r.Log.WithValues("trigger", req.NamespacedName) - log.Info("Reconciling HNS") - - return ctrl.Result{}, nil -} - -func (r *HierarchicalNamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&api.HierarchicalNamespace{}). - Complete(r) -} diff --git a/incubator/hnc/pkg/reconcilers/hierarchy_config.go b/incubator/hnc/pkg/reconcilers/hierarchy_config.go index 0a4ce3db3..87281f9ff 100644 --- a/incubator/hnc/pkg/reconcilers/hierarchy_config.go +++ b/incubator/hnc/pkg/reconcilers/hierarchy_config.go @@ -41,16 +41,16 @@ import ( "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/pkg/stats" ) -// HierarchyConfigReconciler is responsible for determining the forest structure from the Hierarchy CRs, +// HierarchyReconciler is responsible for determining the forest structure from the Hierarchy CRs, // as well as ensuring all objects in the forest are propagated correctly when the hierarchy // changes. It can also set the status of the Hierarchy CRs, as well as (in rare cases) override // part of its spec (i.e., if a parent namespace no longer exists). -type HierarchyConfigReconciler struct { +type HierarchyReconciler struct { client.Client Log logr.Logger // Forest is the in-memory data structure that is shared with all other reconcilers. - // HierarchyConfigReconciler is responsible for keeping it up-to-date, but the other reconcilers + // HierarchyReconciler is responsible for keeping it up-to-date, but the other reconcilers // use it to determine how to propagate objects. Forest *forest.Forest @@ -70,7 +70,7 @@ type HierarchyConfigReconciler struct { // +kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;update;patch // Reconcile sets up some basic variables and then calls the business logic. -func (r *HierarchyConfigReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { +func (r *HierarchyReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { if ex[req.Namespace] { return ctrl.Result{}, nil } @@ -86,7 +86,7 @@ func (r *HierarchyConfigReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er return ctrl.Result{}, r.reconcile(ctx, log, ns) } -func (r *HierarchyConfigReconciler) reconcile(ctx context.Context, log logr.Logger, nm string) error { +func (r *HierarchyReconciler) reconcile(ctx context.Context, log logr.Logger, nm string) error { // Get the singleton and namespace. inst, nsInst, err := r.getInstances(ctx, log, nm) if err != nil { @@ -137,7 +137,7 @@ func (r *HierarchyConfigReconciler) reconcile(ctx context.Context, log logr.Logg // be able to proceed until this one is finished. While the results of the reconiliation may not be // fully written back to the apiserver yet, each namespace is reconciled in isolation (apart from // the in-memory forest) so this is fine. -func (r *HierarchyConfigReconciler) syncWithForest(log logr.Logger, nsInst *corev1.Namespace, inst *api.HierarchyConfiguration) { +func (r *HierarchyReconciler) syncWithForest(log logr.Logger, nsInst *corev1.Namespace, inst *api.HierarchyConfiguration) { r.Forest.Lock() defer r.Forest.Unlock() ns := r.Forest.Get(inst.ObjectMeta.Namespace) @@ -165,7 +165,7 @@ func (r *HierarchyConfigReconciler) syncWithForest(log logr.Logger, nsInst *core r.syncConditions(log, inst, ns, hadCrit) } -func (r *HierarchyConfigReconciler) onMissingNamespace(log logr.Logger, ns *forest.Namespace, nsInst *corev1.Namespace) bool { +func (r *HierarchyReconciler) onMissingNamespace(log logr.Logger, ns *forest.Namespace, nsInst *corev1.Namespace) bool { if nsInst.Name != "" { return false } @@ -189,7 +189,7 @@ func (r *HierarchyConfigReconciler) onMissingNamespace(log logr.Logger, ns *fore // markExisting marks the namespace as existing. If this is the first time we're reconciling this namespace, // mark all possible relatives as being affected since they may have been waiting for this namespace. -func (r *HierarchyConfigReconciler) markExisting(log logr.Logger, ns *forest.Namespace) { +func (r *HierarchyReconciler) markExisting(log logr.Logger, ns *forest.Namespace) { if ns.SetExists() { log.Info("Reconciling new namespace") r.enqueueAffected(log, "relative of newly synced/created namespace", ns.RelativesNames()...) @@ -202,7 +202,7 @@ func (r *HierarchyConfigReconciler) markExisting(log logr.Logger, ns *forest.Nam // syncRequiredChildOf propagates the required child value from the forest to the spec if possible // (the spec itself will be synced next), or removes the requiredChildOf value if there's a problem // and notifies the would-be parent namespace. -func (r *HierarchyConfigReconciler) syncRequiredChildOf(log logr.Logger, inst *api.HierarchyConfiguration, ns *forest.Namespace) { +func (r *HierarchyReconciler) syncRequiredChildOf(log logr.Logger, inst *api.HierarchyConfiguration, ns *forest.Namespace) { if ns.RequiredChildOf == "" { return } @@ -224,7 +224,7 @@ func (r *HierarchyConfigReconciler) syncRequiredChildOf(log logr.Logger, inst *a // stealing this after it's "released" from another parent. } -func (r *HierarchyConfigReconciler) syncParent(log logr.Logger, inst *api.HierarchyConfiguration, ns *forest.Namespace) { +func (r *HierarchyReconciler) syncParent(log logr.Logger, inst *api.HierarchyConfiguration, ns *forest.Namespace) { // Sync this namespace with its current parent. var curParent *forest.Namespace if inst.Spec.Parent != "" { @@ -260,7 +260,7 @@ func (r *HierarchyConfigReconciler) syncParent(log logr.Logger, inst *api.Hierar } } -func (r *HierarchyConfigReconciler) syncLabel(log logr.Logger, nsInst *corev1.Namespace, ns *forest.Namespace) { +func (r *HierarchyReconciler) syncLabel(log logr.Logger, nsInst *corev1.Namespace, ns *forest.Namespace) { // Depth label only makes sense if there's no error condition. if ns.HasCritCondition() { return @@ -288,7 +288,7 @@ func (r *HierarchyConfigReconciler) syncLabel(log logr.Logger, nsInst *corev1.Na // syncChildren looks at the current list of children and compares it to the children that // have been marked as required. If any required children are missing, we add them to the in-memory // forest and enqueue the (missing) child for reconciliation; we also handle various error cases. -func (r *HierarchyConfigReconciler) syncChildren(log logr.Logger, inst *api.HierarchyConfiguration, ns *forest.Namespace) { +func (r *HierarchyReconciler) syncChildren(log logr.Logger, inst *api.HierarchyConfiguration, ns *forest.Namespace) { // Update the most recent list of children from the forest inst.Status.Children = ns.ChildNames() @@ -367,7 +367,7 @@ func (r *HierarchyConfigReconciler) syncChildren(log logr.Logger, inst *api.Hier } } -func (r *HierarchyConfigReconciler) syncConditions(log logr.Logger, inst *api.HierarchyConfiguration, ns *forest.Namespace, hadCrit bool) { +func (r *HierarchyReconciler) syncConditions(log logr.Logger, inst *api.HierarchyConfiguration, ns *forest.Namespace, hadCrit bool) { // Sync critical conditions after all locally-set conditions are updated. r.syncCritConditions(log, ns, hadCrit) @@ -378,7 +378,7 @@ func (r *HierarchyConfigReconciler) syncConditions(log logr.Logger, inst *api.Hi // syncCritConditions enqueues the children of a namespace if the existing critical conditions in the // namespace are gone or critical conditions are newly found. -func (r *HierarchyConfigReconciler) syncCritConditions(log logr.Logger, ns *forest.Namespace, hadCrit bool) { +func (r *HierarchyReconciler) syncCritConditions(log logr.Logger, ns *forest.Namespace, hadCrit bool) { hasCrit := ns.HasLocalCritCondition() // Early exit if there's no need to enqueue relatives. @@ -416,7 +416,7 @@ func setCritAncestorCondition(log logr.Logger, inst *api.HierarchyConfiguration, // enqueueAffected enqueues all affected namespaces for later reconciliation. This occurs in a // goroutine so the caller doesn't block; since the reconciler is never garbage-collected, this is // safe. -func (r *HierarchyConfigReconciler) enqueueAffected(log logr.Logger, reason string, affected ...string) { +func (r *HierarchyReconciler) enqueueAffected(log logr.Logger, reason string, affected ...string) { go func() { for _, nm := range affected { log.Info("Enqueuing for reconcilation", "affected", nm, "reason", reason) @@ -429,7 +429,7 @@ func (r *HierarchyConfigReconciler) enqueueAffected(log logr.Logger, reason stri }() } -func (r *HierarchyConfigReconciler) getInstances(ctx context.Context, log logr.Logger, nm string) (inst *api.HierarchyConfiguration, ns *corev1.Namespace, err error) { +func (r *HierarchyReconciler) getInstances(ctx context.Context, log logr.Logger, nm string) (inst *api.HierarchyConfiguration, ns *corev1.Namespace, err error) { inst, err = r.getSingleton(ctx, nm) if err != nil { log.Error(err, "Couldn't read singleton") @@ -444,7 +444,7 @@ func (r *HierarchyConfigReconciler) getInstances(ctx context.Context, log logr.L return inst, ns, nil } -func (r *HierarchyConfigReconciler) writeInstances(ctx context.Context, log logr.Logger, oldHC, newHC *api.HierarchyConfiguration, oldNS, newNS *corev1.Namespace) (bool, error) { +func (r *HierarchyReconciler) writeInstances(ctx context.Context, log logr.Logger, oldHC, newHC *api.HierarchyConfiguration, oldNS, newNS *corev1.Namespace) (bool, error) { ret := false if updated, err := r.writeHierarchy(ctx, log, oldHC, newHC); err != nil { return false, err @@ -460,7 +460,7 @@ func (r *HierarchyConfigReconciler) writeInstances(ctx context.Context, log logr return ret, nil } -func (r *HierarchyConfigReconciler) writeHierarchy(ctx context.Context, log logr.Logger, orig, inst *api.HierarchyConfiguration) (bool, error) { +func (r *HierarchyReconciler) writeHierarchy(ctx context.Context, log logr.Logger, orig, inst *api.HierarchyConfiguration) (bool, error) { if reflect.DeepEqual(orig, inst) { return false, nil } @@ -483,7 +483,7 @@ func (r *HierarchyConfigReconciler) writeHierarchy(ctx context.Context, log logr return true, nil } -func (r *HierarchyConfigReconciler) writeNamespace(ctx context.Context, log logr.Logger, orig, inst *corev1.Namespace) (bool, error) { +func (r *HierarchyReconciler) writeNamespace(ctx context.Context, log logr.Logger, orig, inst *corev1.Namespace) (bool, error) { if reflect.DeepEqual(orig, inst) { return false, nil } @@ -507,7 +507,7 @@ func (r *HierarchyConfigReconciler) writeNamespace(ctx context.Context, log logr } // updateObjects calls all type reconcillers in this namespace. -func (r *HierarchyConfigReconciler) updateObjects(ctx context.Context, log logr.Logger, ns string) error { +func (r *HierarchyReconciler) updateObjects(ctx context.Context, log logr.Logger, ns string) error { // Use mutex to guard the read from the types list of the forest to prevent the ConfigReconciler // from modifying the list at the same time. r.Forest.Lock() @@ -523,7 +523,7 @@ func (r *HierarchyConfigReconciler) updateObjects(ctx context.Context, log logr. } // getSingleton returns the singleton if it exists, or creates an empty one if it doesn't. -func (r *HierarchyConfigReconciler) getSingleton(ctx context.Context, nm string) (*api.HierarchyConfiguration, error) { +func (r *HierarchyReconciler) getSingleton(ctx context.Context, nm string) (*api.HierarchyConfiguration, error) { nnm := types.NamespacedName{Namespace: nm, Name: api.Singleton} inst := &api.HierarchyConfiguration{} if err := r.Get(ctx, nnm, inst); err != nil { @@ -542,7 +542,7 @@ func (r *HierarchyConfigReconciler) getSingleton(ctx context.Context, nm string) // getNamespace returns the namespace if it exists, or returns an invalid, blank, unnamed one if it // doesn't. This allows it to be trivially identified as a namespace that doesn't exist, and also // allows us to easily modify it if we want to create it. -func (r *HierarchyConfigReconciler) getNamespace(ctx context.Context, nm string) (*corev1.Namespace, error) { +func (r *HierarchyReconciler) getNamespace(ctx context.Context, nm string) (*corev1.Namespace, error) { ns := &corev1.Namespace{} nnm := types.NamespacedName{Name: nm} if err := r.Get(ctx, nnm, ns); err != nil { @@ -554,7 +554,7 @@ func (r *HierarchyConfigReconciler) getNamespace(ctx context.Context, nm string) return ns, nil } -func (r *HierarchyConfigReconciler) SetupWithManager(mgr ctrl.Manager, maxReconciles int) error { +func (r *HierarchyReconciler) SetupWithManager(mgr ctrl.Manager, maxReconciles int) error { // Maps namespaces to their singletons nsMapFn := handler.ToRequestsFunc( func(a handler.MapObject) []reconcile.Request { diff --git a/incubator/hnc/pkg/reconcilers/hnc_config.go b/incubator/hnc/pkg/reconcilers/hnc_config.go index fa52d40a1..755478199 100644 --- a/incubator/hnc/pkg/reconcilers/hnc_config.go +++ b/incubator/hnc/pkg/reconcilers/hnc_config.go @@ -5,6 +5,8 @@ import ( "fmt" "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/source" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/errors" @@ -29,6 +31,11 @@ type ConfigReconciler struct { // Forest is the in-memory data structure that is shared with all other reconcilers. Forest *forest.Forest + // Igniter is a channel of event.GenericEvent (see "Watching Channels" in + // https://book-v1.book.kubebuilder.io/beyond_basics/controller_watches.html) + // that is used to enqueue the singleton for initial reconciliation. + Igniter chan event.GenericEvent + // HierarchyConfigUpdates is a channel of events used to update hierarchy configuration changes performed by // ObjectReconcilers. It is passed on to ObjectReconcilers for the updates. The ConfigReconciler itself does // not use it. @@ -56,7 +63,7 @@ func (r *ConfigReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { // achieve the reconciliation. r.Log.Info("Reconciling cluster-wide HNC configuration.") - // Create cooresponding ObjectReconcilers for newly added types, if needed. + // Create corresponding ObjectReconcilers for newly added types, if needed. // TODO: Returning an error here will make controller-runtime to requeue the object to be reconciled again. // A better way to handle this is to display the error as part of the HNCConfig.status field to suggest the error is a // permanent problem and to stop controller-runtime from retrying. @@ -126,8 +133,8 @@ func (r *ConfigReconciler) createObjectReconcilers(inst *api.HNCConfiguration) e // reconcilers. It is sufficient because both write and read access to the list are guarded by the same mutex. // // We use the forest mutex to guard ObjectReconciler creation together with types list modification so that the - // forest cannot be changed by the HierarchyConfigReconciler until both actions are finished. If we do not use the - // forest mutex to guard both actions, HierarchyConfigReconciler might change the forest structure and read the types list + // forest cannot be changed by the HierarchyReconciler until both actions are finished. If we do not use the + // forest mutex to guard both actions, HierarchyReconciler might change the forest structure and read the types list // from the forest for the object reconciliation, after we create ObjectReconcilers but before we write the // ObjectReconcilers to the types list. As a result, objects of the newly added types might not be propagated correctly // using the latest forest structure. @@ -171,9 +178,38 @@ func (r *ConfigReconciler) createObjectReconciler(gvk schema.GroupVersionKind) e return nil } +// forceInitialReconcile forces reconciliation to start after setting up the +// controller with the manager. This is used to create a default singleton if +// there is no singleton in the cluster. This occurs in a goroutine so the +// caller doesn't block; since the reconciler is never garbage-collected, +// this is safe. +func (r *ConfigReconciler) forceInitialReconcile(log logr.Logger, reason string) { + go func() { + log.Info("Enqueuing for reconciliation", "reason", reason) + // The watch handler doesn't care about anything except the metadata. + inst := &api.HNCConfiguration{} + inst.ObjectMeta.Name = api.HNCConfigSingleton + r.Igniter <- event.GenericEvent{Meta: inst} + }() +} + // SetupWithManager builds a controller with the reconciler. func (r *ConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). + if err := ctrl.NewControllerManagedBy(mgr). For(&api.HNCConfiguration{}). - Complete(r) + Watches(&source.Channel{Source: r.Igniter}, &handler.EnqueueRequestForObject{}). + Complete(r); err != nil { + return err + } + // Create a default singleton if there is no singleton in the cluster. + // + // The cache used by the client to retrieve objects might not be populated + // at this point. As a result, we cannot use r.Get() to determine the existence + // of the singleton and then use r.Create() to create the singleton if + // it does not exist. As a workaround, we decide to enforce reconciliation. The + // cache is populated at the reconciliation stage. A default singleton will be + // created during the reconciliation if there is no singleton in the cluster. + r.forceInitialReconcile(r.Log, "Enforce reconciliation to create a default"+ + "HNCConfiguration singleton if it does not exist.") + return nil } diff --git a/incubator/hnc/pkg/reconcilers/hnc_config_test.go b/incubator/hnc/pkg/reconcilers/hnc_config_test.go index d0978bc5e..8e90e35fd 100644 --- a/incubator/hnc/pkg/reconcilers/hnc_config_test.go +++ b/incubator/hnc/pkg/reconcilers/hnc_config_test.go @@ -2,12 +2,13 @@ package reconcilers_test import ( "context" - "time" api "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/api/v1alpha1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) @@ -22,6 +23,11 @@ var _ = Describe("HNCConfiguration", func() { BeforeEach(func() { fooName = createNS(ctx, "foo") barName = createNS(ctx, "bar") + + // Give foo a role. + makeRole(ctx, fooName, "foo-role") + // Give foo a role binding. + makeRoleBinding(ctx, fooName, "foo-role", "foo-admin", "foo-role-binding") }) AfterEach(func() { @@ -31,32 +37,56 @@ var _ = Describe("HNCConfiguration", func() { }).Should(Succeed()) }) - It("should propagate objects whose types have been added to HNCConfiguration", func() { + It("should set mode of Roles and RoleBindings as propagate by default", func() { + config := getHNCConfig(ctx) + + Eventually(hasTypeWithMode("rbac.authorization.k8s.io/v1", "Role", api.Propagate, config)).Should(BeTrue()) + Eventually(hasTypeWithMode("rbac.authorization.k8s.io/v1", "RoleBinding", api.Propagate, config)).Should(BeTrue()) + }) + + It("should propagate Roles by default", func() { setParent(ctx, barName, fooName) - makeSecret(ctx, fooName, "foo-sec") - // Wait 1 second to give "foo-sec" a chance to be propagated to bar, if it can be propagated. - time.Sleep(1 * time.Second) - // Foo should have "foo-sec" since we created it there. - Eventually(hasSecret(ctx, fooName, "foo-sec")).Should(BeTrue()) - // "foo-sec" is not propagated to bar because Secret hasn't been configured in HNCConfiguration. - Eventually(hasSecret(ctx, barName, "foo-sec")).Should(BeFalse()) + Eventually(hasRole(ctx, barName, "foo-role")).Should(BeTrue()) + Expect(roleInheritedFrom(ctx, barName, "foo-role")).Should(Equal(fooName)) + }) - Eventually(func() error { - c := getHNCConfig(ctx) - return addSecretToHNCConfig(ctx, c) - }).Should(Succeed()) + It("should propagate RoleBindings by default", func() { + setParent(ctx, barName, fooName) + Eventually(hasRoleBinding(ctx, barName, "foo-role-binding")).Should(BeTrue()) + Expect(roleBindingInheritedFrom(ctx, barName, "foo-role-binding")).Should(Equal(fooName)) + }) + + It("should only propagate objects whose types are in HNCConfiguration", func() { + setParent(ctx, barName, fooName) + makeSecret(ctx, fooName, "foo-sec") + makeResourceQuota(ctx, fooName, "foo-resource-quota") + + addToHNCConfig(ctx, "v1", "Secret", api.Propagate) + + // Foo should have both "foo-sec" and "foo-resource-quota" since we created there. + Eventually(hasSecret(ctx, fooName, "foo-sec")).Should(BeTrue()) + Eventually(hasResourceQuota(ctx, fooName, "foo-resource-quota")).Should(BeTrue()) // "foo-sec" should now be propagated from foo to bar. Eventually(hasSecret(ctx, barName, "foo-sec")).Should(BeTrue()) Expect(secretInheritedFrom(ctx, barName, "foo-sec")).Should(Equal(fooName)) + // "foo-resource-quota" should not be propagated from foo to bar because ResourceQuota + // is not added to HNCConfiguration. + Expect(hasResourceQuota(ctx, barName, "foo-resource-quota")).Should(BeFalse()) }) }) -func addSecretToHNCConfig(ctx context.Context, c *api.HNCConfiguration) error { - secSpec := api.TypeSynchronizationSpec{APIVersion: "v1", Kind: "Secret", Mode: api.Propagate} - c.Spec.Types = append(c.Spec.Types, secSpec) - return updateHNCConfig(ctx, c) +func hasTypeWithMode(apiVersion, kind string, mode api.SynchronizationMode, config *api.HNCConfiguration) func() bool { + // `Eventually` only works with a fn that doesn't take any args + return func() bool { + for _, t := range config.Spec.Types { + if t.APIVersion == apiVersion && t.Kind == kind && t.Mode == mode { + return true + } + } + return false + } } func makeSecret(ctx context.Context, nsName, secretName string) { @@ -89,3 +119,63 @@ func secretInheritedFrom(ctx context.Context, nsName, secretName string) string lif, _ := sec.ObjectMeta.Labels["hnc.x-k8s.io/inheritedFrom"] return lif } + +func makeRoleBinding(ctx context.Context, nsName, roleName, userName, roleBindingName string) { + roleBinding := &v1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleBindingName, + Namespace: nsName, + }, + Subjects: []v1.Subject{ + { + Kind: "User", + Name: userName, + }, + }, + RoleRef: v1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: roleName, + }, + } + ExpectWithOffset(1, k8sClient.Create(ctx, roleBinding)).Should(Succeed()) +} + +func hasRoleBinding(ctx context.Context, nsName, roleBindingName string) func() bool { + // `Eventually` only works with a fn that doesn't take any args + return func() bool { + nnm := types.NamespacedName{Namespace: nsName, Name: roleBindingName} + roleBinding := &v1.RoleBinding{} + err := k8sClient.Get(ctx, nnm, roleBinding) + return err == nil + } +} + +func roleBindingInheritedFrom(ctx context.Context, nsName, roleBindingName string) string { + nnm := types.NamespacedName{Namespace: nsName, Name: roleBindingName} + roleBinding := &v1.RoleBinding{} + if err := k8sClient.Get(ctx, nnm, roleBinding); err != nil { + // should have been caught above + return err.Error() + } + if roleBinding.ObjectMeta.Labels == nil { + return "" + } + lif, _ := roleBinding.ObjectMeta.Labels["hnc.x-k8s.io/inheritedFrom"] + return lif +} + +func makeResourceQuota(ctx context.Context, nsName, resourceQuotaName string) { + resourceQuota := &corev1.ResourceQuota{} + resourceQuota.Name = resourceQuotaName + resourceQuota.Namespace = nsName + ExpectWithOffset(1, k8sClient.Create(ctx, resourceQuota)).Should(Succeed()) +} + +func hasResourceQuota(ctx context.Context, nsName, resourceQuotaName string) bool { + // `Eventually` only works with a fn that doesn't take any args + nnm := types.NamespacedName{Namespace: nsName, Name: resourceQuotaName} + resourceQuota := &corev1.ResourceQuota{} + err := k8sClient.Get(ctx, nnm, resourceQuota) + return err == nil +} diff --git a/incubator/hnc/pkg/reconcilers/object.go b/incubator/hnc/pkg/reconcilers/object.go index c6b245800..b203351bf 100644 --- a/incubator/hnc/pkg/reconcilers/object.go +++ b/incubator/hnc/pkg/reconcilers/object.go @@ -57,7 +57,7 @@ type ObjectReconciler struct { client.Client Log logr.Logger - // Forest is the in-memory forest managed by the HierarchyConfigReconciler. + // Forest is the in-memory forest managed by the HierarchyReconciler. Forest *forest.Forest // GVK is the group/version/kind handled by this reconciler. @@ -74,7 +74,7 @@ type ObjectReconciler struct { // +kubebuilder:rbac:groups=*,resources=*,verbs=get;list;watch;create;update;patch;delete -// SyncNamespace can be called manually by the HierarchyConfigReconciler when the hierarchy changes. +// SyncNamespace can be called manually by the HierarchyReconciler when the hierarchy changes. // It enqueues all the current objects in the namespace and local copies of the original objects // in the ancestors. func (r *ObjectReconciler) SyncNamespace(ctx context.Context, log logr.Logger, ns string) error { diff --git a/incubator/hnc/pkg/reconcilers/object_test.go b/incubator/hnc/pkg/reconcilers/object_test.go index 2ca1fbe89..194c9ab32 100644 --- a/incubator/hnc/pkg/reconcilers/object_test.go +++ b/incubator/hnc/pkg/reconcilers/object_test.go @@ -10,7 +10,6 @@ import ( corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) @@ -28,7 +27,7 @@ var _ = Describe("Secret", func() { barName = createNS(ctx, "bar") bazName = createNS(ctx, "baz") - // Give them each a secret + // Give them each a role. makeRole(ctx, fooName, "foo-role") makeRole(ctx, barName, "bar-role") makeRole(ctx, bazName, "baz-role") @@ -60,7 +59,7 @@ var _ = Describe("Secret", func() { // Creates an empty ConfigMap. We use ConfigMap for this test because the apiserver will not // add additional fields to an empty ConfigMap object to make it non-empty. makeConfigMap(ctx, fooName, "foo-config") - addConfigMapToHNCConfig(ctx) + addToHNCConfig(ctx, "v1", "ConfigMap", api.Propagate) // "foo-config" should now be propagated from foo to bar. Eventually(hasConfigMap(ctx, barName, "foo-config")).Should(BeTrue()) @@ -243,52 +242,6 @@ func newOrGetHierarchy(ctx context.Context, nm string) *api.HierarchyConfigurati return hier } -func makeRole(ctx context.Context, nsName, roleName string) { - role := &v1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Name: roleName, - Namespace: nsName, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Role", - APIVersion: "rbac.authorization.k8s.io/v1", - }, - Rules: []v1.PolicyRule{ - // Allow the users to read all secrets, namespaces and configmaps. - { - APIGroups: []string{""}, - Resources: []string{"secrets", "namespaces", "configmaps"}, - Verbs: []string{"get", "watch", "list"}, - }, - }, - } - ExpectWithOffset(1, k8sClient.Create(ctx, role)).Should(Succeed()) -} - -func hasRole(ctx context.Context, nsName, roleName string) func() bool { - // `Eventually` only works with a fn that doesn't take any args - return func() bool { - nnm := types.NamespacedName{Namespace: nsName, Name: roleName} - role := &v1.Role{} - err := k8sClient.Get(ctx, nnm, role) - return err == nil - } -} - -func roleInheritedFrom(ctx context.Context, nsName, roleName string) string { - nnm := types.NamespacedName{Namespace: nsName, Name: roleName} - role := &v1.Role{} - if err := k8sClient.Get(ctx, nnm, role); err != nil { - // should have been caught above - return err.Error() - } - if role.ObjectMeta.Labels == nil { - return "" - } - lif, _ := role.ObjectMeta.Labels["hnc.x-k8s.io/inheritedFrom"] - return lif -} - func modifyRole(ctx context.Context, nsName, roleName string) { nnm := types.NamespacedName{Namespace: nsName, Name: roleName} role := &v1.Role{} @@ -332,15 +285,6 @@ func removeRole(ctx context.Context, nsName, roleName string) { ExpectWithOffset(1, k8sClient.Delete(ctx, role)).Should(Succeed()) } -func addConfigMapToHNCConfig(ctx context.Context) { - Eventually(func() error { - c := getHNCConfig(ctx) - configMap := api.TypeSynchronizationSpec{APIVersion: "v1", Kind: "ConfigMap", Mode: api.Propagate} - c.Spec.Types = append(c.Spec.Types, configMap) - return updateHNCConfig(ctx, c) - }).Should(Succeed()) -} - // Makes an empty ConfigMap object. func makeConfigMap(ctx context.Context, nsName, configMapName string) { configMap := &corev1.ConfigMap{} diff --git a/incubator/hnc/pkg/reconcilers/setup.go b/incubator/hnc/pkg/reconcilers/setup.go index a51c7bf29..c50d24671 100644 --- a/incubator/hnc/pkg/reconcilers/setup.go +++ b/incubator/hnc/pkg/reconcilers/setup.go @@ -24,8 +24,8 @@ var ex = map[string]bool{ func Create(mgr ctrl.Manager, f *forest.Forest, maxReconciles int) error { hcChan := make(chan event.GenericEvent) - // Create the HierarchyConfigReconciler. - hr := &HierarchyConfigReconciler{ + // Create the HierarchyReconciler. + hr := &HierarchyReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("reconcilers").WithName("Hierarchy"), Forest: f, @@ -41,20 +41,12 @@ func Create(mgr ctrl.Manager, f *forest.Forest, maxReconciles int) error { Log: ctrl.Log.WithName("reconcilers").WithName("HNCConfiguration"), Manager: mgr, Forest: f, + Igniter: make(chan event.GenericEvent), HierarchyConfigUpdates: hcChan, } if err := cr.SetupWithManager(mgr); err != nil { return fmt.Errorf("cannot create Config reconciler: %s", err.Error()) } - // Create HierarchicalNamespaceReconciler. - hnsr := &HierarchicalNamespaceReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("reconcilers").WithName("HierarchicalNamespace"), - } - if err := hnsr.SetupWithManager(mgr); err != nil { - return fmt.Errorf("cannot create HierarchicalNamespace reconciler: %s", err.Error()) - } - return nil } diff --git a/incubator/hnc/pkg/reconcilers/suite_test.go b/incubator/hnc/pkg/reconcilers/suite_test.go index 835013f8a..b41be3e7f 100644 --- a/incubator/hnc/pkg/reconcilers/suite_test.go +++ b/incubator/hnc/pkg/reconcilers/suite_test.go @@ -16,7 +16,6 @@ limitations under the License. package reconcilers_test import ( - "context" "path/filepath" "testing" "time" @@ -36,7 +35,6 @@ import ( // +kubebuilder:scaffold:imports api "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/api/v1alpha1" - "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/pkg/config" "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/pkg/forest" ) @@ -59,6 +57,8 @@ func TestAPIs(t *testing.T) { []Reporter{envtest.NewlineReporter{}}) } +// All tests in the reconcilers_test package are in one suite. As a result, they +// share the same test environment (e.g., same api server). var _ = BeforeSuite(func(done Done) { logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) @@ -92,12 +92,6 @@ var _ = BeforeSuite(func(done Done) { k8sClient = k8sManager.GetClient() Expect(k8sClient).ToNot(BeNil()) - // Setup HNCConfiguration object here because it is a cluster-wide singleton shared by all reconcilers. - hncConfig := newHNCConfig() - Expect(hncConfig).ToNot(BeNil()) - ctx := context.Background() - updateHNCConfig(ctx, hncConfig) - go func() { err = k8sManager.Start(ctrl.SetupSignalHandler()) Expect(err).ToNot(HaveOccurred()) @@ -111,10 +105,3 @@ var _ = AfterSuite(func() { err := testEnv.Stop() Expect(err).ToNot(HaveOccurred()) }) - -func newHNCConfig() *api.HNCConfiguration { - hncConfig := &api.HNCConfiguration{} - hncConfig.ObjectMeta.Name = api.HNCConfigSingleton - hncConfig.Spec = config.GetDefaultConfigSpec() - return hncConfig -} diff --git a/incubator/hnc/pkg/reconcilers/test_helpers_test.go b/incubator/hnc/pkg/reconcilers/test_helpers_test.go index e0e0ecf44..ac909a9b9 100644 --- a/incubator/hnc/pkg/reconcilers/test_helpers_test.go +++ b/incubator/hnc/pkg/reconcilers/test_helpers_test.go @@ -7,6 +7,8 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" api "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/api/v1alpha1" @@ -93,3 +95,58 @@ func getHNCConfigWithOffset(offset int, ctx context.Context) *api.HNCConfigurati }).Should(Succeed()) return config } + +func addToHNCConfig(ctx context.Context, apiVersion, kind string, mode api.SynchronizationMode) { + Eventually(func() error { + c := getHNCConfig(ctx) + spec := api.TypeSynchronizationSpec{APIVersion: apiVersion, Kind: kind, Mode: mode} + c.Spec.Types = append(c.Spec.Types, spec) + return updateHNCConfig(ctx, c) + }).Should(Succeed()) +} + +func makeRole(ctx context.Context, nsName, roleName string) { + role := &v1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleName, + Namespace: nsName, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Role", + APIVersion: "rbac.authorization.k8s.io/v1", + }, + Rules: []v1.PolicyRule{ + // Allow the users to read all secrets, namespaces and configmaps. + { + APIGroups: []string{""}, + Resources: []string{"secrets", "namespaces", "configmaps"}, + Verbs: []string{"get", "watch", "list"}, + }, + }, + } + ExpectWithOffset(1, k8sClient.Create(ctx, role)).Should(Succeed()) +} + +func hasRole(ctx context.Context, nsName, roleName string) func() bool { + // `Eventually` only works with a fn that doesn't take any args + return func() bool { + nnm := types.NamespacedName{Namespace: nsName, Name: roleName} + role := &v1.Role{} + err := k8sClient.Get(ctx, nnm, role) + return err == nil + } +} + +func roleInheritedFrom(ctx context.Context, nsName, roleName string) string { + nnm := types.NamespacedName{Namespace: nsName, Name: roleName} + role := &v1.Role{} + if err := k8sClient.Get(ctx, nnm, role); err != nil { + // should have been caught above + return err.Error() + } + if role.ObjectMeta.Labels == nil { + return "" + } + lif, _ := role.ObjectMeta.Labels["hnc.x-k8s.io/inheritedFrom"] + return lif +}