Repository: google/kf (go)

Issue:
Virtualservices aren't cleaned up when routeclaims are deleted
<!-- If you need to report a security issue with kf, send an email to kf-security@google.com. -->

## What version of kf?

develop (e75aff2)

## Expected Behavior

kf delete-route (or kubectl delete routeclaim) should cause the associated virtualservice to be deleted.

## Actual Behavior

Virtualservices in the kf namespace are not deleted and remain even after the route and space are deleted.


## Steps to Reproduce the Problem
1. Create a space `myspace`
2. `kf create-route example.com --hostname myhost`
3. `kf delete-route example.com --hostname myhost` or `kf delete-space myspace`
4. `kubectl get virtualservice -n kf` to see that the virtualservice still exists

Pull request:
Move virtualservices to same namespace as routeclaims
<!-- Include the issue number below -->
Fixes #802

## Proposed Changes

* Virtualservices associated with routeclaims/routes currently live in the `kf` namespace. Moving them to the same namespace as the routeclaim enables the virtualservices to be cleaned up properly when a routeclaim or namespace is deleted.

## Release Notes

<!-- kf follows the Keep A Changelog standard for release notes.

https://keepachangelog.com/en/1.0.0/

Changelog entries should be one per line and start with one of the following
words:

`Added` for new features.
`Changed` for changes in existing functionality.
`Deprecated` for soon-to-be removed features.
`Removed` for now removed features.
`Fixed` for any bug fixes.
`Security` in case of vulnerabilities.

If one of the changes is breaking include that as a second word e.g.

  Removed BREAKING support for manifests v1
-->

```release-note
Changed namespace for kf-managed virtualservices
```


