Skip to content
This repository has been archived by the owner on Jun 26, 2023. It is now read-only.

Commit

Permalink
Handle multiple configurations of the same type
Browse files Browse the repository at this point in the history
This PR handles multiple configurations for the same type in ConfigReconciler. If there are multiple configurations for the same type, only the first configuration will be followed, the rest will be ignored.

Tested:unit tests, GKE cluster

Issue: #411
  • Loading branch information
sophieliu15 committed Mar 23, 2020
1 parent adc7019 commit bde8491
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 4 deletions.
8 changes: 6 additions & 2 deletions incubator/hnc/api/v1alpha1/hnc_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ const (
// HNCConfigurationCondition codes. *All* codes must also be documented in the
// comment to HNCConfigurationCondition.Code.
const (
CritSingletonNameInvalid HNCConfigurationCode = "critSingletonNameInvalid"
ObjectReconcilerCreationFailed HNCConfigurationCode = "objectReconcilerCreationFailed"
CritSingletonNameInvalid HNCConfigurationCode = "critSingletonNameInvalid"
ObjectReconcilerCreationFailed HNCConfigurationCode = "objectReconcilerCreationFailed"
MultipleConfigurationsForOneType HNCConfigurationCode = "multipleConfigurationsForOneType"
)

// TypeSynchronizationSpec defines the desired synchronization state of a specific kind.
Expand Down Expand Up @@ -133,6 +134,9 @@ type HNCConfigurationCondition struct {
//
// - "objectReconcilerCreationFailed": an error exists when creating the object
// reconciler for the type specified in Msg.
//
// - "multipleConfigurationsForOneType": Multiple configurations exist for the type specified
// in the Msg. One type should only have one configuration.
Code HNCConfigurationCode `json:"code"`

// A human-readable description of the condition, if the `code` field is not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ spec:
the specified singleton name is invalid. The name should be
the same as HNCConfigSingleton. \n - \"objectReconcilerCreationFailed\":
an error exists when creating the object reconciler for the
type specified in Msg."
type specified in Msg. \n - \"multipleConfigurationsForOneType\":
Multiple configurations exist for the type specified in the
Msg. One type should only have one configuration."
type: string
msg:
description: A human-readable description of the condition, if
Expand Down
31 changes: 30 additions & 1 deletion incubator/hnc/pkg/reconcilers/hnc_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ type ConfigReconciler struct {
HierarchyConfigUpdates chan event.GenericEvent
}

type gvkSet map[schema.GroupVersionKind]bool

// Reconcile sets up some basic variable and logs the Spec.
// TODO: Updates the comment above when adding more logic to the Reconcile method.
func (r *ConfigReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()

// Validate the singleton name.
// TODO: Add a validating admission controller to prevent the problem in the first place.
if err := r.validateSingletonName(ctx, req.NamespacedName.Name); err != nil {
r.Log.Error(err, "Singleton name validation failed")
return ctrl.Result{}, nil
Expand Down Expand Up @@ -222,7 +223,14 @@ func (r *ConfigReconciler) syncObjectReconcilers(ctx context.Context, inst *api.
// syncActiveReconcilers syncs object reconcilers for types that are in the Spec. If an object reconciler exists, it sets
// its mode according to the Spec; otherwise, it creates the object reconciler.
func (r *ConfigReconciler) syncActiveReconcilers(ctx context.Context, inst *api.HNCConfiguration) error {
// exist keeps track of existing types in the `config` singleton.
exist := gvkSet{}
for _, t := range inst.Spec.Types {
// If there are multiple configurations of the same type, we will follow the first
// configuration and ignore the rest.
if !r.ensureNoDuplicateTypeConfigurations(inst, t, exist) {
continue
}
gvk := schema.FromAPIVersionAndKind(t.APIVersion, t.Kind)
if ts := r.Forest.GetTypeSyncer(gvk); ts != nil {
if err := ts.SetMode(ctx, t.Mode, r.Log); err != nil {
Expand All @@ -235,6 +243,27 @@ func (r *ConfigReconciler) syncActiveReconcilers(ctx context.Context, inst *api.
return nil
}

// ensureNoDuplicateTypeConfigurations checks whether the configuration of a type already exists and sets
// a condition in the status if the type exists. The method returns true if the type configuration does not exist;
// otherwise, returns false.
func (r *ConfigReconciler) ensureNoDuplicateTypeConfigurations(inst *api.HNCConfiguration, t api.TypeSynchronizationSpec,
mp gvkSet) bool {
gvk := schema.FromAPIVersionAndKind(t.APIVersion, t.Kind)
if !mp[gvk] {
mp[gvk] = true
return true
}
specMsg := fmt.Sprintf("APIVersion: %s, Kind: %s, Mode: %s", t.APIVersion, t.Kind, t.Mode)
r.Log.Info(fmt.Sprintf("Ignoring the configuration: %s", specMsg))
condition := api.HNCConfigurationCondition{
Code: api.MultipleConfigurationsForOneType,
Msg: fmt.Sprintf("Ignore the configuration: %s because the configuration of the type already exists; "+
"only the first configuration will be applied", specMsg),
}
inst.Status.Conditions = append(inst.Status.Conditions, condition)
return false
}

// syncRemovedReconcilers sets object reconcilers to "ignore" mode for types that are removed from the Spec.
func (r *ConfigReconciler) syncRemovedReconcilers(ctx context.Context, inst *api.HNCConfiguration) error {
// If a type exists in the forest but not exists in the Spec, we will
Expand Down
44 changes: 44 additions & 0 deletions incubator/hnc/pkg/reconcilers/hnc_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package reconcilers_test

import (
"context"
"fmt"
"strings"
"time"

Expand Down Expand Up @@ -116,6 +117,30 @@ var _ = Describe("HNCConfiguration", func() {
Eventually(hasHNCConfigurationConditionWithMsg(ctx, api.ObjectReconcilerCreationFailed, "/v2, Kind=LimitRange")).Should(BeFalse())
})

It("should set MultipleConfigurationsForOneType if there are multiple configurations for one type", func() {
// Add multiple configurations for a type.
addToHNCConfig(ctx, "v1", "Secret", api.Propagate)
addToHNCConfig(ctx, "v1", "Secret", api.Remove)

// The second configuration should be ignored.
Eventually(hasHNCConfigurationConditionWithMsg(ctx, api.MultipleConfigurationsForOneType,
fmt.Sprintf("APIVersion: v1, Kind: Secret, Mode: %s", api.Remove))).Should(BeTrue())
})

It("should unset MultipleConfigurationsForOneType if extra configurations are later removed", func() {
// Add multiple configurations for a type.
addToHNCConfig(ctx, "v1", "Secret", api.Propagate)
addToHNCConfig(ctx, "v1", "Secret", api.Remove)

Eventually(hasHNCConfigurationConditionWithMsg(ctx, api.MultipleConfigurationsForOneType,
fmt.Sprintf("APIVersion: v1, Kind: Secret, Mode: %s", api.Remove))).Should(BeTrue())

removeHNCConfigTypeWithMode(ctx, "v1", "Secret", api.Remove)

Eventually(hasHNCConfigurationConditionWithMsg(ctx, api.MultipleConfigurationsForOneType,
fmt.Sprintf("APIVersion: v1, Kind: Secret, Mode: %s", api.Remove))).Should(BeFalse())
})

It("should not propagate objects if the type is not in HNCConfiguration", func() {
setParent(ctx, barName, fooName)
makeObject(ctx, "ResourceQuota", fooName, "foo-resource-quota")
Expand Down Expand Up @@ -361,6 +386,25 @@ func removeHNCConfigType(ctx context.Context, apiVersion, kind string) {
}).Should(Succeed())
}

func removeHNCConfigTypeWithMode(ctx context.Context, apiVersion, kind string, mode api.SynchronizationMode) {
Eventually(func() error {
c := getHNCConfig(ctx)
i := 0
for ; i < len(c.Spec.Types); i++ {
if c.Spec.Types[i].APIVersion == apiVersion && c.Spec.Types[i].Kind == kind && c.Spec.Types[i].Mode == mode {
break
}
}
// The type does not exist. Nothing to remove.
if i == len(c.Spec.Types) {
return nil
}
c.Spec.Types[i] = c.Spec.Types[len(c.Spec.Types)-1]
c.Spec.Types = c.Spec.Types[:len(c.Spec.Types)-1]
return updateHNCConfig(ctx, c)
}).Should(Succeed())
}

func createCronTabCRD(ctx context.Context) {
crontab := v1beta1.CustomResourceDefinition{
TypeMeta: metav1.TypeMeta{
Expand Down

0 comments on commit bde8491

Please sign in to comment.