Repository: istio/istio (go)

Issue:
DestinationRule: subsets labels with empty values 
### Bug Description

I tested a Istio traffic management use-case I want to implement, where I am using multiple subsets. The selection of the subset should be done by a label without value:

k get deploy nginx-v1-1 -o yaml | grep -A3 labels                                                                        
      labels:
        app: svc-b
        dev/early-adopter: ""
        dev/stable: ""
        
k get deploy nginx-v1-2 -o yaml | grep -A3 labels                                                                         
      labels:
        app: svc-b
        dev/main-dev: ""
        version: v1.2

So I am using the labels `dev/main-dev`, `dev/early-adopter` and `dev/stable` to identify the subset. Using a simple "dev" label would not working in my case, because I need to apply multiple values to one deployment. When I am now creating a DestinationRule for that, traffic is not routed to the correct deployment. Traffic is still spitted 50:50 between both deployments. When I add a value to my labels, it is working correctly. 

A dr would look like this: 
```
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: dr-svc-b
  namespace: svc-b
spec:
  host: "svc-b.svc-b.svc.cluster.local"
  subsets:
  - name: main-dev
    labels:
      dev/main-dev: ""
```
```
istioctl x describe pod nginx-v1-2-596847b5bc-9nvhl 
Pod: nginx-v1-2-596847b5bc-9nvhl
   Pod Ports: 80 (nginx), 15090 (istio-proxy)
--------------------
Service: svc-b
   Port: http 80/HTTP targets pod port 80
DestinationRule: dr-svc-b for "svc-b.svc-b.svc.cluster.local"
   Matching subsets: main-dev
   No Traffic Policy
VirtualService: vs-svc-b
   1 HTTP route(s)
```

### Version

```prose
istioctl version
client version: 1.11.3
control plane version: 1.11.3
data plane version: 1.11.3 (12 proxies)
kubectl version --short
Client Version: v1.21.2
Server Version: v1.21.2-eks-0389ca3
```


### Additional Information


Target cluster context: arn:aws:eks:us-east-1:494495723522:cluster/eks-u9q42vv8

Running with the following config: 

istio-namespace: istio-system
full-secrets: false
timeout (mins): 30
include: {  }
exclude: { Namespaces: kube-system, kube-public, kube-node-lease, local-path-storage } AND { Namespaces: kube-system, kube-public, kube-node-lease, local-path-storage }
end-time: 2021-10-25 13:23:26.781095 +0200 CEST


The following Istio control plane revisions/versions were found in the cluster:
Revision default:
&version.MeshInfo{
    {
        Component: "pilot",
        Info:      version.BuildInfo{Version:"1.11.3", GitRevision:"6bda7c161d3925c48fbea3f297ffa52461893f3b", GolangVersion:"", BuildStatus:"Clean", GitTag:"1.11.3"},
    },
}

The following proxy revisions/versions were found in the cluster:
Revision default: Versions {1.11.3}


Fetching proxy logs for the following containers:

