Skip to content

Commit 78beae6

Browse files
authored
Update MEKO to MCK OLM tests (#97)
Instead of relying on MEKO -> MCK upgrade path in a catalog metadata, we now use uninstall-then-install migration approach. While MEKO -> MCK upgrade path is technically possible, it is not allowed in operatorhub.io and Red Hat certified operators catalogs as it is prevented by validation.
1 parent 2ce0bd0 commit 78beae6

File tree

4 files changed

+148
-68
lines changed

4 files changed

+148
-68
lines changed

config/manifests/bases/mongodb-kubernetes.clusterserviceversion.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,5 +443,4 @@ spec:
443443
maturity: stable
444444
provider:
445445
name: MongoDB, Inc
446-
replaces: mongodb-enterprise.v1.33.0
447446
version: 0.0.0

docker/mongodb-kubernetes-tests/tests/olm/olm_meko_operator_upgrade_with_resources.py

Lines changed: 138 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from kubetester.mongodb_user import MongoDBUser
1717
from kubetester.opsmanager import MongoDBOpsManager
1818
from pytest import fixture
19+
from tests import test_logger
1920
from tests.conftest import LEGACY_OPERATOR_NAME, OPERATOR_NAME
2021
from tests.olm.olm_test_commons import (
2122
get_catalog_image,
@@ -28,18 +29,22 @@
2829
wait_for_operator_ready,
2930
)
3031
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__)
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
93123
def 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,

scripts/evergreen/operator-sdk/prepare-openshift-bundles-for-e2e.sh

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@ set -Eeou pipefail
44

55
# This script prepares and publishes OpenShift bundles and catalog source for operator upgrade tests using OLM.
66
# It enables testing two upgrade scenarios:
7-
# 1. Upgrade from the latest release of MEKO (MongoDB Enterprise Kubernetes Operator)
8-
# to the current version of MCK (MongoDB Controllers for Kubernetes).
7+
# 1. Migration from the latest release of MEKO (MongoDB Enterprise Kubernetes Operator)
8+
# to the current version of MCK (MongoDB Controllers for Kubernetes)
9+
# by uninstalling MEKO and installing MCK.
910
# 2. Upgrade from the latest release of MCK to the current version of MCK.
1011
#
1112
# The script builds a test catalog with:
1213
# - MEKO package (mongodb-enterprise) with one channel:
1314
# - stable channel: latest release of MEKO
14-
# - MCK package (mongodb-kubernetes) with two channels:
15+
# - MCK package (mongodb-kubernetes) with three channels:
1516
# - stable channel: latest release of MCK
1617
# - fast channel: current version of MCK with an upgrade path from stable
17-
# - migration channel: current version of MCK with an upgrade path from stable of MEKO
18+
# - migration channel: current version of MCK. This will be used to test
19+
# migration from MEKO to MCK by uninstalling MEKO and installing MCK
20+
# so this channel has an entry without the replaces field.
1821
#
1922
# Required env vars:
2023
# - BASE_REPO_URL (or REGISTRY for EVG run)
@@ -103,7 +106,6 @@ function generate_mck_catalog_metadata() {
103106
current_bundle_version=$5
104107
current_bundle_image=$6
105108
meko_package_name=$7
106-
meko_latest_bundle_version=$8
107109

108110
catalog_yaml="${catalog_dir}/${mck_package_name}.yaml"
109111

@@ -154,8 +156,7 @@ schema: olm.channel
154156
package: ${mck_package_name}
155157
name: migration
156158
entries:
157-
- name: ${mck_package_name}.v${current_bundle_version}
158-
replaces: ${meko_package_name}.v${meko_latest_bundle_version}" >>"${catalog_yaml}"
159+
- name: ${mck_package_name}.v${current_bundle_version}" >>"${catalog_yaml}"
159160
}
160161

161162
function generate_meko_catalog_metadata() {
@@ -271,7 +272,7 @@ scripts/evergreen/operator-sdk/prepare-openshift-bundles.sh
271272
# publish two-channel catalog source to be used in e2e test.
272273
header "Building and pushing the catalog:"
273274
catalog_dir="$(init_test_catalog "${certified_repo_cloned}")"
274-
generate_mck_catalog_metadata "${catalog_dir}" "${mck_package_name}" "${mck_latest_released_operator_version}" "${mck_latest_certified_bundle_image}" "${current_incremented_operator_version_from_release_json}" "${current_bundle_image}" "${meko_package_name}" "${meko_latest_released_operator_version}"
275+
generate_mck_catalog_metadata "${catalog_dir}" "${mck_package_name}" "${mck_latest_released_operator_version}" "${mck_latest_certified_bundle_image}" "${current_incremented_operator_version_from_release_json}" "${current_bundle_image}" "${meko_package_name}"
275276
generate_meko_catalog_metadata "${catalog_dir}" "${meko_package_name}" "${meko_latest_released_operator_version}" "${meko_latest_certified_bundle_image}"
276277
build_and_publish_test_catalog "${catalog_dir}" "${test_catalog_image}"
277278

scripts/evergreen/release/update_helm_values_files.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,23 +94,10 @@ def update_cluster_service_version(operator_version):
9494
image_repo = ":".join(image_parts[:-1])
9595

9696
if old_operator_version != operator_version:
97-
olm_package_name = "mongodb-kubernetes"
98-
# TODO: CLOUDP-310820 - After 1.0.0 release we need to clean this up: remove this condition
99-
if Version(operator_version) <= Version("1.0.0"):
100-
# MCK version 1.0.0 is a special case, where we need to
101-
# set the olm_package_name to "mongodb-enterprise" because
102-
# this is the version which provides a migration path
103-
# from the old mongodb-enterprise operator (MEKO)
104-
# to the new mongodb-kubernetes (MCK).
105-
olm_package_name = "mongodb-enterprise"
106-
# This is the latest MEKO version we are going to release.
107-
# We hardcode it for now. Later this whole condition will be removed.
108-
old_operator_version = "1.33.0"
109-
11097
set_value_in_yaml_file(
11198
"config/manifests/bases/mongodb-kubernetes.clusterserviceversion.yaml",
11299
"spec.replaces",
113-
f"{olm_package_name}.v{old_operator_version}",
100+
f"mongodb-kubernetes.v{old_operator_version}",
114101
preserve_quotes=True,
115102
)
116103

0 commit comments

Comments
 (0)