Repository: grpc/grpc-go

Issue:
xds: Expose Dualstack support using an environment variable
Presently not all LB policies handle endpoints, some still read `resolver.Addresses`. These LB policies will need to be changed incrementally to handle endpoints correctly. The clusterresolver will be able to produce multiple addresses for each backend after https://github.com/grpc/grpc-go/pull/7858 is submitted, however that feature is behind a variable that can't be controlled via an env variable to hide the Dualstack support while its under development.

We should make the feature flag configurable with an environment variable once LB polices are updated.

Discussion:

Solution patch:
diff --git a/internal/envconfig/xds.go b/internal/envconfig/xds.go
index 30a7dc32..429f743c 100644
--- a/internal/envconfig/xds.go
+++ b/internal/envconfig/xds.go
@@ -56,9 +56,7 @@ var (
 
 	// XDSDualstackEndpointsEnabled is true if gRPC should read the
 	// "additional addresses" in the xDS endpoint resource.
-	// TODO: https://github.com/grpc/grpc-go/issues/7866 - Control this using
-	// an env variable when all LB policies handle endpoints.
-	XDSDualstackEndpointsEnabled = false
+	XDSDualstackEndpointsEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS", false)
 
 	// XDSSystemRootCertsEnabled is true when xDS enabled gRPC clients can use
 	// the system's default root certificates for TLS certificate validation.



Test patch:
diff --git a/test/healthcheck_test.go b/test/healthcheck_test.go
index fac56524..850b8e9a 100644
--- a/test/healthcheck_test.go
+++ b/test/healthcheck_test.go
@@ -30,7 +30,7 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/balancer"
 	"google.golang.org/grpc/balancer/pickfirst"
-	"google.golang.org/grpc/balancer/pickfirst/pickfirstleaf"
+	"google.golang.org/grpc/balancer/roundrobin"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/connectivity"
 	"google.golang.org/grpc/credentials/insecure"
@@ -38,7 +38,6 @@ import (
 	"google.golang.org/grpc/internal"
 	"google.golang.org/grpc/internal/balancer/stub"
 	"google.golang.org/grpc/internal/channelz"
-	"google.golang.org/grpc/internal/envconfig"
 	"google.golang.org/grpc/internal/grpctest"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/resolver"
@@ -51,47 +50,6 @@ import (
 	testpb "google.golang.org/grpc/interop/grpc_testing"
 )
 
-const healthCheckingPetiolePolicyName = "health_checking_petiole_policy"
-
-var (
-	// healthCheckTestPolicyName is the LB policy used for testing the health check
-	// service.
-	healthCheckTestPolicyName = "round_robin"
-)
-
-func init() {
-	balancer.Register(&healthCheckingPetiolePolicyBuilder{})
-	// Till dualstack changes are not implemented and round_robin doesn't
-	// delegate to pickfirst, test a fake petiole policy that delegates to
-	// the new pickfirst balancer.
-	// TODO: https://github.com/grpc/grpc-go/issues/7906 - Remove the fake
-	// petiole policy one round robin starts delegating to pickfirst.
-	if envconfig.NewPickFirstEnabled {
-		healthCheckTestPolicyName = healthCheckingPetiolePolicyName
-	}
-}
-
-type healthCheckingPetiolePolicyBuilder struct{}
-
-func (bb *healthCheckingPetiolePolicyBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
-	return &healthCheckingPetiolePolicy{
-		Balancer: balancer.Get(pickfirstleaf.Name).Build(cc, opts),
-	}
-}
-
-func (bb *healthCheckingPetiolePolicyBuilder) Name() string {
-	return healthCheckingPetiolePolicyName
-}
-
-func (b *healthCheckingPetiolePolicy) UpdateClientConnState(state balancer.ClientConnState) error {
-	state.ResolverState = pickfirstleaf.EnableHealthListener(state.ResolverState)
-	return b.Balancer.UpdateClientConnState(state)
-}
-
-type healthCheckingPetiolePolicy struct {
-	balancer.Balancer
-}
-
 func newTestHealthServer() *testHealthServer {
 	return newTestHealthServerWithWatchFunc(defaultWatchFunc)
 }
@@ -312,7 +270,7 @@ func (s) TestHealthCheckHealthServerNotRegistered(t *testing.T) {
 				"serviceName": "foo"
 			},
 			"loadBalancingConfig": [{"%s":{}}]
-		}`, healthCheckTestPolicyName))})
+		}`, roundrobin.Name))})
 
 	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
 	defer cancel()