istio-system//nginx/nginx
istio-system/grafana/grafana-68cc7d6d78-bdpmp/grafana
istio-system/istio-egressgateway/istio-egressgateway-566697b759-jsdz6/istio-proxy
istio-system/istio-ingressgateway/istio-ingressgateway-7776dd578-8r7vv/istio-proxy
istio-system/istiod/istiod-b9c8c9487-g4wwv/discovery
istio-system/jaeger/jaeger-5d44bc5c5d-h9dr5/jaeger
istio-system/kiali/kiali-fd9f88575-9xdlx/kiali
istio-system/prometheus/prometheus-77b49cb997-lcnnd/prometheus-server
istio-system/prometheus/prometheus-77b49cb997-lcnnd/prometheus-server-configmap-reload
svc-a/nginx-v1-2/nginx-v1-2-74cb4b7666-58kld/istio-proxy
svc-a/nginx-v1-2/nginx-v1-2-74cb4b7666-58kld/nginx
svc-a/nginx-v1-2/nginx-v1-2-74cb4b7666-lcqcg/istio-proxy
svc-a/nginx-v1-2/nginx-v1-2-74cb4b7666-lcqcg/nginx
svc-a/nginx-v1-3/nginx-v1-3-8bf97cb5b-nsvjd/istio-proxy
svc-a/nginx-v1-3/nginx-v1-3-8bf97cb5b-nsvjd/nginx
svc-a/nginx-v1-3/nginx-v1-3-8bf97cb5b-rbzc4/istio-proxy
svc-a/nginx-v1-3/nginx-v1-3-8bf97cb5b-rbzc4/nginx
svc-a/nginx-v2-0/nginx-v2-0-5f9c6764b4-9jqst/istio-proxy
svc-a/nginx-v2-0/nginx-v2-0-5f9c6764b4-9jqst/nginx
svc-a/nginx-v2-0/nginx-v2-0-5f9c6764b4-b4m99/istio-proxy
svc-a/nginx-v2-0/nginx-v2-0-5f9c6764b4-b4m99/nginx
svc-b/nginx-v1-1/nginx-v1-1-6d95f74456-h5qdh/istio-proxy
svc-b/nginx-v1-1/nginx-v1-1-6d95f74456-h5qdh/nginx
svc-b/nginx-v1-1/nginx-v1-1-6d95f74456-tcx4d/istio-proxy
svc-b/nginx-v1-1/nginx-v1-1-6d95f74456-tcx4d/nginx
svc-b/nginx-v1-2/nginx-v1-2-596847b5bc-9nvhl/istio-proxy
svc-b/nginx-v1-2/nginx-v1-2-596847b5bc-9nvhl/nginx
svc-b/nginx-v1-2/nginx-v1-2-596847b5bc-w7gb2/istio-proxy
svc-b/nginx-v1-2/nginx-v1-2-596847b5bc-w7gb2/nginx

Fetching Istio control plane information from cluster.

Running istio analyze on all namespaces and report as below:
Analysis Report:
Info [IST0102] (Namespace default) The namespace is not enabled for Istio injection. Run 'kubectl label namespace default istio-injection=enabled' to enable it, or 'kubectl label namespace default istio-injection=disabled' to explicitly mark it as not needing injection.
Info [IST0102] (Namespace istio-system) The namespace is not enabled for Istio injection. Run 'kubectl label namespace istio-system istio-injection=enabled' to enable it, or 'kubectl label namespace istio-system istio-injection=disabled' to explicitly mark it as not needing injection.
Info [IST0118] (Service grafana.istio-system) Port name service (port: 3000, targetPort: 3000) doesn't follow the naming convention of Istio port.
Info [IST0118] (Service jaeger-collector.istio-system) Port name jaeger-collector-grpc (port: 14250, targetPort: 14250) doesn't follow the naming convention of Istio port.
Info [IST0118] (Service jaeger-collector.istio-system) Port name jaeger-collector-http (port: 14268, targetPort: 14268) doesn't follow the naming convention of Istio port.
Creating an archive at bug-report.tar.gz.
Cleaning up temporary files in /var/folders/3c/sq2jbt0j6_dc5yk3s_mshxbmdl6w_5/T/bug-report.
Done.

Pull request description:
`Instance.SubsetOf` should check if key exists
Signed-off-by: dddddai <dddwq@foxmail.com>

**Please provide a description of this PR:**
If there is a label whose value is `""`, `Instance.SubsetOf` will return true though the key does NOT exist in the other

It may lead to inconsistency when the label value is an empty string, so this PR fixes it

Fix #35722