Patch:
diff --git a/ci/concourse/pipelines/ci-image.yml b/ci/concourse/pipelines/ci-image.yml
new file mode 100644
index 0000000..d76855d
--- /dev/null
+++ b/ci/concourse/pipelines/ci-image.yml
@@ -0,0 +1,60 @@
+# Copyright 2019 Google LLC
+#
+# Licensed 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
+#
+#     https://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.
+
+---
+# Store these in your credential manager.
+vars:
+
+# ci config vars
+- &ci_git_uri ((ci_git_uri))
+- &ci_git_branch ((ci_git_branch))
+- &ci_image_uri ((ci_image_uri))
+
+# ---- end vars ----
+
+resources:
+- name: ci-image-src
+  type: git
+  source:
+    branch: *ci_git_branch
+    uri: *ci_git_uri
+    paths:
+    - ci/concourse/image
+- name: ci-image-kf-source
+  type: docker-image
+  source:
+    repository: gcr.io/kf-source/ci-image
+    username: _json_key
+    password: ((kf_source_service_account_json))
+- name: ci-image-kf-int
+  type: docker-image
+  source:
+    repository: gcr.io/kf-int/ci-image
+    username: _json_key
+    password: ((kf_int_service_account_json))
+
+jobs:
+# builds the ci image for later steps/other pipelines
+- name: ci-image
+  serial: true
+  plan:
+  - get: ci-image-src
+    trigger: true
+  - aggregate:
+    - put: ci-image-kf-source
+      params:
+        build: ci-image-src/ci/concourse/image
+    - put: ci-image-kf-int
+      params:
+        build: ci-image-src/ci/concourse/image
diff --git a/ci/concourse/pipelines/develop.yml b/ci/concourse/pipelines/develop.yml
new file mode 100644
index 0000000..da15f86
--- /dev/null
+++ b/ci/concourse/pipelines/develop.yml
@@ -0,0 +1,237 @@
+# Copyright 2019 Google LLC
+#
+# Licensed 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
+#
+#     https://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.
+
+vars:
+# ci config vars
+- &ci_image_uri ((ci_image_uri))
+- &service_account_json ((service_account_json))
+
+# git config vars
+- &git_uri ((git_uri))
+- &git_branch develop
+
+# k8s config
+- &gcp_project_id ((gcp_project_id))
+
+# go config
+- &go_proxy https://proxy.golang.org
+- &go_sum_db sum.golang.org
+
+# build vars
+- &release_service_account_json ((release_service_account_json))
+- &release_project ((release_project))
+- &ko_docker_repo ((ko_docker_repo))
+
+templates:
+- &ci-image-source
+  repository: *ci_image_uri
+  username: _json_key
+  password: *service_account_json
+
+resources:
+- name: kf
+  type: git
+  source:
+    uri: *git_uri
+    branch: *git_branch
+
+jobs:
+- name: test
+  plan:
+  - get: kf
+    trigger: true
+  - aggregate:
+    - task: check-go-tidy
+      timeout: 10m
+      config:
+        platform: linux
+        image_resource:
+          type: docker-image
+          source: *ci-image-source
+        inputs:
+        - name: kf
+        params:
+          GOPROXY: *go_proxy
+          GOSUMDB: *go_sum_db
+          SERVICE_ACCOUNT_JSON: *service_account_json
+        run:
+          path: bash
+          args:
+          - -exc
+          - |
+            pushd kf
+              ./hack/tidy.sh
+              ./hack/check-clean-git-state.sh
+            popd
+    - task: check-linters
+      timeout: 10m
+      config:
+        platform: linux
+        image_resource:
+          type: docker-image
+          source: *ci-image-source
+        inputs:
+        - name: kf
+        params:
+          GOPROXY: *go_proxy
+          GOSUMDB: *go_sum_db
+          SERVICE_ACCOUNT_JSON: *service_account_json
+        run:
+          path: bash
+          args:
+          - -exc
+          - |
+            pushd kf
+              ./hack/check-linters.sh
+              ./hack/tidy.sh
+              ./hack/check-clean-git-state.sh
+            popd
+    - task: check-go-generate
+      timeout: 10m
+      config:
+        platform: linux
+        image_resource:
+          type: docker-image
+          source: *ci-image-source
+        inputs:
+        - name: kf
+        params:
+          GOPROXY: *go_proxy
+          GOSUMDB: *go_sum_db
+          SERVICE_ACCOUNT_JSON: *service_account_json
+        run:
+          path: bash
+          args:
+          - -exc
+          - |
+            pushd kf
+              ./hack/go-generate.sh
+              ./hack/tidy.sh
+              ./hack/check-clean-git-state.sh
+            popd
+    - task: check-code-generator
+      timeout: 10m
+      config:
+        platform: linux
+        image_resource:
+          type: docker-image
+          source: *ci-image-source
+        inputs:
+        - name: kf
+        params:
+          GOPROXY: *go_proxy
+          GOSUMDB: *go_sum_db
+          SERVICE_ACCOUNT_JSON: *service_account_json
+        run:
+          path: bash
+          args:
+          - -exc
+          - |
+            pushd kf
+              ./hack/update-codegen.sh
+              ./hack/tidy.sh
+              ./hack/check-clean-git-state.sh
+            popd
+    - task: check-vendor-license
+      timeout: 10m
+      config:
+        platform: linux
+        image_resource:
+          type: docker-image
+          source: *ci-image-source
+        inputs:
+        - name: kf
+        params:
+          GOPROXY: *go_proxy
+          GOSUMDB: *go_sum_db
+          SERVICE_ACCOUNT_JSON: *service_account_json
+        run:
+          path: bash
+          args:
+          - -exc
+          - |
+            pushd kf
+              ./hack/update-vendor-license.sh
+              ./hack/tidy.sh
+              ./hack/check-clean-git-state.sh
+            popd
+    - task: check-go-build
+      timeout: 10m
+      config:
+        platform: linux
+        image_resource:
+          type: docker-image
+          source: *ci-image-source
+        inputs:
+        - name: kf
+        params:
+          GOPROXY: *go_proxy
+          GOSUMDB: *go_sum_db
+          SERVICE_ACCOUNT_JSON: *service_account_json
+        run:
+          path: bash
+          args:
+          - -exc
+          - |
+            pushd kf
+              ./hack/go-build.sh
+              ./hack/tidy.sh
+              ./hack/check-clean-git-state.sh
+            popd
+    - task: unit
+      timeout: 10m
+      config:
+        platform: linux
+        image_resource:
+          type: docker-image
+          source: *ci-image-source
+        inputs:
+        - name: kf
+        run:
+          path: bash
+          args:
+          - -exc
+          - |
+            pushd kf
+              ./hack/unit-test.sh
+              ./hack/tidy.sh
+              ./hack/check-clean-git-state.sh
+            popd
+        params:
+          GOPROXY: *go_proxy
+          GOSUMDB: *go_sum_db
+    - task: build-cli
+      config:
+        platform: linux
+        image_resource:
+          type: docker-image
+          source: *ci-image-source
+        params:
+          GOPROXY: *go_proxy
+          GOSUMDB: *go_sum_db
+          SERVICE_ACCOUNT_JSON: *release_service_account_json
+          GCP_PROJECT_ID: *release_project
+        inputs:
+        - name: kf
+        run:
+          path: bash
+          args:
+          - -exc
+          - |
+            artifacts=`pwd`/artifacts
+            pushd kf
+              ./hack/build.sh
+              ./hack/tidy.sh
+              ./hack/check-clean-git-state.sh
+            popd
diff --git a/ci/concourse/pipelines/kf-pipeline.yml b/ci/concourse/pipelines/kf-pipeline.yml
deleted file mode 100644
index d6c6d83..0000000
--- a/ci/concourse/pipelines/kf-pipeline.yml
+++ /dev/null
@@ -1,151 +0,0 @@
-# Copyright 2019 Google LLC
-#
-# Licensed 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
-#
-#     https://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.
-
----
-# Store these in your credential manager.
-vars:
-
-# pull request vars
-- &github_repo ((github_repo))
-- &github_access_token ((github_access_token))
-- &github_webhook_token ((github_webhook_token))
-
-# ci config vars
-- &ci_git_uri ((ci_git_uri))
-- &ci_git_branch ((ci_git_branch))
-- &ci_image_uri ((ci_image_uri))
-- &fly_user ((fly_user))
-- &fly_password ((fly_password))
-- &fly_target ((fly_target))
-- &fly_url ((fly_url))
-- &fly_team ((fly_team))
-- &service_account_json ((service_account_json))
-
-# nightly build vars
-- &release_service_account_json ((release_service_account_json))
-- &ko_docker_release_repo ((ko_docker_release_repo))
-- &gcp_project_id ((gcp_project_id))
-
-# kf-release
-- &release_major_version ((release_major_version))
-- &release_minor_version ((release_minor_version))
-- &release_patch_version ((release_patch_version))
-
-# ---- end vars ----
-
-resource_types:
-- name: pull-request
-  type: docker-image
-  source:
-    repository: m0pt0pmatt/github-pr-resource
-
-resources:
-- name: ci-image-src
-  type: git
-  source:
-    branch: *ci_git_branch
-    uri: *ci_git_uri
-    paths:
-    - ci/concourse/image
-- name: ci-image
-  type: docker-image
-  source: &ci-image-source
-    repository: *ci_image_uri
-    username: _json_key
-    password: *service_account_json
-- name: pr-cleanup-trigger
-  type: time
-  source:
-    start: 0:00
-    stop: 1:00
-    location: America/Los_Angeles
-
-jobs:
-# builds the ci image for later steps/other pipelines
-- name: ci-image
-  plan:
-  - get: ci-image-src
-    trigger: true
-  - put: ci-image
-    params:
-      build: ci-image-src/ci/concourse/image
-
-# destroys any leftover pr pipelines
-- name: pr-cleanup
-  plan:
-  - get: pr-cleanup-trigger
-    trigger: true
-  - task: cleanup
-    config:
-      platform: linux
-      image_resource:
-        type: docker-image
-        source: *ci-image-source
-      params:
-        FLY_TARGET: *fly_target
-        FLY_USER: *fly_user
-        FLY_PASSWORD: *fly_password
-        FLY_URL: *fly_url
-        FLY_TEAM: *fly_team
-        GITHUB_REPO: *github_repo
-        GITHUB_TOKEN: *github_access_token
-      run:
-        dir: ci-pipelines-src
-        path: bash
-        args:
-        - -exc
-        - |
-          # Download the fly executable for the given concourse server
-          wget "${FLY_URL}/api/v1/cli?arch=amd64&platform=linux" -O fly
-          chmod +x ./fly
-
-          # Login
-          set +x
-          echo fly login...
-          ./fly login\
-            -t "$FLY_TARGET" \
-            -c "$FLY_URL" \
-            -n "$FLY_TEAM" \
-            -u "$FLY_USER" \
-            -p "$FLY_PASSWORD"
-          set -x
-
-          # Query Github for open PR numbers
-          prs=$(mktemp)
-          set +x
-          curl -H "Authorization: token ${GITHUB_TOKEN}" \
-            -L "https://api.github.com/repos/${GITHUB_REPO}/pulls?state=open" \
-            | jq .[].number \
-            | sort \
-            | uniq > $prs
-          set -x
-
-          # Query concourse for pipelines
-          pipelines=$(mktemp)
-          ./fly -t "$FLY_TARGET" pipelines \
-            | awk '{print $1}' \
-            | grep '^[0-9]*$'  \
-            | sort \
-            | uniq > $pipelines
-
-          # Create a list of pipelines which can be safely removed.
-          extra=$(mktemp)
-          comm -13 $prs $pipelines > $extra
-
-          echo "removing the following pipelines as their PRs are no longer open:"
-          cat $extra
-          for extra_pipeline in $(cat $extra); do
-            ./fly -t "$FLY_TARGET" destroy-pipeline -n -p $extra_pipeline
-          done
-          rm -f $prs $pipelines $extra
diff --git a/hack/concourse-login.sh b/hack/concourse-login.sh
deleted file mode 100755
index b226c29..0000000
--- a/hack/concourse-login.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env bash
-
-# Copyright 2019 Google LLC
-#
-# Licensed 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
-#
-#     https://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.
-
-set -eu
-
-readonly target=${1:?Error: Please supply a target}
-
-concourse_url=https://sunrisecafe.ci.cloud-graphite.com/
-team="-n kf"
-
-echo fly login -t $target -c $concourse_url $team
-fly login -t $target -c $concourse_url $team
diff --git a/hack/concourse-pr-destroy.sh b/hack/concourse-pr-destroy.sh
deleted file mode 100755
index cd7a268..0000000
--- a/hack/concourse-pr-destroy.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env bash
-
-# Copyright 2019 Google LLC
-#
-# Licensed 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
-#
-#     https://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.
-
-set -eu
-
-readonly target=${1:?Error: Please supply a target}
-shift
-readonly pipeline=${1:?Error: Please supply a pr number}
-shift
-
-set -x
-
-fly -t $target destroy-pipeline -p $pipeline
diff --git a/hack/concourse-pr-set.sh b/hack/concourse-pr-set.sh
deleted file mode 100755
index 2901917..0000000
--- a/hack/concourse-pr-set.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env bash
-
-# Copyright 2019 Google LLC
-#
-# Licensed 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
-#
-#     https://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.
-
-set -e
-
-usage="Usage: $0 -t fly-target -p pull-request -u git-uri -b git-branch"
-
-while getopts t:p:u:b: o
-do case "$o" in
-  t)  target="$OPTARG";;
-  p)  pipeline="$OPTARG";;
-  u)  uri="$OPTARG";;
-  b)  branch="$OPTARG";;
-  [?])  echo $usage >&2 && exit 1;;
-  esac
-done
-
-[ "$target" != "" ] || (echo "Error: Please supply a target. $usage" >&2 && exit 1)
-[ "$pipeline" != "" ] || (echo "Error: Please supply a pipeline. $usage" >&2 && exit 1)
-[ "$uri" != "" ] || (echo "Error: Please supply a uri. $usage" >&2 && exit 1)
-[ "$branch" != "" ] || (echo "Error: Please supply a branch. $usage" >&2 && exit 1)
-
-set -x
-
-config=ci/concourse/pipelines/pr-pipeline.yml
-
-fly -t $target set-pipeline -p $pipeline -c $config -v pr_number=$pipeline -v git_uri=$uri -v git_branch=$branch
diff --git a/hack/concourse-set.sh b/hack/concourse-set.sh
deleted file mode 100755
index 1465a73..0000000
--- a/hack/concourse-set.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-# Copyright 2019 Google LLC
-#
-# Licensed 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
-#
-#     https://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.
-
-set -eu
-
-readonly target=${1:?Error: Please supply a target}
-shift
-
-set -x
-
-pipeline=kf
-config=ci/concourse/pipelines/kf-pipeline.yml
-
-fly -t $target set-pipeline -p $pipeline -c $config
diff --git a/pkg/apis/kf/v1alpha1/utils.go b/pkg/apis/kf/v1alpha1/utils.go
index 31a6970..4f6267f 100644
--- a/pkg/apis/kf/v1alpha1/utils.go
+++ b/pkg/apis/kf/v1alpha1/utils.go
@@ -109,7 +109,9 @@ func (ci *conditionImpl) MarkReconciliationError(action string, err error) error
 	msg := fmt.Sprintf("Error occurred while %s %s: %s", action, ci.childType, err)
 
 	switch {
-	case apierrs.IsConflict(err):
+	case apierrs.IsConflict(err) || apierrs.IsAlreadyExists(err):
+		// Both of these are 409 errors returned by different implementations of
+		// Kubernetes controllers.
 		ci.manager.MarkUnknown(ci.destination, "CacheOutdated", msg)
 
 		// In the future, additional retryable errors can be added here if
diff --git a/pkg/reconciler/route/controller.go b/pkg/reconciler/route/controller.go
index 85c8788..e777934 100644
--- a/pkg/reconciler/route/controller.go
+++ b/pkg/reconciler/route/controller.go
@@ -67,7 +67,7 @@ func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl
 	)
 
 	vsInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
