Skip to content

Commit

Permalink
Enable HNC leader election in controllers
Browse files Browse the repository at this point in the history
  • Loading branch information
joe2far committed Oct 20, 2021
1 parent c715d45 commit a3dbadc
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 9 deletions.
6 changes: 6 additions & 0 deletions cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ func main() {
// TODO: Better understand the behaviour of Burst, and consider making it equal to QPS if
// it turns out to be harmful.
cfg.Burst = int(cfg.QPS * 1.5)

// If leader election is disabled then treat this instance as the HNC leader
if !enableLeaderElection {
config.IsLeader = true
}

mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Expand Down
9 changes: 9 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,12 @@ rules:
- get
- patch
- update
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- create
- get
- list
- update
10 changes: 10 additions & 0 deletions internal/anchor/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ type Reconciler struct {
// Reconcile sets up some basic variables and then calls the business logic. It currently
// only handles the creation of the namespaces but no deletion or state reporting yet.
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// If not leader exiting, without performing any write operations
if !config.IsLeader {
return ctrl.Result{}, nil
}

log := logutils.WithRID(r.Log).WithValues("trigger", req.NamespacedName)
log.V(1).Info("Reconciling anchor")

Expand Down Expand Up @@ -421,3 +426,8 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
Watches(&source.Kind{Type: &corev1.Namespace{}}, handler.EnqueueRequestsFromMapFunc(nsMapFn)).
Complete(r)
}

// BecomeLeader requeues anchors (required when instance becomes leader)
func (r *Reconciler) BecomeLeader() {
// TODO
}
3 changes: 3 additions & 0 deletions internal/config/default_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ package config
// This value is controlled by the --unpropagated-annotation command line, which may be set multiple
// times.
var UnpropagatedAnnotations []string

// IsLeader is global which repesents whether this HNC instance is the leader
var IsLeader bool
5 changes: 3 additions & 2 deletions internal/forest/forest.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ type namedNamespaces map[string]*Namespace
// TypeSyncer syncs objects of a specific type. Reconcilers implement the interface so that they can be
// called by the HierarchyReconciler if the hierarchy changes.
type TypeSyncer interface {
// SyncNamespace syncs objects of a namespace for a specific type.
SyncNamespace(context.Context, logr.Logger, string) error
// SyncObjects syncs objects of a namespace for a specific type
// string is namespace, or "" for all namespaces
SyncObjects(context.Context, logr.Logger, string) error

// Provides the GVK that is handled by the reconciler who implements the interface.
GetGVK() schema.GroupVersionKind
Expand Down
12 changes: 11 additions & 1 deletion internal/hierarchyconfig/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ func (r *Reconciler) reconcile(ctx context.Context, log logr.Logger, nm string)
// Sync the Hierarchy singleton with the in-memory forest.
needUpdateObjects := r.syncWithForest(log, nsInst, inst, deletingCRD, anms)

// If not leader exit early, without performing any write operations
if !config.IsLeader {
return nil
}

// Write back if anything's changed. Early-exit if we just write back exactly what we had and this
// isn't the first time we're syncing.
updated, err := r.writeInstances(ctx, log, origHC, inst, origNS, nsInst)
Expand Down Expand Up @@ -679,7 +684,7 @@ func (r *Reconciler) updateObjects(ctx context.Context, log logr.Logger, ns stri
trs := r.Forest.GetTypeSyncers()
r.Forest.Unlock()
for _, tr := range trs {
if err := tr.SyncNamespace(ctx, log, ns); err != nil {
if err := tr.SyncObjects(ctx, log, ns); err != nil {
return err
}
}
Expand Down Expand Up @@ -783,3 +788,8 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, maxReconciles int) error
WithOptions(opts).
Complete(r)
}

// BecomeLeader requeues hierarchy configs (required when instance becomes leader)
func (r *Reconciler) BecomeLeader() {
// TODO
}
21 changes: 21 additions & 0 deletions internal/hncconfig/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/event"

api "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2"
"sigs.k8s.io/hierarchical-namespaces/internal/config"
"sigs.k8s.io/hierarchical-namespaces/internal/crd"
"sigs.k8s.io/hierarchical-namespaces/internal/forest"
"sigs.k8s.io/hierarchical-namespaces/internal/objects"
Expand Down Expand Up @@ -131,6 +132,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
// Load all conditions
r.loadNamespaceConditions(inst)

// Exit early if not the leader
if !config.IsLeader {
return ctrl.Result{}, nil
}

// Write back to the apiserver.
if err := r.writeSingleton(ctx, inst); err != nil {
r.Log.Error(err, "Couldn't write singleton")
Expand Down Expand Up @@ -631,3 +637,18 @@ func gvkForGR(gr schema.GroupResource, allRes []*restmapper.APIGroupResources) (
}
return schema.GroupVersionKind{}, &GVKErr{api.ReasonResourceNotFound, fmt.Sprintf("Resource %q not found", gr)}
}

// BecomeLeader calls SyncObjects on all type reconcillers (required when instance becomes leader)
func (r *Reconciler) BecomeLeader() {
r.Log.V(1).Info("Requeue all objects (hierarchy updated or new namespace found)")
// 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()
trs := r.Forest.GetTypeSyncers()
r.Forest.Unlock()
for _, tr := range trs {
if err := tr.SyncObjects(context.Background(), r.Log, ""); err != nil {
r.Log.V(1).Error(err, "Failed to SyncObjects for HNCConfig")
}
}
}
26 changes: 20 additions & 6 deletions internal/objects/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,20 @@ type HNCConfigReconcilerType interface {
//
// +kubebuilder:rbac:groups=*,resources=*,verbs=*

// SyncNamespace can be called manually by the HierarchyConfigReconciler when the hierarchy changes.
// SyncObjects can be called manually by the HierarchyConfigReconciler 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 *Reconciler) SyncNamespace(ctx context.Context, log logr.Logger, ns string) error {
func (r *Reconciler) SyncObjects(ctx context.Context, log logr.Logger, ns string) error {
log = log.WithValues("gvk", r.GVK)

// If ns is empty, then enqueue all objects (used when HNC instances become leader)
if ns == "" {
if err := r.enqueueAllObjects(ctx, log); err != nil {
return err
}
return nil
}

// Enqueue all the current objects in the namespace because some of them may have been deleted.
if err := r.enqueueLocalObjects(ctx, log, ns); err != nil {
return err
Expand Down Expand Up @@ -225,7 +233,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu

// Sync with the forest and perform any required actions.
actions, srcInst := r.syncWithForest(ctx, log, inst)
return resp, r.operate(ctx, log, actions, inst, srcInst)

// Only complete actions, if reconciler is HNC leader instance
if !config.IsLeader {
return resp, nil
}
err := r.operate(ctx, log, actions, inst, srcInst)
return resp, err
}

// syncWithForest syncs the object instance with the in-memory forest. It returns the action to take on
Expand Down Expand Up @@ -513,9 +527,9 @@ func (r *Reconciler) enqueueLocalObjects(ctx context.Context, log logr.Logger, n
return nil
}

// enqueuePropagatedObjects is only called from SyncNamespace. It's the only place a forest lock is
// needed in SyncNamespace, so we made it into a function with forest lock instead of holding the
// lock for the entire SyncNamespace.
// enqueuePropagatedObjects is only called from SyncObjects. It's the only place a forest lock is
// needed in SyncObjects, so we made it into a function with forest lock instead of holding the
// lock for the entire SyncObjects.
func (r *Reconciler) enqueuePropagatedObjects(ctx context.Context, log logr.Logger, ns string) {
r.Forest.Lock()
defer r.Forest.Unlock()
Expand Down
12 changes: 12 additions & 0 deletions internal/setup/reconcilers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/event"

"sigs.k8s.io/hierarchical-namespaces/internal/anchor"
"sigs.k8s.io/hierarchical-namespaces/internal/config"
"sigs.k8s.io/hierarchical-namespaces/internal/crd"
"sigs.k8s.io/hierarchical-namespaces/internal/forest"
"sigs.k8s.io/hierarchical-namespaces/internal/hierarchyconfig"
Expand Down Expand Up @@ -59,5 +60,16 @@ func CreateReconcilers(mgr ctrl.Manager, f *forest.Forest, maxReconciles int, us
return fmt.Errorf("cannot create Hierarchy reconciler: %s", err.Error())
}

// If LeaderElection is enabled then Watch for Elected() to enable controller writes
if !config.IsLeader {
go func() {
<-mgr.Elected()
config.IsLeader = true
ar.BecomeLeader()
hnccfgr.BecomeLeader()
hcr.BecomeLeader()
}()
}

return nil
}

0 comments on commit a3dbadc

Please sign in to comment.