Patch:
diff --git a/go.mod b/go.mod
index 9f42281eaa..c35e7fb110 100644
--- a/go.mod
+++ b/go.mod
@@ -90,7 +90,7 @@ require (
 	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 	helm.sh/helm/v3 v3.7.1
-	istio.io/api v0.0.0-20211101212207-41c1a93a583e
+	istio.io/api v0.0.0-20211102233813-291096a6adbe
 	istio.io/client-go v1.12.0-alpha.5.0.20211101212935-00b618a0b48b
 	istio.io/gogo-genproto v0.0.0-20211011213929-4191e6ba26c2
 	istio.io/pkg v0.0.0-20211028192150-1580783584bc
diff --git a/go.sum b/go.sum
index bbca312c3e..1b96389124 100644
--- a/go.sum
+++ b/go.sum
@@ -1906,8 +1906,9 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-istio.io/api v0.0.0-20211101212207-41c1a93a583e h1:cAaXyrjnULEBeIqhwHXC08vFAH7CNwksf2wOFmmhsWw=
 istio.io/api v0.0.0-20211101212207-41c1a93a583e/go.mod h1:lavaUNsnT7RGyMFNOGgV5XvOgP3fkTSZkxP/0H/ISt4=
+istio.io/api v0.0.0-20211102233813-291096a6adbe h1:gVjYuAEphk50IwctFPWsdqrWBKaUuuGEF6kd2lnYH6M=
+istio.io/api v0.0.0-20211102233813-291096a6adbe/go.mod h1:lavaUNsnT7RGyMFNOGgV5XvOgP3fkTSZkxP/0H/ISt4=
 istio.io/client-go v1.12.0-alpha.5.0.20211101212935-00b618a0b48b h1:K6wvMnbmfhPcRnWvwKlR06GtYjequx+EEktkS9HERvU=
 istio.io/client-go v1.12.0-alpha.5.0.20211101212935-00b618a0b48b/go.mod h1:gQN48jWGtqaIy5aDWDqS1qVsnWcKpC6kHFeSkrd0QKI=
 istio.io/gogo-genproto v0.0.0-20210113155706-4daf5697332f/go.mod h1:6BwTZRNbWS570wHX/uR1Wqk5e0157TofTAUMzT7N4+s=
diff --git a/manifests/charts/base/crds/crd-all.gen.yaml b/manifests/charts/base/crds/crd-all.gen.yaml
index cc1f2d56b8..70df417a08 100644
--- a/manifests/charts/base/crds/crd-all.gen.yaml
+++ b/manifests/charts/base/crds/crd-all.gen.yaml
@@ -5984,6 +5984,9 @@ spec:
                     randomSamplingPercentage:
                       nullable: true
                       type: number
+                    useRequestIdForTraceSampling:
+                      nullable: true
+                      type: boolean
                   type: object
                 type: array
             type: object
diff --git a/manifests/charts/base/files/gen-istio-cluster.yaml b/manifests/charts/base/files/gen-istio-cluster.yaml
index 185b8c4bdd..9bca1d6957 100644
--- a/manifests/charts/base/files/gen-istio-cluster.yaml
+++ b/manifests/charts/base/files/gen-istio-cluster.yaml
@@ -5986,6 +5986,9 @@ spec:
                     randomSamplingPercentage:
                       nullable: true
                       type: number
+                    useRequestIdForTraceSampling:
+                      nullable: true
+                      type: boolean
                   type: object
                 type: array
             type: object
diff --git a/manifests/charts/istiod-remote/templates/crd-all.gen.yaml b/manifests/charts/istiod-remote/templates/crd-all.gen.yaml
index ba1c7a4a54..0d70d88c54 100644
--- a/manifests/charts/istiod-remote/templates/crd-all.gen.yaml
+++ b/manifests/charts/istiod-remote/templates/crd-all.gen.yaml
@@ -5985,6 +5985,9 @@ spec:
                     randomSamplingPercentage:
                       nullable: true
                       type: number
+                    useRequestIdForTraceSampling:
+                      nullable: true
+                      type: boolean
                   type: object
                 type: array
             type: object
diff --git a/pkg/config/labels/instance.go b/pkg/config/labels/instance.go
index 0789056a99..b823d38573 100644
--- a/pkg/config/labels/instance.go
+++ b/pkg/config/labels/instance.go
@@ -64,8 +64,8 @@ func (i Instance) SubsetOf(that Instance) bool {
 		return false
 	}
 
-	for k, v := range i {
-		if that[k] != v {
+	for k, v1 := range i {
+		if v2, ok := that[k]; !ok || v1 != v2 {
 			return false
 		}
 	}


Test patch:
diff --git a/pkg/config/labels/instance_test.go b/pkg/config/labels/instance_test.go
index a93633b1f2..74c317521d 100644
--- a/pkg/config/labels/instance_test.go
+++ b/pkg/config/labels/instance_test.go
@@ -21,28 +21,57 @@ import (
 )
 
 func TestInstance(t *testing.T) {
-	a := labels.Instance{"app": "a"}
-	a1 := labels.Instance{"app": "a", "prod": "env"}
-	a2 := labels.Instance{"app": "b", "prod": "env"}
-
-	if !labels.Instance(nil).SubsetOf(a) {
-		t.Errorf("nil.SubsetOf({a}) => Got false")
-	}
-
-	if a.SubsetOf(nil) {
-		t.Errorf("{a}.SubsetOf(nil) => Got true")
-	}
-
-	if a1.SubsetOf(a) {
-		t.Errorf("%v.SubsetOf(%v) => Got true", a1, a)
-	}
-
-	if !a.SubsetOf(a1) {
-		t.Errorf("%v.SubsetOf(%v) => Got false", a, a1)
+	cases := []struct {
+		left     labels.Instance
+		right    labels.Instance
+		expected bool
+	}{
+		{
+			left:     nil,
+			right:    labels.Instance{"app": "a"},
+			expected: true,
+		},
+		{
+			left:     labels.Instance{"app": "a"},
+			right:    nil,
+			expected: false,
+		},
+		{
+			left:     labels.Instance{"app": "a"},
+			right:    labels.Instance{"app": "a", "prod": "env"},
+			expected: true,
+		},
+		{
+			left:     labels.Instance{"app": "a", "prod": "env"},
+			right:    labels.Instance{"app": "a"},
+			expected: false,
+		},
+		{
+			left:     labels.Instance{"app": "a"},
+			right:    labels.Instance{"app": "b", "prod": "env"},
+			expected: false,
+		},
+		{
+			left:     labels.Instance{"foo": ""},
+			right:    labels.Instance{"app": "a"},
+			expected: false,
+		},
+		{
+			left:     labels.Instance{"foo": ""},
+			right:    labels.Instance{"app": "a", "foo": ""},
+			expected: true,
+		},
+		{
+			left:     labels.Instance{"app": "a", "foo": ""},
+			right:    labels.Instance{"foo": ""},
+			expected: false,
+		},
 	}
-
-	if a.SubsetOf(a2) {
-		t.Errorf("%v.SubsetOf(%v) => Got true", a, a2)
+	for _, c := range cases {
+		got := c.left.SubsetOf(c.right)
+		if got != c.expected {
+			t.Errorf("%v.SubsetOf(%v) got %v, expected %v", c.left, c.right, got, c.expected)
+		}
 	}
 }
 
diff --git a/tests/integration/operator/switch_cr_test.go b/tests/integration/operator/switch_cr_test.go
index d44cb3a384..9ae6e7829a 100644
--- a/tests/integration/operator/switch_cr_test.go
+++ b/tests/integration/operator/switch_cr_test.go
@@ -255,6 +255,22 @@ func cleanupInClusterCRs(t framework.TestContext, cs cluster.Cluster) {
 	} else {
 		t.Logf("failed to list existing CR: %v", err.Error())
 	}
+
+	// wait for pods in istio-system to be deleted
+	err = retry.UntilSuccess(func() error {
+		podList, err := cs.Kube().CoreV1().Pods(IstioNamespace).List(context.TODO(), kubeApiMeta.ListOptions{})
+		if err != nil {
+			return err
+		}
+		if len(podList.Items) == 0 {
+			return nil
+		}
+		return fmt.Errorf("pods still remain in %s", IstioNamespace)
+	}, retry.Timeout(retryTimeOut), retry.Delay(retryDelay))
+
+	if err != nil {
+		t.Logf("failed to delete pods in %s: %v", IstioNamespace, err)
+	}
 }
 
 func installWithCRFile(t framework.TestContext, ctx resource.Context, cs cluster.Cluster, s *image.Settings,