Skip to content

Commit

Permalink
Merge pull request #53 from pdettori/status
Browse files Browse the repository at this point in the history
✨ Complete ControlPlane status implementation
  • Loading branch information
pdettori authored Jun 20, 2023
2 parents 2e1a9d8 + d17ceb8 commit fbc1eab
Show file tree
Hide file tree
Showing 14 changed files with 265 additions and 169 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ _certs
bin
dist/
*.tgz
.DS_Store
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ GIT_COMMIT := $(shell git rev-parse --short HEAD || echo 'local')
GIT_DIRTY := $(shell git diff --quiet && echo 'clean' || echo 'dirty')
GIT_VERSION := $(shell go mod edit -json | jq '.Require[] | select(.Path == "k8s.io/client-go") | .Version' --raw-output)+kflex-$(shell git describe --tags --match='v*' --abbrev=14 "$(GIT_COMMIT)^{commit}" 2>/dev/null || echo v0.0.0-$(GIT_COMMIT))
BUILD_DATE := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
MAIN_VERSION := $(shell git describe --tags --match='v*' --abbrev=14 "$(GIT_COMMIT)^{commit}")
MAIN_VERSION := $(shell git tag -l --sort=-v:refname | head -n1)
LDFLAGS := \
-X main.Version=${MAIN_VERSION}.${GIT_COMMIT} \
-X main.BuildDate=${BUILD_DATE} \
Expand Down Expand Up @@ -186,7 +186,7 @@ CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest

## Tool Versions
KUSTOMIZE_VERSION ?= v5.0.0
KUSTOMIZE_VERSION ?= v5.1.0
CONTROLLER_TOOLS_VERSION ?= v0.11.3

KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
Expand Down
88 changes: 85 additions & 3 deletions api/v1alpha1/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ type ControlPlaneCondition struct {
}

