Repository: apache/camel-k
Pull request #2198 (2021-04-09 04:33:25)

Add OLM upgrade test
<!-- Description -->




<!--
Enter your extended release note in the below block. If the PR requires
additional action from users switching to the new release, include the string
"action required". If no release note is required, write "NONE". 

You can (optionally) mark this PR with labels "kind/bug" or "kind/feature" to make sure
the text is added to the right section of the release notes. 
-->

**Release Note**
```release-note
NONE
```


bouskaJ: @astefanutti Finally, the PR is there. Please review.
bouskaJ: @astefanutti the CI seems to be faulty. I see a lot of various errors that are not caused by my changes.
astefanutti: @bouskaJ awesome!

It's possible that CI is currently broken as the Camel / Camel Quarkus upgrade is being rolling out and needs #2197 to complete.
nicolaferraro: > @bouskaJ awesome!
> 
> It's possible that CI is currently broken as the Camel / Camel Quarkus upgrade is being rolling out and needs #2197 to complete.

Yes, we may merge https://github.com/apache/camel-k/pull/2197 to have less errors, as the only issue seems in runtime: https://github.com/apache/camel-k-runtime/issues/651. Wdyt?
astefanutti: > > @bouskaJ awesome!
> > It's possible that CI is currently broken as the Camel / Camel Quarkus upgrade is being rolling out and needs #2197 to complete.
> 
> Yes, we may merge #2197 to have less errors, as the only issue seems in runtime: [apache/camel-k-runtime#651](https://github.com/apache/camel-k-runtime/issues/651). Wdyt?

Good idea, +1 for me, then we can rebase the pending PRs.
bouskaJ: @astefanutti can you rerun the CI?
astefanutti: @bouskaJ could you please rebase to have the changes from #2197 taken into account?

Patch:
diff --git a/.github/workflows/upgrade.yml b/.github/workflows/upgrade.yml
index b3b2e2a1f..9f606424c 100644
--- a/.github/workflows/upgrade.yml
+++ b/.github/workflows/upgrade.yml
@@ -64,9 +64,19 @@ jobs:
       with:
         version: "11"
     - name: Set Go
-      uses: actions/setup-go@v1
+      uses: actions/setup-go@v2
       with:
         go-version: 1.13.x
+    - name: Set up operator-sdk
+      run: |
+        curl -L https://github.com/operator-framework/operator-sdk/releases/download/v1.3.0/operator-sdk_linux_amd64 -o operator-sdk
+        chmod +x operator-sdk
+        sudo mv operator-sdk /usr/local/bin/
+    - name: Set up opm tool
+      run: |
+        curl -L https://github.com/operator-framework/operator-registry/releases/download/v1.16.1/linux-amd64-opm -o opm
+        chmod +x opm
+        sudo mv opm /usr/local/bin/
     - name: Kubernetes KinD Cluster
       uses: container-tools/kind-action@v1
       with:
@@ -75,6 +85,12 @@ jobs:
       run: |
         kubectl cluster-info
         kubectl describe nodes
+    - name: Install OLM
+      run: |
+        kubectl apply -f https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.17.0/crds.yaml
+        # wait for a while to be sure CRDs are installed
+        sleep 1
+        kubectl apply -f https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.17.0/olm.yaml
     - name: Get Kamel CLI
       run: |
         export KAMEL_VERSION=$(make get-last-released-version)
@@ -92,12 +108,52 @@ jobs:
         cp ./kamel ./build/_output/bin/
         echo "Building the images"
         export LOCAL_IMAGE=$KIND_REGISTRY/apache/camel-k:$(make get-version)
+        echo "LOCAL_IMAGE=${LOCAL_IMAGE}" >> $GITHUB_ENV
         docker build -t "${LOCAL_IMAGE}" -f build/Dockerfile .
         docker push ${LOCAL_IMAGE}
+    - name: Build Operator bundle
+      run: |
+        echo "Build Operator bundle"
+
+        # reinstall kustomize to be always on the same version
+        sudo rm $(which kustomize)
+
+        make kustomize
+
+        # replace image
+        $(cd config/manifests && kustomize edit set image "docker.io/apache/camel-k=${{ env.LOCAL_IMAGE }}")
+
+
+        # CSV the patch to force OLM upgrade
+        export RELEASE_VERSION=$(make get-version | grep -Po "\d.\d.\d")
+        cat <<EOF > config/manifests/patch.yml
+        apiVersion: operators.coreos.com/v1alpha1
+        kind: ClusterServiceVersion
+        metadata:
+          namespace: placeholder
+          name: camel-k.v$(make get-last-released-version)
+          annotations:
+            olm.skipRange: ">=1.0.0 <${RELEASE_VERSION}"
+        EOF
+
+        echo "Patching CSV with"
+        cat config/manifests/patch.yml
+        $(cd config/manifests && kustomize edit add patch patch.yml)
+
+        export LOCAL_IMAGE_BUNDLE=$KIND_REGISTRY/apache/camel-k-bundle:$(make get-version)
+        echo "LOCAL_IMAGE_BUNDLE=${LOCAL_IMAGE_BUNDLE}" >> $GITHUB_ENV
+        make bundle-build METADATA_IMAGE_NAME=${LOCAL_IMAGE_BUNDLE} BUNDLE_METADATA_OPTS="--channels stable"
+        docker push ${LOCAL_IMAGE_BUNDLE}
+    - name: Create new index image
+      run: |
+        export LOCAL_IIB=$KIND_REGISTRY/apache/camel-k-iib:$(make get-version)
+        echo "LOCAL_IIB=${LOCAL_IIB}" >> $GITHUB_ENV
+        opm index add --bundles ${{ env.LOCAL_IMAGE_BUNDLE }} --from-index quay.io/operatorhubio/catalog:latest --tag ${LOCAL_IIB} --skip-tls
+        podman push --tls-verify=false ${LOCAL_IIB}
     - name: Run IT
       run: |
         # Use the last released Kamel CLI
-        export KAMEL_BIN=/usr/local/bin/kamel
+        export RELEASED_KAMEL_BIN=/usr/local/bin/kamel
 
         # Configure install options
         export KAMEL_INSTALL_BUILD_PUBLISH_STRATEGY=Spectrum
@@ -105,10 +161,9 @@ jobs:
         export KAMEL_INSTALL_REGISTRY=$KIND_REGISTRY
         export KAMEL_INSTALL_REGISTRY_INSECURE=true
 
-        # TODO: Install OLM and cover upgrade managed by OLM
-        export KAMEL_INSTALL_OLM=false
-
         # Configure test options
+        export CAMEL_K_PREV_IIB=quay.io/operatorhubio/catalog:latest
+        export CAMEL_K_NEW_IIB=${{ env.LOCAL_IIB }}
         export KAMEL_K_TEST_RELEASE_VERSION=$(make get-last-released-version)
         export KAMEL_K_TEST_OPERATOR_CURRENT_IMAGE=$KIND_REGISTRY/apache/camel-k:$(make get-version)
 
diff --git a/config/manifests/kustomization.yaml b/config/manifests/kustomization.yaml
index 8697eb828..3dba3bd97 100644
--- a/config/manifests/kustomization.yaml
+++ b/config/manifests/kustomization.yaml
@@ -19,3 +19,4 @@ resources:
 - ../default
 - ../samples
 - ../scorecard
