package clusterctl import ( "context" "database/sql" "encoding/json" "net/http" "net/http/httptest" "os" "strings" "testing" "time" "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" containerAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/container/v1beta1" "github.com/golang/mock/gomock" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" bsltypes "edge-infra.dev/pkg/edge/api/bsl/types" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/api/mocks" "edge-infra.dev/pkg/edge/api/services" "edge-infra.dev/pkg/edge/api/testutils/seededpostgres" "edge-infra.dev/pkg/edge/api/types" "edge-infra.dev/pkg/edge/controllers/clusterctl/pkg/plugins" "edge-infra.dev/pkg/edge/controllers/clusterctl/pkg/plugins/clustersecrets" loglevels "edge-infra.dev/pkg/edge/controllers/clusterctl/pkg/plugins/log-levels" "edge-infra.dev/pkg/edge/controllers/clusterctl/pkg/plugins/multikustomization" "edge-infra.dev/pkg/edge/k8objectsutils" "edge-infra.dev/pkg/edge/registration" ipranger "edge-infra.dev/pkg/f8n/ipranger/server" "edge-infra.dev/pkg/k8s/runtime/controller" ff "edge-infra.dev/pkg/lib/featureflag" fftest "edge-infra.dev/pkg/lib/featureflag/testutil" "edge-infra.dev/test" "edge-infra.dev/test/framework" "edge-infra.dev/test/framework/gcp" "edge-infra.dev/test/framework/integration" "edge-infra.dev/test/framework/k8s" "edge-infra.dev/test/framework/k8s/envtest" ) var trackSecretManagerSecrets = make(map[string]struct{}) func TestMain(m *testing.M) { framework.HandleFlags() os.Exit(m.Run()) } type Suite struct { *framework.Framework *k8s.K8s Scheme *runtime.Scheme ctx context.Context timeout time.Duration tick time.Duration ClusterClient ContainerClusterClientFunc ProjectID string Banner *model.Banner Organization string ClusterName string Location string NodeVersion string MachineType string NumNodes int TopLevelProjectID string TopLevelCNRMSA string DB *sql.DB } func TestClusterController(t *testing.T) { testEnv := envtest.Setup() defer testEnv.Stop() //nolint: errcheck sp, err := seededpostgres.New() if err != nil { t.Fatal(err) } defer sp.Close() db, err := sp.DB() if err != nil { t.Fatal(err) } defer db.Close() registerTestPlugins(t, db) scheme := createScheme() // mock feature flags fftest.MustInitTestFeatureFlags(map[string]bool{ ff.UseMasterAuthorizedNetworks: true, }) // func for creating k8 client from container cluster var createClient ContainerClusterClientFunc var waitForSetTimeout time.Duration if integration.IsIntegrationTest() { createClient = k8objectsutils.CreateClient // Enable WaitForSet during integration testing. waitForSetTimeout = 5 * time.Second } else { k8s.Timeouts.Tick = 50 * time.Millisecond fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() createClient = func(_ containerAPI.ContainerCluster, _ client.Options) (client.Client, error) { return fakeClient, nil } } sert := assert.New(t) totpSecret := "totp-secret" srv := httptest.NewServer(http.HandlerFunc(registration.GraphQLHandler(sert, registration.WithTotpSecret(totpSecret)))) iprsrv := httptest.NewServer(http.HandlerFunc(mockIPRanger())) iprhost := strings.TrimPrefix(iprsrv.URL, "http://") bslConfig := bsltypes.BSPConfig{ OrganizationPrefix: "edge-test1", Endpoint: "https://api.ncr.com", Root: "/customers", } cfg := Config{ CreateClient: createClient, EdgeAPI: srv.URL, IPRangerClient: ipranger.NewClient(iprhost), DefaultRequeue: 10 * time.Millisecond, TopLevelProjectID: "ret-edge-test", TopLevelCNRMSA: "top-level@ret-edge-test.iam.gserviceaccount.com", TotpSecret: totpSecret, Domain: "edge-domain.ncr.com", BSLConfig: bslConfig, DatasyncDNSName: "edge-dev.ncr.com", DatasyncDNSZone: "datasync-dns-zone", DatabaseName: "postgres", DB: db, WaitForSetTimeout: waitForSetTimeout, GCPRegion: "us-east1", GCPZone: "c", ClusterReconcilerConcurrency: 4, GKEClusterReconcilerConcurrency: 4, PluginConcurrency: 4, HelmCacheLimit: 10, EdgeSecMaxLeasePeriod: "48h", EdgeSecMaxValidityPeriod: "60d", } mgr, _, err := Create(cfg, controller.WithCfg(testEnv.Config), controller.WithMetricsAddress("0")) test.NoError(err) k := k8s.New(testEnv.Config, k8s.WithCtrlManager(mgr), k8s.WithKonfigKonnector()) f := framework.New("clusterctl"). Component("clusterctl"). Register(k) // Due to foreign key constraints, an existing banner_edge_id is needed when inserting clusters into the database bannerEdgeID, bannerName, err := getBannerFromDatabase(db) if err != nil { t.Fatal(err) } s := &Suite{ Framework: f, K8s: k, ctx: context.Background(), timeout: k8s.Timeouts.DefaultTimeout, tick: k8s.Timeouts.Tick, Scheme: scheme, ClusterClient: createClient, ProjectID: cfg.TopLevelProjectID, Banner: &model.Banner{ Name: bannerName, BannerEdgeID: bannerEdgeID, }, Organization: "edge-dev0-test", ClusterName: uuid.New().String(), Location: "us-east1-c", NodeVersion: "1.21.9-gke.300", MachineType: "e2-highmem-2", NumNodes: 3, TopLevelCNRMSA: cfg.TopLevelCNRMSA, TopLevelProjectID: cfg.TopLevelProjectID, DB: db, } if integration.IsIntegrationTest() { s.ProjectID = gcp.GCloud.ProjectID // TODO set up Cluster, Banner and Organization for integration testing //s.Banner = //s.Organization = //s.ClusterName = } suite.Run(t, s) } func getBannerFromDatabase(db *sql.DB) (id, name string, err error) { err = db.QueryRow("SELECT banner_edge_id, banner_name FROM banners LIMIT 1").Scan(&id, &name) return } func registerTestPlugins(t *testing.T, db *sql.DB) { mockCtrl := gomock.NewController(t) mockSecretManager := mocks.NewMockSecretManagerService(mockCtrl) mockSecretManager.EXPECT().GetLatestSecretValueInfo(gomock.Any(), gomock.Any()).AnyTimes().Return(&secretmanagerpb.SecretVersion{}, status.Error(codes.NotFound, "secret not found")) mockSecretManager.EXPECT().GetLatestSecretValue(gomock.Any(), gomock.Any()).AnyTimes().Return([]byte{}, nil) mockSecretManager.EXPECT().GetSecretVersionValue(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]byte{}, status.Error(codes.NotFound, "secret not found")) mockSecretManager.EXPECT(). AddSecret(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(func(_ context.Context, secretID string, _ []byte, _ map[string]string, _ bool, _ *time.Time, _ string) error { trackSecretManagerSecrets[secretID] = struct{}{} return nil }).AnyTimes() mockSecretManager.EXPECT().DeleteSecret(gomock.Any(), gomock.Any()). DoAndReturn(func(_ context.Context, secretID string) error { delete(trackSecretManagerSecrets, secretID) return nil }).AnyTimes() plugins.Register(clustersecrets.Plugin{ SecretManagerProvider: func(_ context.Context, _ string) (types.SecretManagerService, error) { return mockSecretManager, nil }, TopLevelProjectID: "t", }) plugins.Register(multikustomization.NewPlugin(services.NewStoreClusterService(nil, nil, db, nil, nil, nil))) plugins.Register(loglevels.LogLevelsPlugin{ DB: db, }) plugins.Register(plugins.RemoteAccessIPPlugin{ DB: db, }) } func mockIPRanger() func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, _ *http.Request) { netcfg := ipranger.NetcfgResp{ Network: "networks/test", Subnetwork: "subnetworks/test", Netmask: "/21", } _ = json.NewEncoder(w).Encode(netcfg) } }