@@ -339,7 +297,7 @@ func (s) TestHealthCheckWithGoAway(t *testing.T) {
 				"serviceName": "foo"
 			},
 			"loadBalancingConfig": [{"%s":{}}]
-		}`, healthCheckTestPolicyName))})
+		}`, roundrobin.Name))})
 
 	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
 	defer cancel()
@@ -417,7 +375,7 @@ func (s) TestHealthCheckWithConnClose(t *testing.T) {
 				"serviceName": "foo"
 			},
 			"loadBalancingConfig": [{"%s":{}}]
-		}`, healthCheckTestPolicyName))})
+		}`, roundrobin.Name))})
 
 	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
 	defer cancel()
@@ -465,7 +423,7 @@ func (s) TestHealthCheckWithAddrConnDrain(t *testing.T) {
 			"serviceName": "foo"
 		},
 		"loadBalancingConfig": [{"%s":{}}]
-	}`, healthCheckTestPolicyName))
+	}`, roundrobin.Name))
 	r.UpdateState(resolver.State{
 		Addresses:     []resolver.Address{{Addr: lis.Addr().String()}},
 		ServiceConfig: sc,
@@ -547,7 +505,7 @@ func (s) TestHealthCheckWithClientConnClose(t *testing.T) {
 				"serviceName": "foo"
 			},
 			"loadBalancingConfig": [{"%s":{}}]
-		}`, healthCheckTestPolicyName)))})
+		}`, roundrobin.Name)))})
 
 	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
 	defer cancel()
@@ -614,7 +572,7 @@ func (s) TestHealthCheckWithoutSetConnectivityStateCalledAddrConnShutDown(t *tes
 			"serviceName": "delay"
 		},
 		"loadBalancingConfig": [{"%s":{}}]
-	}`, healthCheckTestPolicyName))
+	}`, roundrobin.Name))
 	r.UpdateState(resolver.State{
 		Addresses:     []resolver.Address{{Addr: lis.Addr().String()}},
 		ServiceConfig: sc,
@@ -679,7 +637,7 @@ func (s) TestHealthCheckWithoutSetConnectivityStateCalled(t *testing.T) {
 				"serviceName": "delay"
 			},
 			"loadBalancingConfig": [{"%s":{}}]
-		}`, healthCheckTestPolicyName))})
+		}`, roundrobin.Name))})
 
 	select {
 	case <-hcExitChan:
@@ -717,7 +675,7 @@ func testHealthCheckDisableWithDialOption(t *testing.T, addr string) {
 				"serviceName": "foo"
 			},
 			"loadBalancingConfig": [{"%s":{}}]
-		}`, healthCheckTestPolicyName))})
+		}`, roundrobin.Name))})
 
 	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
 	defer cancel()
@@ -823,7 +781,7 @@ func (s) TestHealthCheckChannelzCountingCallSuccess(t *testing.T) {
 				"serviceName": "channelzSuccess"
 			},
 			"loadBalancingConfig": [{"%s":{}}]
-		}`, healthCheckTestPolicyName))})
+		}`, roundrobin.Name))})
 
 	if err := verifyResultWithDelay(func() (bool, error) {
 		cm, _ := channelz.GetTopChannels(0, 0)
@@ -872,7 +830,7 @@ func (s) TestHealthCheckChannelzCountingCallFailure(t *testing.T) {
 				"serviceName": "channelzFailure"
 			},
 			"loadBalancingConfig": [{"%s":{}}]
-		}`, healthCheckTestPolicyName))})
+		}`, roundrobin.Name))})
 
 	if err := verifyResultWithDelay(func() (bool, error) {
 		cm, _ := channelz.GetTopChannels(0, 0)
@@ -985,7 +943,7 @@ func (s) TestHealthCheckFailure(t *testing.T) {
 		name:     "tcp-tls",
 		network:  "tcp",
 		security: "tls",
-		balancer: healthCheckTestPolicyName,
+		balancer: roundrobin.Name,
 	}
 	te := newTest(t, e)
 	te.declareLogNoise(
diff --git a/test/kokoro/psm-dualstack.cfg b/test/kokoro/psm-dualstack.cfg
new file mode 100644
index 00000000..8c2ba9dd
--- /dev/null
+++ b/test/kokoro/psm-dualstack.cfg
@@ -0,0 +1,17 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-go/test/kokoro/psm-interop-test-go.sh"
+timeout_mins: 360
+
+action {
+  define_artifacts {
+    regex: "artifacts/**/*sponge_log.xml"
+    regex: "artifacts/**/*.log"
+    strip_prefix: "artifacts"
+  }
+}
+env_vars {
+  key: "PSM_TEST_SUITE"
+  value: "dualstack"
+}