1616from kubetester .mongodb_user import MongoDBUser
1717from kubetester .opsmanager import MongoDBOpsManager
1818from pytest import fixture
19+ from tests import test_logger
1920from tests .conftest import LEGACY_OPERATOR_NAME , OPERATOR_NAME
2021from tests .olm .olm_test_commons import (
2122 get_catalog_image ,
2829 wait_for_operator_ready ,
2930)
3031from 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__ )
3234
3335# See docs how to run this locally: https://wiki.corp.mongodb.com/display/MMS/E2E+Tests+Notes#E2ETestsNotes-OLMtests
3436
3537# 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:
3741# - deploy latest released MEKO operator using OLM
3842# - deploy OM
3943# - deploy backup-required MongoDB: oplog, s3, blockstore
4044# - deploy TLS-enabled sharded MongoDB
4145# - 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
4348# - wait for resources to be rolling-updated due to updated stateful sets by the new operator
4449# - check everything is running and connectable
4550
@@ -59,15 +64,13 @@ def catalog_source(namespace: str, version_id: str):
5964
6065
6166@fixture
62- def subscription (namespace : str , catalog_source : CustomObject ):
67+ def meko_subscription (namespace : str , catalog_source : CustomObject ):
6368 """
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.
6770 """
6871 static_value = get_default_architecture ()
6972 return get_subscription_custom_object (
70- "mongodb-enterprise-operator" ,
73+ LEGACY_OPERATOR_NAME ,
7174 namespace ,
7275 {
7376 "channel" : "stable" , # stable channel contains latest released operator in RedHat's certified repository
@@ -89,6 +92,33 @@ def subscription(namespace: str, catalog_source: CustomObject):
8992 )
9093
9194
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+
92122@fixture
93123def latest_released_meko_version ():
94124 return get_latest_released_operator_version ("mongodb-enterprise" )
@@ -100,12 +130,10 @@ def test_meko_install_stable_operator_version(
100130 version_id : str ,
101131 latest_released_meko_version : str ,
102132 catalog_source : CustomObject ,
103- subscription : CustomObject ,
133+ meko_subscription : CustomObject ,
104134):
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 } " )
109137
110138
111139# install resources on the latest released version of the operator
@@ -344,59 +372,124 @@ def test_resources_in_running_state_before_upgrade(
344372 mdb_sharded .assert_reaches_phase (Phase .Running )
345373
346374
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+ )
348398
349399
350400@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 )
355466
356467
357468@pytest .mark .e2e_olm_meko_operator_upgrade_with_resources
358- def test_meko_operator_upgrade_to_mck (
469+ def test_install_mck_operator (
359470 namespace : str ,
360471 version_id : str ,
361472 catalog_source : CustomObject ,
362- subscription : CustomObject ,
363473):
364474 current_operator_version = get_current_operator_version ()
365475 incremented_operator_version = increment_patch_version (current_operator_version )
366476
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 ()
386480
387481 wait_for_operator_ready (namespace , OPERATOR_NAME , f"mongodb-kubernetes.v{ incremented_operator_version } " )
388482
389483
390484@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
395488 ops_manager .om_status ().assert_reaches_phase (Phase .Pending , timeout = 600 )
396489
397490
398491@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 (
400493 ops_manager : MongoDBOpsManager ,
401494 oplog_replica_set : MongoDB ,
402495 blockstore_replica_set : MongoDB ,
@@ -414,7 +507,7 @@ def test_resources_in_running_state_after_upgrade(
414507
415508
416509@pytest .mark .e2e_olm_meko_operator_upgrade_with_resources
417- def test_resources_connectivity_after_upgrade (
510+ def test_resources_connectivity_after_migration (
418511 ca_path : str ,
419512 ops_manager : MongoDBOpsManager ,
420513 oplog_replica_set : MongoDB ,
0 commit comments