Skip to content

Commit

Permalink
refactor(reconciler): update harbor cluster ctrl
Browse files Browse the repository at this point in the history
- define a status object to handle the status updates
- add fields to the HarborClusterStatus object
- concurently provisioning/checking the dependent services
- update the overall cluster reconciler logic

Signed-off-by: Steven Zou <szou@vmware.com>
  • Loading branch information
steven-zou committed Nov 30, 2020
1 parent ff824c8 commit 919c802
Show file tree
Hide file tree
Showing 10 changed files with 476 additions and 227 deletions.
20 changes: 20 additions & 0 deletions apis/goharbor.io/v1alpha2/harborcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,21 @@ type MinIOSpec struct {
type HarborClusterStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Status indicates the overall status of the Harbor cluster
// Status can be "unknown", "creating", "healthy" and "unhealthy"
Status ClusterStatus `json:"status"`

// Revision of the status
// Use unix nano
Revision int64 `json:"revision"`

// Conditions of each components
Conditions []HarborClusterCondition `json:"conditions,omitempty"`
}

// ClusterStatus is a type for cluster status
type ClusterStatus string

// HarborClusterConditionType is a valid value for HarborClusterConditionType.Type
type HarborClusterConditionType string

Expand All @@ -152,6 +163,14 @@ const (
StorageReady HarborClusterConditionType = "StorageReady"
// ServiceReady means the Service of Harbor is ready.
ServiceReady HarborClusterConditionType = "ServiceReady"
// StatusUnknown is the status of unknown
StatusUnknown ClusterStatus = "unknown"
// StatusCreating is the status of creating
StatusCreating ClusterStatus = "creating"
// StatusHealthy is the status of healthy
StatusHealthy ClusterStatus = "healthy"
// StatusUnHealthy is the status of unhealthy
StatusUnHealthy ClusterStatus = "unhealthy"
)

