From 2d8d62f5d4e54e47004093faa4c63c65b742c05e Mon Sep 17 00:00:00 2001 From: tengu-alt Date: Wed, 6 Mar 2024 10:45:57 +0200 Subject: [PATCH] test for cluster webhooks were implemented & cluster webhooks were refactored --- .secrets.baseline | 1040 ++++++++++++++++- apis/clusters/v1beta1/cadence_webhook.go | 3 +- apis/clusters/v1beta1/cassandra_types.go | 2 +- apis/clusters/v1beta1/cassandra_webhook.go | 15 +- .../v1beta1/cassandra_webhook_test.go | 596 ++++++++++ apis/clusters/v1beta1/kafka_types.go | 8 +- apis/clusters/v1beta1/kafka_webhook.go | 28 +- apis/clusters/v1beta1/kafka_webhook_test.go | 956 +++++++++++---- apis/clusters/v1beta1/kafkaconnect_types.go | 8 +- apis/clusters/v1beta1/kafkaconnect_webhook.go | 25 +- .../v1beta1/kafkaconnect_webhook_test.go | 443 +++++++ apis/clusters/v1beta1/opensearch_types.go | 2 +- apis/clusters/v1beta1/opensearch_webhook.go | 13 +- .../v1beta1/opensearch_webhook_test.go | 899 +++++++++----- apis/clusters/v1beta1/postgresql_webhook.go | 20 +- .../v1beta1/postgresql_webhook_test.go | 631 ++++++++++ apis/clusters/v1beta1/redis_webhook.go | 27 +- apis/clusters/v1beta1/redis_webhook_test.go | 524 +++++++++ apis/clusters/v1beta1/validation.go | 11 +- apis/clusters/v1beta1/validation_test.go | 32 + apis/clusters/v1beta1/zookeeper_webhook.go | 4 - .../v1beta1/zookeeper_webhook_test.go | 119 ++ .../clusters.instaclustr.com_cassandras.yaml | 2 + ...lusters.instaclustr.com_kafkaconnects.yaml | 4 + .../clusters.instaclustr.com_kafkas.yaml | 2 + ...clusters.instaclustr.com_opensearches.yaml | 1 + 26 files changed, 4725 insertions(+), 690 deletions(-) create mode 100644 apis/clusters/v1beta1/cassandra_webhook_test.go create mode 100644 apis/clusters/v1beta1/kafkaconnect_webhook_test.go create mode 100644 apis/clusters/v1beta1/postgresql_webhook_test.go create mode 100644 apis/clusters/v1beta1/redis_webhook_test.go create mode 100644 apis/clusters/v1beta1/zookeeper_webhook_test.go diff --git a/.secrets.baseline b/.secrets.baseline index ca6d12f63..f8504e30d 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -75,6 +75,10 @@ { "path": "detect_secrets.filters.allowlist.is_line_allowlisted" }, + { + "path": "detect_secrets.filters.common.is_baseline_file", + "filename": ".secrets.baseline" + }, { "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", "min_level": 2 @@ -105,24 +109,9 @@ }, { "path": "detect_secrets.filters.heuristic.is_templated_secret" - }, - { - "path": "detect_secrets.filters.regex.should_exclude_file", - "pattern": [ - "^vendor/" - ] } ], "results": { - ".idea/workspace.xml": [ - { - "type": "Base64 High Entropy String", - "filename": ".idea/workspace.xml", - "hashed_secret": "ceda554256d1b9fa42791b09d9798634f930ad04", - "is_verified": false, - "line_number": 42 - } - ], "apis/clusterresources/v1beta1/cassandrauser_types.go": [ { "type": "Secret Keyword", @@ -156,7 +145,7 @@ "filename": "apis/clusterresources/v1beta1/structs.go", "hashed_secret": "e03127a337f643c1c0eb2b0e8683e9140e19120d", "is_verified": false, - "line_number": 57 + "line_number": 65 } ], "apis/clusterresources/v1beta1/zz_generated.deepcopy.go": [ @@ -165,7 +154,7 @@ "filename": "apis/clusterresources/v1beta1/zz_generated.deepcopy.go", "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", "is_verified": false, - "line_number": 547 + "line_number": 548 } ], "apis/clusters/v1beta1/cadence_types.go": [ @@ -213,7 +202,16 @@ "filename": "apis/clusters/v1beta1/cassandra_webhook.go", "hashed_secret": "e0a46b27231f798fe22dc4d5d82b5feeb5dcf085", "is_verified": false, - "line_number": 232 + "line_number": 222 + } + ], + "apis/clusters/v1beta1/cassandra_webhook_test.go": [ + { + "type": "Secret Keyword", + "filename": "apis/clusters/v1beta1/cassandra_webhook_test.go", + "hashed_secret": "70d622122a413701e3da0319ced58bcfb039dbda", + "is_verified": false, + "line_number": 517 } ], "apis/clusters/v1beta1/kafka_types.go": [ @@ -222,14 +220,14 @@ "filename": "apis/clusters/v1beta1/kafka_types.go", "hashed_secret": "964c67cddfe8e6707157152dcf319126502199dc", "is_verified": false, - "line_number": 262 + "line_number": 264 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/kafka_types.go", "hashed_secret": "589a0ad3cc6bc886a00c46a22e5065c48bd8e1b2", "is_verified": false, - "line_number": 422 + "line_number": 424 } ], "apis/clusters/v1beta1/kafkaconnect_types.go": [ @@ -238,70 +236,70 @@ "filename": "apis/clusters/v1beta1/kafkaconnect_types.go", "hashed_secret": "46fe9b29395041087f91b33bd8c5c6177cd42fd1", "is_verified": false, - "line_number": 268 + "line_number": 272 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/kafkaconnect_types.go", "hashed_secret": "4b3af1508421e2fa591c5b260c36dd06fdd872a5", "is_verified": false, - "line_number": 312 + "line_number": 316 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/kafkaconnect_types.go", "hashed_secret": "cf45830dd81b7e1a8b5ffbc2d95b112771524117", "is_verified": false, - "line_number": 322 + "line_number": 326 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/kafkaconnect_types.go", "hashed_secret": "138905ac46675150bf790088ec56b2efc6a64697", "is_verified": false, - "line_number": 333 + "line_number": 337 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/kafkaconnect_types.go", "hashed_secret": "3948059919ffeee8ecc42149cb386f43d2f06f74", "is_verified": false, - "line_number": 338 + "line_number": 342 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/kafkaconnect_types.go", "hashed_secret": "87f1180476a944c4162d1af55efedc8f3e3b609c", "is_verified": false, - "line_number": 489 + "line_number": 493 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/kafkaconnect_types.go", "hashed_secret": "f0f06c9167ce61a586749bb183ac6a3756dd6010", "is_verified": false, - "line_number": 499 + "line_number": 503 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/kafkaconnect_types.go", "hashed_secret": "2042128e13ef5ede4af44271160c72f64564c632", "is_verified": false, - "line_number": 510 + "line_number": 514 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/kafkaconnect_types.go", "hashed_secret": "82dc9ca8ba09262ce948227aeb5d9db8084eeb5d", "is_verified": false, - "line_number": 515 + "line_number": 519 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/kafkaconnect_types.go", "hashed_secret": "5f915325aef923cdc945f639f14c2f854b4214d6", "is_verified": false, - "line_number": 539 + "line_number": 543 } ], "apis/clusters/v1beta1/postgresql_types.go": [ @@ -310,21 +308,37 @@ "filename": "apis/clusters/v1beta1/postgresql_types.go", "hashed_secret": "5ffe533b830f08a0326348a9160afafc8ada44db", "is_verified": false, - "line_number": 369 + "line_number": 370 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/postgresql_types.go", "hashed_secret": "a3d7d4a96d18c8fc5a1cf9c9c01c45b4690b4008", "is_verified": false, - "line_number": 375 + "line_number": 376 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/postgresql_types.go", "hashed_secret": "a57ce131bd944bdf8ba2f2f93e179dc416ed0315", "is_verified": false, - "line_number": 438 + "line_number": 425 + } + ], + "apis/clusters/v1beta1/postgresql_webhook_test.go": [ + { + "type": "Secret Keyword", + "filename": "apis/clusters/v1beta1/postgresql_webhook_test.go", + "hashed_secret": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", + "is_verified": false, + "line_number": 235 + }, + { + "type": "Secret Keyword", + "filename": "apis/clusters/v1beta1/postgresql_webhook_test.go", + "hashed_secret": "840ee65672ca001c19c80f016c09dec581c03609", + "is_verified": false, + "line_number": 243 } ], "apis/clusters/v1beta1/redis_types.go": [ @@ -333,21 +347,21 @@ "filename": "apis/clusters/v1beta1/redis_types.go", "hashed_secret": "bc1c5ae5fd4a238d86261f422e62c489de408c22", "is_verified": false, - "line_number": 171 + "line_number": 169 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/redis_types.go", "hashed_secret": "d62d56668a8c859e768e8250ed2fb690d03cead3", "is_verified": false, - "line_number": 227 + "line_number": 225 }, { "type": "Secret Keyword", "filename": "apis/clusters/v1beta1/redis_types.go", "hashed_secret": "d0e8e6fc5dce4d2b452e344ae41900b566ac01d1", "is_verified": false, - "line_number": 272 + "line_number": 270 } ], "apis/clusters/v1beta1/redis_webhook.go": [ @@ -356,7 +370,23 @@ "filename": "apis/clusters/v1beta1/redis_webhook.go", "hashed_secret": "bc1c5ae5fd4a238d86261f422e62c489de408c22", "is_verified": false, - "line_number": 323 + "line_number": 319 + } + ], + "apis/clusters/v1beta1/redis_webhook_test.go": [ + { + "type": "Secret Keyword", + "filename": "apis/clusters/v1beta1/redis_webhook_test.go", + "hashed_secret": "7cb6efb98ba5972a9b5090dc2e517fe14d12cb04", + "is_verified": false, + "line_number": 46 + }, + { + "type": "Secret Keyword", + "filename": "apis/clusters/v1beta1/redis_webhook_test.go", + "hashed_secret": "70d622122a413701e3da0319ced58bcfb039dbda", + "is_verified": false, + "line_number": 201 } ], "apis/clusters/v1beta1/zz_generated.deepcopy.go": [ @@ -365,7 +395,7 @@ "filename": "apis/clusters/v1beta1/zz_generated.deepcopy.go", "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", "is_verified": false, - "line_number": 676 + "line_number": 665 } ], "apis/kafkamanagement/v1beta1/kafkauser_types.go": [ @@ -504,9 +534,16 @@ { "type": "Secret Keyword", "filename": "controllers/clusters/cadence_controller.go", - "hashed_secret": "5ffe533b830f08a0326348a9160afafc8ada44db", + "hashed_secret": "bcf196cdeea4d7ed8b04dcbbd40111eb5e9abeac", + "is_verified": false, + "line_number": 644 + }, + { + "type": "Secret Keyword", + "filename": "controllers/clusters/cadence_controller.go", + "hashed_secret": "192d703e91a60432ce06bfe26adfd12f5c7b931f", "is_verified": false, - "line_number": 750 + "line_number": 677 } ], "controllers/clusters/datatest/kafka_v1beta1.yaml": [ @@ -533,14 +570,14 @@ "filename": "controllers/clusters/helpers.go", "hashed_secret": "5ffe533b830f08a0326348a9160afafc8ada44db", "is_verified": false, - "line_number": 119 + "line_number": 213 }, { "type": "Secret Keyword", "filename": "controllers/clusters/helpers.go", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 124 + "line_number": 218 } ], "controllers/clusters/kafkaconnect_controller_test.go": [ @@ -558,7 +595,7 @@ "filename": "controllers/clusters/postgresql_controller.go", "hashed_secret": "5ffe533b830f08a0326348a9160afafc8ada44db", "is_verified": false, - "line_number": 1192 + "line_number": 1221 } ], "controllers/clusters/zookeeper_controller_test.go": [ @@ -723,7 +760,7 @@ "filename": "pkg/instaclustr/client.go", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 2084 + "line_number": 2078 } ], "pkg/instaclustr/mock/client.go": [ @@ -1073,35 +1110,35 @@ "filename": "pkg/models/operator.go", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 155 + "line_number": 154 }, { "type": "Secret Keyword", "filename": "pkg/models/operator.go", "hashed_secret": "d65d45369e8aef106a8ca1c3bad151ad24163494", "is_verified": false, - "line_number": 185 + "line_number": 184 }, { "type": "Secret Keyword", "filename": "pkg/models/operator.go", "hashed_secret": "638724dcc0799a22cc4adce12434fcac73c8af58", "is_verified": false, - "line_number": 186 + "line_number": 185 }, { "type": "Secret Keyword", "filename": "pkg/models/operator.go", "hashed_secret": "4fe486f255f36f8787d5c5cc1185e3d5d5c91c03", "is_verified": false, - "line_number": 187 + "line_number": 186 }, { "type": "Secret Keyword", "filename": "pkg/models/operator.go", "hashed_secret": "2331919a92cbb5c2d530947171fa5e1a1415af2f", "is_verified": false, - "line_number": 188 + "line_number": 187 } ], "pkg/utils/user_creds_from_secret_test.go": [ @@ -1128,7 +1165,910 @@ "is_verified": false, "line_number": 6 } + ], + "vendor/github.com/Azure/go-autorest/autorest/adal/token.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/adal/token.go", + "hashed_secret": "44e3029bcab9206e67d86efc33b07882cb6ce252", + "is_verified": false, + "line_number": 77 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/adal/token.go", + "hashed_secret": "f4e7a8740db0b7a0bfd8e63077261475f61fc2a6", + "is_verified": false, + "line_number": 83 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/adal/token.go", + "hashed_secret": "884f4f4a0a0eac729dd185412ba057957d45b9ac", + "is_verified": false, + "line_number": 333 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/adal/token.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 468 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/adal/token.go", + "hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4", + "is_verified": false, + "line_number": 475 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/adal/token.go", + "hashed_secret": "2331919a92cbb5c2d530947171fa5e1a1415af2f", + "is_verified": false, + "line_number": 597 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/adal/token.go", + "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", + "is_verified": false, + "line_number": 627 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/adal/token.go", + "hashed_secret": "9b8b876c2782fa992fab14095267bb8757b9fabc", + "is_verified": false, + "line_number": 660 + } + ], + "vendor/github.com/Azure/go-autorest/autorest/authorization.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/authorization.go", + "hashed_secret": "1cac0b180e6e6530fdb7969cc941b5f68055decc", + "is_verified": false, + "line_number": 32 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/authorization.go", + "hashed_secret": "2e6839a2915329c931ab765ad7273b30814c2906", + "is_verified": false, + "line_number": 95 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/authorization.go", + "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", + "is_verified": false, + "line_number": 282 + } + ], + "vendor/github.com/Azure/go-autorest/autorest/authorization_storage.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/github.com/Azure/go-autorest/autorest/authorization_storage.go", + "hashed_secret": "a62f2225bf70bfaccbc7f1ef2a397836717377de", + "is_verified": false, + "line_number": 85 + } + ], + "vendor/github.com/go-openapi/jsonpointer/.travis.yml": [ + { + "type": "Base64 High Entropy String", + "filename": "vendor/github.com/go-openapi/jsonpointer/.travis.yml", + "hashed_secret": "efd0d98f857d051cf307cfe1f46cedc41d467fd7", + "is_verified": false, + "line_number": 13 + } + ], + "vendor/github.com/go-openapi/jsonreference/.travis.yml": [ + { + "type": "Base64 High Entropy String", + "filename": "vendor/github.com/go-openapi/jsonreference/.travis.yml", + "hashed_secret": "4379a68aceb9e2d4f97ba8aed93d667a5a2b2631", + "is_verified": false, + "line_number": 22 + } + ], + "vendor/github.com/google/gnostic/openapiv2/OpenAPIv2.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/github.com/google/gnostic/openapiv2/OpenAPIv2.go", + "hashed_secret": "47c81ac7f8cc2b30a403d75ebef1221b34f2672f", + "is_verified": false, + "line_number": 5096 + } + ], + "vendor/github.com/google/gnostic/openapiv2/OpenAPIv2.pb.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/github.com/google/gnostic/openapiv2/OpenAPIv2.pb.go", + "hashed_secret": "e3c6da52d343cfb13bc6343a3615e9746b2d8667", + "is_verified": false, + "line_number": 6546 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/google/gnostic/openapiv2/OpenAPIv2.pb.go", + "hashed_secret": "ce083c611747f9ecf3008f60f2de19394663ac1c", + "is_verified": false, + "line_number": 6548 + } + ], + "vendor/github.com/google/gnostic/openapiv3/OpenAPIv3.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/github.com/google/gnostic/openapiv3/OpenAPIv3.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 5893 + } + ], + "vendor/github.com/google/gnostic/openapiv3/OpenAPIv3.pb.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/github.com/google/gnostic/openapiv3/OpenAPIv3.pb.go", + "hashed_secret": "44f9a90d59ba01e4771c55ea0c0463ed301a8c16", + "is_verified": false, + "line_number": 6950 + } + ], + "vendor/github.com/openshift/api/config/v1/types_oauth.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/github.com/openshift/api/config/v1/types_oauth.go", + "hashed_secret": "56910cc4d6ffe86e07d6e8f982b66c62aee3201a", + "is_verified": false, + "line_number": 87 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/openshift/api/config/v1/types_oauth.go", + "hashed_secret": "9b8b876c2782fa992fab14095267bb8757b9fabc", + "is_verified": false, + "line_number": 90 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/openshift/api/config/v1/types_oauth.go", + "hashed_secret": "1aa5580bc895a2c17806c31476b97356b395dac8", + "is_verified": false, + "line_number": 93 + } + ], + "vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go", + "hashed_secret": "7484a66c37635bd267e84f11316f9637c87b7059", + "is_verified": false, + "line_number": 1110 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go", + "hashed_secret": "8e5c0b8553d63ef7e3f59459ece79e60345d81f7", + "is_verified": false, + "line_number": 1921 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 2120 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go", + "hashed_secret": "1acf6b28625f71ad27ffbfb1eae4e2d3ca3364d1", + "is_verified": false, + "line_number": 2700 + }, + { + "type": "Secret Keyword", + "filename": "vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go", + "hashed_secret": "9fda0fc891b6c2c2b2c4c2225d4dfbbf4d8ad1e1", + "is_verified": false, + "line_number": 3061 + } + ], + "vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go": [ + { + "type": "Base64 High Entropy String", + "filename": "vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go", + "hashed_secret": "2c2b978da31e00da3891f13049ade1bb3712357b", + "is_verified": false, + "line_number": 29 + } + ], + "vendor/golang.org/x/crypto/pkcs12/pkcs12.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/crypto/pkcs12/pkcs12.go", + "hashed_secret": "4e23b8a4a0ec90dfbfeb74973bb3a1794c69a3a1", + "is_verified": false, + "line_number": 92 + }, + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/crypto/pkcs12/pkcs12.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 263 + }, + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/crypto/pkcs12/pkcs12.go", + "hashed_secret": "b5366a2d2ac98dae978423083f8b09e5cddc705d", + "is_verified": false, + "line_number": 312 + } + ], + "vendor/golang.org/x/oauth2/google/google.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/oauth2/google/google.go", + "hashed_secret": "525c26e89dabe70b50e73cbaef91f7bd25ea34af", + "is_verified": false, + "line_number": 67 + }, + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/oauth2/google/google.go", + "hashed_secret": "ff279b9e8bae2affb84c07ad4f95fe528ee45b70", + "is_verified": false, + "line_number": 95 + }, + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/oauth2/google/google.go", + "hashed_secret": "9e311e5a3b413b56b38ada05a29b6c33c783ffd8", + "is_verified": false, + "line_number": 97 + }, + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/oauth2/google/google.go", + "hashed_secret": "6b6ca3eb5e5ae52d92f8e396c2763e98f3916cd6", + "is_verified": false, + "line_number": 138 + }, + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/oauth2/google/google.go", + "hashed_secret": "04d90ea3a81dc39468145625890972d3879b483f", + "is_verified": false, + "line_number": 157 + } + ], + "vendor/golang.org/x/oauth2/google/internal/externalaccount/aws.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/oauth2/google/internal/externalaccount/aws.go", + "hashed_secret": "6df0130410a34d6355f17c422f7b2f7ae9feae66", + "is_verified": false, + "line_number": 389 + } + ], + "vendor/golang.org/x/oauth2/google/internal/externalaccount/basecredentials.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/oauth2/google/internal/externalaccount/basecredentials.go", + "hashed_secret": "62aabf4334b2752f689893e98c37ab6dd372cd4d", + "is_verified": false, + "line_number": 241 + } + ], + "vendor/golang.org/x/oauth2/google/sdk.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/oauth2/google/sdk.go", + "hashed_secret": "d18feeb9fbff3e1184b0069ad7042b926f4bee81", + "is_verified": false, + "line_number": 107 + } + ], + "vendor/golang.org/x/sys/unix/zerrors_linux.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/sys/unix/zerrors_linux.go", + "hashed_secret": "34dfcaaf6c3877da805bae48cf15041490baf22a", + "is_verified": false, + "line_number": 2402 + }, + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/sys/unix/zerrors_linux.go", + "hashed_secret": "caf23000dc45a59da8787716b7ccfe327e3f381d", + "is_verified": false, + "line_number": 2812 + } + ], + "vendor/golang.org/x/sys/unix/zsysnum_zos_s390x.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/sys/unix/zsysnum_zos_s390x.go", + "hashed_secret": "633f7fc1838a75de8e4f326ff8086563fddfc38e", + "is_verified": false, + "line_number": 650 + }, + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/sys/unix/zsysnum_zos_s390x.go", + "hashed_secret": "16fe93d8226bbdd8ea2c26c96ad00e8f0fc078ef", + "is_verified": false, + "line_number": 2059 + }, + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/sys/unix/zsysnum_zos_s390x.go", + "hashed_secret": "594efd58e67eee002dc0c8d4071d54ea982e300c", + "is_verified": false, + "line_number": 2462 + } + ], + "vendor/golang.org/x/sys/unix/ztypes_linux.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/sys/unix/ztypes_linux.go", + "hashed_secret": "5f26813cca3d5b4ce2f4645d3e61912eb201272f", + "is_verified": false, + "line_number": 4146 + }, + { + "type": "Secret Keyword", + "filename": "vendor/golang.org/x/sys/unix/ztypes_linux.go", + "hashed_secret": "addd6b52338d47593e9de16d35df63c698b3b7e2", + "is_verified": false, + "line_number": 4538 + } + ], + "vendor/k8s.io/api/core/v1/generated.pb.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/generated.pb.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 7330 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/generated.pb.go", + "hashed_secret": "bdc41bc1b28e844c400ff71d2cfd77264e9283d9", + "is_verified": false, + "line_number": 60840 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/generated.pb.go", + "hashed_secret": "ccefa309400e66cd745ab6768e7be5d802a0727c", + "is_verified": false, + "line_number": 60843 + } + ], + "vendor/k8s.io/api/core/v1/types.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "c637aebe3a2825fb1f2be997a9434f3ce48a4b93", + "is_verified": false, + "line_number": 6066 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "f0cc16b493b62b1e7254b425b1bd91450f5f1d0f", + "is_verified": false, + "line_number": 6074 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "551806c2060353451a91155c24f5a813171faa6d", + "is_verified": false, + "line_number": 6093 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "74ca35492ddc2e7c256cc3d18a322e1b9f26c583", + "is_verified": false, + "line_number": 6102 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "cf520cdcce5ff33385bcfa225986c1c0cfb0621f", + "is_verified": false, + "line_number": 6112 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", + "is_verified": false, + "line_number": 6117 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "ea0fda40f6a6350bd05b58ab059f1278f9ffb0f1", + "is_verified": false, + "line_number": 6123 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "462dcc839822202407e3a7f0e39794aa009028c3", + "is_verified": false, + "line_number": 6126 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "dec51dbe05d0ff8e5aecd2ccf9497641a3aae1cb", + "is_verified": false, + "line_number": 6135 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "bac4a635cdee6434ccb1f1bf4039fe4ee1d6ce49", + "is_verified": false, + "line_number": 6140 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/types.go", + "hashed_secret": "99de2cc4594a0707cc62569f6d9a38b2df702b69", + "is_verified": false, + "line_number": 6144 + } + ], + "vendor/k8s.io/api/core/v1/zz_generated.deepcopy.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/api/core/v1/zz_generated.deepcopy.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 155 + } + ], + "vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types_jsonschema.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types_jsonschema.go", + "hashed_secret": "9f049d452ac7fd1a611d443a25f2ed748dd0cbe1", + "is_verified": false, + "line_number": 49 + } + ], + "vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types_jsonschema.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types_jsonschema.go", + "hashed_secret": "9f049d452ac7fd1a611d443a25f2ed748dd0cbe1", + "is_verified": false, + "line_number": 49 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/cephfspersistentvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/cephfspersistentvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 76 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/cephfsvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/cephfsvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 76 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/cinderpersistentvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/cinderpersistentvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 64 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/cindervolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/cindervolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 64 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/csipersistentvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/csipersistentvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 91 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/csivolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/csivolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 79 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/envfromsource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/envfromsource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 55 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/envvarsource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/envvarsource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 64 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/flexpersistentvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/flexpersistentvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 57 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/flexvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/flexvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 57 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/iscsipersistentvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/iscsipersistentvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 121 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/iscsivolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/iscsivolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 121 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/rbdpersistentvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/rbdpersistentvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 94 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/rbdvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/rbdvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 94 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/scaleiopersistentvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/scaleiopersistentvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 62 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/scaleiovolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/scaleiovolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 62 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/storageospersistentvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/storageospersistentvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 73 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/storageosvolumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/storageosvolumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 73 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/volume.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/volume.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 86 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/volumeprojection.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/volumeprojection.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 40 + } + ], + "vendor/k8s.io/client-go/applyconfigurations/core/v1/volumesource.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/applyconfigurations/core/v1/volumesource.go", + "hashed_secret": "f32b67c7e26342af42efabc674d441dca0a281c5", + "is_verified": false, + "line_number": 105 + } + ], + "vendor/k8s.io/client-go/kubernetes/typed/core/v1/secret.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/kubernetes/typed/core/v1/secret.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 186 + } + ], + "vendor/k8s.io/client-go/pkg/apis/clientauthentication/v1/zz_generated.conversion.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/pkg/apis/clientauthentication/v1/zz_generated.conversion.go", + "hashed_secret": "00fd68f71e26a60eb411fd9259cc2d36ca7a5cbb", + "is_verified": false, + "line_number": 185 + } + ], + "vendor/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/zz_generated.conversion.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/zz_generated.conversion.go", + "hashed_secret": "00fd68f71e26a60eb411fd9259cc2d36ca7a5cbb", + "is_verified": false, + "line_number": 185 + } + ], + "vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go", + "hashed_secret": "a0281cd072cea8e80e7866b05dc124815760b6c9", + "is_verified": false, + "line_number": 40 + } + ], + "vendor/k8s.io/client-go/rest/config.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/rest/config.go", + "hashed_secret": "59dde004ddcece9b4a98e93882b5de144346c03a", + "is_verified": false, + "line_number": 636 + } + ], + "vendor/k8s.io/client-go/rest/transport.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/rest/transport.go", + "hashed_secret": "a52aa984b951a26ba53048e3f3308ce2e3eb503a", + "is_verified": false, + "line_number": 102 + } + ], + "vendor/k8s.io/client-go/tools/auth/clientauth.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/auth/clientauth.go", + "hashed_secret": "5000b46de965119adb970eeae19e92234b28c24b", + "is_verified": false, + "line_number": 110 + } + ], + "vendor/k8s.io/client-go/tools/clientcmd/api/helpers.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/api/helpers.go", + "hashed_secret": "0c2ae2709572a888b458259733b293ad6f25c4f5", + "is_verified": false, + "line_number": 96 + } + ], + "vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.conversion.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.conversion.go", + "hashed_secret": "93a69b6ee70202fa6569f0acf46f37180889a835", + "is_verified": false, + "line_number": 165 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.conversion.go", + "hashed_secret": "a7aa947d41c52a455d5a687ba440cfac274e3a04", + "is_verified": false, + "line_number": 174 + } + ], + "vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.deepcopy.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.deepcopy.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 36 + } + ], + "vendor/k8s.io/client-go/tools/clientcmd/api/zz_generated.deepcopy.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/api/zz_generated.deepcopy.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 36 + } + ], + "vendor/k8s.io/client-go/tools/clientcmd/client_config.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/client_config.go", + "hashed_secret": "2427dd65053ea94734d64cec5a7b8b6f92a6dfe1", + "is_verified": false, + "line_number": 272 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/client_config.go", + "hashed_secret": "0b791de12d0d0ddbfbe2e8bd795e3a5cada0c355", + "is_verified": false, + "line_number": 288 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/client_config.go", + "hashed_secret": "e7df32c44f7bc55034450859fa7cd104f93fd69a", + "is_verified": false, + "line_number": 302 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/client_config.go", + "hashed_secret": "5000b46de965119adb970eeae19e92234b28c24b", + "is_verified": false, + "line_number": 312 + } + ], + "vendor/k8s.io/client-go/tools/clientcmd/overrides.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/overrides.go", + "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", + "is_verified": false, + "line_number": 162 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/overrides.go", + "hashed_secret": "08e20cd39f91a4e1a5234c76138891ecbcb46686", + "is_verified": false, + "line_number": 183 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/tools/clientcmd/overrides.go", + "hashed_secret": "9c160dbd126099bcc67d8f22e1788890b941f514", + "is_verified": false, + "line_number": 189 + } + ], + "vendor/k8s.io/client-go/util/keyutil/key.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/util/keyutil/key.go", + "hashed_secret": "2990cb9a78f2e64545bc4e7824eecf3cbe910b4c", + "is_verified": false, + "line_number": 36 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/util/keyutil/key.go", + "hashed_secret": "d65d45369e8aef106a8ca1c3bad151ad24163494", + "is_verified": false, + "line_number": 38 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/util/keyutil/key.go", + "hashed_secret": "4e23b8a4a0ec90dfbfeb74973bb3a1794c69a3a1", + "is_verified": false, + "line_number": 40 + }, + { + "type": "Secret Keyword", + "filename": "vendor/k8s.io/client-go/util/keyutil/key.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 165 + } + ], + "vendor/kubevirt.io/api/core/v1/deepcopy_generated.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/kubevirt.io/api/core/v1/deepcopy_generated.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 40 + } + ], + "vendor/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1/zz_generated.deepcopy.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1/zz_generated.deepcopy.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 887 + } + ], + "vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/kubectl.go": [ + { + "type": "Secret Keyword", + "filename": "vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/kubectl.go", + "hashed_secret": "7951d1263b9f64c7aa3a5a70dacc8938497ac1ce", + "is_verified": false, + "line_number": 65 + }, + { + "type": "Secret Keyword", + "filename": "vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/kubectl.go", + "hashed_secret": "9fab85f50bc67abdc51b1e8511285af1467e908b", + "is_verified": false, + "line_number": 68 + } ] }, - "generated_at": "2024-03-05T16:46:11Z" + "generated_at": "2024-03-05T11:11:16Z" } diff --git a/apis/clusters/v1beta1/cadence_webhook.go b/apis/clusters/v1beta1/cadence_webhook.go index 2d884e11c..ba8b77d5a 100644 --- a/apis/clusters/v1beta1/cadence_webhook.go +++ b/apis/clusters/v1beta1/cadence_webhook.go @@ -196,8 +196,7 @@ func (cv *cadenceValidator) ValidateUpdate(ctx context.Context, old runtime.Obje } } - // ensuring if the cluster is ready for the spec updating - if (c.Status.CurrentClusterOperationStatus != models.NoOperation || c.Status.State != models.RunningStatus) && c.Generation != oldCluster.Generation { + if IsClusterNotReadyForSpecUpdate(c.Status.CurrentClusterOperationStatus, c.Status.State, c.Generation, oldCluster.Generation) { return models.ErrClusterIsNotReadyToUpdate } diff --git a/apis/clusters/v1beta1/cassandra_types.go b/apis/clusters/v1beta1/cassandra_types.go index e4c5cff7c..60b701522 100644 --- a/apis/clusters/v1beta1/cassandra_types.go +++ b/apis/clusters/v1beta1/cassandra_types.go @@ -55,7 +55,7 @@ type CassandraSpec struct { GenericClusterSpec `json:",inline"` RestoreFrom *CassandraRestoreFrom `json:"restoreFrom,omitempty" dcomparisonSkip:"true"` - DataCentres []*CassandraDataCentre `json:"dataCentres,omitempty"` + DataCentres []*CassandraDataCentre `json:"dataCentres"` LuceneEnabled bool `json:"luceneEnabled,omitempty"` PasswordAndUserAuth bool `json:"passwordAndUserAuth,omitempty"` BundledUseOnly bool `json:"bundledUseOnly,omitempty"` diff --git a/apis/clusters/v1beta1/cassandra_webhook.go b/apis/clusters/v1beta1/cassandra_webhook.go index 67f877074..40fd5376d 100644 --- a/apis/clusters/v1beta1/cassandra_webhook.go +++ b/apis/clusters/v1beta1/cassandra_webhook.go @@ -84,11 +84,7 @@ func (cv *cassandraValidator) ValidateCreate(ctx context.Context, obj runtime.Ob } if c.Spec.RestoreFrom != nil { - if c.Spec.RestoreFrom.ClusterID == "" { - return fmt.Errorf("restore clusterID field is empty") - } else { - return nil - } + return nil } err = c.Spec.GenericClusterSpec.ValidateCreation() @@ -107,10 +103,6 @@ func (cv *cassandraValidator) ValidateCreate(ctx context.Context, obj runtime.Ob return err } - if len(c.Spec.DataCentres) == 0 { - return fmt.Errorf("data centres field is empty") - } - for _, dc := range c.Spec.DataCentres { //TODO: add support of multiple DCs for OnPrem clusters if len(c.Spec.DataCentres) > 1 && dc.CloudProvider == models.ONPREMISES { @@ -180,11 +172,9 @@ func (cv *cassandraValidator) ValidateUpdate(ctx context.Context, old runtime.Ob return fmt.Errorf("cannot update immutable fields: %v", err) } - // ensuring if the cluster is ready for the spec updating - if (c.Status.CurrentClusterOperationStatus != models.NoOperation || c.Status.State != models.RunningStatus) && c.Generation != oldCluster.Generation { + if IsClusterNotReadyForSpecUpdate(c.Status.CurrentClusterOperationStatus, c.Status.State, c.Generation, oldCluster.Generation) { return models.ErrClusterIsNotReadyToUpdate } - return nil } @@ -297,6 +287,7 @@ func (cs *CassandraSpec) validateDataCentresUpdate(oldSpec CassandraSpec) error return fmt.Errorf("number of nodes must be a multiple of replication factor: %v", newDC.ReplicationFactor) } + continue } newDCImmutableFields := newDC.newImmutableFields() diff --git a/apis/clusters/v1beta1/cassandra_webhook_test.go b/apis/clusters/v1beta1/cassandra_webhook_test.go new file mode 100644 index 000000000..8281787e6 --- /dev/null +++ b/apis/clusters/v1beta1/cassandra_webhook_test.go @@ -0,0 +1,596 @@ +package v1beta1 + +import ( + "context" + "testing" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/instaclustr/operator/pkg/instaclustr/mock/appversionsmock" + "github.com/instaclustr/operator/pkg/models" + "github.com/instaclustr/operator/pkg/validation" +) + +func Test_cassandraValidator_ValidateCreate(t *testing.T) { + api := appversionsmock.NewInstAPI() + + type fields struct { + Client client.Client + API validation.Validation + } + type args struct { + ctx context.Context + obj runtime.Object + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "PrivateIPBroadcastForDiscovery with public network cluster", + fields: fields{API: api}, + args: args{ + obj: &Cassandra{ + Spec: CassandraSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + PrivateNetwork: false, + }, + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + ReplicationFactor: 3, + PrivateIPBroadcastForDiscovery: true, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "invalid NodesNumber", + fields: fields{API: api}, + args: args{ + obj: &Cassandra{ + Spec: CassandraSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 4, + ReplicationFactor: 3, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "valid cluster", + fields: fields{API: api}, + args: args{ + obj: &Cassandra{ + Spec: CassandraSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + ReplicationFactor: 3, + }}, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cv := &cassandraValidator{ + Client: tt.fields.Client, + API: tt.fields.API, + } + if err := cv.ValidateCreate(tt.args.ctx, tt.args.obj); (err != nil) != tt.wantErr { + t.Errorf("ValidateCreate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_cassandraValidator_ValidateUpdate(t *testing.T) { + api := appversionsmock.NewInstAPI() + + type fields struct { + Client client.Client + API validation.Validation + } + type args struct { + ctx context.Context + old runtime.Object + new runtime.Object + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "update operation with BundledUseOnly enabled", + fields: fields{ + API: api, + }, + args: args{ + old: &Cassandra{ + ObjectMeta: v1.ObjectMeta{ + Generation: 1, + }, + Spec: CassandraSpec{ + BundledUseOnly: true, + }, + Status: CassandraStatus{ + GenericStatus: GenericStatus{ + ID: "test", + }, + }, + }, + new: &Cassandra{ + ObjectMeta: v1.ObjectMeta{ + Generation: 2, + }, + Spec: CassandraSpec{ + BundledUseOnly: true, + }, + Status: CassandraStatus{ + GenericStatus: GenericStatus{ + ID: "test", + }, + }, + }, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + API: api, + }, + args: args{ + old: &Cassandra{ + ObjectMeta: v1.ObjectMeta{ + Generation: 1, + }, + Status: CassandraStatus{ + GenericStatus: GenericStatus{ + State: models.RunningStatus, + CurrentClusterOperationStatus: models.NoOperation, + ID: "test", + }, + }, + }, + new: &Cassandra{ + ObjectMeta: v1.ObjectMeta{ + Generation: 2, + }, + Status: CassandraStatus{ + GenericStatus: GenericStatus{ + State: models.RunningStatus, + CurrentClusterOperationStatus: models.NoOperation, + ID: "test", + }, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cv := &cassandraValidator{ + Client: tt.fields.Client, + API: tt.fields.API, + } + if err := cv.ValidateUpdate(tt.args.ctx, tt.args.old, tt.args.new); (err != nil) != tt.wantErr { + t.Errorf("ValidateUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCassandraSpec_validateDataCentresUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + RestoreFrom *CassandraRestoreFrom + DataCentres []*CassandraDataCentre + LuceneEnabled bool + PasswordAndUserAuth bool + BundledUseOnly bool + PCICompliance bool + UserRefs References + ResizeSettings GenericResizeSettings + } + type args struct { + oldSpec CassandraSpec + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "change DC name", + fields: fields{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "new", + }, + }}, + }, + args: args{ + oldSpec: CassandraSpec{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "old", + }, + }}}, + }, + wantErr: true, + }, + { + name: "PrivateIPBroadcastForDiscovery with public network cluster", + fields: fields{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "old", + }, + NodesNumber: 3, + ReplicationFactor: 3, + }, + { + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "new", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + ReplicationFactor: 3, + PrivateIPBroadcastForDiscovery: true, + }}, + GenericClusterSpec: GenericClusterSpec{ + PrivateNetwork: false, + }, + }, + args: args{ + oldSpec: CassandraSpec{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "old", + }, + NodesNumber: 3, + ReplicationFactor: 3, + }}, + GenericClusterSpec: GenericClusterSpec{ + PrivateNetwork: false, + }, + }, + }, + wantErr: true, + }, + { + name: "invalid NodesNumber", + fields: fields{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "old", + }, + NodesNumber: 3, + ReplicationFactor: 3, + }, + { + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "new", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 4, + ReplicationFactor: 3, + }}, + GenericClusterSpec: GenericClusterSpec{ + PrivateNetwork: false, + }, + }, + args: args{ + oldSpec: CassandraSpec{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "old", + }, + NodesNumber: 3, + ReplicationFactor: 3, + }}, + GenericClusterSpec: GenericClusterSpec{ + PrivateNetwork: false, + }, + }, + }, + wantErr: true, + }, + { + name: "change immutable field", + fields: fields{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "new", + }, + NodesNumber: 3, + ReplicationFactor: 3, + }, + }, + GenericClusterSpec: GenericClusterSpec{ + PrivateNetwork: false, + }, + }, + args: args{ + oldSpec: CassandraSpec{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "old", + }, + NodesNumber: 3, + ReplicationFactor: 3, + }}, + GenericClusterSpec: GenericClusterSpec{ + PrivateNetwork: false, + }, + }, + }, + wantErr: true, + }, + { + name: "deleting nodes", + fields: fields{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + }, + NodesNumber: 3, + ReplicationFactor: 3, + }, + }, + GenericClusterSpec: GenericClusterSpec{ + PrivateNetwork: false, + }, + }, + args: args{ + oldSpec: CassandraSpec{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + }, + NodesNumber: 6, + ReplicationFactor: 3, + }}, + GenericClusterSpec: GenericClusterSpec{ + PrivateNetwork: false, + }, + }, + }, + wantErr: true, + }, + { + name: "unequal debezium", + fields: fields{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + }, + NodesNumber: 3, + ReplicationFactor: 3, + Debezium: []*DebeziumCassandraSpec{{}, {}}, + }, + }, + }, + args: args{ + oldSpec: CassandraSpec{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + }, + NodesNumber: 3, + ReplicationFactor: 3, + Debezium: []*DebeziumCassandraSpec{{}}, + }}, + }, + }, + wantErr: true, + }, + { + name: "unequal ShotoverProxy", + fields: fields{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + }, + NodesNumber: 3, + ReplicationFactor: 3, + ShotoverProxy: []*ShotoverProxySpec{{}, {}}, + }, + }, + }, + args: args{ + oldSpec: CassandraSpec{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + }, + NodesNumber: 3, + ReplicationFactor: 3, + ShotoverProxy: []*ShotoverProxySpec{{}}, + }}, + }, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "old", + }, + NodesNumber: 3, + ReplicationFactor: 3, + }, + { + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "new", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + ReplicationFactor: 3, + }}, + GenericClusterSpec: GenericClusterSpec{ + PrivateNetwork: false, + }, + }, + args: args{ + oldSpec: CassandraSpec{ + DataCentres: []*CassandraDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "old", + }, + NodesNumber: 3, + ReplicationFactor: 3, + }}, + GenericClusterSpec: GenericClusterSpec{ + PrivateNetwork: false, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cs := &CassandraSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + RestoreFrom: tt.fields.RestoreFrom, + DataCentres: tt.fields.DataCentres, + LuceneEnabled: tt.fields.LuceneEnabled, + PasswordAndUserAuth: tt.fields.PasswordAndUserAuth, + BundledUseOnly: tt.fields.BundledUseOnly, + PCICompliance: tt.fields.PCICompliance, + UserRefs: tt.fields.UserRefs, + ResizeSettings: tt.fields.ResizeSettings, + } + if err := cs.validateDataCentresUpdate(tt.args.oldSpec); (err != nil) != tt.wantErr { + t.Errorf("validateDataCentresUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCassandraSpec_validateResizeSettings(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + RestoreFrom *CassandraRestoreFrom + DataCentres []*CassandraDataCentre + LuceneEnabled bool + PasswordAndUserAuth bool + BundledUseOnly bool + PCICompliance bool + UserRefs References + ResizeSettings GenericResizeSettings + } + type args struct { + nodeNumber int + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "concurrency greater than number of nodes", + fields: fields{ + ResizeSettings: []*ResizeSettings{{ + Concurrency: 5, + }}, + }, + args: args{ + nodeNumber: 3, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + ResizeSettings: []*ResizeSettings{{ + Concurrency: 3, + }, { + Concurrency: 2, + }}, + }, + args: args{ + nodeNumber: 3, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &CassandraSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + RestoreFrom: tt.fields.RestoreFrom, + DataCentres: tt.fields.DataCentres, + LuceneEnabled: tt.fields.LuceneEnabled, + PasswordAndUserAuth: tt.fields.PasswordAndUserAuth, + BundledUseOnly: tt.fields.BundledUseOnly, + PCICompliance: tt.fields.PCICompliance, + UserRefs: tt.fields.UserRefs, + ResizeSettings: tt.fields.ResizeSettings, + } + if err := c.validateResizeSettings(tt.args.nodeNumber); (err != nil) != tt.wantErr { + t.Errorf("validateResizeSettings() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/apis/clusters/v1beta1/kafka_types.go b/apis/clusters/v1beta1/kafka_types.go index 5007b03b8..2f57d2684 100644 --- a/apis/clusters/v1beta1/kafka_types.go +++ b/apis/clusters/v1beta1/kafka_types.go @@ -87,11 +87,13 @@ type KafkaSpec struct { RestProxy []*RestProxy `json:"restProxy,omitempty"` KarapaceRestProxy []*KarapaceRestProxy `json:"karapaceRestProxy,omitempty"` KarapaceSchemaRegistry []*KarapaceSchemaRegistry `json:"karapaceSchemaRegistry,omitempty"` - Kraft []*Kraft `json:"kraft,omitempty"` - ResizeSettings GenericResizeSettings `json:"resizeSettings,omitempty" dcomparisonSkip:"true"` + //+kubebuilder:validation:MaxItems:=1 + Kraft []*Kraft `json:"kraft,omitempty"` + ResizeSettings GenericResizeSettings `json:"resizeSettings,omitempty" dcomparisonSkip:"true"` } type Kraft struct { + //+kubebuilder:validation:Max:=3 ControllerNodeCount int `json:"controllerNodeCount"` } @@ -100,7 +102,7 @@ type KafkaDataCentre struct { NodesNumber int `json:"nodesNumber"` NodeSize string `json:"nodeSize"` - + //+kubebuilder:validation:MaxItems:=1 PrivateLink []*PrivateLink `json:"privateLink,omitempty"` } diff --git a/apis/clusters/v1beta1/kafka_webhook.go b/apis/clusters/v1beta1/kafka_webhook.go index ee558d331..e78857b4d 100644 --- a/apis/clusters/v1beta1/kafka_webhook.go +++ b/apis/clusters/v1beta1/kafka_webhook.go @@ -102,10 +102,6 @@ func (kv *kafkaValidator) ValidateCreate(ctx context.Context, obj runtime.Object return err } - if len(k.Spec.DataCentres) == 0 { - return models.ErrZeroDataCentres - } - for _, dc := range k.Spec.DataCentres { //TODO: add support of multiple DCs for OnPrem clusters if len(k.Spec.DataCentres) > 1 && dc.CloudProvider == models.ONPREMISES { @@ -116,16 +112,6 @@ func (kv *kafkaValidator) ValidateCreate(ctx context.Context, obj runtime.Object return err } - if len(dc.PrivateLink) > 1 { - return fmt.Errorf("private link should not have more than 1 item") - } - - for _, pl := range dc.PrivateLink { - if len(pl.AdvertisedHostname) < 3 { - return fmt.Errorf("the advertised hostname must be at least 3 characters. Provided hostname: %s", pl.AdvertisedHostname) - } - } - err = validateReplicationFactor(models.KafkaReplicationFactors, k.Spec.ReplicationFactor) if err != nil { return err @@ -144,16 +130,6 @@ func (kv *kafkaValidator) ValidateCreate(ctx context.Context, obj runtime.Object } } - if len(k.Spec.Kraft) > 1 { - return models.ErrMoreThanOneKraft - } - - for _, kraft := range k.Spec.Kraft { - if kraft.ControllerNodeCount > 3 { - return models.ErrMoreThanThreeControllerNodeCount - } - } - for _, rs := range k.Spec.ResizeSettings { err = validateSingleConcurrentResize(rs.Concurrency) if err != nil { @@ -203,11 +179,9 @@ func (kv *kafkaValidator) ValidateUpdate(ctx context.Context, old runtime.Object } } - // ensuring if the cluster is ready for the spec updating - if (k.Status.CurrentClusterOperationStatus != models.NoOperation || k.Status.State != models.RunningStatus) && k.Generation != oldKafka.Generation { + if IsClusterNotReadyForSpecUpdate(k.Status.CurrentClusterOperationStatus, k.Status.State, k.Generation, oldKafka.Generation) { return models.ErrClusterIsNotReadyToUpdate } - return nil } diff --git a/apis/clusters/v1beta1/kafka_webhook_test.go b/apis/clusters/v1beta1/kafka_webhook_test.go index 6743f9620..ecaef585d 100644 --- a/apis/clusters/v1beta1/kafka_webhook_test.go +++ b/apis/clusters/v1beta1/kafka_webhook_test.go @@ -2,226 +2,750 @@ package v1beta1 import ( "context" - "os" + "testing" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/util/yaml" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/instaclustr/operator/pkg/instaclustr/mock/appversionsmock" "github.com/instaclustr/operator/pkg/models" + "github.com/instaclustr/operator/pkg/validation" ) -var _ = Describe("Kafka Controller", Ordered, func() { - kafkaManifest := Kafka{} - testKafkaManifest := Kafka{} - - It("Reading kafka manifest", func() { - yfile, err := os.ReadFile("../../../controllers/clusters/datatest/kafka_v1beta1.yaml") - Expect(err).Should(Succeed()) - - err = yaml.Unmarshal(yfile, &kafkaManifest) - Expect(err).Should(Succeed()) - testKafkaManifest = kafkaManifest - }) - - ctx := context.Background() - - When("apply a Kafka manifest", func() { - It("should test kafka creation flow", func() { - - testKafkaManifest.Spec.TwoFactorDelete = []*TwoFactorDelete{kafkaManifest.Spec.TwoFactorDelete[0], kafkaManifest.Spec.TwoFactorDelete[0]} - Expect(k8sClient.Create(ctx, &testKafkaManifest)).ShouldNot(Succeed()) - testKafkaManifest.Spec.TwoFactorDelete = kafkaManifest.Spec.TwoFactorDelete - - testKafkaManifest.Spec.SLATier = "some SLATier that is not supported" - Expect(k8sClient.Create(ctx, &testKafkaManifest)).ShouldNot(Succeed()) - testKafkaManifest.Spec.SLATier = kafkaManifest.Spec.SLATier - - testKafkaManifest.Spec.Kraft = []*Kraft{kafkaManifest.Spec.Kraft[0], kafkaManifest.Spec.Kraft[0]} - Expect(k8sClient.Create(ctx, &testKafkaManifest)).ShouldNot(Succeed()) - testKafkaManifest.Spec.Kraft = []*Kraft{testKafkaManifest.Spec.Kraft[0]} - - testKafkaManifest.Spec.Kraft[0].ControllerNodeCount = 4 - Expect(k8sClient.Create(ctx, &testKafkaManifest)).ShouldNot(Succeed()) - testKafkaManifest.Spec.Kraft[0].ControllerNodeCount = 3 - - testKafkaManifest.Spec.Version += ".1" - Expect(k8sClient.Create(ctx, &testKafkaManifest)).ShouldNot(Succeed()) - testKafkaManifest.Spec.Version = kafkaManifest.Spec.Version +func Test_kafkaValidator_ValidateCreate(t *testing.T) { + api := appversionsmock.NewInstAPI() + + type fields struct { + API validation.Validation + Client client.Client + } + type args struct { + ctx context.Context + obj runtime.Object + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "invalid nodes number", + fields: fields{ + API: api, + }, + args: args{ + obj: &Kafka{ + Spec: KafkaSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + Version: "1.0.0", + }, + DataCentres: []*KafkaDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: models.AWSVPC, + Network: "10.1.0.0/16", + }, + NodesNumber: 4, + NodeSize: "test", + }}, + PartitionsNumber: 1, + ReplicationFactor: 3, + }, + }, + }, + wantErr: true, + }, + { + name: "privateLint with no PrivateNetwork cluster", + fields: fields{ + API: api, + }, + args: args{ + obj: &Kafka{ + Spec: KafkaSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + Version: "1.0.0", + PrivateNetwork: false, + }, + DataCentres: []*KafkaDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: models.AWSVPC, + Network: "10.1.0.0/16", + }, + PrivateLink: PrivateLinkSpec{{ + AdvertisedHostname: "test", + }}, + NodesNumber: 3, + NodeSize: "test", + }}, + PartitionsNumber: 1, + ReplicationFactor: 3, + }, + }, + }, + wantErr: true, + }, + { + name: "privateLint with no aws cloudProvider", + fields: fields{ + API: api, + }, + args: args{ + obj: &Kafka{ + Spec: KafkaSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + Version: "1.0.0", + PrivateNetwork: true, + }, + DataCentres: []*KafkaDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "asia-east1", + CloudProvider: models.GCP, + Network: "10.1.0.0/16", + }, + PrivateLink: PrivateLinkSpec{{ + AdvertisedHostname: "test", + }}, + NodesNumber: 3, + NodeSize: "test", + }}, + PartitionsNumber: 1, + ReplicationFactor: 3, + }, + }, + }, + wantErr: true, + }, + { + name: "privateLint with no aws cloudProvider", + fields: fields{ + API: api, + }, + args: args{ + obj: &Kafka{ + Spec: KafkaSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + Version: "1.0.0", + PrivateNetwork: true, + }, + DataCentres: []*KafkaDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "asia-east1", + CloudProvider: models.GCP, + Network: "10.1.0.0/16", + }, + PrivateLink: PrivateLinkSpec{{ + AdvertisedHostname: "test", + }}, + NodesNumber: 3, + NodeSize: "test", + }}, + PartitionsNumber: 1, + ReplicationFactor: 3, + }, + }, + }, + wantErr: true, + }, + { + name: "valid cluster", + fields: fields{ + API: api, + }, + args: args{ + obj: &Kafka{ + Spec: KafkaSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + Version: "1.0.0", + PrivateNetwork: true, + }, + DataCentres: []*KafkaDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: models.AWSVPC, + Network: "10.1.0.0/16", + }, + PrivateLink: PrivateLinkSpec{{ + AdvertisedHostname: "test", + }}, + NodesNumber: 3, + NodeSize: "test", + }}, + PartitionsNumber: 1, + ReplicationFactor: 3, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kv := &kafkaValidator{ + API: tt.fields.API, + Client: tt.fields.Client, + } + if err := kv.ValidateCreate(tt.args.ctx, tt.args.obj); (err != nil) != tt.wantErr { + t.Errorf("ValidateCreate() error = %v, wantErr %v", err, tt.wantErr) + } }) - }) - - When("updating a Kafka manifest", func() { - It("should test kafka update flow", func() { - testKafkaManifest.Status.State = models.RunningStatus - Expect(k8sClient.Create(ctx, &testKafkaManifest)).Should(Succeed()) - - patch := testKafkaManifest.NewPatch() - testKafkaManifest.Status.ID = models.CreatedEvent - Expect(k8sClient.Status().Patch(ctx, &testKafkaManifest, patch)).Should(Succeed()) - - testKafkaManifest.Spec.Name += "newValue" - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.Name = kafkaManifest.Spec.Name - - testKafkaManifest.Spec.Version += ".1" - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.Version = kafkaManifest.Spec.Version - - testKafkaManifest.Spec.PCICompliance = !kafkaManifest.Spec.PCICompliance - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.PCICompliance = kafkaManifest.Spec.PCICompliance - - testKafkaManifest.Spec.PrivateNetwork = !kafkaManifest.Spec.PrivateNetwork - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.PrivateNetwork = kafkaManifest.Spec.PrivateNetwork - - testKafkaManifest.Spec.PartitionsNumber++ - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.PartitionsNumber = kafkaManifest.Spec.PartitionsNumber - - testKafkaManifest.Spec.AllowDeleteTopics = !kafkaManifest.Spec.AllowDeleteTopics - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.AllowDeleteTopics = kafkaManifest.Spec.AllowDeleteTopics - - testKafkaManifest.Spec.AutoCreateTopics = !kafkaManifest.Spec.AutoCreateTopics - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.AutoCreateTopics = kafkaManifest.Spec.AutoCreateTopics - - testKafkaManifest.Spec.ClientToClusterEncryption = !kafkaManifest.Spec.ClientToClusterEncryption - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.ClientToClusterEncryption = kafkaManifest.Spec.ClientToClusterEncryption - - testKafkaManifest.Spec.BundledUseOnly = !kafkaManifest.Spec.BundledUseOnly - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.BundledUseOnly = kafkaManifest.Spec.BundledUseOnly - - testKafkaManifest.Spec.PrivateNetwork = !kafkaManifest.Spec.PrivateNetwork - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.PrivateNetwork = kafkaManifest.Spec.PrivateNetwork - - testKafkaManifest.Spec.ClientBrokerAuthWithMTLS = !kafkaManifest.Spec.ClientBrokerAuthWithMTLS - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.ClientBrokerAuthWithMTLS = kafkaManifest.Spec.ClientBrokerAuthWithMTLS - - prevSchemaRegistry := kafkaManifest.Spec.SchemaRegistry - testKafkaManifest.Spec.SchemaRegistry = []*SchemaRegistry{prevSchemaRegistry[0], prevSchemaRegistry[0]} - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.SchemaRegistry = prevSchemaRegistry - - prevKarapaceSchemaRegistry := kafkaManifest.Spec.KarapaceSchemaRegistry - testKafkaManifest.Spec.KarapaceSchemaRegistry = []*KarapaceSchemaRegistry{prevKarapaceSchemaRegistry[0], prevKarapaceSchemaRegistry[0]} - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.KarapaceSchemaRegistry = prevKarapaceSchemaRegistry - - prevRestProxy := kafkaManifest.Spec.RestProxy - testKafkaManifest.Spec.RestProxy = []*RestProxy{prevRestProxy[0], prevRestProxy[0]} - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.RestProxy = prevRestProxy - - prevRestProxyVersion := prevRestProxy[0].Version - testKafkaManifest.Spec.RestProxy[0].Version += ".0" - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.RestProxy[0].Version = prevRestProxyVersion - - prevKraft := kafkaManifest.Spec.Kraft - testKafkaManifest.Spec.Kraft = []*Kraft{prevKraft[0], prevKraft[0]} - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.Kraft = prevKraft - - prevKraftControllerNodeCount := prevKraft[0].ControllerNodeCount - testKafkaManifest.Spec.Kraft[0].ControllerNodeCount++ - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.Kraft[0].ControllerNodeCount = prevKraftControllerNodeCount - - prevKarapaceRestProxy := kafkaManifest.Spec.KarapaceRestProxy - testKafkaManifest.Spec.KarapaceRestProxy = []*KarapaceRestProxy{prevKarapaceRestProxy[0], prevKarapaceRestProxy[0]} - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.KarapaceRestProxy = prevKarapaceRestProxy - - prevConcurrency := kafkaManifest.Spec.ResizeSettings[0].Concurrency - testKafkaManifest.Spec.ResizeSettings[0].Concurrency++ - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.ResizeSettings[0].Concurrency = prevConcurrency - - prevDedicatedZookeeper := kafkaManifest.Spec.DedicatedZookeeper - testKafkaManifest.Spec.DedicatedZookeeper = []*DedicatedZookeeper{prevDedicatedZookeeper[0], prevDedicatedZookeeper[0]} - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DedicatedZookeeper = prevDedicatedZookeeper - - prevDedicatedZookeeperNodesNumber := kafkaManifest.Spec.DedicatedZookeeper[0].NodesNumber - testKafkaManifest.Spec.DedicatedZookeeper[0].NodesNumber++ - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DedicatedZookeeper[0].NodesNumber = prevDedicatedZookeeperNodesNumber - - testKafkaManifest.Spec.DataCentres = []*KafkaDataCentre{} - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres = kafkaManifest.Spec.DataCentres - - By("changing datacentres fields") - prevIntField := kafkaManifest.Spec.DataCentres[0].NodesNumber - testKafkaManifest.Spec.DataCentres[0].NodesNumber++ - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].NodesNumber = prevIntField - - prevIntField = kafkaManifest.Spec.DataCentres[0].NodesNumber - testKafkaManifest.Spec.DataCentres[0].NodesNumber = 0 - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].NodesNumber = prevIntField - - prevStringField := kafkaManifest.Spec.DataCentres[0].Name - testKafkaManifest.Spec.DataCentres[0].Name += "test" - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].Name = prevStringField - - prevStringField = kafkaManifest.Spec.DataCentres[0].Region - testKafkaManifest.Spec.DataCentres[0].Region += "test" - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].Region = prevStringField - - prevStringField = kafkaManifest.Spec.DataCentres[0].CloudProvider - testKafkaManifest.Spec.DataCentres[0].CloudProvider += "test" - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].CloudProvider = prevStringField - - prevStringField = kafkaManifest.Spec.DataCentres[0].ProviderAccountName - testKafkaManifest.Spec.DataCentres[0].ProviderAccountName += "test" - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].ProviderAccountName = prevStringField - - prevStringField = kafkaManifest.Spec.DataCentres[0].Network - testKafkaManifest.Spec.DataCentres[0].Network += "test" - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].Network = prevStringField - - prevAWSSettings := kafkaManifest.Spec.DataCentres[0].AWSSettings - testKafkaManifest.Spec.DataCentres[0].AWSSettings = []*AWSSettings{prevAWSSettings[0], prevAWSSettings[0]} - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].AWSSettings = prevAWSSettings - - prevGCPSettings := kafkaManifest.Spec.DataCentres[0].GCPSettings - gcpSettings := &GCPSettings{CustomVirtualNetworkID: "test-network-id", DisableSnapshotAutoExpiry: true} - testKafkaManifest.Spec.DataCentres[0].GCPSettings = []*GCPSettings{gcpSettings, gcpSettings} - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].GCPSettings = prevGCPSettings - - prevAzureSettings := kafkaManifest.Spec.DataCentres[0].AzureSettings - azureSettings := &AzureSettings{ResourceGroup: "test-resource-group", CustomVirtualNetworkID: "test-network-id", StorageNetwork: "test-storage-network"} - testKafkaManifest.Spec.DataCentres[0].AzureSettings = []*AzureSettings{azureSettings, azureSettings} - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].AzureSettings = prevAzureSettings - - testKafkaManifest.Spec.DataCentres[0].Tags["test"] = "test" - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - delete(testKafkaManifest.Spec.DataCentres[0].Tags, "test") - - delete(testKafkaManifest.Spec.DataCentres[0].Tags, "tag") - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].Tags["tag"] = "oneTag" - - testKafkaManifest.Spec.DataCentres[0].Tags["tag"] = "test" - Expect(k8sClient.Patch(ctx, &testKafkaManifest, patch)).ShouldNot(Succeed()) - testKafkaManifest.Spec.DataCentres[0].Tags["tag"] = "oneTag" + } +} + +func Test_kafkaValidator_ValidateUpdate(t *testing.T) { + type fields struct { + API validation.Validation + Client client.Client + } + type args struct { + ctx context.Context + old runtime.Object + new runtime.Object + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "update operation with BundledUseOnly enabled", + fields: fields{}, + args: args{ + new: &Kafka{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 2, + }, + Spec: KafkaSpec{ + BundledUseOnly: true, + }, + Status: KafkaStatus{ + GenericStatus: GenericStatus{ + ID: "test", + }, + }, + }, + old: &Kafka{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: KafkaSpec{ + BundledUseOnly: true, + }, + Status: KafkaStatus{ + GenericStatus: GenericStatus{ + ID: "test", + }, + }, + }, + }, + wantErr: true, + }, + { + name: "valid update", + fields: fields{}, + args: args{ + new: &Kafka{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 2, + }, + Status: KafkaStatus{ + GenericStatus: GenericStatus{ + State: models.RunningStatus, + CurrentClusterOperationStatus: models.NoOperation, + ID: "test", + }, + }, + }, + old: &Kafka{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Status: KafkaStatus{ + GenericStatus: GenericStatus{ + State: models.RunningStatus, + CurrentClusterOperationStatus: models.NoOperation, + ID: "test", + }, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kv := &kafkaValidator{ + API: tt.fields.API, + Client: tt.fields.Client, + } + if err := kv.ValidateUpdate(tt.args.ctx, tt.args.old, tt.args.new); (err != nil) != tt.wantErr { + t.Errorf("ValidateUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestKafkaSpec_validateUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + ReplicationFactor int + PartitionsNumber int + AllowDeleteTopics bool + AutoCreateTopics bool + ClientToClusterEncryption bool + ClientBrokerAuthWithMTLS bool + BundledUseOnly bool + PCICompliance bool + UserRefs References + DedicatedZookeeper []*DedicatedZookeeper + DataCentres []*KafkaDataCentre + SchemaRegistry []*SchemaRegistry + RestProxy []*RestProxy + KarapaceRestProxy []*KarapaceRestProxy + KarapaceSchemaRegistry []*KarapaceSchemaRegistry + Kraft []*Kraft + ResizeSettings GenericResizeSettings + } + type args struct { + old *KafkaSpec + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "update immutable field", + fields: fields{ + PartitionsNumber: 2, + }, + args: args{ + old: &KafkaSpec{ + PartitionsNumber: 1, + }, + }, + wantErr: true, + }, + { + name: "change DCs number", + fields: fields{ + DataCentres: []*KafkaDataCentre{{}, {}}, + }, + args: args{ + old: &KafkaSpec{ + DataCentres: []*KafkaDataCentre{{}}, + }, + }, + wantErr: true, + }, + { + name: "invalid nodes number", + fields: fields{ + DataCentres: []*KafkaDataCentre{{ + NodesNumber: 4, + }}, + ReplicationFactor: 3, + }, + args: args{ + old: &KafkaSpec{ + DataCentres: []*KafkaDataCentre{{ + NodesNumber: 3, + }}, + ReplicationFactor: 3, + }, + }, + wantErr: true, + }, + { + name: "change SchemaRegistry", + fields: fields{ + SchemaRegistry: []*SchemaRegistry{{ + Version: "2", + }}, + }, + args: args{ + old: &KafkaSpec{ + SchemaRegistry: []*SchemaRegistry{{ + Version: "1", + }}, + }, + }, + wantErr: true, + }, + { + name: "change KarapaceSchemaRegistry", + fields: fields{ + KarapaceSchemaRegistry: []*KarapaceSchemaRegistry{{ + Version: "2", + }}, + }, + args: args{ + old: &KafkaSpec{ + KarapaceSchemaRegistry: []*KarapaceSchemaRegistry{{ + Version: "1", + }}, + }, + }, + wantErr: true, + }, + { + name: "change RestProxy", + fields: fields{ + RestProxy: []*RestProxy{{ + Version: "2", + }}, + }, + args: args{ + old: &KafkaSpec{ + RestProxy: []*RestProxy{{ + Version: "1", + }}, + }, + }, + wantErr: true, + }, + { + name: "change Kraft", + fields: fields{ + Kraft: []*Kraft{{ + ControllerNodeCount: 2, + }}, + }, + args: args{ + old: &KafkaSpec{ + Kraft: []*Kraft{{ + ControllerNodeCount: 1, + }}, + }, + }, + wantErr: true, + }, + { + name: "change KarapaceRestProxy", + fields: fields{ + KarapaceRestProxy: []*KarapaceRestProxy{{ + Version: "2", + }}, + }, + args: args{ + old: &KafkaSpec{ + KarapaceRestProxy: []*KarapaceRestProxy{{ + Version: "1", + }}, + }, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + BundledUseOnly: true, + }, + args: args{ + old: &KafkaSpec{ + BundledUseOnly: true, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ks := &KafkaSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + ReplicationFactor: tt.fields.ReplicationFactor, + PartitionsNumber: tt.fields.PartitionsNumber, + AllowDeleteTopics: tt.fields.AllowDeleteTopics, + AutoCreateTopics: tt.fields.AutoCreateTopics, + ClientToClusterEncryption: tt.fields.ClientToClusterEncryption, + ClientBrokerAuthWithMTLS: tt.fields.ClientBrokerAuthWithMTLS, + BundledUseOnly: tt.fields.BundledUseOnly, + PCICompliance: tt.fields.PCICompliance, + UserRefs: tt.fields.UserRefs, + DedicatedZookeeper: tt.fields.DedicatedZookeeper, + DataCentres: tt.fields.DataCentres, + SchemaRegistry: tt.fields.SchemaRegistry, + RestProxy: tt.fields.RestProxy, + KarapaceRestProxy: tt.fields.KarapaceRestProxy, + KarapaceSchemaRegistry: tt.fields.KarapaceSchemaRegistry, + Kraft: tt.fields.Kraft, + ResizeSettings: tt.fields.ResizeSettings, + } + if err := ks.validateUpdate(tt.args.old); (err != nil) != tt.wantErr { + t.Errorf("validateUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_validateZookeeperUpdate(t *testing.T) { + type args struct { + new []*DedicatedZookeeper + old []*DedicatedZookeeper + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "nil zookeeoer", + args: args{ + new: nil, + old: nil, + }, + want: true, + }, + { + name: "change len", + args: args{ + new: []*DedicatedZookeeper{{}, {}}, + old: []*DedicatedZookeeper{{}}, + }, + want: false, + }, + { + name: "change NodesNumber", + args: args{ + new: []*DedicatedZookeeper{{ + NodesNumber: 2, + }}, + old: []*DedicatedZookeeper{{ + NodesNumber: 1, + }}, + }, + want: false, + }, + { + name: "unchanged NodesNumber", + args: args{ + new: []*DedicatedZookeeper{{ + NodesNumber: 1, + }}, + old: []*DedicatedZookeeper{{ + NodesNumber: 1, + }}, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := validateZookeeperUpdate(tt.args.new, tt.args.old); got != tt.want { + t.Errorf("validateZookeeperUpdate() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_isPrivateLinkValid(t *testing.T) { + type args struct { + new []*PrivateLink + old []*PrivateLink + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "both nil", + args: args{ + new: nil, + old: nil, + }, + want: true, + }, + { + name: "change len", + args: args{ + new: []*PrivateLink{{}, {}}, + old: []*PrivateLink{{}}, + }, + want: false, + }, + { + name: "change AdvertisedHostname", + args: args{ + new: []*PrivateLink{{ + AdvertisedHostname: "new", + }}, + old: []*PrivateLink{{ + AdvertisedHostname: "old", + }}, + }, + want: false, + }, + { + name: "unchanged AdvertisedHostname", + args: args{ + new: []*PrivateLink{{ + AdvertisedHostname: "old", + }}, + old: []*PrivateLink{{ + AdvertisedHostname: "old", + }}, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isPrivateLinkValid(tt.args.new, tt.args.old); got != tt.want { + t.Errorf("isPrivateLinkValid() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestKafkaSpec_validateImmutableDataCentresFieldsUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + ReplicationFactor int + PartitionsNumber int + AllowDeleteTopics bool + AutoCreateTopics bool + ClientToClusterEncryption bool + ClientBrokerAuthWithMTLS bool + BundledUseOnly bool + PCICompliance bool + UserRefs References + DedicatedZookeeper []*DedicatedZookeeper + DataCentres []*KafkaDataCentre + SchemaRegistry []*SchemaRegistry + RestProxy []*RestProxy + KarapaceRestProxy []*KarapaceRestProxy + KarapaceSchemaRegistry []*KarapaceSchemaRegistry + Kraft []*Kraft + ResizeSettings GenericResizeSettings + } + type args struct { + oldSpec *KafkaSpec + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "change DC name", + fields: fields{ + DataCentres: []*KafkaDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "new", + }, + }}, + }, + args: args{ + oldSpec: &KafkaSpec{ + DataCentres: []*KafkaDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "old", + }, + }}, + }, + }, + wantErr: true, + }, + { + name: "change immutable DC field", + fields: fields{ + DataCentres: []*KafkaDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Region: "new", + }, + }}, + }, + args: args{ + oldSpec: &KafkaSpec{ + DataCentres: []*KafkaDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Region: "old", + }, + }}, + }, + }, + wantErr: true, + }, + { + name: "deleting node", + fields: fields{ + DataCentres: []*KafkaDataCentre{{ + NodesNumber: 1, + }}, + }, + args: args{ + oldSpec: &KafkaSpec{ + DataCentres: []*KafkaDataCentre{{ + NodesNumber: 2, + }}, + }, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + DataCentres: []*KafkaDataCentre{{ + NodesNumber: 2, + }}, + }, + args: args{ + oldSpec: &KafkaSpec{ + DataCentres: []*KafkaDataCentre{{ + NodesNumber: 1, + }}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ks := &KafkaSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + ReplicationFactor: tt.fields.ReplicationFactor, + PartitionsNumber: tt.fields.PartitionsNumber, + AllowDeleteTopics: tt.fields.AllowDeleteTopics, + AutoCreateTopics: tt.fields.AutoCreateTopics, + ClientToClusterEncryption: tt.fields.ClientToClusterEncryption, + ClientBrokerAuthWithMTLS: tt.fields.ClientBrokerAuthWithMTLS, + BundledUseOnly: tt.fields.BundledUseOnly, + PCICompliance: tt.fields.PCICompliance, + UserRefs: tt.fields.UserRefs, + DedicatedZookeeper: tt.fields.DedicatedZookeeper, + DataCentres: tt.fields.DataCentres, + SchemaRegistry: tt.fields.SchemaRegistry, + RestProxy: tt.fields.RestProxy, + KarapaceRestProxy: tt.fields.KarapaceRestProxy, + KarapaceSchemaRegistry: tt.fields.KarapaceSchemaRegistry, + Kraft: tt.fields.Kraft, + ResizeSettings: tt.fields.ResizeSettings, + } + if err := ks.validateImmutableDataCentresFieldsUpdate(tt.args.oldSpec); (err != nil) != tt.wantErr { + t.Errorf("validateImmutableDataCentresFieldsUpdate() error = %v, wantErr %v", err, tt.wantErr) + } }) - }) -}) + } +} diff --git a/apis/clusters/v1beta1/kafkaconnect_types.go b/apis/clusters/v1beta1/kafkaconnect_types.go index 661e0c22c..42e968fae 100644 --- a/apis/clusters/v1beta1/kafkaconnect_types.go +++ b/apis/clusters/v1beta1/kafkaconnect_types.go @@ -31,9 +31,11 @@ import ( type TargetCluster struct { // Details to connect to a Non-Instaclustr managed cluster. Cannot be provided if targeting an Instaclustr managed cluster. + //+kubebuilder:validation:MaxItems:=1 ExternalCluster []*ExternalCluster `json:"externalCluster,omitempty"` // Details to connect to an Instaclustr managed cluster. Cannot be provided if targeting an external cluster. + //+kubebuilder:validation:MaxItems:=1 ManagedCluster []*ManagedCluster `json:"managedCluster,omitempty"` } @@ -113,10 +115,12 @@ type KafkaConnectDataCentre struct { type KafkaConnectSpec struct { GenericClusterSpec `json:",inline"` - DataCentres []*KafkaConnectDataCentre `json:"dataCentres"` - TargetCluster []*TargetCluster `json:"targetCluster"` + DataCentres []*KafkaConnectDataCentre `json:"dataCentres"` + //+kubebuilder:validation:MaxItems:=1 + TargetCluster []*TargetCluster `json:"targetCluster"` // CustomConnectors defines the location for custom connector storage and access info. + //+kubebuilder:validation:MaxItems:=1 CustomConnectors []*CustomConnectors `json:"customConnectors,omitempty"` } diff --git a/apis/clusters/v1beta1/kafkaconnect_webhook.go b/apis/clusters/v1beta1/kafkaconnect_webhook.go index 3e1ba3cc0..3ce5e97f9 100644 --- a/apis/clusters/v1beta1/kafkaconnect_webhook.go +++ b/apis/clusters/v1beta1/kafkaconnect_webhook.go @@ -102,18 +102,7 @@ func (kcv *kafkaConnectValidator) ValidateCreate(ctx context.Context, obj runtim return err } - if len(kc.Spec.TargetCluster) > 1 { - return fmt.Errorf("targetCluster array size must be between 0 and 1") - } - for _, tc := range kc.Spec.TargetCluster { - if len(tc.ManagedCluster) > 1 { - return fmt.Errorf("managedCluster array size must be between 0 and 1") - } - - if len(tc.ExternalCluster) > 1 { - return fmt.Errorf("externalCluster array size must be between 0 and 1") - } for _, mc := range tc.ManagedCluster { if (mc.TargetKafkaClusterID == "" && mc.ClusterRef == nil) || (mc.TargetKafkaClusterID != "" && mc.ClusterRef != nil) { @@ -134,14 +123,6 @@ func (kcv *kafkaConnectValidator) ValidateCreate(ctx context.Context, obj runtim } } - if len(kc.Spec.CustomConnectors) > 1 { - return fmt.Errorf("customConnectors array size must be between 0 and 1") - } - - if len(kc.Spec.DataCentres) == 0 { - return fmt.Errorf("data centres field is empty") - } - for _, dc := range kc.Spec.DataCentres { //TODO: add support of multiple DCs for OnPrem clusters if len(kc.Spec.DataCentres) > 1 && dc.CloudProvider == models.ONPREMISES { @@ -198,8 +179,7 @@ func (kcv *kafkaConnectValidator) ValidateUpdate(ctx context.Context, old runtim return fmt.Errorf("cannot update immutable fields: %v", err) } - // ensuring if the cluster is ready for the spec updating - if (kc.Status.CurrentClusterOperationStatus != models.NoOperation || kc.Status.State != models.RunningStatus) && kc.Generation != oldCluster.Generation { + if IsClusterNotReadyForSpecUpdate(kc.Status.CurrentClusterOperationStatus, kc.Status.State, kc.Generation, oldCluster.Generation) { return models.ErrClusterIsNotReadyToUpdate } @@ -310,9 +290,6 @@ func (kdc *KafkaConnectDataCentre) newImmutableFields() *immutableKafkaConnectDC } func (kc *KafkaConnectSpec) validateImmutableTargetClusterFieldsUpdate(new, old []*TargetCluster) error { - if len(new) == 0 && len(old) == 0 { - return models.ErrImmutableTargetCluster - } if len(old) != len(new) { return models.ErrImmutableTargetCluster diff --git a/apis/clusters/v1beta1/kafkaconnect_webhook_test.go b/apis/clusters/v1beta1/kafkaconnect_webhook_test.go new file mode 100644 index 000000000..0e8e23abb --- /dev/null +++ b/apis/clusters/v1beta1/kafkaconnect_webhook_test.go @@ -0,0 +1,443 @@ +package v1beta1 + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/instaclustr/operator/apis/clusterresources/v1beta1" + "github.com/instaclustr/operator/pkg/instaclustr/mock/appversionsmock" + "github.com/instaclustr/operator/pkg/validation" +) + +func Test_kafkaConnectValidator_ValidateCreate(t *testing.T) { + api := appversionsmock.NewInstAPI() + + type fields struct { + API validation.Validation + Client client.Client + } + type args struct { + ctx context.Context + obj runtime.Object + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "ClusterID and ClusterRef are filled", + fields: fields{ + API: api, + }, + args: args{ + obj: &KafkaConnect{ + Spec: KafkaConnectSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*KafkaConnectDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + ReplicationFactor: 3, + }}, + TargetCluster: []*TargetCluster{{ + ManagedCluster: []*ManagedCluster{{ + TargetKafkaClusterID: "test", + ClusterRef: &v1beta1.ClusterRef{ + Name: "test", + Namespace: "test", + }, + KafkaConnectVPCType: "test", + }}, + }}, + CustomConnectors: nil, + }, + }, + }, + wantErr: true, + }, + { + name: "invalid TargetKafkaClusterID", + fields: fields{ + API: api, + }, + args: args{ + obj: &KafkaConnect{ + Spec: KafkaConnectSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*KafkaConnectDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + ReplicationFactor: 3, + }}, + TargetCluster: []*TargetCluster{{ + ManagedCluster: []*ManagedCluster{{ + TargetKafkaClusterID: "test", + KafkaConnectVPCType: "test", + }}, + }}, + CustomConnectors: nil, + }, + }, + }, + wantErr: true, + }, + { + name: "invalid KafkaConnectVPCType", + fields: fields{ + API: api, + }, + args: args{ + obj: &KafkaConnect{ + Spec: KafkaConnectSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*KafkaConnectDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + ReplicationFactor: 3, + }}, + TargetCluster: []*TargetCluster{{ + ManagedCluster: []*ManagedCluster{{ + TargetKafkaClusterID: "9b5ba158-12f5-4681-a279-df5c371b417c", + KafkaConnectVPCType: "test", + }}, + }}, + CustomConnectors: nil, + }, + }, + }, + wantErr: true, + }, { + name: "invalid number of nodes", + fields: fields{ + API: api, + }, + args: args{ + obj: &KafkaConnect{ + Spec: KafkaConnectSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*KafkaConnectDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 5, + ReplicationFactor: 3, + }}, + TargetCluster: []*TargetCluster{{ + ManagedCluster: []*ManagedCluster{{ + TargetKafkaClusterID: "9b5ba158-12f5-4681-a279-df5c371b417c", + KafkaConnectVPCType: "KAFKA_VPC", + }}, + }}, + CustomConnectors: nil, + }, + }, + }, + wantErr: true, + }, + { + name: "valid cluster", + fields: fields{ + API: api, + }, + args: args{ + obj: &KafkaConnect{ + Spec: KafkaConnectSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*KafkaConnectDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + ReplicationFactor: 3, + }}, + TargetCluster: []*TargetCluster{{ + ManagedCluster: []*ManagedCluster{{ + TargetKafkaClusterID: "9b5ba158-12f5-4681-a279-df5c371b417c", + KafkaConnectVPCType: "KAFKA_VPC", + }}, + }}, + CustomConnectors: nil, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kcv := &kafkaConnectValidator{ + API: tt.fields.API, + Client: tt.fields.Client, + } + if err := kcv.ValidateCreate(tt.args.ctx, tt.args.obj); (err != nil) != tt.wantErr { + t.Errorf("ValidateCreate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestKafkaConnectSpec_validateImmutableDataCentresFieldsUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + DataCentres []*KafkaConnectDataCentre + TargetCluster []*TargetCluster + CustomConnectors []*CustomConnectors + } + type args struct { + oldSpec KafkaConnectSpec + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "deleting nodes", + fields: fields{ + DataCentres: []*KafkaConnectDataCentre{{ + NodesNumber: 0, + }}, + }, + args: args{ + oldSpec: KafkaConnectSpec{ + DataCentres: []*KafkaConnectDataCentre{{ + NodesNumber: 1, + }}, + }, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + DataCentres: []*KafkaConnectDataCentre{{ + NodesNumber: 3, + ReplicationFactor: 3, + }}, + }, + args: args{ + oldSpec: KafkaConnectSpec{ + DataCentres: []*KafkaConnectDataCentre{{ + NodesNumber: 3, + ReplicationFactor: 3, + }}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kc := &KafkaConnectSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + DataCentres: tt.fields.DataCentres, + TargetCluster: tt.fields.TargetCluster, + CustomConnectors: tt.fields.CustomConnectors, + } + if err := kc.validateImmutableDataCentresFieldsUpdate(tt.args.oldSpec); (err != nil) != tt.wantErr { + t.Errorf("validateImmutableDataCentresFieldsUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestKafkaConnectSpec_validateImmutableTargetClusterFieldsUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + DataCentres []*KafkaConnectDataCentre + TargetCluster []*TargetCluster + CustomConnectors []*CustomConnectors + } + type args struct { + new []*TargetCluster + old []*TargetCluster + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "changing length of TargetCluster", + fields: fields{}, + args: args{ + new: []*TargetCluster{{}}, + old: []*TargetCluster{{}, {}}, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{}, + args: args{ + new: []*TargetCluster{{}}, + old: []*TargetCluster{{}}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kc := &KafkaConnectSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + DataCentres: tt.fields.DataCentres, + TargetCluster: tt.fields.TargetCluster, + CustomConnectors: tt.fields.CustomConnectors, + } + if err := kc.validateImmutableTargetClusterFieldsUpdate(tt.args.new, tt.args.old); (err != nil) != tt.wantErr { + t.Errorf("validateImmutableTargetClusterFieldsUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_validateImmutableExternalClusterFields(t *testing.T) { + type args struct { + new *TargetCluster + old *TargetCluster + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "changing ExternalCluster field", + args: args{ + new: &TargetCluster{ + ExternalCluster: []*ExternalCluster{{ + SecurityProtocol: "new", + }}, + }, + old: &TargetCluster{ + ExternalCluster: []*ExternalCluster{{ + SecurityProtocol: "old", + }}, + }, + }, + wantErr: true, + }, + { + name: "valid case", + args: args{ + new: &TargetCluster{ + ExternalCluster: []*ExternalCluster{{ + SecurityProtocol: "old", + }}, + }, + old: &TargetCluster{ + ExternalCluster: []*ExternalCluster{{ + SecurityProtocol: "old", + }}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateImmutableExternalClusterFields(tt.args.new, tt.args.old); (err != nil) != tt.wantErr { + t.Errorf("validateImmutableExternalClusterFields() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_validateImmutableManagedClusterFields(t *testing.T) { + type args struct { + new *TargetCluster + old *TargetCluster + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "changing ManagedCluster field", + args: args{ + new: &TargetCluster{ + ManagedCluster: []*ManagedCluster{{ + TargetKafkaClusterID: "new", + }}, + }, + old: &TargetCluster{ + ManagedCluster: []*ManagedCluster{{ + TargetKafkaClusterID: "old", + }}, + }, + }, + wantErr: true, + }, + { + name: "valid case", + args: args{ + new: &TargetCluster{ + ManagedCluster: []*ManagedCluster{{ + TargetKafkaClusterID: "old", + }}, + }, + old: &TargetCluster{ + ManagedCluster: []*ManagedCluster{{ + TargetKafkaClusterID: "old", + }}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateImmutableManagedClusterFields(tt.args.new, tt.args.old); (err != nil) != tt.wantErr { + t.Errorf("validateImmutableManagedClusterFields() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/apis/clusters/v1beta1/opensearch_types.go b/apis/clusters/v1beta1/opensearch_types.go index 4adfa437e..2d5384307 100644 --- a/apis/clusters/v1beta1/opensearch_types.go +++ b/apis/clusters/v1beta1/opensearch_types.go @@ -34,7 +34,7 @@ type OpenSearchSpec struct { GenericClusterSpec `json:",inline"` RestoreFrom *OpenSearchRestoreFrom `json:"restoreFrom,omitempty"` - DataCentres []*OpenSearchDataCentre `json:"dataCentres,omitempty"` + DataCentres []*OpenSearchDataCentre `json:"dataCentres"` DataNodes []*OpenSearchDataNodes `json:"dataNodes,omitempty"` Dashboards []*OpenSearchDashboards `json:"opensearchDashboards,omitempty"` ClusterManagerNodes []*ClusterManagerNodes `json:"clusterManagerNodes"` diff --git a/apis/clusters/v1beta1/opensearch_webhook.go b/apis/clusters/v1beta1/opensearch_webhook.go index fd94e4953..34422cf2f 100644 --- a/apis/clusters/v1beta1/opensearch_webhook.go +++ b/apis/clusters/v1beta1/opensearch_webhook.go @@ -94,11 +94,7 @@ func (osv *openSearchValidator) ValidateCreate(ctx context.Context, obj runtime. } if os.Spec.RestoreFrom != nil { - if os.Spec.RestoreFrom.ClusterID == "" { - return fmt.Errorf("restore clusterID field is empty") - } else { - return nil - } + return nil } err = os.Spec.ValidateCreation() @@ -106,10 +102,6 @@ func (osv *openSearchValidator) ValidateCreate(ctx context.Context, obj runtime. return err } - if len(os.Spec.DataCentres) == 0 { - return models.ErrZeroDataCentres - } - err = os.Spec.validateDedicatedManager() if err != nil { return err @@ -203,8 +195,7 @@ func (osv *openSearchValidator) ValidateUpdate(ctx context.Context, old runtime. return err } - // ensuring if the cluster is ready for the spec updating - if (os.Status.CurrentClusterOperationStatus != models.NoOperation || os.Status.State != models.RunningStatus) && os.Generation != oldCluster.Generation { + if IsClusterNotReadyForSpecUpdate(os.Status.CurrentClusterOperationStatus, os.Status.State, os.Generation, oldCluster.Generation) { return models.ErrClusterIsNotReadyToUpdate } diff --git a/apis/clusters/v1beta1/opensearch_webhook_test.go b/apis/clusters/v1beta1/opensearch_webhook_test.go index a8c536646..f1efa52a0 100644 --- a/apis/clusters/v1beta1/opensearch_webhook_test.go +++ b/apis/clusters/v1beta1/opensearch_webhook_test.go @@ -2,303 +2,616 @@ package v1beta1 import ( "context" - "os" + "testing" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/util/yaml" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "github.com/instaclustr/operator/pkg/instaclustr/mock/appversionsmock" "github.com/instaclustr/operator/pkg/models" + "github.com/instaclustr/operator/pkg/validation" ) -var _ = Describe("Kafka Controller", Ordered, func() { - openSearchManifest := OpenSearch{} - testOpenSearchManifest := OpenSearch{} - - It("Reading kafka manifest", func() { - yfile, err := os.ReadFile("../../../controllers/clusters/datatest/opensearch_v1beta1.yaml") - Expect(err).Should(Succeed()) - - err = yaml.Unmarshal(yfile, &openSearchManifest) - Expect(err).Should(Succeed()) - openSearchManifest.Spec.TwoFactorDelete = []*TwoFactorDelete{{Email: "emailTEST", Phone: "phoneTEST"}} - testOpenSearchManifest = openSearchManifest - }) - - ctx := context.Background() - - When("apply an OpenSearch manifest", func() { - It("should test OpenSearch creation flow", func() { - testOpenSearchManifest.Spec.TwoFactorDelete = []*TwoFactorDelete{openSearchManifest.Spec.TwoFactorDelete[0], openSearchManifest.Spec.TwoFactorDelete[0]} - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.TwoFactorDelete = openSearchManifest.Spec.TwoFactorDelete - - testOpenSearchManifest.Spec.SLATier = "some SLATier that is not supported" - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.SLATier = openSearchManifest.Spec.SLATier - - testOpenSearchManifest.Spec.DataCentres = []*OpenSearchDataCentre{} - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres = openSearchManifest.Spec.DataCentres - - prevDataNode := openSearchManifest.Spec.DataNodes[0] - testOpenSearchManifest.Spec.DataNodes = []*OpenSearchDataNodes{} - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataNodes = []*OpenSearchDataNodes{prevDataNode} - - prevDC := *openSearchManifest.Spec.DataCentres[0] - testOpenSearchManifest.Spec.DataCentres[0].CloudProvider = "test" - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - - testOpenSearchManifest.Spec.DataCentres[0].CloudProvider = "AWS_VPC" - testOpenSearchManifest.Spec.DataCentres[0].Region = "test" - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - - testOpenSearchManifest.Spec.DataCentres[0].CloudProvider = "AZURE_AZ" - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - - testOpenSearchManifest.Spec.DataCentres[0].CloudProvider = "GCP" - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0] = &prevDC - - prevStringValue := openSearchManifest.Spec.DataCentres[0].ProviderAccountName - testOpenSearchManifest.Spec.DataCentres[0].ProviderAccountName = models.DefaultAccountName - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].ProviderAccountName = prevStringValue - - awsSettings := openSearchManifest.Spec.DataCentres[0].AWSSettings[0] - openSearchManifest.Spec.DataCentres[0].AWSSettings = []*AWSSettings{awsSettings, awsSettings} - Expect(k8sClient.Create(ctx, &openSearchManifest)).ShouldNot(Succeed()) - openSearchManifest.Spec.DataCentres[0].AWSSettings = []*AWSSettings{awsSettings} - - prevStringValue = openSearchManifest.Spec.DataCentres[0].Network - testOpenSearchManifest.Spec.DataCentres[0].Network = "test/test" - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].Network = prevStringValue - - testOpenSearchManifest.Spec.DataNodes[0].NodesNumber++ - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataNodes[0].NodesNumber-- - - testOpenSearchManifest.Spec.DataCentres[0].NumberOfRacks++ - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].NumberOfRacks-- - - testOpenSearchManifest.Spec.DataCentres[0].CloudProvider = "GCP" - testOpenSearchManifest.Spec.DataCentres[0].PrivateLink = true - testOpenSearchManifest.Spec.DataCentres[0].Region = "us-east1" - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].CloudProvider = "AWS_VPC" - testOpenSearchManifest.Spec.DataCentres[0].Region = "US_EAST_1" - - testOpenSearchManifest.Spec.PrivateNetwork = !testOpenSearchManifest.Spec.PrivateNetwork - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].PrivateLink = false - testOpenSearchManifest.Spec.PrivateNetwork = !testOpenSearchManifest.Spec.PrivateNetwork - - testOpenSearchManifest.Spec.ResizeSettings[0].Concurrency++ - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.ResizeSettings[0].Concurrency-- - - prevManagedNode := openSearchManifest.Spec.ClusterManagerNodes[0] - testOpenSearchManifest.Spec.ClusterManagerNodes = []*ClusterManagerNodes{prevManagedNode, prevManagedNode, prevManagedNode, prevManagedNode} - testOpenSearchManifest.Spec.ResizeSettings[0].Concurrency += 3 - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.ResizeSettings[0].Concurrency -= 3 - testOpenSearchManifest.Spec.ClusterManagerNodes = []*ClusterManagerNodes{prevManagedNode} - - testOpenSearchManifest.Spec.Version += ".1" - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.Version = openSearchManifest.Spec.Version - +func Test_validateOpenSearchNumberOfRacks(t *testing.T) { + type args struct { + numberOfRacks int + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "invalid NumberOfRacks", + args: args{ + numberOfRacks: 1, + }, + wantErr: true, + }, + { + name: "valid NumberOfRacks", + args: args{ + numberOfRacks: 3, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateOpenSearchNumberOfRacks(tt.args.numberOfRacks); (err != nil) != tt.wantErr { + t.Errorf("validateOpenSearchNumberOfRacks() error = %v, wantErr %v", err, tt.wantErr) + } }) - }) - - When("updating an OpenSearch manifest", func() { - It("should test OpenSearch update flow", func() { - testOpenSearchManifest.Status.State = models.RunningStatus - Expect(k8sClient.Create(ctx, &testOpenSearchManifest)).Should(Succeed()) - - patch := testOpenSearchManifest.NewPatch() - testOpenSearchManifest.Status.ID = models.CreatedEvent - Expect(k8sClient.Status().Patch(ctx, &testOpenSearchManifest, patch)).Should(Succeed()) - - testOpenSearchManifest.Spec.Name += "newValue" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.Name = openSearchManifest.Spec.Name - - testOpenSearchManifest.Spec.BundledUseOnly = !testOpenSearchManifest.Spec.BundledUseOnly - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.BundledUseOnly = !testOpenSearchManifest.Spec.BundledUseOnly - - testOpenSearchManifest.Spec.ICUPlugin = !testOpenSearchManifest.Spec.ICUPlugin - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.ICUPlugin = !testOpenSearchManifest.Spec.ICUPlugin - - testOpenSearchManifest.Spec.AsynchronousSearchPlugin = !testOpenSearchManifest.Spec.AsynchronousSearchPlugin - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.AsynchronousSearchPlugin = !testOpenSearchManifest.Spec.AsynchronousSearchPlugin - - testOpenSearchManifest.Spec.KNNPlugin = !testOpenSearchManifest.Spec.KNNPlugin - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.KNNPlugin = !testOpenSearchManifest.Spec.KNNPlugin - - testOpenSearchManifest.Spec.ReportingPlugin = !testOpenSearchManifest.Spec.ReportingPlugin - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.ReportingPlugin = !testOpenSearchManifest.Spec.ReportingPlugin - - testOpenSearchManifest.Spec.SQLPlugin = !testOpenSearchManifest.Spec.SQLPlugin - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.SQLPlugin = !testOpenSearchManifest.Spec.SQLPlugin - - testOpenSearchManifest.Spec.NotificationsPlugin = !testOpenSearchManifest.Spec.NotificationsPlugin - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.NotificationsPlugin = !testOpenSearchManifest.Spec.NotificationsPlugin - - testOpenSearchManifest.Spec.AnomalyDetectionPlugin = !testOpenSearchManifest.Spec.AnomalyDetectionPlugin - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.AnomalyDetectionPlugin = !testOpenSearchManifest.Spec.AnomalyDetectionPlugin - - testOpenSearchManifest.Spec.LoadBalancer = !testOpenSearchManifest.Spec.LoadBalancer - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.LoadBalancer = !testOpenSearchManifest.Spec.LoadBalancer - - testOpenSearchManifest.Spec.IndexManagementPlugin = !testOpenSearchManifest.Spec.IndexManagementPlugin - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.IndexManagementPlugin = !testOpenSearchManifest.Spec.IndexManagementPlugin - - testOpenSearchManifest.Spec.AlertingPlugin = !testOpenSearchManifest.Spec.AlertingPlugin - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.AlertingPlugin = !testOpenSearchManifest.Spec.AlertingPlugin - - testOpenSearchManifest.Spec.PCICompliance = !testOpenSearchManifest.Spec.PCICompliance - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.PCICompliance = !testOpenSearchManifest.Spec.PCICompliance - - testOpenSearchManifest.Spec.PrivateNetwork = !testOpenSearchManifest.Spec.PrivateNetwork - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.PrivateNetwork = !testOpenSearchManifest.Spec.PrivateNetwork - - prevStringValue := openSearchManifest.Spec.SLATier - testOpenSearchManifest.Spec.SLATier = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.SLATier = prevStringValue - - prevTwoFactorDelete := testOpenSearchManifest.Spec.TwoFactorDelete - testOpenSearchManifest.Spec.TwoFactorDelete = []*TwoFactorDelete{prevTwoFactorDelete[0], prevTwoFactorDelete[0]} - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.TwoFactorDelete = []*TwoFactorDelete{prevTwoFactorDelete[0]} - - testOpenSearchManifest.Spec.TwoFactorDelete = []*TwoFactorDelete{} - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.TwoFactorDelete = []*TwoFactorDelete{prevTwoFactorDelete[0]} - - prevStringValue = openSearchManifest.Spec.TwoFactorDelete[0].Email - testOpenSearchManifest.Spec.TwoFactorDelete[0].Email = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.TwoFactorDelete[0].Email = prevStringValue - - prevIngestNodes := testOpenSearchManifest.Spec.IngestNodes - testOpenSearchManifest.Spec.IngestNodes = []*OpenSearchIngestNodes{prevIngestNodes[0], prevIngestNodes[0]} - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.IngestNodes = []*OpenSearchIngestNodes{prevIngestNodes[0]} - - testOpenSearchManifest.Spec.IngestNodes = []*OpenSearchIngestNodes{} - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.IngestNodes = []*OpenSearchIngestNodes{prevIngestNodes[0]} - - prevStringValue = openSearchManifest.Spec.IngestNodes[0].NodeSize - testOpenSearchManifest.Spec.IngestNodes[0].NodeSize = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.IngestNodes[0].NodeSize = prevStringValue - - prevClusterManagedNodes := testOpenSearchManifest.Spec.ClusterManagerNodes - testOpenSearchManifest.Spec.ClusterManagerNodes = []*ClusterManagerNodes{prevClusterManagedNodes[0], prevClusterManagedNodes[0]} - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.ClusterManagerNodes = []*ClusterManagerNodes{prevClusterManagedNodes[0]} - - testOpenSearchManifest.Spec.ClusterManagerNodes = []*ClusterManagerNodes{} - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.ClusterManagerNodes = []*ClusterManagerNodes{prevClusterManagedNodes[0]} - - prevStringValue = openSearchManifest.Spec.ClusterManagerNodes[0].NodeSize - testOpenSearchManifest.Spec.ClusterManagerNodes[0].NodeSize = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.ClusterManagerNodes[0].NodeSize = prevStringValue - - By("changing datacentres fields") - - prevDCs := openSearchManifest.Spec.DataCentres - testOpenSearchManifest.Spec.DataCentres = []*OpenSearchDataCentre{prevDCs[0], prevDCs[0]} - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres = []*OpenSearchDataCentre{prevDCs[0]} - - prevStringValue = prevDCs[0].Name - testOpenSearchManifest.Spec.DataCentres[0].Name = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].Name = prevStringValue - - prevStringValue = prevDCs[0].Region - testOpenSearchManifest.Spec.DataCentres[0].Region = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].Region = prevStringValue - - prevStringValue = prevDCs[0].CloudProvider - testOpenSearchManifest.Spec.DataCentres[0].CloudProvider = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].CloudProvider = prevStringValue - - prevStringValue = prevDCs[0].ProviderAccountName - testOpenSearchManifest.Spec.DataCentres[0].ProviderAccountName = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].ProviderAccountName = prevStringValue - - prevStringValue = prevDCs[0].Network - testOpenSearchManifest.Spec.DataCentres[0].Network = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].Network = prevStringValue - - testOpenSearchManifest.Spec.DataCentres[0].PrivateLink = !testOpenSearchManifest.Spec.DataCentres[0].PrivateLink - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].PrivateLink = !testOpenSearchManifest.Spec.DataCentres[0].PrivateLink - - testOpenSearchManifest.Spec.DataCentres[0].NumberOfRacks += 1 - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].NumberOfRacks -= 1 - - prevAWSSettings := openSearchManifest.Spec.DataCentres[0].AWSSettings - openSearchManifest.Spec.DataCentres[0].AWSSettings = []*AWSSettings{prevAWSSettings[0], prevAWSSettings[0]} - Expect(k8sClient.Patch(ctx, &openSearchManifest, patch)).ShouldNot(Succeed()) - openSearchManifest.Spec.DataCentres[0].AWSSettings = prevAWSSettings - - prevStringValue = openSearchManifest.Spec.DataCentres[0].AWSSettings[0].DiskEncryptionKey - testOpenSearchManifest.Spec.DataCentres[0].AWSSettings[0].DiskEncryptionKey = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].AWSSettings[0].DiskEncryptionKey = prevStringValue - - prevStringValue = openSearchManifest.Spec.DataCentres[0].AWSSettings[0].CustomVirtualNetworkID - testOpenSearchManifest.Spec.DataCentres[0].AWSSettings[0].CustomVirtualNetworkID = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].AWSSettings[0].CustomVirtualNetworkID = prevStringValue - - prevStringValue = openSearchManifest.Spec.DataCentres[0].AWSSettings[0].BackupBucket - testOpenSearchManifest.Spec.DataCentres[0].AWSSettings[0].BackupBucket = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].AWSSettings[0].BackupBucket = prevStringValue - - testOpenSearchManifest.Spec.DataCentres[0].Tags["test"] = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - delete(testOpenSearchManifest.Spec.DataCentres[0].Tags, "test") - - delete(testOpenSearchManifest.Spec.DataCentres[0].Tags, "tag") - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].Tags["tag"] = "oneTag" - - testOpenSearchManifest.Spec.DataCentres[0].Tags["tag"] = "test" - Expect(k8sClient.Patch(ctx, &testOpenSearchManifest, patch)).ShouldNot(Succeed()) - testOpenSearchManifest.Spec.DataCentres[0].Tags["tag"] = "oneTag" - + } +} + +func Test_openSearchValidator_ValidateUpdate(t *testing.T) { + api := appversionsmock.NewInstAPI() + + type fields struct { + API validation.Validation + } + type args struct { + ctx context.Context + old runtime.Object + new runtime.Object + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "update with BundledUseOnly", + fields: fields{ + API: api, + }, + args: args{ + old: &OpenSearch{ + Spec: OpenSearchSpec{ + BundledUseOnly: true, + KNNPlugin: true, + }, + Status: OpenSearchStatus{ + GenericStatus: GenericStatus{ + ID: "test", + }, + }, + }, + new: &OpenSearch{ + Spec: OpenSearchSpec{ + BundledUseOnly: true, + KNNPlugin: false, + }, + Status: OpenSearchStatus{ + GenericStatus: GenericStatus{ + ID: "test", + }, + }, + }, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + API: api, + }, + args: args{ + old: &OpenSearch{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: OpenSearchSpec{ + KNNPlugin: true, + }, + Status: OpenSearchStatus{ + GenericStatus: GenericStatus{ + ID: "test", + CurrentClusterOperationStatus: models.NoOperation, + State: models.RunningStatus, + }, + }, + }, + new: &OpenSearch{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 2, + }, + Spec: OpenSearchSpec{ + KNNPlugin: true, + }, + Status: OpenSearchStatus{ + GenericStatus: GenericStatus{ + ID: "test", + State: models.RunningStatus, + CurrentClusterOperationStatus: models.NoOperation, + }, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osv := &openSearchValidator{ + API: tt.fields.API, + } + if err := osv.ValidateUpdate(tt.args.ctx, tt.args.old, tt.args.new); (err != nil) != tt.wantErr { + t.Errorf("ValidateUpdate() error = %v, wantErr %v", err, tt.wantErr) + } }) - - }) -}) + } +} + +func TestOpenSearchSpec_validateDedicatedManager(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + RestoreFrom *OpenSearchRestoreFrom + DataCentres []*OpenSearchDataCentre + DataNodes []*OpenSearchDataNodes + Dashboards []*OpenSearchDashboards + ClusterManagerNodes []*ClusterManagerNodes + ICUPlugin bool + AsynchronousSearchPlugin bool + KNNPlugin bool + ReportingPlugin bool + SQLPlugin bool + NotificationsPlugin bool + AnomalyDetectionPlugin bool + LoadBalancer bool + IndexManagementPlugin bool + AlertingPlugin bool + BundledUseOnly bool + PCICompliance bool + UserRefs References + ResizeSettings []*ResizeSettings + IngestNodes []*OpenSearchIngestNodes + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "dedicated manager without data nodes", + fields: fields{ + ClusterManagerNodes: []*ClusterManagerNodes{{ + DedicatedManager: true, + }}, + DataNodes: nil, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + ClusterManagerNodes: []*ClusterManagerNodes{{ + DedicatedManager: true, + }}, + DataNodes: []*OpenSearchDataNodes{ + { + NodesNumber: 3, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + oss := &OpenSearchSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + RestoreFrom: tt.fields.RestoreFrom, + DataCentres: tt.fields.DataCentres, + DataNodes: tt.fields.DataNodes, + Dashboards: tt.fields.Dashboards, + ClusterManagerNodes: tt.fields.ClusterManagerNodes, + ICUPlugin: tt.fields.ICUPlugin, + AsynchronousSearchPlugin: tt.fields.AsynchronousSearchPlugin, + KNNPlugin: tt.fields.KNNPlugin, + ReportingPlugin: tt.fields.ReportingPlugin, + SQLPlugin: tt.fields.SQLPlugin, + NotificationsPlugin: tt.fields.NotificationsPlugin, + AnomalyDetectionPlugin: tt.fields.AnomalyDetectionPlugin, + LoadBalancer: tt.fields.LoadBalancer, + IndexManagementPlugin: tt.fields.IndexManagementPlugin, + AlertingPlugin: tt.fields.AlertingPlugin, + BundledUseOnly: tt.fields.BundledUseOnly, + PCICompliance: tt.fields.PCICompliance, + UserRefs: tt.fields.UserRefs, + ResizeSettings: tt.fields.ResizeSettings, + IngestNodes: tt.fields.IngestNodes, + } + if err := oss.validateDedicatedManager(); (err != nil) != tt.wantErr { + t.Errorf("validateDedicatedManager() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestOpenSearchSpec_validateUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + RestoreFrom *OpenSearchRestoreFrom + DataCentres []*OpenSearchDataCentre + DataNodes []*OpenSearchDataNodes + Dashboards []*OpenSearchDashboards + ClusterManagerNodes []*ClusterManagerNodes + ICUPlugin bool + AsynchronousSearchPlugin bool + KNNPlugin bool + ReportingPlugin bool + SQLPlugin bool + NotificationsPlugin bool + AnomalyDetectionPlugin bool + LoadBalancer bool + IndexManagementPlugin bool + AlertingPlugin bool + BundledUseOnly bool + PCICompliance bool + UserRefs References + ResizeSettings []*ResizeSettings + IngestNodes []*OpenSearchIngestNodes + } + type args struct { + oldSpec OpenSearchSpec + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "change immutable field", + fields: fields{ + KNNPlugin: false, + }, + args: args{ + oldSpec: OpenSearchSpec{ + KNNPlugin: true, + }, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + KNNPlugin: false, + }, + args: args{ + oldSpec: OpenSearchSpec{ + KNNPlugin: false, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + oss := &OpenSearchSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + RestoreFrom: tt.fields.RestoreFrom, + DataCentres: tt.fields.DataCentres, + DataNodes: tt.fields.DataNodes, + Dashboards: tt.fields.Dashboards, + ClusterManagerNodes: tt.fields.ClusterManagerNodes, + ICUPlugin: tt.fields.ICUPlugin, + AsynchronousSearchPlugin: tt.fields.AsynchronousSearchPlugin, + KNNPlugin: tt.fields.KNNPlugin, + ReportingPlugin: tt.fields.ReportingPlugin, + SQLPlugin: tt.fields.SQLPlugin, + NotificationsPlugin: tt.fields.NotificationsPlugin, + AnomalyDetectionPlugin: tt.fields.AnomalyDetectionPlugin, + LoadBalancer: tt.fields.LoadBalancer, + IndexManagementPlugin: tt.fields.IndexManagementPlugin, + AlertingPlugin: tt.fields.AlertingPlugin, + BundledUseOnly: tt.fields.BundledUseOnly, + PCICompliance: tt.fields.PCICompliance, + UserRefs: tt.fields.UserRefs, + ResizeSettings: tt.fields.ResizeSettings, + IngestNodes: tt.fields.IngestNodes, + } + if err := oss.validateUpdate(tt.args.oldSpec); (err != nil) != tt.wantErr { + t.Errorf("validateUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestOpenSearchSpec_validateImmutableDataCentresUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + RestoreFrom *OpenSearchRestoreFrom + DataCentres []*OpenSearchDataCentre + DataNodes []*OpenSearchDataNodes + Dashboards []*OpenSearchDashboards + ClusterManagerNodes []*ClusterManagerNodes + ICUPlugin bool + AsynchronousSearchPlugin bool + KNNPlugin bool + ReportingPlugin bool + SQLPlugin bool + NotificationsPlugin bool + AnomalyDetectionPlugin bool + LoadBalancer bool + IndexManagementPlugin bool + AlertingPlugin bool + BundledUseOnly bool + PCICompliance bool + UserRefs References + ResizeSettings []*ResizeSettings + IngestNodes []*OpenSearchIngestNodes + } + type args struct { + oldDCs []*OpenSearchDataCentre + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "change number of the dcs", + fields: fields{ + DataCentres: []*OpenSearchDataCentre{{}, {}}, + }, + args: args{ + oldDCs: []*OpenSearchDataCentre{{}}, + }, + wantErr: true, + }, + { + name: "change name of the dc", + fields: fields{ + DataCentres: []*OpenSearchDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "new", + }, + }}, + }, + args: args{ + oldDCs: []*OpenSearchDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "old", + }, + }}, + }, + wantErr: true, + }, + { + name: "change immutable field", + fields: fields{ + DataCentres: []*OpenSearchDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Region: "new", + }, + }}, + }, + args: args{ + oldDCs: []*OpenSearchDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Region: "old", + }, + }}, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + DataCentres: []*OpenSearchDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Region: "test", + }, + }}, + }, + args: args{ + oldDCs: []*OpenSearchDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Region: "test", + }, + }}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + oss := &OpenSearchSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + RestoreFrom: tt.fields.RestoreFrom, + DataCentres: tt.fields.DataCentres, + DataNodes: tt.fields.DataNodes, + Dashboards: tt.fields.Dashboards, + ClusterManagerNodes: tt.fields.ClusterManagerNodes, + ICUPlugin: tt.fields.ICUPlugin, + AsynchronousSearchPlugin: tt.fields.AsynchronousSearchPlugin, + KNNPlugin: tt.fields.KNNPlugin, + ReportingPlugin: tt.fields.ReportingPlugin, + SQLPlugin: tt.fields.SQLPlugin, + NotificationsPlugin: tt.fields.NotificationsPlugin, + AnomalyDetectionPlugin: tt.fields.AnomalyDetectionPlugin, + LoadBalancer: tt.fields.LoadBalancer, + IndexManagementPlugin: tt.fields.IndexManagementPlugin, + AlertingPlugin: tt.fields.AlertingPlugin, + BundledUseOnly: tt.fields.BundledUseOnly, + PCICompliance: tt.fields.PCICompliance, + UserRefs: tt.fields.UserRefs, + ResizeSettings: tt.fields.ResizeSettings, + IngestNodes: tt.fields.IngestNodes, + } + if err := oss.validateImmutableDataCentresUpdate(tt.args.oldDCs); (err != nil) != tt.wantErr { + t.Errorf("validateImmutableDataCentresUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestOpenSearchDataCentre_validateDataNode(t *testing.T) { + type fields struct { + GenericDataCentreSpec GenericDataCentreSpec + PrivateLink bool + NumberOfRacks int + } + type args struct { + nodes []*OpenSearchDataNodes + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "invalid number of data nodes", + fields: fields{ + NumberOfRacks: 3, + }, + args: args{ + nodes: []*OpenSearchDataNodes{{ + NodesNumber: 4, + }}, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + NumberOfRacks: 3, + }, + args: args{ + nodes: []*OpenSearchDataNodes{{ + NodesNumber: 9, + }}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dc := &OpenSearchDataCentre{ + GenericDataCentreSpec: tt.fields.GenericDataCentreSpec, + PrivateLink: tt.fields.PrivateLink, + NumberOfRacks: tt.fields.NumberOfRacks, + } + if err := dc.validateDataNode(tt.args.nodes); (err != nil) != tt.wantErr { + t.Errorf("validateDataNode() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_validateDataNode(t *testing.T) { + type args struct { + newNodes []*OpenSearchDataNodes + oldNodes []*OpenSearchDataNodes + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "deleting dataNode", + args: args{ + newNodes: []*OpenSearchDataNodes{{ + NodesNumber: 1, + }}, + oldNodes: []*OpenSearchDataNodes{{ + NodesNumber: 2, + }}, + }, + wantErr: true, + }, + { + name: "valid case", + args: args{ + newNodes: []*OpenSearchDataNodes{{ + NodesNumber: 3, + }}, + oldNodes: []*OpenSearchDataNodes{{ + NodesNumber: 2, + }}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateDataNode(tt.args.newNodes, tt.args.oldNodes); (err != nil) != tt.wantErr { + t.Errorf("validateDataNode() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestOpenSearchDataCentre_ValidatePrivateLink(t *testing.T) { + type fields struct { + GenericDataCentreSpec GenericDataCentreSpec + PrivateLink bool + NumberOfRacks int + } + type args struct { + privateNetworkCluster bool + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "privateLink with the not aws cloudProvider", + fields: fields{ + GenericDataCentreSpec: GenericDataCentreSpec{ + CloudProvider: models.GCP, + }, + PrivateLink: true, + }, + args: args{}, + wantErr: true, + }, + { + name: "privateLink with no privateNetworkCluster", + fields: fields{ + GenericDataCentreSpec: GenericDataCentreSpec{ + CloudProvider: models.AWSVPC, + }, + PrivateLink: true, + }, + args: args{ + privateNetworkCluster: false, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + GenericDataCentreSpec: GenericDataCentreSpec{ + CloudProvider: models.AWSVPC, + }, + PrivateLink: true, + }, + args: args{ + privateNetworkCluster: true, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dc := &OpenSearchDataCentre{ + GenericDataCentreSpec: tt.fields.GenericDataCentreSpec, + PrivateLink: tt.fields.PrivateLink, + NumberOfRacks: tt.fields.NumberOfRacks, + } + if err := dc.ValidatePrivateLink(tt.args.privateNetworkCluster); (err != nil) != tt.wantErr { + t.Errorf("ValidatePrivateLink() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/apis/clusters/v1beta1/postgresql_webhook.go b/apis/clusters/v1beta1/postgresql_webhook.go index 5e2a0ec06..83521da99 100644 --- a/apis/clusters/v1beta1/postgresql_webhook.go +++ b/apis/clusters/v1beta1/postgresql_webhook.go @@ -86,11 +86,7 @@ func (pgv *pgValidator) ValidateCreate(ctx context.Context, obj runtime.Object) } if pg.Spec.PgRestoreFrom != nil { - if pg.Spec.PgRestoreFrom.ClusterID == "" { - return fmt.Errorf("restore clusterID field is empty") - } else { - return nil - } + return nil } err = pg.Spec.GenericClusterSpec.ValidateCreation() @@ -109,10 +105,6 @@ func (pgv *pgValidator) ValidateCreate(ctx context.Context, obj runtime.Object) return err } - if len(pg.Spec.DataCentres) == 0 { - return models.ErrZeroDataCentres - } - for _, dc := range pg.Spec.DataCentres { //TODO: add support of multiple DCs for OnPrem clusters if len(pg.Spec.DataCentres) > 1 && dc.CloudProvider == models.ONPREMISES { @@ -134,9 +126,6 @@ func (pgv *pgValidator) ValidateCreate(ctx context.Context, obj runtime.Object) return fmt.Errorf("interDataCentreReplication field is required when more than 1 data centre") } - if len(dc.IntraDataCentreReplication) != 1 { - return fmt.Errorf("intraDataCentreReplication required to have 1 item") - } if !validation.Contains(dc.IntraDataCentreReplication[0].ReplicationMode, models.ReplicationModes) { return fmt.Errorf("replicationMode '%s' is unavailable, available values: %v", dc.IntraDataCentreReplication[0].ReplicationMode, @@ -201,8 +190,7 @@ func (pgv *pgValidator) ValidateUpdate(ctx context.Context, old runtime.Object, } } - // ensuring if the cluster is ready for the spec updating - if (pg.Status.CurrentClusterOperationStatus != models.NoOperation || pg.Status.State != models.RunningStatus) && pg.Generation != oldCluster.Generation { + if IsClusterNotReadyForSpecUpdate(pg.Status.CurrentClusterOperationStatus, pg.Status.State, pg.Generation, oldCluster.Generation) { return models.ErrClusterIsNotReadyToUpdate } @@ -335,10 +323,6 @@ func (pgs *PgSpec) validateImmutableDCsFieldsUpdate(oldSpec PgSpec) error { return err } - if newDC.NodesNumber != oldDC.NodesNumber { - return models.ErrImmutableNodesNumber - } - } return nil diff --git a/apis/clusters/v1beta1/postgresql_webhook_test.go b/apis/clusters/v1beta1/postgresql_webhook_test.go new file mode 100644 index 000000000..7ca0f6fdd --- /dev/null +++ b/apis/clusters/v1beta1/postgresql_webhook_test.go @@ -0,0 +1,631 @@ +package v1beta1 + +import ( + "context" + "testing" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/instaclustr/operator/pkg/instaclustr/mock/appversionsmock" + "github.com/instaclustr/operator/pkg/models" + "github.com/instaclustr/operator/pkg/validation" +) + +func Test_pgValidator_ValidateCreate(t *testing.T) { + api := appversionsmock.NewInstAPI() + + type fields struct { + API validation.Validation + K8sClient client.Client + } + type args struct { + ctx context.Context + obj runtime.Object + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "multiDC with zero interDataCentreReplication", + fields: fields{ + API: api, + }, + args: args{ + obj: &PostgreSQL{ + Spec: PgSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*PgDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + ProviderAccountName: "test", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + + IntraDataCentreReplication: []*IntraDataCentreReplication{{ + ReplicationMode: "ASYNCHRONOUS", + }}, + }, + { + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + ProviderAccountName: "test", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + + IntraDataCentreReplication: []*IntraDataCentreReplication{{ + ReplicationMode: "ASYNCHRONOUS", + }}, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "valid multiDC", + fields: fields{ + API: api, + }, + args: args{ + obj: &PostgreSQL{ + Spec: PgSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*PgDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + ProviderAccountName: "test", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + + IntraDataCentreReplication: []*IntraDataCentreReplication{{ + ReplicationMode: "ASYNCHRONOUS", + }}, + InterDataCentreReplication: []*InterDataCentreReplication{{ + IsPrimaryDataCentre: true, + }}, + }, + { + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + ProviderAccountName: "test", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + + IntraDataCentreReplication: []*IntraDataCentreReplication{{ + ReplicationMode: "ASYNCHRONOUS", + }}, + InterDataCentreReplication: []*InterDataCentreReplication{{ + IsPrimaryDataCentre: true, + }}, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "invalid ReplicationMode", + fields: fields{ + API: api, + }, + args: args{ + obj: &PostgreSQL{ + Spec: PgSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*PgDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + ProviderAccountName: "test", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + + IntraDataCentreReplication: []*IntraDataCentreReplication{{ + ReplicationMode: "test", + }}, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "valid cluster", + fields: fields{ + API: api, + }, + args: args{ + obj: &PostgreSQL{ + Spec: PgSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + DataCentres: []*PgDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + ProviderAccountName: "test", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + NodesNumber: 3, + + IntraDataCentreReplication: []*IntraDataCentreReplication{{ + ReplicationMode: "ASYNCHRONOUS", + }}, + }, + }, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pgv := &pgValidator{ + API: tt.fields.API, + K8sClient: tt.fields.K8sClient, + } + if err := pgv.ValidateCreate(tt.args.ctx, tt.args.obj); (err != nil) != tt.wantErr { + t.Errorf("ValidateCreate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPostgreSQL_ValidateDefaultUserPassword(t *testing.T) { + type fields struct { + TypeMeta v1.TypeMeta + ObjectMeta v1.ObjectMeta + Spec PgSpec + Status PgStatus + } + type args struct { + password string + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "invalid password", + fields: fields{}, + args: args{ + password: "test", + }, + want: false, + }, + { + name: "valid password", + fields: fields{}, + args: args{ + password: "Te2t!", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pg := &PostgreSQL{ + TypeMeta: tt.fields.TypeMeta, + ObjectMeta: tt.fields.ObjectMeta, + Spec: tt.fields.Spec, + Status: tt.fields.Status, + } + if got := pg.ValidateDefaultUserPassword(tt.args.password); got != tt.want { + t.Errorf("ValidateDefaultUserPassword() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPgDataCentre_ValidatePGBouncer(t *testing.T) { + type fields struct { + GenericDataCentreSpec GenericDataCentreSpec + ClientEncryption bool + InterDataCentreReplication []*InterDataCentreReplication + IntraDataCentreReplication []*IntraDataCentreReplication + PGBouncer []*PgBouncer + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "invalid pgBouncerVersion", + fields: fields{ + PGBouncer: []*PgBouncer{{ + PGBouncerVersion: "test", + PoolMode: models.PoolModes[0], + }}, + }, + wantErr: true, + }, + { + name: "invalid poolMode", + fields: fields{ + PGBouncer: []*PgBouncer{{ + PGBouncerVersion: models.PGBouncerVersions[0], + PoolMode: "test", + }}, + }, + wantErr: true, + }, + { + name: "valid PgBouncer", + fields: fields{ + PGBouncer: []*PgBouncer{{ + PGBouncerVersion: models.PGBouncerVersions[0], + PoolMode: models.PoolModes[0], + }}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pdc := &PgDataCentre{ + GenericDataCentreSpec: tt.fields.GenericDataCentreSpec, + ClientEncryption: tt.fields.ClientEncryption, + InterDataCentreReplication: tt.fields.InterDataCentreReplication, + IntraDataCentreReplication: tt.fields.IntraDataCentreReplication, + PGBouncer: tt.fields.PGBouncer, + } + if err := pdc.ValidatePGBouncer(); (err != nil) != tt.wantErr { + t.Errorf("ValidatePGBouncer() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPgSpec_ValidateImmutableFieldsUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + SynchronousModeStrict bool + ClusterConfigurations map[string]string + PgRestoreFrom *PgRestoreFrom + DataCentres []*PgDataCentre + UserRefs []*Reference + ResizeSettings []*ResizeSettings + Extensions PgExtensions + } + type args struct { + oldSpec PgSpec + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "changing immutable field", + fields: fields{ + SynchronousModeStrict: false, + }, + args: args{ + oldSpec: PgSpec{ + SynchronousModeStrict: true, + }, + }, + wantErr: true, + }, + { + name: "changing immutable extensions", + fields: fields{ + Extensions: []PgExtension{{ + Name: "test", + Enabled: true, + }}, + }, + args: args{ + oldSpec: PgSpec{ + Extensions: []PgExtension{{ + Name: "test", + Enabled: false, + }}, + }, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + Extensions: []PgExtension{{ + Name: "test", + Enabled: false, + }}, + }, + args: args{ + oldSpec: PgSpec{ + Extensions: []PgExtension{{ + Name: "test", + Enabled: false, + }}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pgs := &PgSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + SynchronousModeStrict: tt.fields.SynchronousModeStrict, + ClusterConfigurations: tt.fields.ClusterConfigurations, + PgRestoreFrom: tt.fields.PgRestoreFrom, + DataCentres: tt.fields.DataCentres, + ResizeSettings: tt.fields.ResizeSettings, + Extensions: tt.fields.Extensions, + } + if err := pgs.ValidateImmutableFieldsUpdate(tt.args.oldSpec); (err != nil) != tt.wantErr { + t.Errorf("ValidateImmutableFieldsUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPgSpec_validateImmutableDCsFieldsUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + SynchronousModeStrict bool + ClusterConfigurations map[string]string + PgRestoreFrom *PgRestoreFrom + DataCentres []*PgDataCentre + UserRefs []*Reference + ResizeSettings []*ResizeSettings + Extensions PgExtensions + } + type args struct { + oldSpec PgSpec + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "changing immutable DC field", + fields: fields{ + DataCentres: []*PgDataCentre{{ + NodesNumber: 3, + }}, + }, + args: args{ + oldSpec: PgSpec{ + DataCentres: []*PgDataCentre{{ + NodesNumber: 4, + }}, + }, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + DataCentres: []*PgDataCentre{{ + NodesNumber: 3, + }}, + }, + args: args{ + oldSpec: PgSpec{ + DataCentres: []*PgDataCentre{{ + NodesNumber: 4, + }}, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pgs := &PgSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + SynchronousModeStrict: tt.fields.SynchronousModeStrict, + ClusterConfigurations: tt.fields.ClusterConfigurations, + PgRestoreFrom: tt.fields.PgRestoreFrom, + DataCentres: tt.fields.DataCentres, + ResizeSettings: tt.fields.ResizeSettings, + Extensions: tt.fields.Extensions, + } + if err := pgs.validateImmutableDCsFieldsUpdate(tt.args.oldSpec); (err != nil) != tt.wantErr { + t.Errorf("validateImmutableDCsFieldsUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPgDataCentre_validateInterDCImmutableFields(t *testing.T) { + type fields struct { + GenericDataCentreSpec GenericDataCentreSpec + ClientEncryption bool + NodeSize string + NodesNumber int + InterDataCentreReplication []*InterDataCentreReplication + IntraDataCentreReplication []*IntraDataCentreReplication + PGBouncer []*PgBouncer + } + type args struct { + oldInterDC []*InterDataCentreReplication + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "change len of InterDataCentreReplication", + fields: fields{ + InterDataCentreReplication: []*InterDataCentreReplication{{}, {}}, + }, + args: args{ + oldInterDC: []*InterDataCentreReplication{{}}, + }, + wantErr: true, + }, + { + name: "change field of InterDataCentreReplication", + fields: fields{ + InterDataCentreReplication: []*InterDataCentreReplication{{ + IsPrimaryDataCentre: false, + }}, + }, + args: args{ + oldInterDC: []*InterDataCentreReplication{{ + IsPrimaryDataCentre: true, + }}, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + InterDataCentreReplication: []*InterDataCentreReplication{{ + IsPrimaryDataCentre: false, + }}, + }, + args: args{ + oldInterDC: []*InterDataCentreReplication{{ + IsPrimaryDataCentre: false, + }}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pdc := &PgDataCentre{ + GenericDataCentreSpec: tt.fields.GenericDataCentreSpec, + ClientEncryption: tt.fields.ClientEncryption, + NodeSize: tt.fields.NodeSize, + NodesNumber: tt.fields.NodesNumber, + InterDataCentreReplication: tt.fields.InterDataCentreReplication, + IntraDataCentreReplication: tt.fields.IntraDataCentreReplication, + PGBouncer: tt.fields.PGBouncer, + } + if err := pdc.validateInterDCImmutableFields(tt.args.oldInterDC); (err != nil) != tt.wantErr { + t.Errorf("validateInterDCImmutableFields() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPgDataCentre_validateIntraDCImmutableFields(t *testing.T) { + type fields struct { + GenericDataCentreSpec GenericDataCentreSpec + ClientEncryption bool + NodeSize string + NodesNumber int + InterDataCentreReplication []*InterDataCentreReplication + IntraDataCentreReplication []*IntraDataCentreReplication + PGBouncer []*PgBouncer + } + type args struct { + oldIntraDC []*IntraDataCentreReplication + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "change len of IntraDataCentreReplication", + fields: fields{ + IntraDataCentreReplication: []*IntraDataCentreReplication{{}, {}}, + }, + args: args{ + oldIntraDC: []*IntraDataCentreReplication{{}}, + }, + wantErr: true, + }, + { + name: "change field of IntraDataCentreReplication", + fields: fields{ + IntraDataCentreReplication: []*IntraDataCentreReplication{{ + ReplicationMode: "new", + }}, + }, + args: args{ + oldIntraDC: []*IntraDataCentreReplication{{ + ReplicationMode: "old", + }}, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + IntraDataCentreReplication: []*IntraDataCentreReplication{{ + ReplicationMode: "old", + }}, + }, + args: args{ + oldIntraDC: []*IntraDataCentreReplication{{ + ReplicationMode: "old", + }}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pdc := &PgDataCentre{ + GenericDataCentreSpec: tt.fields.GenericDataCentreSpec, + ClientEncryption: tt.fields.ClientEncryption, + NodeSize: tt.fields.NodeSize, + NodesNumber: tt.fields.NodesNumber, + InterDataCentreReplication: tt.fields.InterDataCentreReplication, + IntraDataCentreReplication: tt.fields.IntraDataCentreReplication, + PGBouncer: tt.fields.PGBouncer, + } + if err := pdc.validateIntraDCImmutableFields(tt.args.oldIntraDC); (err != nil) != tt.wantErr { + t.Errorf("validateIntraDCImmutableFields() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/apis/clusters/v1beta1/redis_webhook.go b/apis/clusters/v1beta1/redis_webhook.go index b0aaa1176..09fcbd3d9 100644 --- a/apis/clusters/v1beta1/redis_webhook.go +++ b/apis/clusters/v1beta1/redis_webhook.go @@ -97,11 +97,7 @@ func (rv *redisValidator) ValidateCreate(ctx context.Context, obj runtime.Object } if r.Spec.RestoreFrom != nil { - if r.Spec.RestoreFrom.ClusterID == "" { - return fmt.Errorf("restore clusterID field is empty") - } else { - return nil - } + return nil } err = r.Spec.GenericClusterSpec.ValidateCreation() @@ -125,10 +121,6 @@ func (rv *redisValidator) ValidateCreate(ctx context.Context, obj runtime.Object return err } - if len(r.Spec.DataCentres) == 0 { - return fmt.Errorf("data centres field is empty") - } - if len(r.Spec.DataCentres) > 1 { return models.ErrCreateClusterWithMultiDC } @@ -197,8 +189,7 @@ func (rv *redisValidator) ValidateUpdate(ctx context.Context, old runtime.Object } } - // ensuring if the cluster is ready for the spec updating - if (r.Status.CurrentClusterOperationStatus != models.NoOperation || r.Status.State != models.RunningStatus) && r.Generation != oldRedis.Generation { + if IsClusterNotReadyForSpecUpdate(r.Status.CurrentClusterOperationStatus, r.Status.State, r.Generation, oldRedis.Generation) { return models.ErrClusterIsNotReadyToUpdate } @@ -286,6 +277,11 @@ func (rs *RedisSpec) validateDCsUpdate(oldSpec RedisSpec) error { return err } + err = newDC.ValidateNodesNumber() + if err != nil { + return err + } + err = newDC.validateImmutableCloudProviderSettingsUpdate(&oldDC.GenericDataCentreSpec) if err != nil { return err @@ -340,15 +336,6 @@ func (rdc *RedisDataCentre) newImmutableFields() *immutableRedisDCFields { } } -func (rdc *RedisDataCentre) ValidateUpdate() error { - err := rdc.ValidateNodesNumber() - if err != nil { - return err - } - - return nil -} - func (rdc *RedisDataCentre) ValidateCreate() error { err := rdc.GenericDataCentreSpec.validateCreation() if err != nil { diff --git a/apis/clusters/v1beta1/redis_webhook_test.go b/apis/clusters/v1beta1/redis_webhook_test.go new file mode 100644 index 000000000..1b303e738 --- /dev/null +++ b/apis/clusters/v1beta1/redis_webhook_test.go @@ -0,0 +1,524 @@ +package v1beta1 + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/instaclustr/operator/pkg/instaclustr/mock/appversionsmock" + "github.com/instaclustr/operator/pkg/models" + "github.com/instaclustr/operator/pkg/validation" +) + +func Test_redisValidator_ValidateCreate(t *testing.T) { + api := appversionsmock.NewInstAPI() + type fields struct { + API validation.Validation + Client client.Client + } + type args struct { + ctx context.Context + obj runtime.Object + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "multiple DataCentres", + fields: fields{ + API: api, + }, + args: args{ + ctx: nil, + obj: &Redis{ + Spec: RedisSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + RestoreFrom: nil, + ClientEncryption: false, + PasswordAndUserAuth: false, + DataCentres: []*RedisDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + ProviderAccountName: "test", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + MasterNodes: 3, + ReplicaNodes: 0, + ReplicationFactor: 3, + PrivateLink: nil, + }, { + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + ProviderAccountName: "test", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + MasterNodes: 3, + ReplicaNodes: 0, + ReplicationFactor: 3, + PrivateLink: nil, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "valid cluster", + fields: fields{ + API: api, + Client: nil, + }, + args: args{ + ctx: nil, + obj: &Redis{ + Spec: RedisSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "test", + SLATier: "NON_PRODUCTION", + }, + RestoreFrom: nil, + ClientEncryption: false, + PasswordAndUserAuth: false, + DataCentres: []*RedisDataCentre{{ + GenericDataCentreSpec: GenericDataCentreSpec{ + Name: "test", + Region: "AF_SOUTH_1", + CloudProvider: "AWS_VPC", + ProviderAccountName: "test", + Network: "10.1.0.0/16", + }, + NodeSize: "test", + MasterNodes: 3, + ReplicaNodes: 0, + ReplicationFactor: 3, + PrivateLink: nil, + }}, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rv := &redisValidator{ + API: tt.fields.API, + Client: tt.fields.Client, + } + if err := rv.ValidateCreate(tt.args.ctx, tt.args.obj); (err != nil) != tt.wantErr { + t.Errorf("ValidateCreate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestRedisSpec_ValidateUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + RestoreFrom *RedisRestoreFrom + ClientEncryption bool + PasswordAndUserAuth bool + DataCentres []*RedisDataCentre + ResizeSettings GenericResizeSettings + UserRefs References + } + type args struct { + oldSpec RedisSpec + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "changing generic cluster field", + fields: fields{ + GenericClusterSpec: GenericClusterSpec{ + Name: "new", + }, + }, + args: args{ + oldSpec: RedisSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "old", + }, + }, + }, + wantErr: true, + }, + { + name: "changing specific cluster field", + fields: fields{ + ClientEncryption: false, + }, + args: args{ + oldSpec: RedisSpec{ + ClientEncryption: true, + }, + }, + wantErr: true, + }, + { + name: "unchanged field", + fields: fields{ + GenericClusterSpec: GenericClusterSpec{ + Name: "old", + }, + ClientEncryption: false, + }, + args: args{ + oldSpec: RedisSpec{ + GenericClusterSpec: GenericClusterSpec{ + Name: "old", + }, + ClientEncryption: false, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rs := &RedisSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + RestoreFrom: tt.fields.RestoreFrom, + ClientEncryption: tt.fields.ClientEncryption, + PasswordAndUserAuth: tt.fields.PasswordAndUserAuth, + DataCentres: tt.fields.DataCentres, + ResizeSettings: tt.fields.ResizeSettings, + UserRefs: tt.fields.UserRefs, + } + if err := rs.ValidateUpdate(tt.args.oldSpec); (err != nil) != tt.wantErr { + t.Errorf("ValidateUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestRedisSpec_validateDCsUpdate(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + RestoreFrom *RedisRestoreFrom + ClientEncryption bool + PasswordAndUserAuth bool + DataCentres []*RedisDataCentre + ResizeSettings GenericResizeSettings + UserRefs References + } + type args struct { + oldSpec RedisSpec + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "changing imutable DC field", + fields: fields{ + DataCentres: []*RedisDataCentre{ + { + ReplicationFactor: 0, + }, + }, + }, + args: args{ + oldSpec: RedisSpec{ + DataCentres: []*RedisDataCentre{ + { + ReplicationFactor: 1, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "deleting MasterNodes", + fields: fields{ + DataCentres: []*RedisDataCentre{ + { + MasterNodes: 0, + }, + }, + }, + args: args{ + oldSpec: RedisSpec{ + DataCentres: []*RedisDataCentre{ + { + MasterNodes: 1, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "deleting ReplicaNodes", + fields: fields{ + DataCentres: []*RedisDataCentre{ + { + ReplicaNodes: 0, + }, + }, + }, + args: args{ + oldSpec: RedisSpec{ + DataCentres: []*RedisDataCentre{ + { + ReplicaNodes: 1, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "deleting DC", + fields: fields{ + DataCentres: []*RedisDataCentre{}, + }, + args: args{ + oldSpec: RedisSpec{ + DataCentres: []*RedisDataCentre{{}}, + }, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + DataCentres: []*RedisDataCentre{{ + MasterNodes: 3, + }}, + }, + args: args{ + oldSpec: RedisSpec{ + DataCentres: []*RedisDataCentre{{ + MasterNodes: 3, + }}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rs := &RedisSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + RestoreFrom: tt.fields.RestoreFrom, + ClientEncryption: tt.fields.ClientEncryption, + PasswordAndUserAuth: tt.fields.PasswordAndUserAuth, + DataCentres: tt.fields.DataCentres, + ResizeSettings: tt.fields.ResizeSettings, + UserRefs: tt.fields.UserRefs, + } + if err := rs.validateDCsUpdate(tt.args.oldSpec); (err != nil) != tt.wantErr { + t.Errorf("validateDCsUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestRedisDataCentre_ValidateNodesNumber(t *testing.T) { + type fields struct { + GenericDataCentreSpec GenericDataCentreSpec + NodeSize string + MasterNodes int + ReplicaNodes int + ReplicationFactor int + PrivateLink PrivateLinkSpec + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "more than 100 ReplicaNodes", + fields: fields{ + ReplicaNodes: 101, + }, + wantErr: true, + }, + { + name: "less than 3 ReplicaNodes", + fields: fields{ + ReplicaNodes: 2, + }, + wantErr: true, + }, + { + name: "more than 100 MasterNodes", + fields: fields{ + ReplicaNodes: 101, + }, + wantErr: true, + }, + { + name: "less than 3 MasterNodes", + fields: fields{ + ReplicaNodes: 2, + }, + wantErr: true, + }, + { + name: "valid case", + fields: fields{ + ReplicaNodes: 3, + MasterNodes: 3, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rdc := &RedisDataCentre{ + GenericDataCentreSpec: tt.fields.GenericDataCentreSpec, + NodeSize: tt.fields.NodeSize, + MasterNodes: tt.fields.MasterNodes, + ReplicaNodes: tt.fields.ReplicaNodes, + ReplicationFactor: tt.fields.ReplicationFactor, + PrivateLink: tt.fields.PrivateLink, + } + if err := rdc.ValidateNodesNumber(); (err != nil) != tt.wantErr { + t.Errorf("ValidateNodesNumber() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestRedisDataCentre_ValidatePrivateLink(t *testing.T) { + type fields struct { + GenericDataCentreSpec GenericDataCentreSpec + NodeSize string + MasterNodes int + ReplicaNodes int + ReplicationFactor int + PrivateLink PrivateLinkSpec + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "privateLink for nonAWS cloudPrivider", + fields: fields{ + GenericDataCentreSpec: GenericDataCentreSpec{ + CloudProvider: "test", + }, + PrivateLink: PrivateLinkSpec{{}}, + }, + wantErr: true, + }, + { + name: "privateLink for AWS cloudPrivider", + fields: fields{ + GenericDataCentreSpec: GenericDataCentreSpec{ + CloudProvider: models.AWSVPC, + }, + PrivateLink: PrivateLinkSpec{{}}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rdc := &RedisDataCentre{ + GenericDataCentreSpec: tt.fields.GenericDataCentreSpec, + NodeSize: tt.fields.NodeSize, + MasterNodes: tt.fields.MasterNodes, + ReplicaNodes: tt.fields.ReplicaNodes, + ReplicationFactor: tt.fields.ReplicationFactor, + PrivateLink: tt.fields.PrivateLink, + } + if err := rdc.ValidatePrivateLink(); (err != nil) != tt.wantErr { + t.Errorf("ValidatePrivateLink() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestRedisSpec_ValidatePrivateLink(t *testing.T) { + type fields struct { + GenericClusterSpec GenericClusterSpec + RestoreFrom *RedisRestoreFrom + ClientEncryption bool + PasswordAndUserAuth bool + DataCentres []*RedisDataCentre + ResizeSettings GenericResizeSettings + UserRefs References + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "PrivateLink enabled for multiDC", + fields: fields{ + DataCentres: []*RedisDataCentre{{ + PrivateLink: PrivateLinkSpec{{ + AdvertisedHostname: "test", + }}, + }, { + PrivateLink: PrivateLinkSpec{{ + AdvertisedHostname: "test", + }}, + }}, + }, + wantErr: true, + }, + { + name: "PrivateLink enabled for single DC", + fields: fields{ + DataCentres: []*RedisDataCentre{{ + PrivateLink: PrivateLinkSpec{{ + AdvertisedHostname: "test", + }}, + }}, + }, + wantErr: false, + }, + { + name: "no PrivateLink for multiple DC", + fields: fields{ + DataCentres: []*RedisDataCentre{{}, {}}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rs := &RedisSpec{ + GenericClusterSpec: tt.fields.GenericClusterSpec, + RestoreFrom: tt.fields.RestoreFrom, + ClientEncryption: tt.fields.ClientEncryption, + PasswordAndUserAuth: tt.fields.PasswordAndUserAuth, + DataCentres: tt.fields.DataCentres, + ResizeSettings: tt.fields.ResizeSettings, + UserRefs: tt.fields.UserRefs, + } + if err := rs.ValidatePrivateLink(); (err != nil) != tt.wantErr { + t.Errorf("ValidatePrivateLink() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/apis/clusters/v1beta1/validation.go b/apis/clusters/v1beta1/validation.go index 78c776b10..b22586617 100644 --- a/apis/clusters/v1beta1/validation.go +++ b/apis/clusters/v1beta1/validation.go @@ -84,18 +84,13 @@ func (cps *CloudProviderSettings) ValidateCreation() error { } func validateReplicationFactor(availableReplicationFactors []int, rf int) error { - if rf <= 0 || rf > 300 { - return fmt.Errorf("replication factor must be one of %v, up to a maximum value of 300", - availableReplicationFactors) - } - for _, availableRf := range availableReplicationFactors { if availableRf == rf { return nil } } - return fmt.Errorf("replication factor must be one of %v, up to a maximum value of 300", + return fmt.Errorf("replication factor must be one of %v", availableReplicationFactors) } @@ -380,3 +375,7 @@ func validateNetwork(network string) error { } return nil } + +func IsClusterNotReadyForSpecUpdate(operation, state string, oldGen, newGen int64) bool { + return (operation != models.NoOperation || state != models.RunningStatus) && newGen != oldGen +} diff --git a/apis/clusters/v1beta1/validation_test.go b/apis/clusters/v1beta1/validation_test.go index a912a1941..359ce816f 100644 --- a/apis/clusters/v1beta1/validation_test.go +++ b/apis/clusters/v1beta1/validation_test.go @@ -942,3 +942,35 @@ func TestGenericDataCentreSpec_validateCreation(t *testing.T) { }) } } + +func TestIsClusterNotReadyForSpecUpdate(t *testing.T) { + type args struct { + operation string + state string + oldGen int64 + newGen int64 + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "update with current operation", + args: args{ + operation: "test", + state: "", + oldGen: 0, + newGen: 1, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsClusterNotReadyForSpecUpdate(tt.args.operation, tt.args.state, tt.args.oldGen, tt.args.newGen); got != tt.want { + t.Errorf("IsClusterNotReadyForSpecUpdate() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/apis/clusters/v1beta1/zookeeper_webhook.go b/apis/clusters/v1beta1/zookeeper_webhook.go index a4c67fe18..107661c5d 100644 --- a/apis/clusters/v1beta1/zookeeper_webhook.go +++ b/apis/clusters/v1beta1/zookeeper_webhook.go @@ -98,10 +98,6 @@ func (zv *zookeeperValidator) ValidateCreate(ctx context.Context, obj runtime.Ob return err } - if len(z.Spec.DataCentres) == 0 { - return models.ErrZeroDataCentres - } - for _, dc := range z.Spec.DataCentres { err = dc.GenericDataCentreSpec.validateCreation() if err != nil { diff --git a/apis/clusters/v1beta1/zookeeper_webhook_test.go b/apis/clusters/v1beta1/zookeeper_webhook_test.go new file mode 100644 index 000000000..347122c19 --- /dev/null +++ b/apis/clusters/v1beta1/zookeeper_webhook_test.go @@ -0,0 +1,119 @@ +package v1beta1 + +import ( + "context" + "testing" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/instaclustr/operator/pkg/instaclustr/mock/appversionsmock" + "github.com/instaclustr/operator/pkg/validation" +) + +func Test_zookeeperValidator_ValidateUpdate(t *testing.T) { + api := appversionsmock.NewInstAPI() + type fields struct { + API validation.Validation + } + type args struct { + ctx context.Context + old runtime.Object + new runtime.Object + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "updating cluster without settings update", + fields: fields{ + API: api, + }, + args: args{ + ctx: nil, + old: &Zookeeper{ + ObjectMeta: v1.ObjectMeta{ + Generation: 1, + }, + Spec: ZookeeperSpec{ + GenericClusterSpec: GenericClusterSpec{ + Description: "test", + }, + }, + Status: ZookeeperStatus{ + GenericStatus: GenericStatus{ + ID: "test", + }, + }, + }, + new: &Zookeeper{ + ObjectMeta: v1.ObjectMeta{ + Generation: 2, + }, + Spec: ZookeeperSpec{ + GenericClusterSpec: GenericClusterSpec{ + Description: "test", + }, + }, + Status: ZookeeperStatus{ + GenericStatus: GenericStatus{ + ID: "test", + }, + }}, + }, + wantErr: true, + }, + { + name: "valid update operation", + fields: fields{ + API: api, + }, + args: args{ + ctx: nil, + old: &Zookeeper{ + ObjectMeta: v1.ObjectMeta{ + Generation: 1, + }, + Spec: ZookeeperSpec{ + GenericClusterSpec: GenericClusterSpec{ + Description: "old", + }, + }, + Status: ZookeeperStatus{ + GenericStatus: GenericStatus{ + ID: "test", + }, + }, + }, + new: &Zookeeper{ + ObjectMeta: v1.ObjectMeta{ + Generation: 2, + }, + Spec: ZookeeperSpec{ + GenericClusterSpec: GenericClusterSpec{ + Description: "new", + }, + }, + Status: ZookeeperStatus{ + GenericStatus: GenericStatus{ + ID: "test", + }, + }}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + zv := &zookeeperValidator{ + API: tt.fields.API, + } + if err := zv.ValidateUpdate(tt.args.ctx, tt.args.old, tt.args.new); (err != nil) != tt.wantErr { + t.Errorf("ValidateUpdate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/config/crd/bases/clusters.instaclustr.com_cassandras.yaml b/config/crd/bases/clusters.instaclustr.com_cassandras.yaml index 54bb7d321..afa748902 100644 --- a/config/crd/bases/clusters.instaclustr.com_cassandras.yaml +++ b/config/crd/bases/clusters.instaclustr.com_cassandras.yaml @@ -345,6 +345,8 @@ spec: type: array version: type: string + required: + - dataCentres type: object status: description: CassandraStatus defines the observed state of Cassandra diff --git a/config/crd/bases/clusters.instaclustr.com_kafkaconnects.yaml b/config/crd/bases/clusters.instaclustr.com_kafkaconnects.yaml index 734dc8342..fdcbe2e1b 100644 --- a/config/crd/bases/clusters.instaclustr.com_kafkaconnects.yaml +++ b/config/crd/bases/clusters.instaclustr.com_kafkaconnects.yaml @@ -133,6 +133,7 @@ spec: type: object type: array type: object + maxItems: 1 type: array dataCentres: items: @@ -306,6 +307,7 @@ spec: truststore: type: string type: object + maxItems: 1 type: array managedCluster: description: Details to connect to an Instaclustr managed cluster. @@ -332,8 +334,10 @@ spec: required: - kafkaConnectVpcType type: object + maxItems: 1 type: array type: object + maxItems: 1 type: array twoFactorDelete: items: diff --git a/config/crd/bases/clusters.instaclustr.com_kafkas.yaml b/config/crd/bases/clusters.instaclustr.com_kafkas.yaml index 24bb40720..51077213d 100644 --- a/config/crd/bases/clusters.instaclustr.com_kafkas.yaml +++ b/config/crd/bases/clusters.instaclustr.com_kafkas.yaml @@ -179,6 +179,7 @@ spec: required: - advertisedHostname type: object + maxItems: 1 type: array region: description: Region of the Data Centre. @@ -254,6 +255,7 @@ spec: required: - controllerNodeCount type: object + maxItems: 1 type: array name: description: Name [ 3 .. 32 ] characters. diff --git a/config/crd/bases/clusters.instaclustr.com_opensearches.yaml b/config/crd/bases/clusters.instaclustr.com_opensearches.yaml index 605e47edc..97e38ea03 100644 --- a/config/crd/bases/clusters.instaclustr.com_opensearches.yaml +++ b/config/crd/bases/clusters.instaclustr.com_opensearches.yaml @@ -351,6 +351,7 @@ spec: type: string required: - clusterManagerNodes + - dataCentres type: object status: description: OpenSearchStatus defines the observed state of OpenSearch