// areConditionsEqual compares two ControlPlaneCondition structs and
// returns true if they are equal (excluding LastTransitionTime
//
// and LastUpdateTime), false otherwise.
// returns true if they are equal (excluding LastTransitionTime and LastUpdateTime),
// false otherwise.
func AreConditionsEqual(c1, c2 ControlPlaneCondition) bool {
if c1.Type != c2.Type || c1.Status != c2.Status || c1.Reason != c2.Reason || c1.Message != c2.Message {
return false
Expand Down Expand Up @@ -101,3 +100,86 @@ func AreConditionSlicesSame(c1, c2 []ControlPlaneCondition) bool {
}
return true
}

func EnsureCondition(cp *ControlPlane, newCondition ControlPlaneCondition) {
if cp.Status.Conditions == nil {
cp.Status.Conditions = []ControlPlaneCondition{}
}
cp.Status.Conditions = SetCondition(cp.Status.Conditions, newCondition)
}

// Creating returns a condition that indicates the cp is currently
// being created.
func ConditionCreating() ControlPlaneCondition {
return ControlPlaneCondition{
Type: TypeReady,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
LastUpdateTime: metav1.Now(),
Reason: ReasonCreating,
}
}

// Deleting returns a condition that indicates the cp is currently
// being deleted.
func ConditionDeleting() ControlPlaneCondition {
return ControlPlaneCondition{
Type: TypeReady,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
LastUpdateTime: metav1.Now(),
Reason: ReasonDeleting,
}
}

// Available returns a condition that indicates the resource is
// currently observed to be available for use.
func ConditionAvailable() ControlPlaneCondition {
return ControlPlaneCondition{
Type: TypeReady,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
LastUpdateTime: metav1.Now(),
Reason: ReasonAvailable,
}
}

// Unavailable returns a condition that indicates the resource is not
// currently available for use.
func ConditionUnavailable() ControlPlaneCondition {
return ControlPlaneCondition{
Type: TypeReady,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
LastUpdateTime: metav1.Now(),
Reason: ReasonUnavailable,
}
}

// ReconcileSuccess returns a condition indicating that KubeFlex reconciled the resource
func ConditionReconcileSuccess() ControlPlaneCondition {
return ControlPlaneCondition{
Type: TypeSynced,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
LastUpdateTime: metav1.Now(),
Reason: ReasonReconcileSuccess,
}
}

// ReconcileError returns a condition indicating that KubeFlex encountered an
// error while reconciling the resource.
func ConditionReconcileError(err error) ControlPlaneCondition {
return ControlPlaneCondition{
Type: TypeSynced,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
LastUpdateTime: metav1.Now(),
Reason: ReasonReconcileError,
Message: err.Error(),
}
}

func myAppend(list *[]string, value string) {
*list = append(*list, value)
}
10 changes: 6 additions & 4 deletions api/v1alpha1/controlplane_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ type ControlPlaneStatus struct {
ObservedGeneration int64 `json:"observedGeneration"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:resource:scope=Cluster

// ControlPlane is the Schema for the controlplanes API
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status"
// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:resource:scope=Cluster,shortName=cp
type ControlPlane struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,22 @@ spec:
kind: ControlPlane
listKind: ControlPlaneList
plural: controlplanes
shortNames:
- cp
singular: controlplane
scope: Cluster
versions:
- name: v1alpha1
- additionalPrinterColumns:
- jsonPath: .status.conditions[?(@.type=='Synced')].status
name: SYNCED
type: string
- jsonPath: .status.conditions[?(@.type=='Ready')].status
name: READY
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: ControlPlane is the Schema for the controlplanes API
Expand Down
110 changes: 50 additions & 60 deletions internal/controller/controlplane_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,20 @@ package controller

import (
"context"
"fmt"
"time"

"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
clog "sigs.k8s.io/controller-runtime/pkg/log"

tenancyv1alpha1 "github.com/kubestellar/kubeflex/api/v1alpha1"
"github.com/kubestellar/kubeflex/pkg/certs"
"github.com/kubestellar/kubeflex/pkg/util"
)

// ControlPlaneReconciler reconciles a ControlPlane object
Expand Down Expand Up @@ -80,92 +76,86 @@ func (r *ControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{}, err
}
hcp := hostedControlPlane.DeepCopy()
ownerRef := &v1.OwnerReference{
Kind: hcp.Kind,
APIVersion: hcp.APIVersion,
Name: hcp.Name,
UID: hcp.UID,
}
confGen := certs.ConfigGen{
CpName: hcp.Name,
CpHost: hcp.Name,
CpPort: SecurePort,

// check if API server is already in a ready state
ready, _ := util.IsAPIServerDeploymentReady(r.Client, *hcp)
if ready {
tenancyv1alpha1.EnsureCondition(hcp, tenancyv1alpha1.ConditionAvailable())
} else {
tenancyv1alpha1.EnsureCondition(hcp, tenancyv1alpha1.ConditionUnavailable())
}

if err = r.ReconcileNamespace(ctx, hcp.Name, ownerRef); err != nil {
return ctrl.Result{}, err
if err = r.ReconcileNamespace(ctx, hcp); err != nil {
return r.UpdateStatusForSyncingError(hcp, err)
}

crts, err := r.ReconcileCertsSecret(ctx, hcp.Name, ownerRef)
crts, err := r.ReconcileCertsSecret(ctx, hcp)
if err != nil {
return ctrl.Result{}, err
return r.UpdateStatusForSyncingError(hcp, err)
}

// reconcile kubeconfig for admin
confGen := certs.ConfigGen{CpName: hcp.Name, CpHost: hcp.Name, CpPort: SecurePort}
confGen.Target = certs.Admin
if err = r.ReconcileKubeconfigSecret(ctx, crts, confGen, ownerRef); err != nil {
return ctrl.Result{}, err
if err = r.ReconcileKubeconfigSecret(ctx, crts, confGen, hcp); err != nil {
return r.UpdateStatusForSyncingError(hcp, err)
}

// reconcile kubeconfig for cm
confGen.Target = certs.ControllerManager
confGen.CpHost = hcp.Name
if err = r.ReconcileKubeconfigSecret(ctx, crts, confGen, ownerRef); err != nil {
return ctrl.Result{}, err
if err = r.ReconcileKubeconfigSecret(ctx, crts, confGen, hcp); err != nil {
return r.UpdateStatusForSyncingError(hcp, err)
}

if err = r.ReconcileAPIServerDeployment(ctx, hcp.Name, ownerRef); err != nil {
return ctrl.Result{}, err
if err = r.ReconcileAPIServerDeployment(ctx, hcp); err != nil {
return r.UpdateStatusForSyncingError(hcp, err)
}

if err = r.ReconcileAPIServerService(ctx, hcp.Name, ownerRef); err != nil {
return ctrl.Result{}, err
if err = r.ReconcileAPIServerService(ctx, hcp); err != nil {
return r.UpdateStatusForSyncingError(hcp, err)
}

if err = r.ReconcileAPIServerIngress(ctx, hcp.Name, ownerRef); err != nil {
return ctrl.Result{}, err
if err = r.ReconcileAPIServerIngress(ctx, hcp); err != nil {
return r.UpdateStatusForSyncingError(hcp, err)
}

if err = r.ReconcileCMDeployment(ctx, hcp.Name, ownerRef); err != nil {
return ctrl.Result{}, err
if err = r.ReconcileCMDeployment(ctx, hcp); err != nil {
return r.UpdateStatusForSyncingError(hcp, err)
}

log.Info("Hosted control plane", "my-name-is", hcp.Name)

return ctrl.Result{}, nil
return r.UpdateStatusForSyncingSuccess(ctx, hcp)
}

// SetupWithManager sets up the controller with the Manager.
func (r *ControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) error {
b := ctrl.NewControllerManagedBy(mgr).
return ctrl.NewControllerManagedBy(mgr).
For(&tenancyv1alpha1.ControlPlane{}).
WithOptions(controller.Options{
RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(1*time.Second, 10*time.Second),
})
for _, handler := range r.eventHandlers() {
b.Watches(handler.obj, handler.handler)
}
if _, err := b.Build(r); err != nil {
return fmt.Errorf("failed setting up with a controller manager %w", err)
}
return nil
Owns(&corev1.Service{}).
Owns(&networkingv1.Ingress{}).
Owns(&appsv1.Deployment{}).
Owns(&appsv1.StatefulSet{}).
Owns(&corev1.Secret{}).
Owns(&corev1.ConfigMap{}).
Owns(&corev1.ServiceAccount{}).
Complete(r)
}

type eventHandler struct {
obj client.Object
handler handler.EventHandler
func (r *ControlPlaneReconciler) UpdateStatusForSyncingError(hcp *tenancyv1alpha1.ControlPlane, e error) (ctrl.Result, error) {
tenancyv1alpha1.EnsureCondition(hcp, tenancyv1alpha1.ConditionReconcileError(e))
err := r.Status().Update(context.Background(), hcp)
if err != nil {
return ctrl.Result{}, errors.Wrap(e, err.Error())
}
return ctrl.Result{}, err
}

func (r *ControlPlaneReconciler) eventHandlers() []eventHandler {

handlers := []eventHandler{
{obj: &corev1.Service{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &corev1.Service{}, handler.OnlyControllerOwner())},
{obj: &networkingv1.Ingress{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &networkingv1.Ingress{}, handler.OnlyControllerOwner())},
{obj: &appsv1.Deployment{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &appsv1.Deployment{}, handler.OnlyControllerOwner())},
{obj: &appsv1.StatefulSet{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &appsv1.StatefulSet{}, handler.OnlyControllerOwner())},
{obj: &corev1.Secret{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &corev1.Secret{}, handler.OnlyControllerOwner())},
{obj: &corev1.ConfigMap{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &corev1.ConfigMap{}, handler.OnlyControllerOwner())},
{obj: &corev1.ServiceAccount{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &corev1.ServiceAccount{}, handler.OnlyControllerOwner())},
func (r *ControlPlaneReconciler) UpdateStatusForSyncingSuccess(ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) (ctrl.Result, error) {
_ = clog.FromContext(ctx)
tenancyv1alpha1.EnsureCondition(hcp, tenancyv1alpha1.ConditionReconcileSuccess())
err := r.Status().Update(context.Background(), hcp)
if err != nil {
return ctrl.Result{}, err
}
return handlers
return ctrl.Result{}, err
}
Loading

0 comments on commit fbc1eab

Please sign in to comment.