This is an automated email from the ASF dual-hosted git repository.
astefanutti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git
The following commit(s) were added to refs/heads/main by this push:
new 5dd46e4 fix(integration) Integration not marked as Failed when Camel
is unable to start
5dd46e4 is described below
commit 5dd46e42fdec82e7aa84bfbc335aea7790e224df
Author: Claudio Miranda <[email protected]>
AuthorDate: Thu May 13 16:39:58 2021 -0300
fix(integration) Integration not marked as Failed when Camel is unable to
start
https://github.com/apache/camel-k/issues/2291
* Added unversioned maven directories to .gitignore
---
.gitignore | 2 ++
e2e/common/files/BadRoute.java | 28 ++++++++++++++++++++++
e2e/common/integration_fail_test.go | 45 +++++++++++++++++++++++++++++++++++
pkg/controller/integration/error.go | 36 ++++++++++++++++++++++++++++
pkg/controller/integration/monitor.go | 34 ++++++++++++++++++++++++++
pkg/util/kubernetes/client.go | 16 +++++++++++++
pkg/util/kubernetes/conditions.go | 6 ++---
7 files changed, 164 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
index 94ad8cb..26458eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,8 @@ build/_test
build/_maven_output
build/_maven_overlay
build/_kamelets
+build/_maven_overlay/
+build/maven/target/
/api_*
# envrc
diff --git a/e2e/common/files/BadRoute.java b/e2e/common/files/BadRoute.java
new file mode 100644
index 0000000..9967879
--- /dev/null
+++ b/e2e/common/files/BadRoute.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+import java.lang.Exception;
+import java.lang.Override;
+import org.apache.camel.builder.RouteBuilder;
+
+public class BadRoute extends RouteBuilder {
+ @Override
+ public void configure() throws Exception {
+
from("mongodb:sample?database=sampledb&collection=mycollection&operation=findfoo").throwException(new
RuntimeException("Heyyy")).log("bar");
+ }
+}
+
diff --git a/e2e/common/integration_fail_test.go
b/e2e/common/integration_fail_test.go
new file mode 100644
index 0000000..f2b3940
--- /dev/null
+++ b/e2e/common/integration_fail_test.go
@@ -0,0 +1,45 @@
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go ->
Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+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 common
+
+import (
+ "testing"
+
+ . "github.com/onsi/gomega"
+
+ . "github.com/apache/camel-k/e2e/support"
+ camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+ v1 "k8s.io/api/core/v1"
+)
+
+func TestBadRouteIntegration(t *testing.T) {
+ WithNewTestNamespace(t, func(ns string) {
+ Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
+ t.Run("run bad java route", func(t *testing.T) {
+ Expect(Kamel("run", "-n", ns,
"files/BadRoute.java").Execute()).To(Succeed())
+ Eventually(IntegrationPodPhase(ns, "bad-route"),
TestTimeoutMedium).Should(Equal(v1.PodRunning))
+ Eventually(IntegrationPhase(ns, "bad-route"),
TestTimeoutShort).Should(Equal(camelv1.IntegrationPhaseError))
+ Eventually(IntegrationCondition(ns, "bad-route",
camelv1.IntegrationConditionReady),
TestTimeoutShort).Should(Equal(v1.ConditionFalse))
+ Expect(Kamel("delete", "--all", "-n",
ns).Execute()).To(Succeed())
+ })
+ })
+}
diff --git a/pkg/controller/integration/error.go
b/pkg/controller/integration/error.go
index 5f19458..a7f490c 100644
--- a/pkg/controller/integration/error.go
+++ b/pkg/controller/integration/error.go
@@ -20,8 +20,13 @@ package integration
import (
"context"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+
v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
"github.com/apache/camel-k/pkg/util/digest"
+ "github.com/apache/camel-k/pkg/util/kubernetes"
+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
)
// NewErrorAction creates a new error action for an integration
@@ -56,6 +61,37 @@ func (action *errorAction) Handle(ctx context.Context,
integration *v1.Integrati
return integration, nil
}
+ if kubernetes.IsConditionTrue(integration,
v1.IntegrationConditionDeploymentAvailable) {
+ deployment, err := kubernetes.GetDeployment(ctx, action.client,
integration.Name, integration.Namespace)
+ if err != nil && k8serrors.IsNotFound(err) {
+ return nil, err
+ }
+
+ // if the integration is in error phase, check if the
corresponding pod is running ok, the user may have updated the integration.
+ deployAvailable := false
+ progressingOk := false
+ for _, c := range deployment.Status.Conditions {
+ // first, check if the container is in available state
+ if c.Type == appsv1.DeploymentAvailable {
+ deployAvailable = c.Status ==
corev1.ConditionTrue
+ }
+ // second, check the progressing and the reasons
+ if c.Type == appsv1.DeploymentProgressing {
+ progressingOk = c.Status ==
corev1.ConditionTrue && (c.Reason == "NewReplicaSetAvailable" || c.Reason ==
"ReplicaSetUpdated")
+ }
+ }
+ if deployAvailable && progressingOk {
+ availableCondition := v1.IntegrationCondition{
+ Type: v1.IntegrationConditionReady,
+ Status: corev1.ConditionTrue,
+ Reason:
v1.IntegrationConditionReplicaSetReadyReason,
+ }
+ integration.Status.SetConditions(availableCondition)
+ integration.Status.Phase = v1.IntegrationPhaseRunning
+ return integration, nil
+ }
+ }
+
// TODO check also if deployment matches (e.g. replicas)
return nil, nil
}
diff --git a/pkg/controller/integration/monitor.go
b/pkg/controller/integration/monitor.go
index c92e9e5..515d2d4 100644
--- a/pkg/controller/integration/monitor.go
+++ b/pkg/controller/integration/monitor.go
@@ -108,6 +108,40 @@ func (action *monitorAction) Handle(ctx context.Context,
integration *v1.Integra
timeToFirstReadiness.Observe(duration.Seconds())
}
+ // the integration pod may be in running phase, but the corresponding
container running the integration code
+ // may be in error state, in this case we should check the deployment
status and set the integration status accordingly.
+ if kubernetes.IsConditionTrue(integration,
v1.IntegrationConditionDeploymentAvailable) {
+ deployment, err := kubernetes.GetDeployment(ctx, action.client,
integration.Name, integration.Namespace)
+ if err != nil {
+ return nil, err
+ }
+
+ deployUnavailable := false
+ progressingFailing := false
+ for _, c := range deployment.Status.Conditions {
+ // first, check if the container status is not available
+ if c.Type == appsv1.DeploymentAvailable {
+ deployUnavailable = c.Status ==
corev1.ConditionFalse
+ }
+ // second, check when it is progressing and reason is
the replicas are available but the number of replicas are zero
+ // in this case, the container integration is failing
+ if c.Type == appsv1.DeploymentProgressing {
+ progressingFailing = c.Status ==
corev1.ConditionTrue && c.Reason == "NewReplicaSetAvailable" &&
deployment.Status.AvailableReplicas < 1
+ }
+ }
+ if deployUnavailable && progressingFailing {
+ notAvailableCondition := v1.IntegrationCondition{
+ Type: v1.IntegrationConditionReady,
+ Status: corev1.ConditionFalse,
+ Reason: v1.IntegrationConditionErrorReason,
+ Message: "The corresponding pod(s) may be in
error state, look at the pod status or log for errors",
+ }
+ integration.Status.SetConditions(notAvailableCondition)
+ integration.Status.Phase = v1.IntegrationPhaseError
+ return integration, nil
+ }
+ }
+
return integration, nil
}
diff --git a/pkg/util/kubernetes/client.go b/pkg/util/kubernetes/client.go
index ceecef5..1f8795b 100644
--- a/pkg/util/kubernetes/client.go
+++ b/pkg/util/kubernetes/client.go
@@ -21,6 +21,7 @@ import (
"context"
"fmt"
+ appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -174,3 +175,18 @@ func ResolveValueSource(ctx context.Context, client
ctrl.Reader, namespace strin
return "", nil
}
+
+// GetDeployment --
+func GetDeployment(context context.Context, client ctrl.Reader, name string,
namespace string) (*appsv1.Deployment, error) {
+
+ key := ctrl.ObjectKey{
+ Name: name,
+ Namespace: namespace,
+ }
+ deployment := appsv1.Deployment{}
+ if err := client.Get(context, key, &deployment); err != nil {
+ return nil, err
+ }
+
+ return &deployment, nil
+}
diff --git a/pkg/util/kubernetes/conditions.go
b/pkg/util/kubernetes/conditions.go
index 1c83fca..569bf43 100644
--- a/pkg/util/kubernetes/conditions.go
+++ b/pkg/util/kubernetes/conditions.go
@@ -33,9 +33,9 @@ import (
// nolint: gocritic
func MirrorReadyCondition(ctx context.Context, c client.Client, it
*v1.Integration) {
- if isConditionTrue(it, v1.IntegrationConditionDeploymentAvailable) ||
isConditionTrue(it, v1.IntegrationConditionKnativeServiceAvailable) {
+ if IsConditionTrue(it, v1.IntegrationConditionDeploymentAvailable) ||
IsConditionTrue(it, v1.IntegrationConditionKnativeServiceAvailable) {
mirrorReadyConditionFromReplicaSet(ctx, c, it)
- } else if isConditionTrue(it, v1.IntegrationConditionCronJobAvailable) {
+ } else if IsConditionTrue(it, v1.IntegrationConditionCronJobAvailable) {
mirrorReadyConditionFromCronJob(ctx, c, it)
} else {
it.Status.SetCondition(
@@ -112,7 +112,7 @@ func mirrorReadyConditionFromCronJob(ctx context.Context, c
client.Client, it *v
}
}
-func isConditionTrue(it *v1.Integration, conditionType
v1.IntegrationConditionType) bool {
+func IsConditionTrue(it *v1.Integration, conditionType
v1.IntegrationConditionType) bool {
cond := it.Status.GetCondition(conditionType)
if cond == nil {
return false