astefanutti commented on code in PR #3480:
URL: https://github.com/apache/camel-k/pull/3480#discussion_r936453260


##########
e2e/global/common/kamelet_binding_with_image_test.go:
##########
@@ -0,0 +1,120 @@
+//go:build integration
+// +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 (
+       "github.com/onsi/gomega/gstruct"
+       "testing"
+
+       . "github.com/apache/camel-k/e2e/support"
+       . "github.com/onsi/gomega"
+
+       corev1 "k8s.io/api/core/v1"
+
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+)
+
+func TestBindingWithImage(t *testing.T) {
+       WithNewTestNamespace(t, func(ns string) {
+               operatorID := "camel-k-binding-image"
+               bindingID := "with-image-binding"
+
+               Expect(KamelInstallWithID(operatorID, 
ns).Execute()).To(Succeed())
+
+               from := corev1.ObjectReference{
+                       Kind:       "Kamelet",
+                       Name:       "my-own-timer-source",
+                       APIVersion: v1alpha1.SchemeGroupVersion.String(),
+               }
+
+               to := corev1.ObjectReference{
+                       Kind:       "Kamelet",
+                       Name:       "my-own-log-sink",
+                       APIVersion: v1alpha1.SchemeGroupVersion.String(),
+               }
+
+               emptyMap := map[string]string{}
+
+               annotations1 := map[string]string{
+                       "trait.camel.apache.org/container.image":      
"docker.io/jmalloc/echo-server:0.3.2",
+                       "trait.camel.apache.org/jvm.enabled":          "false",
+                       "trait.camel.apache.org/kamelets.enabled":     "false",
+                       "trait.camel.apache.org/dependencies.enabled": "false",
+                       "test": "1",
+               }
+               annotations2 := map[string]string{
+                       "trait.camel.apache.org/container.image":      
"docker.io/jmalloc/echo-server:0.3.3",
+                       "trait.camel.apache.org/jvm.enabled":          "false",
+                       "trait.camel.apache.org/kamelets.enabled":     "false",
+                       "trait.camel.apache.org/dependencies.enabled": "false",
+                       "test": "2",
+               }
+
+               t.Run("run with initial image", func(t *testing.T) {
+                       expectedImage := 
annotations1["trait.camel.apache.org/container.image"]
+
+                       RegisterTestingT(t)
+
+                       Expect(BindKameletTo(ns, bindingID, annotations1, from, 
to, emptyMap, emptyMap)()).
+                               To(Succeed())
+                       Eventually(IntegrationGeneration(ns, bindingID)).
+                               Should(gstruct.PointTo(BeNumerically("==", 1)))
+                       Eventually(IntegrationAnnotations(ns, bindingID)).
+                               Should(HaveKeyWithValue("test", "1"))
+                       Eventually(IntegrationAnnotations(ns, bindingID)).
+                               
Should(HaveKeyWithValue("trait.camel.apache.org/container.image", 
expectedImage))
+                       Eventually(IntegrationStatusImage(ns, bindingID)).
+                               Should(Equal(expectedImage))
+
+                       Eventually(IntegrationPodPhase(ns, bindingID), 
TestTimeoutLong).
+                               Should(Equal(corev1.PodRunning))
+                       Eventually(IntegrationPodImage(ns, bindingID)).
+                               Should(Equal(expectedImage))
+               })
+
+               t.Run("run with new image", func(t *testing.T) {
+                       expectedImage := 
annotations2["trait.camel.apache.org/container.image"]
+
+                       RegisterTestingT(t)
+
+                       Expect(BindKameletTo(ns, bindingID, annotations2, from, 
to, emptyMap, emptyMap)()).
+                               To(Succeed())
+                       Eventually(IntegrationGeneration(ns, bindingID)).
+                               Should(gstruct.PointTo(BeNumerically("==", 1)))
+                       Eventually(IntegrationAnnotations(ns, bindingID)).

Review Comment:
   I'd suggest to create a _transform_ function, e.g.:
   
   ```go
   func Annotations(object metav1.Object) map[string]string {
        return object.GetAnnotations()
   }
   ```
   
   So it can be re-used for any object, and that can be used as:
   
   ```go
   Eventually(Integration(ns, bindingID)).Should(WithTransform(Annotations, 
HaveKeyWithValue("test", "2")))
   ```
   
   That would ultimately give:
   
   ```go
   Eventually(Integration(ns, bindingID)).Should(WithTransform(Annotations, And(
     HaveKeyWithValue("test", "2"))),
     HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage)),
   )
   ```



##########
pkg/controller/integration/integration_controller.go:
##########
@@ -75,6 +77,115 @@ func newReconciler(mgr manager.Manager, c client.Client) 
reconcile.Reconciler {
        )
 }
 
+func integrationUpdateFunc(old *v1.Integration, it *v1.Integration) bool {
+       // Observe the time to first readiness metric
+       previous := old.Status.GetCondition(v1.IntegrationConditionReady)
+       if next := it.Status.GetCondition(v1.IntegrationConditionReady); 
(previous == nil || previous.Status != corev1.ConditionTrue && 
(previous.FirstTruthyTime == nil || previous.FirstTruthyTime.IsZero())) &&
+               next != nil && next.Status == corev1.ConditionTrue && 
next.FirstTruthyTime != nil && !next.FirstTruthyTime.IsZero() &&
+               it.Status.InitializationTimestamp != nil {
+               duration := 
next.FirstTruthyTime.Time.Sub(it.Status.InitializationTimestamp.Time)
+               Log.WithValues("request-namespace", it.Namespace, 
"request-name", it.Name, "ready-after", duration.Seconds()).
+                       ForIntegration(it).Infof("First readiness after %s", 
duration)
+               timeToFirstReadiness.Observe(duration.Seconds())
+       }
+
+       // If traits have changed, the reconciliation loop must kick in as
+       // traits may have impact
+       sameTraits, err := trait.IntegrationsHaveSameTraits(old, it)
+       if err != nil {
+               Log.ForIntegration(it).Error(
+                       err,
+                       "unable to determine if old and new resource have the 
same traits")
+       }
+       if !sameTraits {
+               return true
+       }
+
+       // Ignore updates to the integration status in which case 
metadata.Generation does not change,
+       // or except when the integration phase changes as it's used to 
transition from one phase
+       // to another.
+       return old.Generation != it.Generation ||
+               old.Status.Phase != it.Status.Phase
+}
+
+func integrationKitEnqueueRequestsFromMapFunc(c client.Client, kit 
*v1.IntegrationKit) []reconcile.Request {
+       var requests []reconcile.Request
+       if kit.Status.Phase != v1.IntegrationKitPhaseReady && kit.Status.Phase 
!= v1.IntegrationKitPhaseError {
+               return requests
+       }
+
+       list := &v1.IntegrationList{}
+       // Do global search in case of global operator (it may be using a 
global platform)
+       var opts []ctrl.ListOption
+       if !platform.IsCurrentOperatorGlobal() {
+               opts = append(opts, ctrl.InNamespace(kit.Namespace))
+       }
+       if err := c.List(context.Background(), list, opts...); err != nil {
+               log.Error(err, "Failed to retrieve integration list")
+               return requests
+       }
+
+       for i := range list.Items {
+               integration := &list.Items[i]
+               log.Debug("Integration Controller: Assessing integration", 
"integration", integration.Name, "namespace", integration.Namespace)

Review Comment:
   Probably not introduced by this PR, but seems better to re-used the scoped 
logger, e.g. `Log.Debug("Filtering integration from kit changes", ...)`.



##########
pkg/controller/integration/integration_controller.go:
##########
@@ -75,6 +77,115 @@ func newReconciler(mgr manager.Manager, c client.Client) 
reconcile.Reconciler {
        )
 }
 
+func integrationUpdateFunc(old *v1.Integration, it *v1.Integration) bool {
+       // Observe the time to first readiness metric
+       previous := old.Status.GetCondition(v1.IntegrationConditionReady)
+       if next := it.Status.GetCondition(v1.IntegrationConditionReady); 
(previous == nil || previous.Status != corev1.ConditionTrue && 
(previous.FirstTruthyTime == nil || previous.FirstTruthyTime.IsZero())) &&
+               next != nil && next.Status == corev1.ConditionTrue && 
next.FirstTruthyTime != nil && !next.FirstTruthyTime.IsZero() &&
+               it.Status.InitializationTimestamp != nil {
+               duration := 
next.FirstTruthyTime.Time.Sub(it.Status.InitializationTimestamp.Time)
+               Log.WithValues("request-namespace", it.Namespace, 
"request-name", it.Name, "ready-after", duration.Seconds()).
+                       ForIntegration(it).Infof("First readiness after %s", 
duration)
+               timeToFirstReadiness.Observe(duration.Seconds())
+       }
+
+       // If traits have changed, the reconciliation loop must kick in as
+       // traits may have impact
+       sameTraits, err := trait.IntegrationsHaveSameTraits(old, it)
+       if err != nil {
+               Log.ForIntegration(it).Error(
+                       err,
+                       "unable to determine if old and new resource have the 
same traits")
+       }
+       if !sameTraits {
+               return true
+       }
+
+       // Ignore updates to the integration status in which case 
metadata.Generation does not change,
+       // or except when the integration phase changes as it's used to 
transition from one phase
+       // to another.
+       return old.Generation != it.Generation ||
+               old.Status.Phase != it.Status.Phase
+}
+
+func integrationKitEnqueueRequestsFromMapFunc(c client.Client, kit 
*v1.IntegrationKit) []reconcile.Request {
+       var requests []reconcile.Request
+       if kit.Status.Phase != v1.IntegrationKitPhaseReady && kit.Status.Phase 
!= v1.IntegrationKitPhaseError {
+               return requests
+       }
+
+       list := &v1.IntegrationList{}
+       // Do global search in case of global operator (it may be using a 
global platform)
+       var opts []ctrl.ListOption
+       if !platform.IsCurrentOperatorGlobal() {
+               opts = append(opts, ctrl.InNamespace(kit.Namespace))
+       }
+       if err := c.List(context.Background(), list, opts...); err != nil {
+               log.Error(err, "Failed to retrieve integration list")
+               return requests
+       }
+
+       for i := range list.Items {
+               integration := &list.Items[i]
+               log.Debug("Integration Controller: Assessing integration", 
"integration", integration.Name, "namespace", integration.Namespace)
+
+               match, err := sameOrMatch(kit, integration)
+               if err != nil {
+                       log.Errorf(err, "Error matching integration %q with kit 
%q", integration.Name, kit.Name)

Review Comment:
   Maybe use `Log.ForIntegration(it).Error(...` for consistency.



##########
pkg/controller/kameletbinding/monitor.go:
##########
@@ -76,14 +77,25 @@ func (action *monitorAction) Handle(ctx context.Context, 
kameletbinding *v1alpha
        operatorIDChanged := v1.GetOperatorIDAnnotation(kameletbinding) != "" &&
                (v1.GetOperatorIDAnnotation(kameletbinding) != 
v1.GetOperatorIDAnnotation(&it))
 
+       sameTraits, err := trait.IntegrationAndBindingSameTraits(&it, 
kameletbinding)
+       if err != nil {
+               return nil, err
+       }
+
        // Check if the integration needs to be changed
        expected, err := CreateIntegrationFor(ctx, action.client, 
kameletbinding)
        if err != nil {
                return nil, err
        }
 
-       if !equality.Semantic.DeepDerivative(expected.Spec, it.Spec) || 
operatorIDChanged {
-               action.L.Info("Monitor: KameletBinding needs a rebuild")
+       semanticEquality := equality.Semantic.DeepDerivative(expected.Spec, 
it.Spec)
+
+       if !semanticEquality || operatorIDChanged || !sameTraits {
+               action.L.Info(
+                       "Monitor: KameletBinding needs a rebuild",

Review Comment:
   `Monitor: KameletBinding needs a rebuild` -> `KameletBinding needs a rebuild`



##########
pkg/controller/kameletbinding/monitor.go:
##########
@@ -76,14 +77,25 @@ func (action *monitorAction) Handle(ctx context.Context, 
kameletbinding *v1alpha
        operatorIDChanged := v1.GetOperatorIDAnnotation(kameletbinding) != "" &&
                (v1.GetOperatorIDAnnotation(kameletbinding) != 
v1.GetOperatorIDAnnotation(&it))
 
+       sameTraits, err := trait.IntegrationAndBindingSameTraits(&it, 
kameletbinding)
+       if err != nil {
+               return nil, err
+       }
+
        // Check if the integration needs to be changed
        expected, err := CreateIntegrationFor(ctx, action.client, 
kameletbinding)
        if err != nil {
                return nil, err
        }
 
-       if !equality.Semantic.DeepDerivative(expected.Spec, it.Spec) || 
operatorIDChanged {
-               action.L.Info("Monitor: KameletBinding needs a rebuild")
+       semanticEquality := equality.Semantic.DeepDerivative(expected.Spec, 
it.Spec)
+
+       if !semanticEquality || operatorIDChanged || !sameTraits {
+               action.L.Info(
+                       "Monitor: KameletBinding needs a rebuild",
+                       "semantic-equality", !semanticEquality,
+                       "operatorid-changed", operatorIDChanged,
+                       "traites-changed", !sameTraits)

Review Comment:
   `traites-changed` -> `traits-changed`



##########
e2e/support/test_support.go:
##########
@@ -545,6 +545,26 @@ func IntegrationSpecReplicas(ns string, name string) 
func() *int32 {
        }
 }
 
+func IntegrationGeneration(ns string, name string) func() *int64 {
+       return func() *int64 {
+               it := Integration(ns, name)()
+               if it == nil {
+                       return nil
+               }
+               return &it.Generation
+       }
+}
+
+func IntegrationStatusObserverGeneration(ns string, name string) func() *int64 
{

Review Comment:
   nit: `IntegrationStatusObserverGeneration` -> 
`IntegrationStatusObservedGeneration` or even `IntegrationObservedGeneration` 
as status can be made implicit.



##########
pkg/trait/util.go:
##########
@@ -29,17 +29,36 @@ import (
        user "github.com/mitchellh/go-homedir"
        "github.com/pkg/errors"
        "github.com/scylladb/go-set/strset"
-
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
        v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
        "github.com/apache/camel-k/pkg/client"
        "github.com/apache/camel-k/pkg/metadata"
        "github.com/apache/camel-k/pkg/util"
        "github.com/apache/camel-k/pkg/util/camel"
        "github.com/apache/camel-k/pkg/util/property"
 )
 
+type Unstructured map[string]map[string]interface{}

Review Comment:
   I'd suggest not to used `Unstructured` to avoid confusion with the eponymous 
type from k8s, maybe `Traits` or `TraitOptions`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscr...@camel.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to