Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BOP-85][BOP-87] Add events and status for Addon and Manifest CRs #16

Merged
merged 2 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions api/v1alpha1/addon_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,50 @@ type ManifestInfo struct {
URL string `json:"url"`
}

// StatusType is a type of condition that may apply to a particular component.
type StatusType string

const (
nwneisen marked this conversation as resolved.
Show resolved Hide resolved
// TypeComponentAvailable indicates that the component is healthy.
TypeComponentAvailable StatusType = "Available"

// TypeComponentProgressing means that the component is in the process of being installed or upgraded.
TypeComponentProgressing StatusType = "Progressing"

// TypeComponentDegraded means the component is not operating as desired and user action is required.
TypeComponentDegraded StatusType = "Degraded"

// TypeComponentReady indicates that the component is healthy and ready.it is identical to Available.
TypeComponentReady StatusType = "Ready"
tppolkow marked this conversation as resolved.
Show resolved Hide resolved

// TypeComponentUnhealthy indicates the component is not functioning as intended.
TypeComponentUnhealthy StatusType = "Unhealthy"
)

type Status struct {
// The type of condition. May be Available, Progressing, or Degraded.
Type StatusType `json:"type"`

// The timestamp representing the start time for the current status.
LastTransitionTime metav1.Time `json:"lastTransitionTime"`

// A brief reason explaining the condition.
Reason string `json:"reason,omitempty"`

// Optionally, a detailed message providing additional context.
Message string `json:"message,omitempty"`
}