+- ./bases/camel-k.clusterserviceversion.yaml
diff --git a/pkg/cmd/bind.go b/pkg/cmd/bind.go
new file mode 100644
index 000000000..277189984
--- /dev/null
+++ b/pkg/cmd/bind.go
@@ -0,0 +1,357 @@
+/*
+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 cmd
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strings"
+
+	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/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/reference"
+	"github.com/apache/camel-k/pkg/util/uri"
+	"github.com/spf13/cobra"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// newCmdBind --
+func newCmdBind(rootCmdOptions *RootCmdOptions) (*cobra.Command, *bindCmdOptions) {
+	options := bindCmdOptions{
+		RootCmdOptions: rootCmdOptions,
+	}
+	cmd := cobra.Command{
+		Use:     "bind [source] [sink] ...",
+		Short:   "Bind Kubernetes resources, such as Kamelets, in an integration flow. Endpoints are expected in the format \"[[apigroup/]version:]kind:[namespace/]name\" or plain Camel URIs.",
+		PreRunE: decode(&options),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if err := options.validate(cmd, args); err != nil {
+				return err
+			}
+			if err := options.run(args); err != nil {
+				fmt.Println(err.Error())
+			}
+
+			return nil
+		},
+	}
+
+	cmd.Flags().String("name", "", "Name for the binding")
+	cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml")
+	cmd.Flags().StringArrayP("property", "p", nil, `Add a binding property in the form of "source.<key>=<value>", "sink.<key>=<value>" or "step-<n>.<key>=<value>"`)
+	cmd.Flags().Bool("skip-checks", false, "Do not verify the binding for compliance with Kamelets and other Kubernetes resources")
+	cmd.Flags().StringArray("step", nil, `Add binding steps as Kubernetes resources, such as Kamelets. Endpoints are expected in the format "[[apigroup/]version:]kind:[namespace/]name" or plain Camel URIs.`)
+
+	return &cmd, &options
+}
+
+const (
+	sourceKey     = "source"
+	sinkKey       = "sink"
+	stepKeyPrefix = "step-"
+)
+
+type bindCmdOptions struct {
+	*RootCmdOptions
+	Name         string   `mapstructure:"name" yaml:",omitempty"`
+	OutputFormat string   `mapstructure:"output" yaml:",omitempty"`
+	Properties   []string `mapstructure:"properties" yaml:",omitempty"`
+	SkipChecks   bool     `mapstructure:"skip-checks" yaml:",omitempty"`
+	Steps        []string `mapstructure:"steps" yaml:",omitempty"`
+}
+
+func (o *bindCmdOptions) validate(cmd *cobra.Command, args []string) error {
+	if len(args) > 2 {
+		return errors.New("too many arguments: expected source and sink")
+	} else if len(args) < 2 {
+		return errors.New("source or sink arguments are missing")
+	}
+
+	for _, p := range o.Properties {
+		if _, _, _, err := o.parseProperty(p); err != nil {
+			return err
+		}
+	}
+
+	if !o.SkipChecks {
+		source, err := o.decode(args[0], sourceKey)
+		if err != nil {
+			return err
+		}
+		if err := o.checkCompliance(cmd, source); err != nil {
+			return err
+		}
+
+		sink, err := o.decode(args[1], sinkKey)
+		if err != nil {
+			return err
+		}
+		if err := o.checkCompliance(cmd, sink); err != nil {
+			return err
+		}
+
+		for idx, stepDesc := range o.Steps {
+			stepKey := fmt.Sprintf("%s%d", stepKeyPrefix, idx)
+			step, err := o.decode(stepDesc, stepKey)
+			if err != nil {
+				return err
+			}
+			if err := o.checkCompliance(cmd, step); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func (o *bindCmdOptions) run(args []string) error {
+	source, err := o.decode(args[0], sourceKey)
+	if err != nil {
+		return err
+	}
+
+	sink, err := o.decode(args[1], sinkKey)
+	if err != nil {
+		return err
+	}
+
+	name := o.nameFor(source, sink)
+
+	binding := v1alpha1.KameletBinding{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: o.Namespace,
+			Name:      name,
+		},
+		Spec: v1alpha1.KameletBindingSpec{
+			Source: source,
+			Sink:   sink,
+		},
+	}
+
+	if len(o.Steps) > 0 {
+		binding.Spec.Steps = make([]v1alpha1.Endpoint, 0)
+		for idx, stepDesc := range o.Steps {
+			stepKey := fmt.Sprintf("%s%d", stepKeyPrefix, idx)
+			step, err := o.decode(stepDesc, stepKey)
+			if err != nil {
+				return err
+			}
+			binding.Spec.Steps = append(binding.Spec.Steps, step)
+		}
+	}
+
+	switch o.OutputFormat {
+	case "":
+		// continue..
+	case "yaml":
+		data, err := kubernetes.ToYAML(&binding)
+		if err != nil {
+			return err
+		}
+		fmt.Print(string(data))
+		return nil
+
+	case "json":
+		data, err := kubernetes.ToJSON(&binding)
+		if err != nil {
+			return err
+		}
+		fmt.Print(string(data))
+		return nil
+
+	default:
+		return fmt.Errorf("invalid output format option '%s', should be one of: yaml|json", o.OutputFormat)
+	}
+
+	client, err := o.GetCmdClient()
+	if err != nil {
+		return err
+	}
+
+	existed := false
+	err = client.Create(o.Context, &binding)
+	if err != nil && k8serrors.IsAlreadyExists(err) {
+		existed = true
+		err = kubernetes.ReplaceResource(o.Context, client, &binding)
+	}
+	if err != nil {
+		return err
+	}
+
+	if !existed {
+		fmt.Printf("kamelet binding \"%s\" created\n", name)
+	} else {
+		fmt.Printf("kamelet binding \"%s\" updated\n", name)
+	}
+	return nil
+}
+
+func (o *bindCmdOptions) decode(res string, key string) (v1alpha1.Endpoint, error) {
+	refConverter := reference.NewConverter(reference.KameletPrefix)
+	endpoint := v1alpha1.Endpoint{}
+	explicitProps := o.getProperties(key)
+	props, err := o.asEndpointProperties(explicitProps)
+	if err != nil {
+		return endpoint, err
+	}
+	endpoint.Properties = props
+
+	ref, err := refConverter.FromString(res)
+	if err != nil {
+		if uri.HasCamelURIFormat(res) {
+			endpoint.URI = &res
+			return endpoint, nil
+		}
+		return endpoint, err
+	}
+	endpoint.Ref = &ref
+	if endpoint.Ref.Namespace == "" {
+		endpoint.Ref.Namespace = o.Namespace
+	}
+	embeddedProps, err := refConverter.PropertiesFromString(res)
+	if err != nil {
+		return endpoint, err
+	}
+	if len(embeddedProps) > 0 {
+		allProps := make(map[string]string)
+		for k, v := range explicitProps {
+			allProps[k] = v
+		}
+		for k, v := range embeddedProps {
+			allProps[k] = v
+		}
+
+		props, err := o.asEndpointProperties(allProps)
+		if err != nil {
+			return endpoint, err
+		}
+		endpoint.Properties = props
+	}
+
+	return endpoint, nil
+}
+
+func (o *bindCmdOptions) nameFor(source, sink v1alpha1.Endpoint) string {
+	if o.Name != "" {
+		return o.Name
+	}
+	sourcePart := o.nameForEndpoint(source)
+	sinkPart := o.nameForEndpoint(sink)
+	name := fmt.Sprintf("%s-to-%s", sourcePart, sinkPart)
+	return kubernetes.SanitizeName(name)
+}
+
+func (o *bindCmdOptions) nameForEndpoint(endpoint v1alpha1.Endpoint) string {
+	if endpoint.URI != nil {
+		return uri.GetComponent(*endpoint.URI)
+	}
+	if endpoint.Ref != nil {
+		return endpoint.Ref.Name
+	}
+	return ""
+}
+
+func (o *bindCmdOptions) asEndpointProperties(props map[string]string) (*v1alpha1.EndpointProperties, error) {
+	if len(props) == 0 {
+		return nil, nil
+	}
+	data, err := json.Marshal(props)
+	if err != nil {
+		return nil, err
+	}
+	return &v1alpha1.EndpointProperties{
+		RawMessage: v1.RawMessage(data),
+	}, nil
+}
+
+func (o *bindCmdOptions) getProperties(refType string) map[string]string {
+	props := make(map[string]string)
+	for _, p := range o.Properties {
+		tp, k, v, err := o.parseProperty(p)
+		if err != nil {
+			continue
+		}
+		if tp == refType {
+			props[k] = v
+		}
+	}
+	return props
+}
+
+func (o *bindCmdOptions) parseProperty(prop string) (string, string, string, error) {
+	parts := strings.SplitN(prop, "=", 2)
+	if len(parts) != 2 {
+		return "", "", "", fmt.Errorf(`property %q does not follow format "[source|sink|step-<n>].<key>=<value>"`, prop)
+	}
+	keyParts := strings.SplitN(parts[0], ".", 2)
+	if len(keyParts) != 2 {
+		return "", "", "", fmt.Errorf(`property key %q does not follow format "[source|sink|step-<n>].<key>"`, parts[0])
+	}
+	isSource := keyParts[0] == sourceKey
+	isSink := keyParts[0] == sinkKey
+	isStep := strings.HasPrefix(keyParts[0], stepKeyPrefix)
+	if !isSource && !isSink && !isStep {
+		return "", "", "", fmt.Errorf(`property key %q does not start with "source.", "sink." or "step-<n>."`, parts[0])
+	}
+	return keyParts[0], keyParts[1], parts[1], nil
+}
+
+func (o *bindCmdOptions) checkCompliance(cmd *cobra.Command, endpoint v1alpha1.Endpoint) error {
+	if endpoint.Ref != nil && endpoint.Ref.Kind == "Kamelet" {
+		c, err := o.GetCmdClient()
+		if err != nil {
+			return err
+		}
+		key := client.ObjectKey{
+			Namespace: endpoint.Ref.Namespace,
+			Name:      endpoint.Ref.Name,
+		}
+		kamelet := v1alpha1.Kamelet{}
+		if err := c.Get(o.Context, key, &kamelet); err != nil {
+			if k8serrors.IsNotFound(err) {
+				// Kamelet may be in the operator namespace, but we currently don't have a way to determine it: we just warn
+				fmt.Fprintf(cmd.ErrOrStderr(), "Warning: Kamelet %q not found in namespace %q\n", key.Name, key.Namespace)
+				return nil
+			}
+			return err
+		}
+		if kamelet.Spec.Definition != nil && len(kamelet.Spec.Definition.Required) > 0 {
+			pMap, err := endpoint.Properties.GetPropertyMap()
+			if err != nil {
+				return err
+			}
+			for _, reqProp := range kamelet.Spec.Definition.Required {
+				found := false
+				if endpoint.Properties != nil {
+					if _, contains := pMap[reqProp]; contains {
+						found = true
+					}
+				}
+				if !found {
+					return fmt.Errorf("binding is missing required property %q for Kamelet %q", reqProp, key.Name)
+				}
+			}
+		}
+	}
+	return nil
+}
diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go
index 0adbac4ce..1692c15e9 100644
--- a/pkg/cmd/install.go
+++ b/pkg/cmd/install.go
@@ -128,6 +128,7 @@ func newCmdInstall(rootCmdOptions *RootCmdOptions) (*cobra.Command, *installCmdO
 
 	// Pod settings
 	cmd.Flags().StringArrayP("toleration", "", nil, "Add a Toleration to the operator Pod")
+	cmd.Flags().StringArrayP("node-selector", "", nil, "Add a NodeSelector to the operator Pod")
 
 	// save
 	cmd.Flags().Bool("save", false, "Save the install parameters into the default kamel configuration file (kamel-config.yaml)")
@@ -175,6 +176,7 @@ type installCmdOptions struct {
 	Properties              []string `mapstructure:"properties"`
 	TraitProfile            string   `mapstructure:"trait-profile"`
 	Tolerations             []string `mapstructure:"tolerations"`
+	NodeSelectors           []string `mapstructure:"node-selectors"`
 	HTTPProxySecret         string   `mapstructure:"http-proxy-secret"`
 
 	registry         v1.IntegrationPlatformRegistrySpec
@@ -221,7 +223,7 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error {
 		if installViaOLM {
 			fmt.Fprintln(cobraCmd.OutOrStdout(), "OLM is available in the cluster")
 			var installed bool
-			if installed, err = olm.Install(o.Context, olmClient, o.Namespace, o.Global, o.olmOptions, collection, o.Tolerations); err != nil {
+			if installed, err = olm.Install(o.Context, olmClient, o.Namespace, o.Global, o.olmOptions, collection, o.Tolerations, o.NodeSelectors); err != nil {
 				return err
 			}
 			if !installed {
@@ -272,7 +274,8 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error {
 					Enabled: o.Monitoring,
 					Port:    o.MonitoringPort,
 				},
-				Tolerations: o.Tolerations,
+				Tolerations:   o.Tolerations,
+				NodeSelectors: o.NodeSelectors,
 			}
 			err = install.OperatorOrCollect(o.Context, c, cfg, collection, o.Force)
 			if err != nil {
diff --git a/pkg/cmd/local_build.go b/pkg/cmd/local_build.go
index 2e4219417..b9606ddfc 100644
--- a/pkg/cmd/local_build.go
+++ b/pkg/cmd/local_build.go
@@ -20,9 +20,10 @@ package cmd
 import (
 	"fmt"
 
-	"github.com/apache/camel-k/pkg/util"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
+
+	"github.com/apache/camel-k/pkg/util"
 )
 
 func newCmdLocalBuild(rootCmdOptions *RootCmdOptions) (*cobra.Command, *localBuildCmdOptions) {
@@ -105,7 +106,7 @@ func (command *localBuildCmdOptions) validate(args []string) error {
 	// Validate properties file.
 	err = validateFiles(command.PropertyFiles)
 	if err != nil {
-		return nil
+		return err
 	}
 
 	// ContainerRegistry should only be specified when building the base image.
@@ -163,8 +164,7 @@ func (command *localBuildCmdOptions) init(args []string) error {
 }
 
 func (command *localBuildCmdOptions) run(cmd *cobra.Command, args []string) error {
-	dependenciesList := []string{}
-	propertyFilesList := []string{}
+	var dependenciesList, propertyFilesList []string
 	routeFiles := args
 	if !command.BaseImage {
 		// Fetch dependencies.
@@ -173,7 +173,7 @@ func (command *localBuildCmdOptions) run(cmd *cobra.Command, args []string) erro
 			return err
 		}
 
-		propertyFiles := []string{}
+		var propertyFiles []string
 		if !command.DependenciesOnly {
 			// Manage integration properties which may come from files or CLI.
 			propertyFiles, err = updateIntegrationProperties(command.Properties, command.PropertyFiles, false)
diff --git a/pkg/cmd/local_run.go b/pkg/cmd/local_run.go
index 3e51d3573..db8c4b490 100644
--- a/pkg/cmd/local_run.go
+++ b/pkg/cmd/local_run.go
@@ -20,9 +20,10 @@ package cmd
 import (
 	"fmt"
 
-	"github.com/apache/camel-k/pkg/util"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
+
+	"github.com/apache/camel-k/pkg/util"
 )
 
 func newCmdLocalRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *localRunCmdOptions) {
@@ -146,7 +147,7 @@ func (command *localRunCmdOptions) run(cmd *cobra.Command, args []string) error
 
 	hasIntegrationDir := command.IntegrationDirectory != ""
 
-	dependencies := []string{}
+	var dependencies []string
 	if hasIntegrationDir {
 		localBuildDependencies, err := getLocalBuildDependencies(command.IntegrationDirectory)
 		if err != nil {
@@ -201,13 +202,13 @@ func (command *localRunCmdOptions) run(cmd *cobra.Command, args []string) error
 			return err
 		}
 	} else {
-		propertieDir := util.GetLocalPropertiesDir()
+		propertiesDir := util.GetLocalPropertiesDir()
 		if hasIntegrationDir {
-			propertieDir = getCustomPropertiesDir(command.IntegrationDirectory)
+			propertiesDir = getCustomPropertiesDir(command.IntegrationDirectory)
 		}
 
 		// Run integration locally.
-		err := RunLocalIntegrationRunCommand(command.Context, propertyFiles, dependencies, routes, propertieDir, cmd.OutOrStdout(), cmd.ErrOrStderr())
+		err := RunLocalIntegrationRunCommand(command.Context, propertyFiles, dependencies, routes, propertiesDir, cmd.OutOrStdout(), cmd.ErrOrStderr())
 		if err != nil {
 			return err
 		}
diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go
index c61a2fab1..06a775b00 100644
--- a/pkg/cmd/root.go
+++ b/pkg/cmd/root.go
@@ -147,6 +147,7 @@ func addKamelSubcommands(cmd *cobra.Command, options *RootCmdOptions) {
 	cmd.AddCommand(cmdOnly(newCmdDebug(options)))
 	cmd.AddCommand(cmdOnly(newCmdDump(options)))
 	cmd.AddCommand(newCmdLocal(options))
+	cmd.AddCommand(cmdOnly(newCmdBind(options)))
 }
 
 func addHelpSubCommands(cmd *cobra.Command, options *RootCmdOptions) error {
@@ -193,24 +194,24 @@ func (command *RootCmdOptions) preRun(cmd *cobra.Command, _ []string) error {
 		// Furthermore, there can be any incompatibilities, as the install command deploys
 		// the operator version it's compatible with.
 		if cmd.Use != installCommand && cmd.Use != operatorCommand {
-			checkAndShowCompatibilityWarning(command.Context, c, command.Namespace)
+			checkAndShowCompatibilityWarning(cmd, command.Context, c, command.Namespace)
 		}
 	}
 
 	return nil
 }
 
-func checkAndShowCompatibilityWarning(ctx context.Context, c client.Client, namespace string) {
+func checkAndShowCompatibilityWarning(cmd *cobra.Command, ctx context.Context, c client.Client, namespace string) {
 	operatorVersion, err := operatorVersion(ctx, c, namespace)
 	if err != nil {
 		if k8serrors.IsNotFound(err) {
-			fmt.Printf("No IntegrationPlatform resource in %s namespace\n", namespace)
+			fmt.Fprintf(cmd.ErrOrStderr(), "No IntegrationPlatform resource in %s namespace\n", namespace)
 		} else {
-			fmt.Printf("Unable to retrieve the operator version: %s", err.Error())
+			fmt.Fprintf(cmd.ErrOrStderr(), "Unable to retrieve the operator version: %s\n", err.Error())
 		}
 	} else {
 		if operatorVersion != "" && !compatibleVersions(operatorVersion, defaults.Version) {
-			fmt.Printf("You're using Camel K %s client with a %s cluster operator, it's recommended to use the same version to improve compatibility.\n\n", defaults.Version, operatorVersion)
+			fmt.Fprintf(cmd.ErrOrStderr(), "You're using Camel K %s client with a %s cluster operator, it's recommended to use the same version to improve compatibility.\n\n", defaults.Version, operatorVersion)
 		}
 	}
 }
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 7caea0083..255feafe7 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -414,7 +414,7 @@ func (o *runCmdOptions) syncIntegration(cmd *cobra.Command, c client.Client, sou
 				}
 			}()
 		} else {
-			fmt.Printf("WARNING: the following URL will not be watched for changes: %s\n", s)
+			fmt.Fprintf(cmd.ErrOrStderr(), "Warning: the following URL will not be watched for changes: %s\n", s)
 		}
 	}
 
diff --git a/pkg/cmd/util_dependencies.go b/pkg/cmd/util_dependencies.go
index 0dbbcb19b..be0dfb7e3 100644
--- a/pkg/cmd/util_dependencies.go
+++ b/pkg/cmd/util_dependencies.go
@@ -311,7 +311,7 @@ func validateFiles(args []string) error {
 	for _, arg := range args {
 		err := validateFile(arg)
 		if err != nil {
-			return nil
+			return err
 		}
 	}
 
@@ -354,7 +354,7 @@ func validateIntegrationFiles(args []string) error {
 	// Validate integration files.
 	err := validateFiles(args)
 	if err != nil {
-		return nil
+		return err
 	}
 
 	return nil
@@ -388,7 +388,10 @@ func updateIntegrationProperties(properties []string, propertyFiles []string, ha
 	var relocatedPropertyFiles []string
 	for _, propertyFile := range propertyFiles {
 		relocatedPropertyFile := path.Join(util.GetLocalPropertiesDir(), path.Base(propertyFile))
-		util.CopyFile(propertyFile, relocatedPropertyFile)
+		_, err = util.CopyFile(propertyFile, relocatedPropertyFile)
+		if err != nil {
+			return nil, err
+		}
 		relocatedPropertyFiles = append(relocatedPropertyFiles, relocatedPropertyFile)
 	}
 
@@ -424,7 +427,10 @@ func updateIntegrationDependencies(dependencies []string) error {
 		} else {
 			targetPath = path.Join(util.GetLocalDependenciesDir(), path.Base(dependency))
 		}
-		util.CopyFile(dependency, targetPath)
+		_, err = util.CopyFile(dependency, targetPath)
+		if err != nil {
+			return err
+		}
 	}
 
 	return nil
@@ -437,7 +443,10 @@ func updateIntegrationRoutes(routes []string) error {
 	}
 
 	for _, route := range routes {
-		util.CopyFile(route, path.Join(util.GetLocalRoutesDir(), path.Base(route)))
+		_, err = util.CopyFile(route, path.Join(util.GetLocalRoutesDir(), path.Base(route)))
+		if err != nil {
+			return err
+		}
 	}
 
 	return nil
diff --git a/pkg/install/operator.go b/pkg/install/operator.go
index 1fc6cf7c9..50d4e5b35 100644
--- a/pkg/install/operator.go
+++ b/pkg/install/operator.go
@@ -55,6 +55,7 @@ type OperatorConfiguration struct {
 	Health                OperatorHealthConfiguration
 	Monitoring            OperatorMonitoringConfiguration
 	Tolerations           []string
+	NodeSelectors         []string
 }
 
 // OperatorHealthConfiguration --
@@ -99,6 +100,18 @@ func OperatorOrCollect(ctx context.Context, c client.Client, cfg OperatorConfigu
 			}
 		}
 
+		if cfg.NodeSelectors != nil {
+			if d, ok := o.(*appsv1.Deployment); ok {
+				if d.Labels["camel.apache.org/component"] == "operator" {
+					nodeSelector, err := kubernetes.NewNodeSelectors(cfg.NodeSelectors)
+					if err != nil {
+						fmt.Println("Warning: could not parse the configured node selectors!")
+					}
+					d.Spec.Template.Spec.NodeSelector = nodeSelector
+				}
+			}
+		}
+
 		if d, ok := o.(*appsv1.Deployment); ok {
 			if d.Labels["camel.apache.org/component"] == "operator" {
 				// Metrics endpoint port
diff --git a/pkg/trait/knative_service.go b/pkg/trait/knative_service.go
index a1e8ac19a..9cc77aaf6 100644
--- a/pkg/trait/knative_service.go
+++ b/pkg/trait/knative_service.go
@@ -102,10 +102,7 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
-		condition := e.Integration.Status.GetCondition(v1.IntegrationConditionKnativeServiceAvailable)
-		return condition != nil && condition.Status == corev1.ConditionTrue, nil
-	} else if !e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseDeploying) {
+	if !e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseDeploying) && !e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
 		return false, nil
 	}
 
@@ -164,6 +161,11 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) {
 		}
 	}
 
+	if e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
+		condition := e.Integration.Status.GetCondition(v1.IntegrationConditionKnativeServiceAvailable)
+		return condition != nil && condition.Status == corev1.ConditionTrue, nil
+	}
+
 	return true, nil
 }
 
diff --git a/pkg/util/kubernetes/factory.go b/pkg/util/kubernetes/factory.go
index a85c4723c..956627c1e 100644
--- a/pkg/util/kubernetes/factory.go
+++ b/pkg/util/kubernetes/factory.go
@@ -26,8 +26,9 @@ import (
 )
 
 var validTaintRegexp = regexp.MustCompile(`^([\w\/_\-\.]+)(=)?([\w_\-\.]+)?:(NoSchedule|NoExecute|PreferNoSchedule):?(\d*)?$`)
+var validNodeSelectorRegexp = regexp.MustCompile(`^([\w\/_\-\.]+)=([\w_\-\.]+)$`)
 
-// GetTolerations build an array of Tolerations from an array of string
+// NewTolerations build an array of Tolerations from an array of string
 func NewTolerations(taints []string) ([]corev1.Toleration, error) {
 	tolerations := make([]corev1.Toleration, 0)
 	for _, t := range taints {
@@ -60,3 +61,17 @@ func NewTolerations(taints []string) ([]corev1.Toleration, error) {
 
 	return tolerations, nil
 }
+
+// NewNodeSelectors build a map of NodeSelectors from an array of string
+func NewNodeSelectors(nsArray []string) (map[string]string, error) {
+	nodeSelectors := make(map[string]string)
+	for _, ns := range nsArray {
+		if !validNodeSelectorRegexp.MatchString(ns) {
+			return nil, fmt.Errorf("could not match node selector %v", ns)
+		}
+		// Parse the regexp groups
+		groups := validNodeSelectorRegexp.FindStringSubmatch(ns)
+		nodeSelectors[groups[1]] = groups[2]
+	}
+	return nodeSelectors, nil
+}
diff --git a/pkg/util/olm/operator.go b/pkg/util/olm/operator.go
index 896b24194..bb9ce2af8 100644
--- a/pkg/util/olm/operator.go
+++ b/pkg/util/olm/operator.go
@@ -138,7 +138,7 @@ func HasPermissionToInstall(ctx context.Context, client client.Client, namespace
 }
 
 // Install creates a subscription for the OLM package
-func Install(ctx context.Context, client client.Client, namespace string, global bool, options Options, collection *kubernetes.Collection, tolerations []string) (bool, error) {
+func Install(ctx context.Context, client client.Client, namespace string, global bool, options Options, collection *kubernetes.Collection, tolerations []string, nodeSelectors []string) (bool, error) {
 	options = fillDefaults(options)
 	if installed, err := IsOperatorInstalled(ctx, client, namespace, global, options); err != nil {
 		return false, err
@@ -171,6 +171,10 @@ func Install(ctx context.Context, client client.Client, namespace string, global
 	if err != nil {
 		return false, errors.Wrap(err, fmt.Sprintf("could not set tolerations"))
 	}
+	err = maybeSetNodeSelectors(&sub, nodeSelectors)
+	if err != nil {
+		return false, errors.Wrap(err, fmt.Sprintf("could not set node selectors"))
+	}
 
 	if collection != nil {
 		collection.Add(&sub)
@@ -216,6 +220,17 @@ func maybeSetTolerations(sub *operatorsv1alpha1.Subscription, tolArray []string)
 	return nil
 }
 
+func maybeSetNodeSelectors(sub *operatorsv1alpha1.Subscription, nsArray []string) error {
+	if nsArray != nil {
+		nodeSelectors, err := kubernetes.NewNodeSelectors(nsArray)
+		if err != nil {
+			return err
+		}
+		sub.Spec.Config.NodeSelector = nodeSelectors
+	}
+	return nil
+}
+
 // Uninstall removes CSV and subscription from the namespace
 func Uninstall(ctx context.Context, client client.Client, namespace string, global bool, options Options) error {
 	sub, err := findSubscription(ctx, client, namespace, global, options)
diff --git a/pkg/util/reference/reference.go b/pkg/util/reference/reference.go
index dc79cd59c..5af887e06 100644
--- a/pkg/util/reference/reference.go
+++ b/pkg/util/reference/reference.go
@@ -18,12 +18,14 @@ limitations under the License.
 package reference
 
 import (
-	"errors"
 	"fmt"
+	"net/url"
 	"regexp"
+	"strings"
 	"unicode"
 
 	camelv1alpha1 "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
 	eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
 	messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1"
@@ -35,8 +37,9 @@ const (
 )
 
 var (
-	simpleNameRegexp = regexp.MustCompile(`^(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)$`)
-	fullNameRegexp   = regexp.MustCompile(`^(?:(?P<apiVersion>(?:[a-z0-9-.]+/)?(?:[a-z0-9-.]+)):)?(?P<kind>[A-Za-z0-9-.]+):(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)$`)
+	simpleNameRegexp = regexp.MustCompile(`^(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)(?:$|[?].*$)`)
+	fullNameRegexp   = regexp.MustCompile(`^(?:(?P<apiVersion>(?:[a-z0-9-.]+/)?(?:[a-z0-9-.]+)):)?(?P<kind>[A-Za-z0-9-.]+):(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)(?:$|[?].*$)`)
+	queryRegexp      = regexp.MustCompile(`^[^?]*[?](?P<query>.*)$`)
 
 	templates = map[string]corev1.ObjectReference{
 		"kamelet": corev1.ObjectReference{
@@ -81,6 +84,41 @@ func (c *Converter) FromString(str string) (corev1.ObjectReference, error) {
 	return ref, nil
 }
 
+func (c *Converter) PropertiesFromString(str string) (map[string]string, error) {
+	if queryRegexp.MatchString(str) {
+		groupNames := queryRegexp.SubexpNames()
+		res := make(map[string]string)
+		var query string
+		for _, match := range queryRegexp.FindAllStringSubmatch(str, -1) {
+			for idx, text := range match {
+				groupName := groupNames[idx]
+				switch groupName {
+				case "query":
+					query = text
+				}
+			}
+		}
+		parts := strings.Split(query, "&")
+		for _, part := range parts {
+			kv := strings.SplitN(part, "=", 2)
+			if len(kv) != 2 {
+				return nil, fmt.Errorf("invalid key=value format for string %q", part)
+			}
+			k, errkey := url.QueryUnescape(kv[0])
+			if errkey != nil {
+				return nil, errors.Wrapf(errkey, "cannot unescape key %q", kv[0])
+			}
+			v, errval := url.QueryUnescape(kv[1])
+			if errval != nil {
+				return nil, errors.Wrapf(errval, "cannot unescape value %q", kv[1])
+			}
+			res[k] = v
+		}
+		return res, nil
+	}
+	return nil, nil
+}
+
 func (c *Converter) expandReference(ref *corev1.ObjectReference) {
 	if template, ok := templates[ref.Kind]; ok {
 		if template.Kind != "" {
diff --git a/pkg/util/source/inspector.go b/pkg/util/source/inspector.go
index 746a03af1..3933a6182 100644
--- a/pkg/util/source/inspector.go
+++ b/pkg/util/source/inspector.go
@@ -324,6 +324,9 @@ func (i *baseInspector) hasOnlyPassiveEndpoints(fromURIs []string) bool {
 
 func (i *baseInspector) containsOnlyURIsIn(fromURI []string, allowed map[string]bool) bool {
 	for _, uri := range fromURI {
+		if uri == "kamelet:source" {
+			continue
+		}
 		prefix := i.getURIPrefix(uri)
 		if enabled, ok := allowed[prefix]; !ok || !enabled {
 			return false
diff --git a/pkg/util/uri/uri.go b/pkg/util/uri/uri.go
index 1e0cf1e83..542eee4c3 100644
--- a/pkg/util/uri/uri.go
+++ b/pkg/util/uri/uri.go
@@ -27,8 +27,15 @@ import (
 	"github.com/apache/camel-k/pkg/util/log"
 )
 
+var uriRegexp = regexp.MustCompile(`^[a-z0-9+][a-zA-Z0-9-+]*:.*$`)
+
 var queryExtractorRegexp = `^[^?]+\?(?:|.*[&])%s=([^&]+)(?:[&].*|$)`
 
+// HasCamelURIFormat tells if a given string may belong to a Camel URI, without checking any catalog
+func HasCamelURIFormat(uri string) bool {
+	return uriRegexp.MatchString(uri)
+}
+
 // GetComponent returns the Camel component used in the URI
 func GetComponent(uri string) string {
 	parts := strings.Split(uri, ":")


Test patch:
diff --git a/e2e/common/cli/dev_mode_test.go b/e2e/common/cli/dev_mode_test.go
index fef9153a5..93cb05207 100644
--- a/e2e/common/cli/dev_mode_test.go
+++ b/e2e/common/cli/dev_mode_test.go
@@ -38,6 +38,7 @@ func TestRunDevMode(t *testing.T) {
 		Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
 
 		t.Run("run yaml dev mode", func(t *testing.T) {
+			RegisterTestingT(t)
 			ctx, cancel := context.WithCancel(TestContext)
 			defer cancel()
 			piper, pipew := io.Pipe()
@@ -65,6 +66,7 @@ func TestRunDevMode(t *testing.T) {
 		})
 
 		t.Run("run yaml remote dev mode", func(t *testing.T) {
+			RegisterTestingT(t)
 			ctx, cancel := context.WithCancel(TestContext)
 			defer cancel()
 			piper, pipew := io.Pipe()
diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go
index 1b4526656..6e2632ffb 100644
--- a/e2e/support/test_support.go
+++ b/e2e/support/test_support.go
@@ -27,6 +27,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	v1alpha12 "github.com/operator-framework/api/pkg/operators/v1alpha1"
 	"io"
 	"io/ioutil"
 	"os"
@@ -34,6 +35,7 @@ import (
 	"strings"
 	"testing"
 	"time"
+	"unsafe"
 
 	"github.com/google/uuid"
 	"github.com/onsi/gomega"
@@ -116,6 +118,7 @@ func init() {
 	client.FastMapperAllowedAPIGroups["eventing.knative.dev"] = true
 	client.FastMapperAllowedAPIGroups["messaging.knative.dev"] = true
 	client.FastMapperAllowedAPIGroups["serving.knative.dev"] = true
+	client.FastMapperAllowedAPIGroups["operators.coreos.com"] = true
 	client.FastMapperAllowedAPIGroups["policy"] = true
 
 	var err error
@@ -670,6 +673,65 @@ func KnativeService(ns string, name string) func() *servingv1.Service {
 	}
 }
 
+func CKClusterServiceVersion(conditions func(v1alpha12.ClusterServiceVersion) bool ,ns string) func() *v1alpha12.ClusterServiceVersion {
+	return func() *v1alpha12.ClusterServiceVersion {
+		lst := v1alpha12.ClusterServiceVersionList{}
+		if err := TestClient().List(TestContext, &lst, ctrl.InNamespace(ns)); err != nil {
+			panic(err)
+		}
+		for _, s := range lst.Items {
+			if strings.Contains(s.Name, "camel-k") && conditions(s){
+				return &s
+			}
+		}
+		return nil
+	}
+}
+
+func CKClusterServiceVersionPhase(conditions func(v1alpha12.ClusterServiceVersion) bool, ns string) func() v1alpha12.ClusterServiceVersionPhase {
+	return func() v1alpha12.ClusterServiceVersionPhase {
+		if csv := CKClusterServiceVersion(conditions, ns)(); csv != nil && unsafe.Sizeof(csv.Status) > 0 {
+			return csv.Status.Phase
+		}
+		return ""
+	}
+}
+
+func CatalogSource(name string, ns string) func() *v1alpha12.CatalogSource {
+	return func() *v1alpha12.CatalogSource {
+		answer := v1alpha12.CatalogSource{
+			TypeMeta: metav1.TypeMeta{
+				Kind:       "CatalogSource",
+				APIVersion: v1alpha1.SchemeGroupVersion.String(),
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: ns,
+				Name:      name,
+			},
+		}
+		key := ctrl.ObjectKey{
+			Namespace: ns,
+			Name:      name,
+		}
+		if err := TestClient().Get(TestContext, key, &answer); err != nil && k8serrors.IsNotFound(err) {
+			return nil
+		} else if err != nil {
+			log.Errorf(err, "Error while retrieving CatalogSource %s", name)
+			return nil
+		}
+		return &answer
+	}
+}
+
+func CatalogSourcePhase(name string, ns string) func() string {
+	return func() string {
+		if source := CatalogSource(name, ns)(); source != nil && source.Status.GRPCConnectionState != nil {
+			return CatalogSource(name, ns)().Status.GRPCConnectionState.LastObservedState
+		}
+		return ""
+	}
+}
+
 func Deployment(ns string, name string) func() *appsv1.Deployment {
 	return func() *appsv1.Deployment {
 		answer := appsv1.Deployment{
diff --git a/e2e/upgrade/olm_upgrade_test.go b/e2e/upgrade/olm_upgrade_test.go
new file mode 100644
index 000000000..291349382
--- /dev/null
+++ b/e2e/upgrade/olm_upgrade_test.go
@@ -0,0 +1,163 @@
+// +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 (
+	"fmt"
+	. "github.com/apache/camel-k/e2e/support"
+	. "github.com/onsi/gomega"
+	"github.com/operator-framework/api/pkg/lib/version"
+	"github.com/operator-framework/api/pkg/operators/v1alpha1"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"os"
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+	"testing"
+)
+
+const CATALOG_SOURCE_NAME = "test-camel-k-source"
+
+func TestOLMAutomaticUpgrade(t *testing.T) {
+	prevIIB := os.Getenv("CAMEL_K_PREV_IIB")
+	newIIB := os.Getenv("CAMEL_K_NEW_IIB")
+	kamel := os.Getenv("RELEASED_KAMEL_BIN")
+
+	if prevIIB == "" || newIIB == "" {
+		t.Skip("OLM Upgrade test needs CAMEL_K_PREV_IIB and CAMEL_K_PREV_IIB ENV variables")
+	}
+
+	WithNewTestNamespace(t, func(ns string) {
+		Expect(createCatalogSource(CATALOG_SOURCE_NAME, prevIIB, ns)).To(Succeed())
+		Eventually(CatalogSourcePhase(CATALOG_SOURCE_NAME, ns), TestTimeoutMedium).Should(Equal("READY"))
+
+		//set KAMEL_BIN only for this test - don't override the ENV variable for all tests
+		Expect(os.Setenv("KAMEL_BIN", kamel)).To(Succeed())
+
+		Expect(Kamel("install", "-n", ns, "--olm=true", "--olm-source", CATALOG_SOURCE_NAME, "--olm-source-namespace", ns).Execute()).To(Succeed())
+
+		//find the only one Camel-K CSV
+		noAdditionalConditions := func(csv v1alpha1.ClusterServiceVersion) bool {
+			return true
+		}
+		Eventually(CKClusterServiceVersionPhase(noAdditionalConditions, ns), TestTimeoutMedium).Should(Equal(v1alpha1.CSVPhaseSucceeded))
+
+		// Refresh the test client to account for the newly installed CRDs
+		SyncClient()
+
+		var prevCSVVersion version.OperatorVersion
+		var newCSVVersion version.OperatorVersion
+
+		// IntegrationPlatform should match at least on the version prefix
+		// CSV patch version can be increased with the OperatorHub respin of the same Camel-K release
+		var prevIPVersionPrefix string
+		var newIPVersionPrefix string
+
+		prevCSVVersion = CKClusterServiceVersion(noAdditionalConditions, ns)().Spec.Version
+		prevIPVersionPrefix = fmt.Sprintf("%d.%d", prevCSVVersion.Version.Major, prevCSVVersion.Version.Minor)
+
+		Expect(OperatorPod(ns)).ToNot(BeNil())
+
+		Expect(Kamel("run", "-n", ns, "files/yaml.yaml").Execute()).To(Succeed())
+		Eventually(IntegrationPodPhase(ns, "yaml"), TestTimeoutMedium).Should(Equal(v1.PodRunning))
+
+		Eventually(PlatformVersion(ns)).Should(ContainSubstring(prevIPVersionPrefix))
+		Expect(IntegrationVersion(ns, "yaml")()).To(ContainSubstring(prevIPVersionPrefix))
+
+		t.Run("OLM upgrade", func(t *testing.T) {
+
+			//invoke OLM upgrade
+			Expect(createCatalogSource(CATALOG_SOURCE_NAME, newIIB, ns)).To(Succeed())
+
+			// previous CSV is REPLACING
+			Eventually(CKClusterServiceVersionPhase(func(csv v1alpha1.ClusterServiceVersion) bool {
+				return csv.Spec.Version.Version.String() == prevCSVVersion.Version.String()
+			}, ns), TestTimeoutMedium).Should(Equal(v1alpha1.CSVPhaseReplacing))
+
+			// the new version is installed
+			Eventually(CKClusterServiceVersionPhase(func(csv v1alpha1.ClusterServiceVersion) bool {
+				return csv.Spec.Version.Version.String() != prevCSVVersion.Version.String()
+			}, ns), TestTimeoutMedium).Should(Equal(v1alpha1.CSVPhaseSucceeded))
+
+			// the old version is gone
+			Eventually(CKClusterServiceVersion(func(csv v1alpha1.ClusterServiceVersion) bool {
+				return csv.Spec.Version.Version.String() == prevCSVVersion.Version.String()
+			}, ns), TestTimeoutMedium).Should(BeNil())
+
+			newCSVVersion = CKClusterServiceVersion(noAdditionalConditions, ns)().Spec.Version
+			newIPVersionPrefix = fmt.Sprintf("%d.%d", newCSVVersion.Version.Major, newCSVVersion.Version.Minor)
+
+			Expect(prevCSVVersion.Version.String()).NotTo(Equal(newCSVVersion.Version.String()))
+
+			Eventually(OperatorPodPhase(ns)).Should(Equal(v1.PodRunning))
+
+			Eventually(PlatformVersion(ns)).Should(ContainSubstring(newIPVersionPrefix))
+		})
+
+		t.Run("Integration upgrade", func(t *testing.T) {
+
+			// Clear the KAMEL_BIN environment variable so that the current version is used from now on
+			Expect(os.Setenv("KAMEL_BIN", "")).To(Succeed())
+
+			Expect(IntegrationVersion(ns, "yaml")()).To(ContainSubstring(prevIPVersionPrefix))
+			Expect(Kamel("rebuild", "yaml", "-n", ns).Execute()).To(Succeed())
+
+			Eventually(IntegrationVersion(ns, "yaml"), TestTimeoutMedium).Should(ContainSubstring(newIPVersionPrefix))
+			Eventually(IntegrationPodPhase(ns, "yaml")).Should(Equal(v1.PodRunning))
+
+			// Clean up
+			Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+			Expect(Kamel("uninstall", "-n", ns).Execute()).To(Succeed())
+		})
+
+	})
+}
+
+func createCatalogSource(name string, image string, ns string) error {
+	catalogSource := v1alpha1.CatalogSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "CatalogSource",
+			APIVersion: v1alpha1.SchemeGroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: ns,
+			Name:      name,
+		},
+	}
+	key := ctrl.ObjectKey{
+		Namespace: ns,
+		Name:      name,
+	}
+	if err := TestClient().Get(TestContext, key, &catalogSource); errors.IsNotFound(err) {
+		catalogSource.Spec = v1alpha1.CatalogSourceSpec{
+			Image:       image,
+			SourceType:  "grpc",
+			DisplayName: "OLM upgrade test Catalog",
+			Publisher:   "grpc",
+		}
+		return TestClient().Create(TestContext, &catalogSource)
+	} else {
+		catalogSource.Spec.Image = image
+		return TestClient().Update(TestContext, &catalogSource)
+	}
+
+}
diff --git a/e2e/upgrade/upgrade_operator_test.go b/e2e/upgrade/upgrade_operator_test.go
index 9510846c3..2ce57759a 100644
--- a/e2e/upgrade/upgrade_operator_test.go
+++ b/e2e/upgrade/upgrade_operator_test.go
@@ -36,6 +36,9 @@ import (
 )
 
 func TestOperatorUpgrade(t *testing.T) {
+	// Clean all cluster-wide resources that could corrupt the test run
+	Expect(Kamel("uninstall", "--all", "--olm=false").Execute()).To(Succeed())
+
 	WithNewTestNamespace(t, func(ns string) {
 		version, ok := os.LookupEnv("KAMEL_K_TEST_RELEASE_VERSION")
 		Expect(ok).To(BeTrue())
@@ -43,8 +46,14 @@ func TestOperatorUpgrade(t *testing.T) {
 		image, ok := os.LookupEnv("KAMEL_K_TEST_OPERATOR_CURRENT_IMAGE")
 		Expect(ok).To(BeTrue())
 
-		Expect(Kamel("install", "--cluster-setup").Execute()).To(Succeed())
-		Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
+		kamel, ok := os.LookupEnv("RELEASED_KAMEL_BIN")
+		Expect(ok).To(BeTrue())
+
+		//set KAMEL_BIN only for this test - don't override the ENV variable for all tests
+		Expect(os.Setenv("KAMEL_BIN", kamel)).To(Succeed())
+
+		Expect(Kamel("install", "--olm=false", "--cluster-setup", "--force").Execute()).To(Succeed())
+		Expect(Kamel("install", "--olm=false", "-n", ns).Execute()).To(Succeed())
 
 		// Check the operator pod is running
 		Eventually(OperatorPodPhase(ns), TestTimeoutMedium).Should(Equal(v1.PodRunning))
@@ -69,8 +78,8 @@ func TestOperatorUpgrade(t *testing.T) {
 
 		// Upgrade the operator by installing the current version
 		// FIXME: it seems forcing the installation does not re-install the CRDs
-		Expect(Kamel("install", "--cluster-setup", "--force").Execute()).To(Succeed())
-		Expect(Kamel("install", "-n", ns, "--force", "--operator-image", image).Execute()).To(Succeed())
+		Expect(Kamel("install", "--olm=false", "--cluster-setup", "--force").Execute()).To(Succeed())
+		Expect(Kamel("install", "-n", ns, "--olm=false", "--force", "--operator-image", image).Execute()).To(Succeed())
 
 		// Check the operator image is the current built one
 		Eventually(OperatorImage(ns)).Should(Equal(image))
@@ -99,5 +108,6 @@ func TestOperatorUpgrade(t *testing.T) {
 
 		// Clean up
 		Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+		Expect(Kamel("uninstall", "--all", "--olm=false").Execute()).To(Succeed())
 	})
 }
diff --git a/e2e/yaks/common/apache-kamelet-catalog/kamelet.feature b/e2e/yaks/common/apache-kamelet-catalog/kamelet.feature
index 648295735..46618d7c8 100644
--- a/e2e/yaks/common/apache-kamelet-catalog/kamelet.feature
+++ b/e2e/yaks/common/apache-kamelet-catalog/kamelet.feature
@@ -1,5 +1,10 @@
 Feature: Camel K can run Kamelets from default catalog
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Integrations can use default catalog
     Given Camel-K integration logger is running
     Then Camel-K integration logger should print Camel K
diff --git a/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature
index f84e78e0a..608c7d01f 100644
--- a/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature
+++ b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature
@@ -4,8 +4,8 @@ Feature: Camel K can load specific secrets for Kamelets
     Given Disable auto removal of Kamelet resources
     Given Disable auto removal of Kubernetes resources
     Given Camel-K resource polling configuration
-      | maxAttempts          | 20   |
-      | delayBetweenAttempts | 1000 |
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
 
   Scenario: KameletBinding can load specific settings for Kamelet
     Given create Kubernetes service stub-service-2 with target port 8081
diff --git a/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature
index 297e93aa1..3662acd70 100644
--- a/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature
+++ b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature
@@ -4,8 +4,8 @@ Feature: Camel K can load default secrets for Kamelets
     Given Disable auto removal of Kamelet resources
     Given Disable auto removal of Kubernetes resources
     Given Camel-K resource polling configuration
-      | maxAttempts          | 20   |
-      | delayBetweenAttempts | 1000 |
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
 
   Scenario: KameletBinding can load default settings for Kamelet
     Given create Kubernetes service stub-service with target port 8080
diff --git a/e2e/yaks/common/kamelet-binding-broker/kamelet.feature b/e2e/yaks/common/kamelet-binding-broker/kamelet.feature
index ea45ada51..afd7a0c56 100644
--- a/e2e/yaks/common/kamelet-binding-broker/kamelet.feature
+++ b/e2e/yaks/common/kamelet-binding-broker/kamelet.feature
@@ -1,5 +1,10 @@
 Feature: Camel K can bind Kamelets to the broker
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Sending event to the broker with KameletBinding
     Given Camel-K integration logger-sink-binding is running
     Then Camel-K integration logger-sink-binding should print message: Hello Custom Event
diff --git a/e2e/yaks/common/kamelet-binding/display.groovy b/e2e/yaks/common/kamelet-binding-http/display.groovy
similarity index 100%
rename from e2e/yaks/common/kamelet-binding/display.groovy
rename to e2e/yaks/common/kamelet-binding-http/display.groovy
diff --git a/e2e/yaks/common/kamelet-binding-http/kamelet.feature b/e2e/yaks/common/kamelet-binding-http/kamelet.feature
new file mode 100644
index 000000000..443082387
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-http/kamelet.feature
@@ -0,0 +1,11 @@
+Feature: Camel K can bind Kamelets via HTTP
+
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
+  Scenario: Binding to a HTTP URI should use CloudEvents
+    Given Camel-K integration display is running
+    Then Camel-K integration display should print type: org.apache.camel.event
+    Then Camel-K integration display should print Hello
diff --git a/e2e/yaks/common/kamelet-binding/timer-source-binding-display.yaml b/e2e/yaks/common/kamelet-binding-http/timer-source-binding-display.yaml
similarity index 100%
rename from e2e/yaks/common/kamelet-binding/timer-source-binding-display.yaml
rename to e2e/yaks/common/kamelet-binding-http/timer-source-binding-display.yaml
diff --git a/e2e/yaks/common/kamelet-binding-http/timer-source.kamelet.yaml b/e2e/yaks/common/kamelet-binding-http/timer-source.kamelet.yaml
new file mode 100644
index 000000000..a08cecfbb
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-http/timer-source.kamelet.yaml
@@ -0,0 +1,54 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+apiVersion: camel.apache.org/v1alpha1
+kind: Kamelet
+metadata:
+  name: timer-source
+  labels:
+    camel.apache.org/kamelet.type: "source"
+spec:
+  definition:
+    title: "Timer"
+    description: "Produces periodic events with a custom payload"
+    required:
+      - message
+    properties:
+      period:
+        title: Period
+        description: The time interval between two events
+        type: integer
+        default: 1000
+      message:
+        title: Message
+        description: The message to generate
+        type: string
+  types:
+    out:
+      mediaType: application/json
+      schema:
+        id: text.camel.apache.org
+        type: string
+  flow:
+    from:
+      uri: timer:tick
+      parameters:
+        period: "{{period}}"
+      steps:
+        - set-body:
+            constant: "{{message}}"
+        - to: "kamelet:sink"
diff --git a/e2e/yaks/common/kamelet-binding-http/yaks-config.yaml b/e2e/yaks/common/kamelet-binding-http/yaks-config.yaml
new file mode 100644
index 000000000..de3709b89
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-http/yaks-config.yaml
@@ -0,0 +1,30 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+config:
+  namespace:
+    temporary: true
+pre:
+- name: installation
+  run: |
+    kamel install -n $YAKS_NAMESPACE
+
+    kubectl apply -f timer-source.kamelet.yaml -n $YAKS_NAMESPACE
+
+    kamel run display.groovy -w -n $YAKS_NAMESPACE
+    cat timer-source-binding-display.yaml | sed  's/{namespace}/'"${YAKS_NAMESPACE}"'/' | kubectl apply -n $YAKS_NAMESPACE -f -
+    kubectl wait kameletbinding timer-source-binding-display --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
diff --git a/e2e/yaks/common/kamelet-binding/kamelet.feature b/e2e/yaks/common/kamelet-binding/kamelet.feature
index 4234a3b9c..3cf777569 100644
--- a/e2e/yaks/common/kamelet-binding/kamelet.feature
+++ b/e2e/yaks/common/kamelet-binding/kamelet.feature
@@ -1,10 +1,10 @@
 Feature: Camel K can bind Kamelets
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Running integration using a simple Kamelet with KameletBinding
     Given Camel-K integration logger-sink-binding is running
     Then Camel-K integration logger-sink-binding should print message: Hello Kamelets
-
-  Scenario: Binding to a HTTP URI should use CloudEvents
-    Given Camel-K integration display is running
-    Then Camel-K integration display should print type: org.apache.camel.event
-    Then Camel-K integration display should print Hello
diff --git a/e2e/yaks/common/kamelet-binding/yaks-config.yaml b/e2e/yaks/common/kamelet-binding/yaks-config.yaml
index 1e0370b0c..a75bafe41 100644
--- a/e2e/yaks/common/kamelet-binding/yaks-config.yaml
+++ b/e2e/yaks/common/kamelet-binding/yaks-config.yaml
@@ -33,7 +33,3 @@ pre:
 
     kubectl wait kameletbinding timer-source-binding --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
     kubectl wait kameletbinding logger-sink-binding --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
-
-    kamel run display.groovy -w -n $YAKS_NAMESPACE
-    cat timer-source-binding-display.yaml | sed  's/{namespace}/'"${YAKS_NAMESPACE}"'/' | kubectl apply -n $YAKS_NAMESPACE -f -
-    kubectl wait kameletbinding timer-source-binding-display --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
diff --git a/e2e/yaks/common/kamelet-no-properties/kamelet-no-props.feature b/e2e/yaks/common/kamelet-no-properties/kamelet-no-props.feature
index 04806ef1f..29ab6d6bb 100644
--- a/e2e/yaks/common/kamelet-no-properties/kamelet-no-props.feature
+++ b/e2e/yaks/common/kamelet-no-properties/kamelet-no-props.feature
@@ -3,6 +3,9 @@ Feature: Kamelet may have no properties
   Background:
     Given Disable auto removal of Camel-K resources
     Given Disable auto removal of Kamelet resources
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
 
   Scenario: Create Kamelet
     Given create Kamelet no-props-source with flow
diff --git a/e2e/yaks/common/kamelet-steps/steps-binding.feature b/e2e/yaks/common/kamelet-steps/steps-binding.feature
index f09994597..4166cf412 100644
--- a/e2e/yaks/common/kamelet-steps/steps-binding.feature
+++ b/e2e/yaks/common/kamelet-steps/steps-binding.feature
@@ -1,5 +1,10 @@
 Feature: KameletBindings can have multiple processing steps
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Data is transformed by the steps
     Given Camel-K integration steps-binding is running
     Then Camel-K integration steps-binding should print Hello Apache Camel
diff --git a/e2e/yaks/common/kamelet/kamelet.feature b/e2e/yaks/common/kamelet/kamelet.feature
index a3d92177b..5e2c8963c 100644
--- a/e2e/yaks/common/kamelet/kamelet.feature
+++ b/e2e/yaks/common/kamelet/kamelet.feature
@@ -1,5 +1,10 @@
 Feature: Camel K can run Kamelets
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Integrations can use multiple kamelets
     Given Camel-K integration source-sink is running
     Then Camel-K integration source-sink should print nice echo: Camel K
diff --git a/e2e/yaks/common/knative-sinkbinding/sinkbinding.feature b/e2e/yaks/common/knative-sinkbinding/sinkbinding.feature
index 842ee44a7..6af9673e6 100644
--- a/e2e/yaks/common/knative-sinkbinding/sinkbinding.feature
+++ b/e2e/yaks/common/knative-sinkbinding/sinkbinding.feature
@@ -1,5 +1,10 @@
 Feature: Camel K can run source in sinkbinding mode
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Integration gets the message from the sinkbinding source
     Given Camel-K integration receiver is running
     Then Camel-K integration receiver should print HELLO SINKBINDING
diff --git a/e2e/yaks/common/knative/sources.feature b/e2e/yaks/common/knative/sources.feature
index db0edbeb4..0257e8a97 100644
--- a/e2e/yaks/common/knative/sources.feature
+++ b/e2e/yaks/common/knative/sources.feature
@@ -1,5 +1,10 @@
 Feature: Camel K can run latest released Knative CamelSource
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Integration gets the message from the source
     Given Camel-K integration receiver is running
     Then Camel-K integration receiver should print MagicString!
diff --git a/pkg/metadata/metadata_http_test.go b/pkg/metadata/metadata_http_test.go
index a0ec2bbc7..3f653acdf 100644
--- a/pkg/metadata/metadata_http_test.go
+++ b/pkg/metadata/metadata_http_test.go
@@ -57,6 +57,7 @@ func TestHttpOnlyJavaSource(t *testing.T) {
 			from("direct:bots/cippa").to("log:stash");
 			from("netty-http:uri").to("log:stash");
 			from("seda:path").to("log:stash");
+			from("kamelet:source").to("log:sink");
 		`,
 		},
 		Language: v1.LanguageJavaSource,
diff --git a/pkg/util/kubernetes/factory_test.go b/pkg/util/kubernetes/factory_test.go
new file mode 100644
index 000000000..1bda9f343
--- /dev/null
+++ b/pkg/util/kubernetes/factory_test.go
@@ -0,0 +1,136 @@
+/*
+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 kubernetes
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	v1 "k8s.io/api/core/v1"
+)
+
+func TestValidTolerations(t *testing.T) {
+	validTolerations := [][]string{
+		{"key=value:NoSchedule"},
+		{"key=value:NoExecute"},
+		{"key=value:PreferNoSchedule"},
+		{"key=value:NoSchedule:120"},
+		{"key=value:NoExecute:120"},
+		{"key=value:PreferNoSchedule:120"},
+		{"existKey:NoSchedule"},
+		{"existKey:NoExecute"},
+		{"existKey:PreferNoSchedule"},
+		{"existKey:NoSchedule:120"},
+		{"existKey:NoExecute:120"},
+		{"existKey:PreferNoSchedule:120"},
+	}
+	for _, vd := range validTolerations {
+		_, err := NewTolerations(vd)
+		assert.Nil(t, err)
+	}
+}
+
+func TestInvalidTolerations(t *testing.T) {
+	validTolerations := [][]string{
+		{"key-NoSchedule"},
+		{"key=value:Something"},
+		{"key@wrong=value:PreferNoSchedule"},
+		{"key=value%wrong:NoSchedule:120"},
+		{"existKey"},
+		{"existKey:"},
+		{"existKey:Something"},
+		{"existKey:PreferNoSchedule:something"},
+	}
+	for _, vd := range validTolerations {
+		_, err := NewTolerations(vd)
+		assert.NotNil(t, err)
+	}
+}
+
+func TestValueTolerations(t *testing.T) {
+	tolerations := []string{
+		"key=value:NoSchedule",
+		"key=value:NoExecute:120",
+		"existKey:PreferNoSchedule",
+		"existKey:NoSchedule:120",
+	}
+	toleration, err := NewTolerations(tolerations)
+	assert.Nil(t, err)
+	assert.Equal(t, 4, len(toleration))
+
+	assert.Equal(t, "key", toleration[0].Key)
+	assert.Equal(t, v1.TolerationOpEqual, toleration[0].Operator)
+	assert.Equal(t, "value", toleration[0].Value)
+	assert.Equal(t, v1.TaintEffectNoSchedule, toleration[0].Effect)
+
+	assert.Equal(t, "key", toleration[1].Key)
+	assert.Equal(t, v1.TolerationOpEqual, toleration[1].Operator)
+	assert.Equal(t, "value", toleration[1].Value)
+	assert.Equal(t, v1.TaintEffectNoExecute, toleration[1].Effect)
+	assert.Equal(t, int64(120), *toleration[1].TolerationSeconds)
+
+	assert.Equal(t, "existKey", toleration[2].Key)
+	assert.Equal(t, v1.TolerationOpExists, toleration[2].Operator)
+	assert.Equal(t, "", toleration[2].Value)
+	assert.Equal(t, v1.TaintEffectPreferNoSchedule, toleration[2].Effect)
+
+	assert.Equal(t, "existKey", toleration[3].Key)
+	assert.Equal(t, v1.TolerationOpExists, toleration[3].Operator)
+	assert.Equal(t, "", toleration[3].Value)
+	assert.Equal(t, v1.TaintEffectNoSchedule, toleration[3].Effect)
+	assert.Equal(t, int64(120), *toleration[3].TolerationSeconds)
+}
+
+func TestValidNodeSelectors(t *testing.T) {
+	validNodeSelectors := [][]string{
+		{"key1=value"},
+		{"kubernetes.io/hostname=worker0"},
+		{"disktype=ssd"},
+		{"key=path-to-value"},
+		{"keyNum=123"},
+	}
+	for _, vds := range validNodeSelectors {
+		_, err := NewNodeSelectors(vds)
+		assert.Nil(t, err)
+	}
+}
+
+func TestInvalidNodeSelectors(t *testing.T) {
+	validNodeSelectors := [][]string{
+		{"key1"},
+		{"kubernetes.io@hostname=worker0"},
+		{"key=path/to/value"},
+	}
+	for _, vds := range validNodeSelectors {
+		_, err := NewNodeSelectors(vds)
+		assert.NotNil(t, err)
+	}
+}
+
+func TestValueNodeSelectors(t *testing.T) {
+	nodeSelectorsArray := []string{
+		"key=value",
+		"kubernetes.io/hostname=worker0",
+	}
+	nodeSelectors, err := NewNodeSelectors(nodeSelectorsArray)
+	assert.Nil(t, err)
+	assert.Equal(t, 2, len(nodeSelectors))
+
+	assert.Equal(t, "value", nodeSelectors["key"])
+	assert.Equal(t, "worker0", nodeSelectors["kubernetes.io/hostname"])
+}
diff --git a/pkg/util/reference/reference_test.go b/pkg/util/reference/reference_test.go
index f13c5d846..ca5be0bc7 100644
--- a/pkg/util/reference/reference_test.go
+++ b/pkg/util/reference/reference_test.go
@@ -33,6 +33,7 @@ func TestExpressions(t *testing.T) {
 		error         bool
 		ref           corev1.ObjectReference
 		stringRef     string
+		properties    map[string]string
 	}{
 		{
 			name:  "lowercase:source",
@@ -123,6 +124,37 @@ func TestExpressions(t *testing.T) {
 			},
 			stringRef: "postgres.org/v1alpha1:PostgreSQL:ns1/db",
 		},
+		{
+			name: "postgres.org/v1alpha1:PostgreSQL:ns1/db?user=user1&password=pwd2&host=192.168.2.2&special=%201&special2=a=1",
+			ref: corev1.ObjectReference{
+				APIVersion: "postgres.org/v1alpha1",
+				Kind:       "PostgreSQL",
+				Namespace:  "ns1",
+				Name:       "db",
+			},
+			stringRef: "postgres.org/v1alpha1:PostgreSQL:ns1/db",
+			properties: map[string]string{
+				"user":     "user1",
+				"password": "pwd2",
+				"host":     "192.168.2.2",
+				"special":  " 1",
+				"special2": "a=1",
+			},
+		},
+		{
+			name: "source?a=b&b=c&d=e",
+			ref: corev1.ObjectReference{
+				Kind:       "Kamelet",
+				APIVersion: "camel.apache.org/v1alpha1",
+				Name:       "source",
+			},
+			stringRef: "camel.apache.org/v1alpha1:Kamelet:source",
+			properties: map[string]string{
+				"a": "b",
+				"b": "c",
+				"d": "e",
+			},
+		},
 	}
 
 	for i, tc := range tests {
@@ -143,6 +175,10 @@ func TestExpressions(t *testing.T) {
 				asString, err2 := converter.ToString(ref)
 				assert.NoError(t, err2)
 
+				props, err3 := converter.PropertiesFromString(tc.name)
+				assert.NoError(t, err3)
+				assert.Equal(t, tc.properties, props)
+
 				assert.NoError(t, err)
 				assert.Equal(t, tc.ref, ref)
 				assert.Equal(t, tc.stringRef, asString)
diff --git a/pkg/util/uri/uri_test.go b/pkg/util/uri/uri_test.go
index ae22ff8a9..19dcdcaa1 100644
--- a/pkg/util/uri/uri_test.go
+++ b/pkg/util/uri/uri_test.go
@@ -142,3 +142,40 @@ func TestAppendParameters(t *testing.T) {
 		})
 	}
 }
+
+func TestCamelURIFormat(t *testing.T) {
+	tests := []struct {
+		uri     string
+		invalid bool
+	}{
+		{
+			uri: "knative:channnel",
+		},
+		{
+			uri: "atomix-value:",
+		},
+		{
+			uri: "aws-ec2:",
+		},
+		{
+			uri: "coap+tcp:",
+		},
+		{
+			uri: "solrCloud:",
+		},
+		{
+			uri:     "PostgreSQL:db",
+			invalid: true,
+		},
+		{
+			uri:     "postgres.org/v1alpha1:PostgreSQL:db",
+			invalid: true,
+		},
+	}
+
+	for i, tc := range tests {
+		t.Run(fmt.Sprintf("%d-%s", i, tc.uri), func(t *testing.T) {
+			assert.Equal(t, !tc.invalid, HasCamelURIFormat(tc.uri))
+		})
+	}
+}