16
16
from kubetester .mongodb_user import MongoDBUser
17
17
from kubetester .opsmanager import MongoDBOpsManager
18
18
from pytest import fixture
19
+ from tests import test_logger
19
20
from tests .conftest import LEGACY_OPERATOR_NAME , OPERATOR_NAME
20
21
from tests .olm .olm_test_commons import (
21
22
get_catalog_image ,
28
29
wait_for_operator_ready ,
29
30
)
30
31
from tests .opsmanager .om_ops_manager_backup import create_aws_secret , create_s3_bucket
31
- from tests .upgrades import downscale_operator_deployment
32
+
33
+ logger = test_logger .get_test_logger (__name__ )
32
34
33
35
# See docs how to run this locally: https://wiki.corp.mongodb.com/display/MMS/E2E+Tests+Notes#E2ETestsNotes-OLMtests
34
36
35
37
# This test performs operator migration from the latest MEKO to MCK while having OM and MongoDB resources deployed.
36
- # It performs the following actions:
38
+ # It uses the uninstall-then-install approach since operatorhub.io and other catalogs
39
+ # do not allow cross-package upgrades with the "replaces" directive.
40
+ # The test performs the following actions:
37
41
# - deploy latest released MEKO operator using OLM
38
42
# - deploy OM
39
43
# - deploy backup-required MongoDB: oplog, s3, blockstore
40
44
# - deploy TLS-enabled sharded MongoDB
41
45
# - check everything is running
42
- # - upgrade the operator to the MCK version built from the current branch
46
+ # - uninstall MEKO operator by deleting Subscription and ClusterServiceVersion resources
47
+ # - install MCK operator using OLM with a fresh subscription
43
48
# - wait for resources to be rolling-updated due to updated stateful sets by the new operator
44
49
# - check everything is running and connectable
45
50
@@ -59,15 +64,13 @@ def catalog_source(namespace: str, version_id: str):
59
64
60
65
61
66
@fixture
62
- def subscription (namespace : str , catalog_source : CustomObject ):
67
+ def meko_subscription (namespace : str , catalog_source : CustomObject ):
63
68
"""
64
- Create subscription for the operator. The subscription is first created
65
- with the latest released version of MEKO operator.
66
- Later in the test, it will be updated to MCK.
69
+ Create subscription for the MEKO operator.
67
70
"""
68
71
static_value = get_default_architecture ()
69
72
return get_subscription_custom_object (
70
- "mongodb-enterprise-operator" ,
73
+ LEGACY_OPERATOR_NAME ,
71
74
namespace ,
72
75
{
73
76
"channel" : "stable" , # stable channel contains latest released operator in RedHat's certified repository
@@ -89,6 +92,33 @@ def subscription(namespace: str, catalog_source: CustomObject):
89
92
)
90
93
91
94
95
+ def get_mck_subscription_object (namespace : str , catalog_source : CustomObject ):
96
+ """
97
+ Create a subscription object for the MCK operator.
98
+ This is a separate function (not a fixture) so it can be called after uninstalling MEKO.
99
+ """
100
+ static_value = get_default_architecture ()
101
+ return get_subscription_custom_object (
102
+ OPERATOR_NAME ,
103
+ namespace ,
104
+ {
105
+ "channel" : "migration" ,
106
+ "name" : "mongodb-kubernetes" ,
107
+ "source" : catalog_source .name ,
108
+ "sourceNamespace" : namespace ,
109
+ "installPlanApproval" : "Automatic" ,
110
+ "config" : {
111
+ "env" : [
112
+ {"name" : "MANAGED_SECURITY_CONTEXT" , "value" : "false" },
113
+ {"name" : "OPERATOR_ENV" , "value" : "dev" },
114
+ {"name" : "MDB_DEFAULT_ARCHITECTURE" , "value" : static_value },
115
+ {"name" : "MDB_OPERATOR_TELEMETRY_SEND_ENABLED" , "value" : "false" },
116
+ ]
117
+ },
118
+ },
119
+ )
120
+
121
+
92
122
@fixture
93
123
def latest_released_meko_version ():
94
124
return get_latest_released_operator_version ("mongodb-enterprise" )
@@ -100,12 +130,10 @@ def test_meko_install_stable_operator_version(
100
130
version_id : str ,
101
131
latest_released_meko_version : str ,
102
132
catalog_source : CustomObject ,
103
- subscription : CustomObject ,
133
+ meko_subscription : CustomObject ,
104
134
):
105
- subscription .update ()
106
- wait_for_operator_ready (
107
- namespace , "mongodb-enterprise-operator" , f"mongodb-enterprise.v{ latest_released_meko_version } "
108
- )
135
+ meko_subscription .update ()
136
+ wait_for_operator_ready (namespace , LEGACY_OPERATOR_NAME , f"mongodb-enterprise.v{ latest_released_meko_version } " )
109
137
110
138
111
139
# install resources on the latest released version of the operator
@@ -344,59 +372,124 @@ def test_resources_in_running_state_before_upgrade(
344
372
mdb_sharded .assert_reaches_phase (Phase .Running )
345
373
346
374
347
- # upgrade the operator
375
+ # uninstall MEKO and install MCK operator instead
376
+
377
+
378
+ def uninstall_meko_operator (namespace : str , meko_subscription : CustomObject ):
379
+ """Uninstall the MEKO operator by deleting Subscription and ClusterServiceVersion"""
380
+
381
+ # Load the subscription from API server
382
+ # so we can get CSV name from status
383
+ meko_subscription .load ()
384
+ csv_name = meko_subscription ["status" ]["installedCSV" ]
385
+
386
+ # Delete the subscription
387
+ meko_subscription .delete ()
388
+
389
+ # Delete ClusterServiceVersion
390
+ api_instance = kubernetes .client .CustomObjectsApi ()
391
+ api_instance .delete_namespaced_custom_object (
392
+ group = "operators.coreos.com" ,
393
+ version = "v1alpha1" ,
394
+ namespace = namespace ,
395
+ plural = "clusterserviceversions" ,
396
+ name = csv_name ,
397
+ )
348
398
349
399
350
400
@pytest .mark .e2e_olm_meko_operator_upgrade_with_resources
351
- def test_downscale_meko (namespace : str ):
352
- # Scale down the existing operator deployment to 0. This is needed as long as the
353
- # initial OLM deployment installs the MEKO operator.
354
- downscale_operator_deployment (deployment_name = LEGACY_OPERATOR_NAME , namespace = namespace )
401
+ def test_uninstall_meko_operator (
402
+ namespace : str ,
403
+ meko_subscription : CustomObject ,
404
+ ):
405
+ # Uninstall the MEKO operator
406
+ uninstall_meko_operator (namespace , meko_subscription )
407
+
408
+ # Get a list of all statefulsets
409
+ api_instance = kubernetes .client .AppsV1Api ()
410
+ statefulsets = api_instance .list_namespaced_stateful_set (namespace )
411
+
412
+ # Kill one pod from each statefulset to simulate reschedule
413
+ for sts in statefulsets .items :
414
+ sts_name = sts .metadata .name
415
+ logger .info (f"Processing StatefulSet { sts_name } " )
416
+
417
+ # Get pods for this statefulset
418
+ if sts .spec .selector and sts .spec .selector .match_labels :
419
+ # Build label selector string from match_labels dictionary
420
+ selector_parts = []
421
+ for key , value in sts .spec .selector .match_labels .items ():
422
+ selector_parts .append (f"{ key } ={ value } " )
423
+ label_selector = "," .join (selector_parts )
424
+
425
+ pods = kubernetes .client .CoreV1Api ().list_namespaced_pod (namespace , label_selector = label_selector )
426
+
427
+ if pods .items :
428
+ # Delete the first pod
429
+ pod_name = pods .items [0 ].metadata .name
430
+ logger .info (f"Deleting pod { pod_name } from StatefulSet { sts_name } " )
431
+ kubernetes .client .CoreV1Api ().delete_namespaced_pod (
432
+ name = pod_name , namespace = namespace , body = kubernetes .client .V1DeleteOptions ()
433
+ )
434
+
435
+ # Wait for all statefulsets to be ready again
436
+ def all_statefulsets_ready ():
437
+ for sts in api_instance .list_namespaced_stateful_set (namespace ).items :
438
+ if sts .status .ready_replicas != sts .status .replicas :
439
+ return (
440
+ False ,
441
+ f"StatefulSet { sts .metadata .name } has { sts .status .ready_replicas } /{ sts .status .replicas } ready replicas" ,
442
+ )
443
+ return True , "All StatefulSets are ready"
444
+
445
+ run_periodically (
446
+ all_statefulsets_ready , timeout = 600 , msg = f"Waiting for all StatefulSets to be ready after pod deletion"
447
+ )
448
+
449
+
450
+ @pytest .mark .e2e_olm_meko_operator_upgrade_with_resources
451
+ def test_connectivity_after_meko_uninstall (
452
+ ca_path : str ,
453
+ ops_manager : MongoDBOpsManager ,
454
+ oplog_replica_set : MongoDB ,
455
+ blockstore_replica_set : MongoDB ,
456
+ s3_replica_set : MongoDB ,
457
+ mdb_sharded : MongoDB ,
458
+ ):
459
+ """Verify resources are still connectable after MEKO operator uninstall but before MCK operator install"""
460
+ wait_for_om_healthy_response (ops_manager )
461
+
462
+ oplog_replica_set .assert_connectivity ()
463
+ blockstore_replica_set .assert_connectivity ()
464
+ s3_replica_set .assert_connectivity ()
465
+ mdb_sharded .assert_connectivity (ca_path = ca_path )
355
466
356
467
357
468
@pytest .mark .e2e_olm_meko_operator_upgrade_with_resources
358
- def test_meko_operator_upgrade_to_mck (
469
+ def test_install_mck_operator (
359
470
namespace : str ,
360
471
version_id : str ,
361
472
catalog_source : CustomObject ,
362
- subscription : CustomObject ,
363
473
):
364
474
current_operator_version = get_current_operator_version ()
365
475
incremented_operator_version = increment_patch_version (current_operator_version )
366
476
367
- # It is very likely that OLM will be doing a series of status updates during this time.
368
- # It's better to employ a retry mechanism and spin here for a while before failing.
369
- def update_subscription () -> bool :
370
- try :
371
- subscription .load ()
372
- # Update MEKO subscription to MCK
373
- subscription ["spec" ]["name" ] = "mongodb-kubernetes"
374
- # Migration channel contains operator build from the current branch,
375
- # with an upgrade path from the latest MEKO release.
376
- subscription ["spec" ]["channel" ] = "migration"
377
- subscription .update ()
378
- return True
379
- except kubernetes .client .ApiException as e :
380
- if e .status == 409 :
381
- return False
382
- else :
383
- raise e
384
-
385
- run_periodically (update_subscription , timeout = 100 , msg = "Subscription to be updated" )
477
+ # Create MCK subscription
478
+ mck_subscription = get_mck_subscription_object (namespace , catalog_source )
479
+ mck_subscription .update ()
386
480
387
481
wait_for_operator_ready (namespace , OPERATOR_NAME , f"mongodb-kubernetes.v{ incremented_operator_version } " )
388
482
389
483
390
484
@pytest .mark .e2e_olm_meko_operator_upgrade_with_resources
391
- def test_one_resources_not_in_running_state (ops_manager : MongoDBOpsManager , mdb_sharded : MongoDB ):
392
- # Wait for the first resource to become reconciling after operator upgrade.
393
- # Only then wait for all to not get a false positive when all resources are ready,
394
- # because the upgraded operator haven't started reconciling
485
+ def test_one_resource_not_in_running_state (ops_manager : MongoDBOpsManager ):
486
+ # Wait for the first resource to become reconciling after operator replacement.
487
+ # This confirms the MCK operator has started reconciling the resources
395
488
ops_manager .om_status ().assert_reaches_phase (Phase .Pending , timeout = 600 )
396
489
397
490
398
491
@pytest .mark .e2e_olm_meko_operator_upgrade_with_resources
399
- def test_resources_in_running_state_after_upgrade (
492
+ def test_resources_in_running_state_after_migration (
400
493
ops_manager : MongoDBOpsManager ,
401
494
oplog_replica_set : MongoDB ,
402
495
blockstore_replica_set : MongoDB ,
@@ -414,7 +507,7 @@ def test_resources_in_running_state_after_upgrade(
414
507
415
508
416
509
@pytest .mark .e2e_olm_meko_operator_upgrade_with_resources
417
- def test_resources_connectivity_after_upgrade (
510
+ def test_resources_connectivity_after_migration (
418
511
ca_path : str ,
419
512
ops_manager : MongoDBOpsManager ,
420
513
oplog_replica_set : MongoDB ,
0 commit comments