// AddonStatus defines the observed state of Addon
type AddonStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
Status `json:",inline"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.type",description="Whether the component is running and stable."

// Addon is the Schema for the addons API
type Addon struct {
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha1/manifest_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type ManifestSpec struct {
type ManifestStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file

nwneisen marked this conversation as resolved.
Show resolved Hide resolved
Status `json:",inline"`
}

// ManifestObject consists of the fields required to update/delete an object
Expand All @@ -33,6 +35,7 @@ type ManifestObject struct {

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.type",description="Whether the component is running and stable."

// Manifest is the Schema for the manifests API
type Manifest struct {
Expand Down
22 changes: 20 additions & 2 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 25 additions & 1 deletion config/crd/bases/boundless.mirantis.com_addons.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ spec:
singular: addon
scope: Namespaced
versions:
- name: v1alpha1
- additionalPrinterColumns:
- description: Whether the component is running and stable.
jsonPath: .status.type
name: Status
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: Addon is the Schema for the addons API
Expand Down Expand Up @@ -79,6 +84,25 @@ spec:
type: object
status:
description: AddonStatus defines the observed state of Addon
properties:
lastTransitionTime:
description: The timestamp representing the start time for the current
status.
format: date-time
type: string
message:
description: Optionally, a detailed message providing additional context.
type: string
reason:
description: A brief reason explaining the condition.
type: string
type:
description: The type of condition. May be Available, Progressing,
or Degraded.
type: string
required:
- lastTransitionTime
- type
type: object
type: object
served: true
Expand Down
26 changes: 25 additions & 1 deletion config/crd/bases/boundless.mirantis.com_manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ spec:
singular: manifest
scope: Namespaced
versions:
- name: v1alpha1
- additionalPrinterColumns:
- description: Whether the component is running and stable.
jsonPath: .status.type
name: Status
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: Manifest is the Schema for the manifests API
Expand Down Expand Up @@ -64,6 +69,25 @@ spec:
type: object
status:
description: ManifestStatus defines the observed state of Manifest
properties:
lastTransitionTime:
description: The timestamp representing the start time for the current
status.
format: date-time
type: string
message:
description: Optionally, a detailed message providing additional context.
type: string
reason:
description: A brief reason explaining the condition.
type: string
type:
description: The type of condition. May be Available, Progressing,
or Degraded.
type: string
required:
- lastTransitionTime
- type
type: object
type: object
served: true
Expand Down
7 changes: 7 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ metadata:
creationTimestamp: null
name: manager-role
rules:
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- boundless.mirantis.com
resources:
Expand Down
58 changes: 53 additions & 5 deletions controllers/addon_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import (
"fmt"
"strings"

"github.com/go-logr/logr"
boundlessv1alpha1 "github.com/mirantis/boundless-operator/api/v1alpha1"
"github.com/mirantis/boundless-operator/pkg/event"
"github.com/mirantis/boundless-operator/pkg/helm"
"github.com/mirantis/boundless-operator/pkg/manifest"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"

boundlessv1alpha1 "github.com/mirantis/boundless-operator/api/v1alpha1"
"github.com/mirantis/boundless-operator/pkg/helm"
"github.com/mirantis/boundless-operator/pkg/manifest"
)

const (
Expand All @@ -28,12 +32,14 @@ const (
// AddonReconciler reconciles a Addon object
type AddonReconciler struct {
client.Client
Scheme *runtime.Scheme
Scheme *runtime.Scheme
Recorder record.EventRecorder
}

//+kubebuilder:rbac:groups=boundless.mirantis.com,resources=addons,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=boundless.mirantis.com,resources=addons/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=boundless.mirantis.com,resources=addons/finalizers,verbs=update
//+kubebuilder:rbac:groups="",resources=events,verbs=create;patch

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
Expand Down Expand Up @@ -75,6 +81,8 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
kind = instance.Spec.Kind
}

// @TODO: Update addon status only once per reconcile; React to Statuses of HelmChart / Manifests

switch kind {
case kindChart:
if instance.Spec.Chart == nil {
Expand Down Expand Up @@ -110,6 +118,7 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
if err := hc.DeleteHelmChart(chart, instance.Spec.Namespace); err != nil {
// if fail to delete the helm chart here, return with error
// so that it can be retried
r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeWarning, event.ReasonFailedDelete, "Failed to Delete Chart Addon %s/%s: %s", instance.Spec.Namespace, instance.Name, err)
return ctrl.Result{}, err
}

Expand All @@ -127,8 +136,13 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
logger.Info("Creating Addon HelmChart resource", "Name", chart.Name, "Version", chart.Version)
if err := hc.CreateHelmChart(chart, instance.Spec.Namespace); err != nil {
logger.Error(err, "failed to install addon", "Name", chart.Name, "Version", chart.Version)
r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeWarning, event.ReasonFailedCreate, "Failed to Create Chart Addon %s/%s : %s", instance.Spec.Namespace, instance.Name, err)
r.updateStatus(ctx, logger, req.NamespacedName, boundlessv1alpha1.TypeComponentUnhealthy, "Failed to Create HelmChart")
return ctrl.Result{Requeue: true}, err
}
r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeNormal, event.ReasonSuccessfulCreate, "Created Chart Addon %s/%s", instance.Spec.Namespace, instance.Name)
r.updateStatus(ctx, logger, req.NamespacedName, boundlessv1alpha1.TypeComponentAvailable, "Chart Addon Created")

case kindManifest:
if instance.Spec.Manifest == nil {
logger.Info("Manifest info is missing")
Expand All @@ -154,6 +168,8 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
if err := mc.DeleteManifest(BoundlessNamespace, instance.Spec.Name, instance.Spec.Manifest.URL); err != nil {
// if fail to delete the manifest here, return with error
// so that it can be retried
r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeWarning, event.ReasonFailedDelete, "Failed to Delete Manifest Addon %s/%s : %s", instance.Spec.Namespace, instance.Name, err)
r.updateStatus(ctx, logger, req.NamespacedName, boundlessv1alpha1.TypeComponentUnhealthy, "Failed to Cleanup Manifest")
return ctrl.Result{}, err
}

Expand All @@ -171,9 +187,14 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
err = mc.CreateManifest(BoundlessNamespace, instance.Spec.Name, instance.Spec.Manifest.URL)
if err != nil {
logger.Error(err, "failed to install addon via manifest", "URL", instance.Spec.Manifest.URL)
r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeWarning, event.ReasonFailedCreate, "Failed to Create Manifest Addon %s/%s : %s", instance.Spec.Namespace, instance.Name, err)
r.updateStatus(ctx, logger, req.NamespacedName, boundlessv1alpha1.TypeComponentUnhealthy, "Failed to Create Manifest")
return ctrl.Result{Requeue: true}, err
}

r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeNormal, event.ReasonSuccessfulCreate, "Created Manifest Addon %s/%s", instance.Spec.Namespace, instance.Name)
r.updateStatus(ctx, logger, req.NamespacedName, boundlessv1alpha1.TypeComponentAvailable, "Manifest Addon Created")

default:
logger.Info("Unknown AddOn kind", "Kind", instance.Spec.Kind)
return ctrl.Result{Requeue: false}, fmt.Errorf("Unknown AddOn Kind: %w", err)
Expand All @@ -189,3 +210,30 @@ func (r *AddonReconciler) SetupWithManager(mgr ctrl.Manager) error {
For(&boundlessv1alpha1.Addon{}).
Complete(r)
}

func (r *AddonReconciler) updateStatus(ctx context.Context, logger logr.Logger, namespacedName types.NamespacedName, conditionTypeToApply boundlessv1alpha1.StatusType, reasonToApply string, messageToApply ...string) error {
addon := &boundlessv1alpha1.Addon{}
err := r.Get(ctx, namespacedName, addon)
if err != nil {
logger.Error(err, "Failed to get addon to update status")
return err
}

if addon.Status.Type == conditionTypeToApply && addon.Status.Reason == reasonToApply {
// avoid infinite reconciliation loops
logger.Info("No updates to status needed")
return nil
}

logger.Info("Update status for addon", "Name", addon.Name)

patch := client.MergeFrom(addon.DeepCopy())
addon.Status.Type = conditionTypeToApply
addon.Status.Reason = reasonToApply
if len(messageToApply) > 0 {
addon.Status.Message = messageToApply[0]
}
addon.Status.LastTransitionTime = metav1.Now()

return r.Status().Patch(ctx, addon, patch)
}
Loading