From b2419e429437a9d0c1bb4a719237f86ccbb0f46b Mon Sep 17 00:00:00 2001 From: Bowei Du Date: Fri, 26 Jun 2026 13:46:20 -0700 Subject: [PATCH] Enable dataplane v2 for network policy support This enables dataplane v2 by default for the provisioned clusters so we have a network policy provider. Make getEnv generic --- tools/setup-gcp/cmd/cluster.go | 22 +++++++- tools/setup-gcp/cmd/common.go | 53 ++++++++++++++---- tools/setup-gcp/cmd/common_test.go | 89 ++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 13 deletions(-) create mode 100644 tools/setup-gcp/cmd/common_test.go diff --git a/tools/setup-gcp/cmd/cluster.go b/tools/setup-gcp/cmd/cluster.go index 2e74b134..3df39912 100644 --- a/tools/setup-gcp/cmd/cluster.go +++ b/tools/setup-gcp/cmd/cluster.go @@ -53,6 +53,12 @@ func deleteCluster(ctx context.Context, cfg *Config) error { func createClusterInternal(ctx context.Context, cfg *Config, client *container.ClusterManagerClient, parent string) error { slog.Info("Cluster does not exist. Creating...", slog.String("cluster", cfg.ClusterName)) + var networkConfig *containerpb.NetworkConfig + if cfg.EnableDataplaneV2 { + networkConfig = &containerpb.NetworkConfig{ + DatapathProvider: containerpb.DatapathProvider_ADVANCED_DATAPATH, + } + } req := &containerpb.CreateClusterRequest{ Parent: parent, Cluster: &containerpb.Cluster{ @@ -73,8 +79,9 @@ func createClusterInternal(ctx context.Context, cfg *Config, client *container.C WorkloadIdentityConfig: &containerpb.WorkloadIdentityConfig{ WorkloadPool: fmt.Sprintf("%s.svc.id.goog", cfg.ProjectID), }, - Network: cfg.Network, - Subnetwork: cfg.Subnetwork, + Network: cfg.Network, + Subnetwork: cfg.Subnetwork, + NetworkConfig: networkConfig, }, } op, err := client.CreateCluster(ctx, req) @@ -126,6 +133,16 @@ func createClusterIdempotent(ctx context.Context, cfg *Config) error { return createClusterInternal(ctx, cfg, client, parent) } + // Recreate cluster if dataplane v2 configuration mismatches. + currentIsV2 := cluster.NetworkConfig != nil && cluster.NetworkConfig.DatapathProvider == containerpb.DatapathProvider_ADVANCED_DATAPATH + if currentIsV2 != cfg.EnableDataplaneV2 { + slog.Info("Mismatch in Dataplane V2 configuration", slog.Bool("current", currentIsV2), slog.Bool("expected", cfg.EnableDataplaneV2)) + if err := deleteCluster(ctx, cfg); err != nil { + return err + } + return createClusterInternal(ctx, cfg, client, parent) + } + expectedWorkloadPool := fmt.Sprintf("%s.svc.id.goog", cfg.ProjectID) currentWorkloadPool := "" if cluster.WorkloadIdentityConfig != nil { @@ -253,4 +270,5 @@ func init() { clusterCmd.Flags().StringVar(&cfg.Network, "network", getEnv("NETWORK", "default"), "VPC network name [env: NETWORK]") clusterCmd.Flags().StringVar(&cfg.Subnetwork, "subnetwork", getEnv("SUBNETWORK", "default"), "VPC subnetwork name [env: SUBNETWORK]") clusterCmd.Flags().StringVar(&cfg.MachineType, "machine-type", getEnv("GVISOR_NODE_MACHINE_TYPE", "c3-standard-4"), "Machine type for the gVisor node pool [env: GVISOR_NODE_MACHINE_TYPE]") + clusterCmd.Flags().BoolVar(&cfg.EnableDataplaneV2, "enable-dataplane-v2", getEnv("ENABLE_DATAPLANE_V2", true), "Enable Dataplane V2 [env: ENABLE_DATAPLANE_V2]") } diff --git a/tools/setup-gcp/cmd/common.go b/tools/setup-gcp/cmd/common.go index 6a815973..f3327277 100644 --- a/tools/setup-gcp/cmd/common.go +++ b/tools/setup-gcp/cmd/common.go @@ -14,27 +14,58 @@ package cmd -import "os" +import ( + "os" + "strconv" +) type Config struct { - ProjectID string - ProjectNumber string - Region string + ProjectID string + ProjectNumber string + Region string + ClusterName string ClusterLocation string ClusterVersion string - Network string - Subnetwork string + + Network string + Subnetwork string + EnableDataplaneV2 bool + NodePoolName string NodePoolVersion string MachineType string - BucketName string - DashboardDir string + + BucketName string + + DashboardDir string +} + +type getEnvType interface { + string | bool } -func getEnv(key, fallback string) string { - if val, ok := os.LookupEnv(key); ok { - return val +// getEnv retrieves an environment variable by key and parses it into the specified type. +// If the environment variable is not set or parsing fails, it returns the fallback value. +func getEnv[T getEnvType](key string, fallback T) T { + val, ok := os.LookupEnv(key) + if !ok { + return fallback } + + var ret any + var err error + + switch any(fallback).(type) { + case string: + ret = val + case bool: + ret, err = strconv.ParseBool(val) + } + + if err == nil { + return ret.(T) + } + return fallback } diff --git a/tools/setup-gcp/cmd/common_test.go b/tools/setup-gcp/cmd/common_test.go new file mode 100644 index 00000000..611f73c1 --- /dev/null +++ b/tools/setup-gcp/cmd/common_test.go @@ -0,0 +1,89 @@ +// Copyright 2026 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 +// +// 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 ( + "os" + "testing" +) + +func TestGetEnv_String(t *testing.T) { + const key = "TEST_ENV_STRING_VAR" + + // Ensure clean environment + os.Unsetenv(key) + defer os.Unsetenv(key) + + // Test fallback when environment variable is not set + if got := getEnv(key, "default"); got != "default" { + t.Errorf("getEnv(%q, %q) = %q; want %q", key, "default", got, "default") + } + + // Test when environment variable is set + os.Setenv(key, "hello") + if got := getEnv(key, "default"); got != "hello" { + t.Errorf("getEnv(%q, %q) = %q; want %q", key, "default", got, "hello") + } +} + +func TestGetEnv_Bool(t *testing.T) { + const key = "TEST_ENV_BOOL_VAR" + + // Ensure clean environment + os.Unsetenv(key) + defer os.Unsetenv(key) + + // Test fallback when environment variable is not set + if got := getEnv(key, true); got != true { + t.Errorf("getEnv(%q, true) = %t; want true", key, got) + } + if got := getEnv(key, false); got != false { + t.Errorf("getEnv(%q, false) = %t; want false", key, got) + } + + // Test when environment variable is set to valid bool strings + tests := []struct { + envVal string + fallback bool + want bool + }{ + {"true", false, true}, + {"TRUE", false, true}, + {"1", false, true}, + {"t", false, true}, + {"T", false, true}, + {"false", true, false}, + {"FALSE", true, false}, + {"0", true, false}, + {"f", true, false}, + {"F", true, false}, + } + + for _, tc := range tests { + os.Setenv(key, tc.envVal) + if got := getEnv(key, tc.fallback); got != tc.want { + t.Errorf("getEnv(%q, %t) with env %q = %t; want %t", key, tc.fallback, tc.envVal, got, tc.want) + } + } + + // Test when environment variable is set to an invalid bool string + os.Setenv(key, "invalid_bool") + if got := getEnv(key, true); got != true { + t.Errorf("getEnv(%q, true) with invalid env = %t; want true", key, got) + } + if got := getEnv(key, false); got != false { + t.Errorf("getEnv(%q, false) with invalid env = %t; want false", key, got) + } +}