diff --git a/pkg/apis/camel/v1/integration_types.go b/pkg/apis/camel/v1/integration_types.go index a68533cda6..96e429bd1a 100644 --- a/pkg/apis/camel/v1/integration_types.go +++ b/pkg/apis/camel/v1/integration_types.go @@ -159,6 +159,8 @@ const ( IntegrationPhaseRunning IntegrationPhase = "Running" // IntegrationPhaseError --. IntegrationPhaseError IntegrationPhase = "Error" + // IntegrationPhaseUnknown --. + IntegrationPhaseUnknown IntegrationPhase = "Unknown" // IntegrationConditionReady --. IntegrationConditionReady IntegrationConditionType = "Ready" diff --git a/pkg/controller/integration/integration_controller.go b/pkg/controller/integration/integration_controller.go index 04cb6017b5..24d9d3538a 100644 --- a/pkg/controller/integration/integration_controller.go +++ b/pkg/controller/integration/integration_controller.go @@ -56,6 +56,8 @@ import ( utilResource "github.com/apache/camel-k/v2/pkg/util/resource" ) +const retryMonitoring = 5 + func Add(ctx context.Context, mgr manager.Manager, c client.Client) error { err := mgr.GetFieldIndexer().IndexField(ctx, &corev1.Pod{}, "status.phase", func(obj ctrl.Object) []string { @@ -529,7 +531,7 @@ func (r *reconcileIntegration) Reconcile(ctx context.Context, request reconcile. if instance.IsSynthetic() { actions = append(actions, NewMonitorSyntheticAction()) } else { - actions = append(actions, NewMonitorAction()) + actions = append(actions, NewMonitorAction(), NewMonitorUnknownAction()) } for _, a := range actions { @@ -557,6 +559,11 @@ func (r *reconcileIntegration) Reconcile(ctx context.Context, request reconcile. camelevent.NotifyIntegrationError(ctx, r.client, r.recorder, &instance, newTarget, err) return reconcile.Result{}, err } + + if newTarget.Status.Phase == v1.IntegrationPhaseUnknown { + // Wait for some time before trying to monitor again + return reconcile.Result{RequeueAfter: retryMonitoring * time.Second}, nil + } } // handle one action at time so the resource diff --git a/pkg/controller/integration/monitor.go b/pkg/controller/integration/monitor.go index af90cc3db7..5c395a86da 100644 --- a/pkg/controller/integration/monitor.go +++ b/pkg/controller/integration/monitor.go @@ -137,7 +137,13 @@ func (action *monitorAction) Handle(ctx context.Context, integration *v1.Integra v1.IntegrationConditionInitializationFailedReason, err.Error()) return integration, err } - + // If the platform is not in ready status (it may happen when a new IntegrationPlatform is created), then, we may not be able to + // properly apply all the traits. We must set the phase in an unknown status which should be periodically reconciled in order to make sure that + // we eventually return in a ready phase (likely once the platform is done) + if environment.Platform != nil && environment.Platform.Status.Phase != v1.IntegrationPlatformPhaseReady { + integration.Status.Phase = v1.IntegrationPhaseUnknown + return integration, nil + } action.checkTraitAnnotationsDeprecatedNotice(integration) return action.monitorPods(ctx, environment, integration) diff --git a/pkg/controller/integration/monitor_test.go b/pkg/controller/integration/monitor_test.go index a655edea9a..dbd56507a1 100644 --- a/pkg/controller/integration/monitor_test.go +++ b/pkg/controller/integration/monitor_test.go @@ -128,6 +128,139 @@ func TestMonitorFailureIntegration(t *testing.T) { assert.Equal(t, v1.IntegrationConditionInitializationFailedReason, handledIt.Status.GetCondition(v1.IntegrationConditionReady).Reason) } +func TestMonitorIntegrationWhilePlatformRecreating(t *testing.T) { + catalog := &v1.CamelCatalog{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1.SchemeGroupVersion.String(), + Kind: v1.CamelCatalogKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "camel-k-catalog", + }, + Spec: v1.CamelCatalogSpec{ + Runtime: v1.RuntimeSpec{ + Provider: v1.RuntimeProviderQuarkus, + Version: defaults.DefaultRuntimeVersion, + }, + }, + } + platform := &v1.IntegrationPlatform{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1.SchemeGroupVersion.String(), + Kind: v1.IntegrationPlatformKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "camel-k", + }, + Status: v1.IntegrationPlatformStatus{ + Phase: v1.IntegrationPlatformPhaseNone, + }, + } + kit := &v1.IntegrationKit{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1.SchemeGroupVersion.String(), + Kind: v1.IntegrationKitKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "my-kit", + }, + Status: v1.IntegrationKitStatus{ + Phase: v1.IntegrationKitPhaseReady, + }, + } + it := &v1.Integration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1.SchemeGroupVersion.String(), + Kind: v1.IntegrationKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "my-it", + }, + Status: v1.IntegrationStatus{ + RuntimeVersion: defaults.DefaultRuntimeVersion, + Phase: v1.IntegrationPhaseRunning, + IntegrationKit: &corev1.ObjectReference{ + Name: kit.Name, + Namespace: kit.Namespace, + Kind: kit.Kind, + APIVersion: kit.APIVersion, + }, + Conditions: []v1.IntegrationCondition{ + { + Type: v1.IntegrationConditionDeploymentAvailable, + Status: corev1.ConditionTrue, + }, + { + Type: v1.IntegrationConditionReady, + Status: corev1.ConditionTrue, + }, + }, + }, + } + hash, _ := digest.ComputeForIntegration(it, nil, nil) + it.Status.Digest = hash + pod := &corev1.Pod{ + TypeMeta: metav1.TypeMeta{ + APIVersion: appsv1.SchemeGroupVersion.String(), + Kind: "Pod", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "my-pod", + Labels: map[string]string{ + v1.IntegrationLabel: "my-it", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "my-cnt", + Image: "my-img", + }, + }, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{ + { + Type: corev1.PodReady, + Status: corev1.ConditionTrue, + }, + }, + }, + } + c, err := test.NewFakeClient(catalog, platform, it, kit, pod) + require.NoError(t, err) + + a := monitorAction{} + a.InjectLogger(log.Log) + a.InjectClient(c) + assert.Equal(t, "monitor", a.Name()) + assert.True(t, a.CanHandle(it)) + handledIt, err := a.Handle(context.TODO(), it) + require.NoError(t, err) + assert.Equal(t, v1.IntegrationPhaseUnknown, handledIt.Status.Phase) +} + +func TestMonitorIntegrationRecoverFromUnknown(t *testing.T) { + c, it, err := nominalEnvironment() + it.Status.Phase = v1.IntegrationPhaseUnknown + require.NoError(t, err) + + a := monitorUnknownAction{} + a.InjectLogger(log.Log) + a.InjectClient(c) + assert.Equal(t, "monitor-unknown", a.Name()) + assert.True(t, a.CanHandle(it)) + handledIt, err := a.Handle(context.TODO(), it) + require.NoError(t, err) + assert.Equal(t, v1.IntegrationPhaseRunning, handledIt.Status.Phase) +} + func nominalEnvironment() (client.Client, *v1.Integration, error) { catalog := &v1.CamelCatalog{ TypeMeta: metav1.TypeMeta{ diff --git a/pkg/controller/integration/monitor_unknown.go b/pkg/controller/integration/monitor_unknown.go new file mode 100644 index 0000000000..e9e1cbc305 --- /dev/null +++ b/pkg/controller/integration/monitor_unknown.go @@ -0,0 +1,61 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "context" + + v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + "github.com/apache/camel-k/v2/pkg/trait" + corev1 "k8s.io/api/core/v1" +) + +// NewMonitorUnknownAction is an action used to verify when an Integration can be monitored back again. +func NewMonitorUnknownAction() Action { + return &monitorUnknownAction{} +} + +type monitorUnknownAction struct { + baseAction +} + +func (action *monitorUnknownAction) Name() string { + return "monitor-unknown" +} + +func (action *monitorUnknownAction) CanHandle(integration *v1.Integration) bool { + return integration.Status.Phase == v1.IntegrationPhaseUnknown +} + +func (action *monitorUnknownAction) Handle(ctx context.Context, integration *v1.Integration) (*v1.Integration, error) { + // Run traits that are enabled for the phase + environment, err := trait.Apply(ctx, action.client, integration, nil) + if err != nil { + integration.Status.Phase = v1.IntegrationPhaseError + integration.SetReadyCondition(corev1.ConditionFalse, + v1.IntegrationConditionInitializationFailedReason, err.Error()) + return integration, err + } + // We're good to monitor this again + if environment.Platform != nil && environment.Platform.Status.Phase == v1.IntegrationPlatformPhaseReady { + integration.Status.Phase = v1.IntegrationPhaseRunning + return integration, nil + } + + return integration, nil +}