-		FilterFunc: FilterVSWithNamespace(v1alpha1.KfNamespace),
+		FilterFunc: FilterVSManagedByKf(),
 		Handler:    controller.HandleAll(logError(logger, EnqueueRoutesOfVirtualService(enqueue, c.routeLister))),
 	})
 
@@ -143,13 +143,13 @@ func BuildEnqueuer(enqueue func(interface{})) func(interface{}) error {
 	}
 }
 
-// FilterVSWithNamespace makes it simple to create FilterFunc's for use with
-// cache.FilteringResourceEventHandler that filter based on a namespace and if
-// the type is a VirtualService.
-func FilterVSWithNamespace(namespace string) func(obj interface{}) bool {
+// FilterVSManagedByKf makes it simple to create FilterFunc's for use with
+// cache.FilteringResourceEventHandler that filter based on the
+// "app.kubernetes.io/managed-by": "kf" label and if the type is a VirtualService.
+func FilterVSManagedByKf() func(obj interface{}) bool {
 	return func(obj interface{}) bool {
 		if object, ok := obj.(metav1.Object); ok {
-			if namespace == object.GetNamespace() {
+			if "kf" == object.GetLabels()[v1alpha1.ManagedByLabel] {
 				_, ok := obj.(*networking.VirtualService)
 				return ok
 			}
diff --git a/pkg/reconciler/route/reconciler.go b/pkg/reconciler/route/reconciler.go
index 2fa9213..1e1b5a1 100644
--- a/pkg/reconciler/route/reconciler.go
+++ b/pkg/reconciler/route/reconciler.go
@@ -103,7 +103,7 @@ func (r *Reconciler) ApplyChanges(
 	if len(claims) == 0 {
 		err := r.SharedClientSet.
 			Networking().
-			VirtualServices(v1alpha1.KfNamespace).
+			VirtualServices(namespace).
 			Delete(v1alpha1.GenerateName(
 				fields.Hostname,
 				fields.Domain,
@@ -141,13 +141,13 @@ func (r *Reconciler) ApplyChanges(
 	}
 
 	actual, err := r.virtualServiceLister.
-		VirtualServices(v1alpha1.KfNamespace).
+		VirtualServices(namespace).
 		Get(desired.Name)
 	if errors.IsNotFound(err) {
 		// VirtualService doesn't exist, make one.
 		if _, err := r.SharedClientSet.
 			Networking().
-			VirtualServices(v1alpha1.KfNamespace).
+			VirtualServices(namespace).
 			Create(desired); err != nil {
 			return err
 		}
diff --git a/pkg/reconciler/route/resources/virtual_service.go b/pkg/reconciler/route/resources/virtual_service.go
index 7c54041..013c18b 100644
--- a/pkg/reconciler/route/resources/virtual_service.go
+++ b/pkg/reconciler/route/resources/virtual_service.go
@@ -81,7 +81,7 @@ func MakeVirtualService(ctx context.Context, claims []*v1alpha1.RouteClaim, rout
 				hostname,
 				domain,
 			),
-			Namespace: v1alpha1.KfNamespace,
+			Namespace: namespace,
 			Labels:    labels,
 			Annotations: map[string]string{
 				"domain":   domain,


Test patch:
diff --git a/pkg/kf/commands/service-bindings/integration_test.go b/pkg/kf/commands/service-bindings/integration_test.go
index 67a2f5f..9dc65c5 100644
--- a/pkg/kf/commands/service-bindings/integration_test.go
+++ b/pkg/kf/commands/service-bindings/integration_test.go
@@ -114,6 +114,7 @@ func TestIntegration_VcapServices(t *testing.T) {
 func TestIntegration_VcapServices_customBindingName(t *testing.T) {
 	checkClusterStatus(t)
 	appName := fmt.Sprintf("integration-binding-app-%d", time.Now().UnixNano())
+	bindingName := fmt.Sprintf("bind-%x", time.Now().UnixNano())
 	appPath := "./samples/apps/envs"
 	RunKfTest(t, func(ctx context.Context, t *testing.T, kf *Kf) {
 		withServiceBroker(ctx, t, kf, func(ctx context.Context) {
@@ -121,7 +122,7 @@ func TestIntegration_VcapServices_customBindingName(t *testing.T) {
 				withApp(ctx, t, kf, appName, appPath, false, func(ctx context.Context) {
 					serviceInstanceName := ServiceInstanceFromContext(ctx)
 					appName := AppFromContext(ctx)
-					kf.BindService(ctx, appName, serviceInstanceName, "--binding-name", "my-binding")
+					kf.BindService(ctx, appName, serviceInstanceName, "--binding-name", bindingName)
 					defer kf.UnbindService(ctx, appName, serviceInstanceName) // cleanup
 
 					vcs := extractVcapServices(ctx, t, kf)
@@ -129,9 +130,9 @@ func TestIntegration_VcapServices_customBindingName(t *testing.T) {
 					expected := cfutil.VcapServicesMap{
 						ServiceClassFromContext(ctx): []cfutil.VcapService{
 							{
-								BindingName:  "my-binding",
+								BindingName:  bindingName,
 								InstanceName: ServiceInstanceFromContext(ctx),
-								Name:         "my-binding",
+								Name:         bindingName,
 								Label:        ServiceClassFromContext(ctx),
 								Tags:         []string{"fake-tag"},
 								Plan:         ServicePlanFromContext(ctx),
diff --git a/pkg/reconciler/route/controller_test.go b/pkg/reconciler/route/controller_test.go
index 190deb5..5b090ce 100644
--- a/pkg/reconciler/route/controller_test.go
+++ b/pkg/reconciler/route/controller_test.go
@@ -83,30 +83,35 @@ func TestBuildEnqueuer(t *testing.T) {
 	}
 }
 
-func TestFilterVSWithNamespace(t *testing.T) {
+func TestFilterVSManagedByKf(t *testing.T) {
 	t.Parallel()
 
-	buildVS := func(namespace string) *networking.VirtualService {
+	buildVS := func(managedBy string) *networking.VirtualService {
 		return &networking.VirtualService{
 			ObjectMeta: metav1.ObjectMeta{
-				Namespace: namespace,
+				Labels: map[string]string{
+					v1alpha1.ManagedByLabel: managedBy,
+				},
 			},
 		}
 	}
 
-	buildPod := func(namespace string) *corev1.Pod {
+	buildPod := func(managedBy string) *corev1.Pod {
 		return &corev1.Pod{
 			ObjectMeta: metav1.ObjectMeta{
-				Namespace: namespace,
+				Labels: map[string]string{
+					v1alpha1.ManagedByLabel: managedBy,
+				},
 			},
 		}
 	}
 
-	f := FilterVSWithNamespace("some-namespace")
+	f := FilterVSManagedByKf()
 	testutil.AssertEqual(t, "non metav1.Object", false, f(99))
-	testutil.AssertEqual(t, "wrong namespace", false, f(buildVS("wrong-namespace")))
-	testutil.AssertEqual(t, "correct namespace, wrong type", false, f(buildPod("some-namespace")))
-	testutil.AssertEqual(t, "correct everything", true, f(buildVS("some-namespace")))
+	testutil.AssertEqual(t, "wrong managed-by label", false, f(buildVS("not-kf")))
+	testutil.AssertEqual(t, "no managed-by label", false, f(buildVS("")))
+	testutil.AssertEqual(t, "correct label, wrong type", false, f(buildPod("kf")))
+	testutil.AssertEqual(t, "correct everything", true, f(buildVS("kf")))
 }
 
 func TestEnqueueRoutesOfVirtualService(t *testing.T) {
diff --git a/pkg/reconciler/route/reconciler_test.go b/pkg/reconciler/route/reconciler_test.go
index e139e78..02bd976 100644
--- a/pkg/reconciler/route/reconciler_test.go
+++ b/pkg/reconciler/route/reconciler_test.go
@@ -214,7 +214,7 @@ func TestReconciler_Reconcile_ApplyChanges(t *testing.T) {
 					Return(nil, nil)
 
 				f.fn.EXPECT().
-					VirtualServices(v1alpha1.KfNamespace).
+					VirtualServices("some-namespace").
 					Return(f.fvsi)
 
 				f.fvsi.EXPECT().
@@ -369,7 +369,7 @@ func TestReconciler_Reconcile_ApplyChanges(t *testing.T) {
 					Return(nil, apierrors.NewNotFound(v1alpha3.Resource("VirtualService"), "VirtualService"))
 
 				f.fn.EXPECT().
-					VirtualServices(v1alpha1.KfNamespace).
+					VirtualServices(gomock.Any()).
 					Return(f.fvsi)
 
 				f.fvsi.EXPECT().
diff --git a/pkg/reconciler/route/resources/virtual_service_test.go b/pkg/reconciler/route/resources/virtual_service_test.go
index cbd3d61..29e25ee 100644
--- a/pkg/reconciler/route/resources/virtual_service_test.go
+++ b/pkg/reconciler/route/resources/virtual_service_test.go
@@ -98,7 +98,7 @@ func TestMakeVirtualService(t *testing.T) {
 
 				testutil.AssertEqual(t, "ObjectMeta", metav1.ObjectMeta{
 					Name:      v1alpha1.GenerateName(route.Spec.Hostname, route.Spec.Domain),
-					Namespace: v1alpha1.KfNamespace,
+					Namespace: "some-namespace",
 					Labels: map[string]string{
 						resources.ManagedByLabel: "kf",
 						v1alpha1.ComponentLabel:  "virtualservice",