// HarborClusterCondition contains details for the current condition of this pod.
Expand Down Expand Up @@ -185,6 +204,7 @@ const (

// +kubebuilder:object:root=true
// +kubebuilder:printcolumn:name="Public URL",type=string,JSONPath=`.spec.externalURL`,description="The public URL to the Harbor application",priority=0
// +kubebuilder:printcolumn:name="Status", type=string,JSONPath=`.status.status`,description="The overall status of the Harbor cluster",priority=0
// +kubebuilder:printcolumn:name="Service Ready", type=string,JSONPath=`.status.conditions[?(@.type=="ServiceReady")].status`,description="The current status of the new Harbor spec",priority=10
// +kubebuilder:printcolumn:name="Cache Ready", type=string,JSONPath=`.status.conditions[?(@.type=="CacheReady")].status`,description="The current status of the new Cache spec",priority=20
// +kubebuilder:printcolumn:name="Database Ready", type=string,JSONPath=`.status.conditions[?(@.type=="DatabaseReady")].status`,description="The current status of the new Database spec",priority=20
Expand Down
2 changes: 1 addition & 1 deletion apis/goharbor.io/v1alpha2/harborcluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (hc *HarborCluster) validateStorage() *field.Error {
// Storage
// External is not configured
if err := hc.Spec.ImageChartStorage.Validate(); err != nil {
clog.Error(err, "validate spec.imageChartStorage")
clog.Info("validate spec.imageChartStorage", "cause", err.Error())

// And in-cluster minIO is not configured
if hc.Spec.InClusterStorage == nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,39 @@ package harborcluster
import (
"context"

"github.com/go-logr/logr"
"github.com/goharbor/harbor-operator/pkg/cluster/controllers/cache"
"github.com/goharbor/harbor-operator/pkg/cluster/controllers/database"
"github.com/goharbor/harbor-operator/pkg/cluster/controllers/harbor"
"github.com/goharbor/harbor-operator/pkg/cluster/controllers/storage"
"github.com/goharbor/harbor-operator/pkg/cluster/lcm"
commonCtrl "github.com/goharbor/harbor-operator/pkg/controller"
"github.com/goharbor/harbor-operator/pkg/factories/application"
"github.com/goharbor/harbor-operator/pkg/k8s"
"github.com/ovh/configstore"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

goharborv1alpha2 "github.com/goharbor/harbor-operator/apis/goharbor.io/v1alpha2"
ctrl "sigs.k8s.io/controller-runtime"
)

// TODO: Refactor to inherit the common reconciler in future
// Reconciler reconciles a HarborCluster object
type Reconciler struct {
*commonCtrl.Controller
client.Client
Log logr.Logger
Scheme *runtime.Scheme

// In case
ConfigStore *configstore.Store
Name string
Version string

CacheCtrl lcm.Controller
DatabaseCtrl lcm.Controller
StorageCtrl lcm.Controller
HarborCtrl *harbor.HarborController
HarborCtrl *harbor.Controller
}

// +kubebuilder:rbac:groups=goharbor.io,resources=harborclusters,verbs=get;list;watch;create;update;patch;delete
Expand All @@ -36,6 +48,7 @@ type Reconciler struct {
func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
r.Client = mgr.GetClient()
r.Scheme = mgr.GetScheme()

dClient, err := k8s.NewDynamicClient()
if err != nil {
r.Log.Error(err, "unable to create dynamic client")
Expand Down Expand Up @@ -68,10 +81,11 @@ func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) err
Complete(r)
}

// New HarborCluster reconciler
func New(ctx context.Context, name string, configStore *configstore.Store) (commonCtrl.Reconciler, error) {

r := &Reconciler{}
r.Controller = commonCtrl.NewController(ctx, name, r, configStore)

return r, nil
return &Reconciler{
Name: name,
Version: application.GetVersion(ctx),
Log: ctrl.Log.WithName(application.GetName(ctx)).WithName("controller").WithValues("controller", name),
}, nil
}
120 changes: 120 additions & 0 deletions controllers/goharbor/harborcluster/harborcluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package harborcluster

import (
"context"
"fmt"
"time"

"github.com/goharbor/harbor-operator/apis/goharbor.io/v1alpha2"
"golang.org/x/sync/errgroup"
apierrors "k8s.io/apimachinery/pkg/api/errors"
ctrl "sigs.k8s.io/controller-runtime"
)

const (
defaultWaitCycle = 10 * time.Second
)

// Reconcile logic of the HarborCluster
func (r *Reconciler) Reconcile(req ctrl.Request) (res ctrl.Result, err error) {
ctx := context.TODO()
log := r.Log.WithValues("resource", req.NamespacedName)

// Get the harborcluster first
harborcluster := &v1alpha2.HarborCluster{}
if err := r.Client.Get(ctx, req.NamespacedName, harborcluster); err != nil {
if apierrors.IsNotFound(err) {
// The resource may have be deleted after reconcile request coming in
// Reconcile is done
return ctrl.Result{}, nil
}

return ctrl.Result{}, fmt.Errorf("get harbor cluster CR error: %w", err)
}

// Check if it is being deleted
if !harborcluster.ObjectMeta.DeletionTimestamp.IsZero() {
log.Info("harbor cluster is being deleted", "name", req.NamespacedName)
return ctrl.Result{}, nil
}

// For tracking status
st := NewStatus(harborcluster).
WithContext(ctx).
WithClient(r.Client).
WithLog(log)

defer func() {
// Execute the status update operation
if er := st.Update(); er != nil {
sec, wait := apierrors.SuggestsClientDelay(err)
if wait {
res.RequeueAfter = time.Duration(sec) * time.Second
r.Log.Info("suggest client delay", "seconds", sec)
}

er = fmt.Errorf("defer: update status error: %s", er)
if err != nil {
err = fmt.Errorf("%s, upstreaming error: %w", er.Error(), err)
} else {
err = er
}
}
}()

// Deploy or check dependent services concurrently and fail earlier
g, _ := errgroup.WithContext(ctx)
g.Go(func() error {
cacheStatus, err := r.CacheCtrl.Apply(ctx, harborcluster)
if cacheStatus != nil {
st.UpdateCache(cacheStatus.Condition)
r.HarborCtrl.WithDependency(v1alpha2.ComponentCache, cacheStatus)
}

return err
})

g.Go(func() error {
dbStatus, err := r.DatabaseCtrl.Apply(ctx, harborcluster)
if dbStatus != nil {
st.UpdateDatabase(dbStatus.Condition)
r.HarborCtrl.WithDependency(v1alpha2.ComponentDatabase, dbStatus)
}

return err
})

g.Go(func() error {
storageStatus, err := r.StorageCtrl.Apply(ctx, harborcluster)
if storageStatus != nil {
st.UpdateStorage(storageStatus.Condition)
r.HarborCtrl.WithDependency(v1alpha2.ComponentStorage, storageStatus)
}

return err
})

if err := g.Wait(); err != nil {
return ctrl.Result{}, fmt.Errorf("reconcile dependent services error: %w", err)
}

if !st.DependsReady() {
r.Log.Info("not all the dependent services are ready")
return ctrl.Result{
RequeueAfter: defaultWaitCycle,
}, nil
}

// Create Harbor instance now
harborStatus, err := r.HarborCtrl.Apply(ctx, harborcluster)
if harborStatus != nil {
st.UpdateHarbor(harborStatus.Condition)
}
if err != nil {
return ctrl.Result{}, fmt.Errorf("reconcile harbor service error: %w", err)
}

// Reconcile done
r.Log.Info("reconcile is completed")
return ctrl.Result{}, nil
}

0 comments on commit 919c802

Please sign in to comment.