diff --git a/e2e/common/cron_test.go b/e2e/common/cron_test.go index ed961a60fe..561b3158e9 100644 --- a/e2e/common/cron_test.go +++ b/e2e/common/cron_test.go @@ -25,6 +25,7 @@ import ( "testing" . "github.com/apache/camel-k/e2e/support" + camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" ) @@ -38,6 +39,7 @@ func TestRunCronExample(t *testing.T) { Expect(Kamel("run", "-n", ns, "files/cron.groovy").Execute()).Should(BeNil()) Eventually(IntegrationCronJob(ns, "cron"), TestTimeoutMedium).ShouldNot(BeNil()) + Eventually(IntegrationCondition(ns, "cron", camelv1.IntegrationConditionReady), TestTimeoutMedium).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "cron"), TestTimeoutMedium).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) @@ -47,6 +49,7 @@ func TestRunCronExample(t *testing.T) { Expect(Kamel("run", "-n", ns, "files/cron-timer.groovy").Execute()).Should(BeNil()) Eventually(IntegrationCronJob(ns, "cron-timer"), TestTimeoutMedium).ShouldNot(BeNil()) + Eventually(IntegrationCondition(ns, "cron-timer", camelv1.IntegrationConditionReady), TestTimeoutMedium).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "cron-timer"), TestTimeoutMedium).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) @@ -56,6 +59,7 @@ func TestRunCronExample(t *testing.T) { Expect(Kamel("run", "-n", ns, "files/cron-fallback.groovy").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "cron-fallback"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "cron-fallback", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "cron-fallback"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) diff --git a/e2e/common/run_test.go b/e2e/common/run_test.go index 96fd9a3d9b..391927d581 100644 --- a/e2e/common/run_test.go +++ b/e2e/common/run_test.go @@ -22,19 +22,15 @@ limitations under the License. package common import ( - "os" "testing" . "github.com/apache/camel-k/e2e/support" + camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" ) func TestRunSimpleExamples(t *testing.T) { - if os.Getenv("KAMEL_INSTALL_BUILD_PUBLISH_STRATEGY") == "Buildah" { - t.Skip("Apparently this test require too much CI resources to be run with Buildah, let's save some...") - return - } WithNewTestNamespace(t, func(ns string) { Expect(Kamel("install", "-n", ns).Execute()).Should(BeNil()) @@ -43,6 +39,7 @@ func TestRunSimpleExamples(t *testing.T) { RegisterTestingT(t) Expect(Kamel("run", "-n", ns, "files/Java.java").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "java"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "java", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "java"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) @@ -51,6 +48,7 @@ func TestRunSimpleExamples(t *testing.T) { RegisterTestingT(t) Expect(Kamel("run", "-n", ns, "files/Prop.java", "--property-file", "files/prop.properties").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "prop"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "prop", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "prop"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) @@ -59,6 +57,7 @@ func TestRunSimpleExamples(t *testing.T) { RegisterTestingT(t) Expect(Kamel("run", "-n", ns, "files/xml.xml").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "xml"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "xml", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "xml"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) @@ -67,6 +66,7 @@ func TestRunSimpleExamples(t *testing.T) { RegisterTestingT(t) Expect(Kamel("run", "-n", ns, "files/groovy.groovy").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "groovy"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "groovy", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "groovy"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) @@ -75,6 +75,7 @@ func TestRunSimpleExamples(t *testing.T) { RegisterTestingT(t) Expect(Kamel("run", "-n", ns, "files/js.js").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "js"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "js", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "js"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) @@ -83,6 +84,7 @@ func TestRunSimpleExamples(t *testing.T) { RegisterTestingT(t) Expect(Kamel("run", "-n", ns, "files/kotlin.kts").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "kotlin"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "kotlin", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "kotlin"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) @@ -91,6 +93,7 @@ func TestRunSimpleExamples(t *testing.T) { RegisterTestingT(t) Expect(Kamel("run", "-n", ns, "files/yaml.yaml").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "yaml"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "yaml", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "yaml"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) @@ -99,6 +102,7 @@ func TestRunSimpleExamples(t *testing.T) { RegisterTestingT(t) Expect(Kamel("run", "-n", ns, "--name", "yaml-quarkus", "files/yaml.yaml", "-t", "quarkus.enabled=true").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "yaml-quarkus"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "yaml-quarkus", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "yaml-quarkus"), TestTimeoutShort).Should(ContainSubstring("powered by Quarkus")) Eventually(IntegrationLogs(ns, "yaml-quarkus"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) @@ -108,6 +112,7 @@ func TestRunSimpleExamples(t *testing.T) { RegisterTestingT(t) Expect(Kamel("run", "-n", ns, "--name", "polyglot", "files/js-polyglot.js", "files/yaml-polyglot.yaml").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "polyglot"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "polyglot", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Eventually(IntegrationLogs(ns, "polyglot"), TestTimeoutShort).Should(ContainSubstring("Magicpolyglot-yaml")) Eventually(IntegrationLogs(ns, "polyglot"), TestTimeoutShort).Should(ContainSubstring("Magicpolyglot-js")) Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) diff --git a/e2e/knative/knative_test.go b/e2e/knative/knative_test.go index 20a996df20..916476c0da 100644 --- a/e2e/knative/knative_test.go +++ b/e2e/knative/knative_test.go @@ -26,6 +26,7 @@ import ( "time" . "github.com/apache/camel-k/e2e/support" + camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" ) @@ -36,10 +37,13 @@ func TestRunServiceCombo(t *testing.T) { Expect(Kamel("install", "-n", ns, "--trait-profile", "knative").Execute()).Should(BeNil()) Expect(Kamel("run", "-n", ns, "files/knative2.groovy").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "knative2"), TestTimeoutLong).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "knative2", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Expect(Kamel("run", "-n", ns, "files/knative3.groovy").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "knative3"), TestTimeoutLong).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "knative3", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) Expect(Kamel("run", "-n", ns, "files/knative1.groovy").Execute()).Should(BeNil()) Eventually(IntegrationPodPhase(ns, "knative1"), TestTimeoutLong).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "knative1", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) // Correct logs Eventually(IntegrationLogs(ns, "knative1"), TestTimeoutMedium).Should(ContainSubstring("Received from 2: Hello from knative2")) Eventually(IntegrationLogs(ns, "knative1"), TestTimeoutMedium).Should(ContainSubstring("Received from 3: Hello from knative3")) diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go index 3a21b74e7c..7395f88e9d 100644 --- a/e2e/support/test_support.go +++ b/e2e/support/test_support.go @@ -269,6 +269,20 @@ func IntegrationPod(ns string, name string) func() *corev1.Pod { } } +func IntegrationCondition(ns string, name string, conditionType v1.IntegrationConditionType) func() corev1.ConditionStatus { + return func() corev1.ConditionStatus { + it := Integration(ns, name)() + if it == nil { + return "IntegrationMissing" + } + c := it.Status.GetCondition(conditionType) + if c == nil { + return "ConditionMissing" + } + return c.Status + } +} + func ConfigMap(ns string, name string) func() *corev1.ConfigMap { return func() *corev1.ConfigMap { cm := corev1.ConfigMap{} diff --git a/pkg/apis/camel/v1/integration_types.go b/pkg/apis/camel/v1/integration_types.go index 91491cd870..52cc07395e 100644 --- a/pkg/apis/camel/v1/integration_types.go +++ b/pkg/apis/camel/v1/integration_types.go @@ -194,6 +194,8 @@ const ( IntegrationConditionJolokiaAvailable IntegrationConditionType = "JolokiaAvailable" // IntegrationConditionProbesAvailable -- IntegrationConditionProbesAvailable IntegrationConditionType = "ProbesAvailable" + // IntegrationConditionReady -- + IntegrationConditionReady IntegrationConditionType = "Ready" // IntegrationConditionKitAvailableReason -- IntegrationConditionKitAvailableReason string = "IntegrationKitAvailable" @@ -231,6 +233,14 @@ const ( IntegrationConditionJolokiaAvailableReason string = "JolokiaAvailable" // IntegrationConditionProbesAvailableReason -- IntegrationConditionProbesAvailableReason string = "ProbesAvailable" + // IntegrationConditionErrorReason -- + IntegrationConditionErrorReason string = "Error" + // IntegrationConditionCronJobCreatedReason -- + IntegrationConditionCronJobCreatedReason string = "CronJobCreated" + // IntegrationConditionReplicaSetReadyReason -- + IntegrationConditionReplicaSetReadyReason string = "ReplicaSetReady" + // IntegrationConditionReplicaSetNotReadyReason -- + IntegrationConditionReplicaSetNotReadyReason string = "ReplicaSetNotReady" ) // IntegrationCondition describes the state of a resource at a certain point. diff --git a/pkg/controller/integration/integration_controller.go b/pkg/controller/integration/integration_controller.go index 1532c45da6..b23c020676 100644 --- a/pkg/controller/integration/integration_controller.go +++ b/pkg/controller/integration/integration_controller.go @@ -22,11 +22,11 @@ import ( camelevent "github.com/apache/camel-k/pkg/event" appsv1 "k8s.io/api/apps/v1" + "k8s.io/api/batch/v1beta1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" - k8sclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" @@ -193,13 +193,24 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { newReplicaSet := e.ObjectNew.(*appsv1.ReplicaSet) // Ignore updates to the ReplicaSet other than the replicas ones, // that are used to reconcile the integration replicas. - return oldReplicaSet.Status.Replicas != newReplicaSet.Status.Replicas + return oldReplicaSet.Status.Replicas != newReplicaSet.Status.Replicas || + oldReplicaSet.Status.ReadyReplicas != newReplicaSet.Status.ReadyReplicas || + oldReplicaSet.Status.AvailableReplicas != newReplicaSet.Status.AvailableReplicas }, }) if err != nil { return err } + // Watch cronjob to update the ready condition + err = c.Watch(&source.Kind{Type: &v1beta1.CronJob{}}, &handler.EnqueueRequestForOwner{ + OwnerType: &v1.Integration{}, + IsController: false, + }) + if err != nil { + return err + } + return nil } diff --git a/pkg/event/manager.go b/pkg/event/manager.go index c773c23bb2..f446b9a449 100644 --- a/pkg/event/manager.go +++ b/pkg/event/manager.go @@ -199,15 +199,11 @@ func notifyIfPhaseUpdated(ctx context.Context, c client.Client, recorder record. func notifyIfConditionUpdated(recorder record.EventRecorder, new runtime.Object, oldConditions, newConditions []v1.ResourceCondition, resourceType, name, reason string) { // Update information about changes in conditions for _, cond := range getCommonChangedConditions(oldConditions, newConditions) { - head := "" - if cond.GetStatus() == corev1.ConditionFalse { - head = "No " - } tail := "" if cond.GetMessage() != "" { tail = fmt.Sprintf(": %s", cond.GetMessage()) } - recorder.Eventf(new, corev1.EventTypeNormal, reason, "%s%s for %s %s%s", head, cond.GetType(), resourceType, name, tail) + recorder.Eventf(new, corev1.EventTypeNormal, reason, "Condition %q is %q for %s %s%s", cond.GetType(), cond.GetStatus(), resourceType, name, tail) } } diff --git a/pkg/trait/deployer.go b/pkg/trait/deployer.go index 836811055c..da55a7e1b4 100644 --- a/pkg/trait/deployer.go +++ b/pkg/trait/deployer.go @@ -110,6 +110,12 @@ func (t *deployerTrait) Apply(e *Environment) error { } return nil }) + + // Mirror ready condition from the sub resource to the integration + e.PostActions = append(e.PostActions, func(e *Environment) error { + kubernetes.MirrorReadyCondition(t.Ctx, t.Client, e.Integration) + return nil + }) } return nil diff --git a/pkg/trait/deployer_test.go b/pkg/trait/deployer_test.go index d5ffb97354..7160f55d45 100644 --- a/pkg/trait/deployer_test.go +++ b/pkg/trait/deployer_test.go @@ -52,7 +52,7 @@ func TestApplyDeployerTraitDoesSucceed(t *testing.T) { err := deployerTrait.Apply(environment) assert.Nil(t, err) - assert.Len(t, environment.PostActions, 1) + assert.Len(t, environment.PostActions, 2) } func TestApplyDeployerTraitInInitializationPhaseDoesSucceed(t *testing.T) { diff --git a/pkg/util/kubernetes/conditions.go b/pkg/util/kubernetes/conditions.go new file mode 100644 index 0000000000..85c3e9b088 --- /dev/null +++ b/pkg/util/kubernetes/conditions.go @@ -0,0 +1,109 @@ +package kubernetes + +import ( + "context" + "errors" + "fmt" + "strconv" + + v1 "github.com/apache/camel-k/pkg/apis/camel/v1" + "github.com/apache/camel-k/pkg/client" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/api/batch/v1beta1" + corev1 "k8s.io/api/core/v1" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +// nolint: gocritic +func MirrorReadyCondition(ctx context.Context, c client.Client, it *v1.Integration) { + if isConditionTrue(it, v1.IntegrationConditionDeploymentAvailable) || isConditionTrue(it, v1.IntegrationConditionKnativeServiceAvailable) { + mirrorReadyConditionFromReplicaSet(ctx, c, it) + } else if isConditionTrue(it, v1.IntegrationConditionCronJobAvailable) { + mirrorReadyConditionFromCronJob(ctx, c, it) + } else { + it.Status.SetCondition( + v1.IntegrationConditionReady, + corev1.ConditionUnknown, + "", + "", + ) + } +} + +func mirrorReadyConditionFromReplicaSet(ctx context.Context, c client.Client, it *v1.Integration) { + list := appsv1.ReplicaSetList{} + opts := runtimeclient.MatchingLabels{ + "camel.apache.org/integration": it.Name, + } + if err := c.List(ctx, &list, opts, runtimeclient.InNamespace(it.Namespace)); err != nil { + setReadyConditionError(it, err) + return + } + + if len(list.Items) == 0 { + setReadyConditionError(it, errors.New("replicaset not found")) + return + } + + var rs *appsv1.ReplicaSet + for _, r := range list.Items { + r := r + if r.Labels["camel.apache.org/generation"] == strconv.FormatInt(it.Generation, 10) { + rs = &r + } + } + if rs == nil { + rs = &list.Items[0] + } + var replicas int32 = 1 + if rs.Spec.Replicas != nil { + replicas = *rs.Spec.Replicas + } + if replicas == rs.Status.ReadyReplicas { + it.Status.SetCondition( + v1.IntegrationConditionReady, + corev1.ConditionTrue, + v1.IntegrationConditionReplicaSetReadyReason, + "", + ) + } else { + it.Status.SetCondition( + v1.IntegrationConditionReady, + corev1.ConditionTrue, + v1.IntegrationConditionReplicaSetReadyReason, + "", + ) + } +} + +func mirrorReadyConditionFromCronJob(ctx context.Context, c client.Client, it *v1.Integration) { + cronJob := v1beta1.CronJob{} + if err := c.Get(ctx, runtimeclient.ObjectKey{Namespace: it.Namespace, Name: it.Name}, &cronJob); err != nil { + setReadyConditionError(it, err) + } else { + // CronJob status is not tracked by Kubernetes + it.Status.SetCondition( + v1.IntegrationConditionReady, + corev1.ConditionTrue, + v1.IntegrationConditionCronJobCreatedReason, + "", + ) + } +} + +func isConditionTrue(it *v1.Integration, conditionType v1.IntegrationConditionType) bool { + cond := it.Status.GetCondition(conditionType) + if cond == nil { + return false + } + return cond.Status == corev1.ConditionTrue +} + +func setReadyConditionError(it *v1.Integration, err error) { + it.Status.SetCondition( + v1.IntegrationConditionReady, + corev1.ConditionUnknown, + v1.IntegrationConditionErrorReason, + fmt.Sprintf("%v", err), + ) +}