Skip to content
Draft
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
1 change: 1 addition & 0 deletions api/v1alpha1/entandoappv2_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ type EntandoAppV2Status struct {
// An EntandoAppV2 deploys the components required to upgrade an Entando App. The server side
// components that are deployed include the Entando App Engine, the Entando Component Manager,
// the Entando App Builder, and the user facing application.
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type=="Ready")].status`,description="state of EntandoApp"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adds additionalPrinterColumns to CRD, in this way it's easier to see its evolution with kubectl

type EntandoAppV2 struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Expand Down
7 changes: 6 additions & 1 deletion config/crd/bases/app.entando.org_entandoappv2s.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ spec:
singular: entandoappv2
scope: Namespaced
versions:
- name: v1alpha1
- additionalPrinterColumns:
- description: state of EntandoApp
jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: An EntandoAppV2 deploys the components required to upgrade an
Expand Down
64 changes: 56 additions & 8 deletions controllers/entandoappv2_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ package controllers

import (
"context"

v1alpha1 "github.com/entgigi/upgrade-operator.git/api/v1alpha1"
"github.com/entgigi/upgrade-operator.git/api/v1alpha1"
"github.com/entgigi/upgrade-operator.git/controllers/reconciliation"
"github.com/entgigi/upgrade-operator.git/utils"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
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/predicate"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/source"
)

const (
Expand All @@ -51,14 +54,57 @@ type EntandoAppV2Reconciler struct {

func (r *EntandoAppV2Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithName(controllerLogName)
log.Info("Start reconciling EntandoAppV2 custom resources")
log.Info("Start reconciling EntandoAppV2 custom resources", "req", req)

entandoAppV2 := v1alpha1.EntandoAppV2{}
deployment := appsv1.Deployment{}
err := r.Client.Get(ctx, req.NamespacedName, &entandoAppV2)
if err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
err2 := r.Client.Get(ctx, req.NamespacedName, &deployment)

if err2 == nil {
log.Info("is a deployment")
// are all deployment ready?
list := []string{"default-sso-in-namespace-deployment", "quickstart-ab-deployment", "quickstart-cm-deployment", "quickstart-deployment"}
counter := 0
for _, componentname := range list {

r.Client.Get(ctx, types.NamespacedName{
Namespace: req.Namespace,
Name: componentname,
}, &deployment)

//log.Info("deployment", "deployment", deployment)
// idx := slices.IndexFunc(deployment.Status.Conditions, func(c appsv1.DeploymentCondition) bool { return c.Type == "Available" && c.Status == "True" })
if deployment.Status.ReadyReplicas > 0 {
counter++
}
log.Info("ReadyReplicas has value", "deployment.Status.ReadyReplicas", deployment.Status.ReadyReplicas)
}
log.Info("counter has value", "counter", counter)
if counter == 4 {
statusUpdater := reconciliation.NewStatusUpdater(r.Client, r.Log)
statusUpdater.SetReady(ctx, types.NamespacedName{
Namespace: req.Namespace,
Name: "entando-app-v2-sample",
})
} else {
statusUpdater := reconciliation.NewStatusUpdater(r.Client, r.Log)
statusUpdater.SetNotReady(ctx, types.NamespacedName{
Namespace: req.Namespace,
Name: "entando-app-v2-sample",
})
}

return ctrl.Result{}, nil

} else {
log.Info("is a CR")
}

/*if err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}*/

// Check if the EntandoApp instance is marked to be deleted, which is
// indicated by the deletion timestamp being set.
isEntandoAppV2MarkedToBeDeleted := entandoAppV2.GetDeletionTimestamp() != nil
Expand Down Expand Up @@ -92,8 +138,10 @@ func (r *EntandoAppV2Reconciler) SetupWithManager(mgr ctrl.Manager) error {
//log := r.Log.WithName("Upgrade Controller")
return ctrl.NewControllerManagedBy(mgr).
// FIXME! add filter on create for EntandoAppV2 cr
For(&v1alpha1.EntandoAppV2{}).
WithEventFilter(predicate.GenerationChangedPredicate{}). //solo modifiche a spec
For(&v1alpha1.EntandoAppV2{}, utils.ForOptionCustom()).
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

custom predicates can be used in For and in Watches methods, in this way it is possible to filter the events based on the type of resource, for example different behaviors from CR and deployments events can be used

Watches(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForObject{}).
//WithEventFilter(predicate.GenerationChangedPredicate{}). //solo modifiche a spec
//Watches(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForObject{}).
Complete(r)
}

Expand Down
122 changes: 67 additions & 55 deletions controllers/reconciliation/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package reconciliation

import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/entgigi/upgrade-operator.git/api/v1alpha1"
"github.com/entgigi/upgrade-operator.git/common"
"github.com/entgigi/upgrade-operator.git/legacy"
"github.com/entgigi/upgrade-operator.git/service"
"github.com/entgigi/upgrade-operator.git/utils"
"github.com/go-logr/logr"
"golang.org/x/exp/slices"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -40,83 +42,93 @@ func NewReconcileManager(client client.Client, log logr.Logger) *ReconcileManage
func (r *ReconcileManager) MainReconcile(ctx context.Context, req ctrl.Request) error {

r.Log.Info("Starting main reconciliation flow")
r.statusUpdater.SetReconcileStarted(ctx, req.NamespacedName, numberOfSteps)
//r.statusUpdater.SetReconcileStarted(ctx, req.NamespacedName, numberOfSteps)

var err error
crReadOnly := &v1alpha1.EntandoAppV2{}
if err := r.Client.Get(ctx, req.NamespacedName, crReadOnly); err != nil {
return err
}

imageManager := service.NewImageManager(r.Log)
var images service.EntandoAppImages

if images, err = imageManager.FetchAndComposeImagesMap(crReadOnly); err != nil {
if err := r.Client.Get(ctx, req.NamespacedName, crReadOnly); err != nil {
return err
}
//r.Log.Info(fmt.Sprintf("%+v\n", images))

//TODO reconcile secrets for ca before EntandoApp components
idx := slices.IndexFunc(crReadOnly.Status.Conditions, func(c metav1.Condition) bool { return c.Type == Initialized && c.Status == "True" })
r.Log.Info("idx has value", "idx", idx)

if _, err = r.reconcileComponent(ctx, req, "Keycloak", r.reconcileKeycloak, images.FetchKeycloak(), crReadOnly); err != nil {
return err
}
// image not written
if idx == -1 {
imageManager := service.NewImageManager(r.Log)
var images service.EntandoAppImages

if _, err = r.reconcileComponent(ctx, req, "DeApp", r.reconcileDeApp, images.FetchDeApp(), crReadOnly); err != nil {
return err
}
if images, err = imageManager.FetchAndComposeImagesMap(crReadOnly); err != nil {
return err
}
//r.Log.Info(fmt.Sprintf("%+v\n", images))

if _, err = r.reconcileComponent(ctx, req, "AppBuilder", r.reconcileAppBuilder, images.FetchAppBuilder(), crReadOnly); err != nil {
return err
}
//TODO reconcile secrets for ca before EntandoApp components

// TODO before check entando-k8s-service app ready
cr := &v1alpha1.EntandoAppV2{}
if cr, err = r.reconcileComponent(ctx, req, "ComponentManager", r.reconcileComponentManager, images.FetchComponentManager(), crReadOnly); err != nil {
return err
}
if _, err = r.reconcileComponent(ctx, req, "Keycloak", r.reconcileKeycloak, images.FetchKeycloak(), crReadOnly); err != nil {
return err
}

// =========================== start legacy section ===========================
// progress step not added because is not a business step but jsut technical
legacyReconcile := legacy.NewLegacyReconcileManager(r.Client, r.Log)
if utils.IsOlmInstallation() {
r.statusUpdater.SetReconcileProcessingComponent(ctx, req.NamespacedName, "Csv")
if _, err = r.reconcileComponent(ctx, req, "DeApp", r.reconcileDeApp, images.FetchDeApp(), crReadOnly); err != nil {
return err
}

if err = legacyReconcile.ReconcileClusterServiceVersion(ctx, req, images); err != nil {
r.statusUpdater.SetReconcileFailed(ctx, req.NamespacedName, "CsvReconciliationFailed")
if _, err = r.reconcileComponent(ctx, req, "AppBuilder", r.reconcileAppBuilder, images.FetchAppBuilder(), crReadOnly); err != nil {
return err
}
} else {
r.statusUpdater.SetReconcileProcessingComponent(ctx, req.NamespacedName, "ImageInfo")
if err = legacyReconcile.ReconcileImageInfo(ctx, req, images); err != nil {
r.statusUpdater.SetReconcileFailed(ctx, req.NamespacedName, "ImageInfoReconciliationFailed")

// TODO before check entando-k8s-service app ready
//cr := &v1alpha1.EntandoAppV2{}
if _, err = r.reconcileComponent(ctx, req, "ComponentManager", r.reconcileComponentManager, images.FetchComponentManager(), crReadOnly); err != nil {
return err
}
}
// =========================== end legacy section =============================

// reconcile k8s-service component taking into account of the legacy Operator behavior,
// in non-olm install we need to reconcile k8s-service deployment
if !utils.IsOlmInstallation() {
// K8sService
r.statusUpdater.SetReconcileProcessingComponent(ctx, req.NamespacedName, "K8sService")
// =========================== start legacy section ===========================
// progress step not added because is not a business step but jsut technical
legacyReconcile := legacy.NewLegacyReconcileManager(r.Client, r.Log)
if utils.IsOlmInstallation() {
//r.statusUpdater.SetReconcileProcessingComponent(ctx, req.NamespacedName, "Csv")

if err = legacyReconcile.ReconcileClusterServiceVersion(ctx, req, images); err != nil {
//r.statusUpdater.SetReconcileFailed(ctx, req.NamespacedName, "CsvReconciliationFailed")
return err
}
} else {
//r.statusUpdater.SetReconcileProcessingComponent(ctx, req.NamespacedName, "ImageInfo")
if err = legacyReconcile.ReconcileImageInfo(ctx, req, images); err != nil {
//r.statusUpdater.SetReconcileFailed(ctx, req.NamespacedName, "ImageInfoReconciliationFailed")
return err
}
}
// =========================== end legacy section =============================

// reconcile k8s-service component taking into account of the legacy Operator behavior,
// in non-olm install we need to reconcile k8s-service deployment
if !utils.IsOlmInstallation() {
// K8sService
//r.statusUpdater.SetReconcileProcessingComponent(ctx, req.NamespacedName, "K8sService")

// TODO decide if add the k8service in the progress count. in that case we could also consider to adapt the k8s-service reconciliation function to the standard format
// TODO decide if add the k8service in the progress count. in that case we could also consider to adapt the k8s-service reconciliation function to the standard format

if err = r.reconcileK8sService(ctx, req, images.FetchK8sService(), crReadOnly); err != nil {
//r.statusUpdater.SetReconcileFailed(ctx, req.NamespacedName, "K8sServiceReconciliationFailed")
return err
}
// legacy K8sCoordinator restart ? no needs

if err = r.reconcileK8sService(ctx, req, images.FetchK8sService(), crReadOnly); err != nil {
r.statusUpdater.SetReconcileFailed(ctx, req.NamespacedName, "K8sServiceReconciliationFailed")
return err
}
// legacy K8sCoordinator restart ? no needs

}
// Check for progress/total mismatch
/*if cr.Status.Progress != numberOfSteps {
r.Log.Info("WARNING: progress different from total at the end of reconciliation", "progress", cr.Status.Progress, "total", numberOfSteps)
}*/

r.statusUpdater.SetInitialized(ctx, req.NamespacedName)

// Check for progress/total mismatch
if cr.Status.Progress != numberOfSteps {
r.Log.Info("WARNING: progress different from total at the end of reconciliation", "progress", cr.Status.Progress, "total", numberOfSteps)
}

r.statusUpdater.SetReconcileSuccessfullyCompleted(ctx, req.NamespacedName)
//r.statusUpdater.SetReconcileSuccessfullyCompleted(ctx, req.NamespacedName)

return nil
}
Expand All @@ -129,12 +141,12 @@ func (r *ReconcileManager) reconcileComponent(ctx context.Context,
imageUrl string,
cr *v1alpha1.EntandoAppV2) (*v1alpha1.EntandoAppV2, error) {

r.statusUpdater.SetReconcileProcessingComponent(ctx, req.NamespacedName, componentName)
//r.statusUpdater.SetReconcileProcessingComponent(ctx, req.NamespacedName, componentName)
if err := reconcile(ctx, imageUrl, req, cr); err != nil {
r.statusUpdater.SetReconcileFailed(ctx, req.NamespacedName, componentName+"ReconciliationFailed")
//r.statusUpdater.SetReconcileFailed(ctx, req.NamespacedName, componentName+"ReconciliationFailed")
return nil, err
}
return r.statusUpdater.IncrementProgress(ctx, req.NamespacedName)
return cr, nil //r.statusUpdater.IncrementProgress(ctx, req.NamespacedName)
}

// mustGetDeployment try to get the first deployment starting by the entando label dedicated to identify the component
Expand Down
62 changes: 60 additions & 2 deletions controllers/reconciliation/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ const (
statusUpdaterLogName = "StatusUpdater"

// Condition Types
Ready = "Ready"
Succeeded = "Succeeded"
Ready = "Ready"
Succeeded = "Succeeded"
Initialized = "Initialized"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only used Ready and Initialized


// Condition Reasons
CustomResourceChanged = "CustomResourceChanged"
Expand Down Expand Up @@ -147,6 +148,63 @@ func (su *StatusUpdater) IncrementProgress(ctx context.Context, key types.Namesp
return cr, err
}

func (su *StatusUpdater) SetInitialized(ctx context.Context, key types.NamespacedName) (*v1alpha1.EntandoAppV2, error) {
cr, err := su.updateStatus(ctx, key, func(cr *v1alpha1.EntandoAppV2) {
meta.SetStatusCondition(&cr.Status.Conditions, metav1.Condition{
Type: Initialized,
Status: metav1.ConditionTrue,
Reason: "ImageWritten",
Message: "Image written in components",
})
})

if err != nil {
su.log.Error(err, "Unable to update EntandoAppV2's status for reconciliation initialized")
}

return cr, err
}

func (su *StatusUpdater) SetReady(ctx context.Context, key types.NamespacedName) (*v1alpha1.EntandoAppV2, error) {
cr, err := su.updateStatus(ctx, key, func(cr *v1alpha1.EntandoAppV2) {
meta.SetStatusCondition(&cr.Status.Conditions, metav1.Condition{
Type: Ready,
Status: metav1.ConditionTrue,
Reason: "Ready",
Message: "All components are Ready",
})
meta.SetStatusCondition(&cr.Status.Conditions, metav1.Condition{
Type: Initialized,
Status: metav1.ConditionFalse,
Reason: "ReconciliationDone",
Message: "Reconciliation process success",
})
})

if err != nil {
su.log.Error(err, "Unable to update EntandoAppV2's status for reconciliation initialized")
}

return cr, err
}

func (su *StatusUpdater) SetNotReady(ctx context.Context, key types.NamespacedName) (*v1alpha1.EntandoAppV2, error) {
cr, err := su.updateStatus(ctx, key, func(cr *v1alpha1.EntandoAppV2) {
meta.SetStatusCondition(&cr.Status.Conditions, metav1.Condition{
Type: Ready,
Status: metav1.ConditionFalse,
Reason: "Ready",
Message: "All components are not Ready",
})
})

if err != nil {
su.log.Error(err, "Unable to update EntandoAppV2's status for reconciliation initialized")
}

return cr, err
}

func (su *StatusUpdater) updateStatus(ctx context.Context, key types.NamespacedName, updateFields func(cr *v1alpha1.EntandoAppV2)) (*v1alpha1.EntandoAppV2, error) {
cr := &v1alpha1.EntandoAppV2{}

Expand Down
Loading