From 806fddcdf4899190362f5466356ff5363d573688 Mon Sep 17 00:00:00 2001 From: Paul Stone Date: Wed, 24 Jun 2026 15:34:27 +0100 Subject: [PATCH 1/6] [patch] activate service mesh external routing configuration --- image/cli/mascli/functions/provision_fyre | 3 +++ image/cli/mascli/functions/provision_roks | 3 +++ python/src/mas/cli/install/argParser.py | 3 ++- tekton/src/tasks/suite-app-install.yml.j2 | 6 ++++++ tekton/src/tasks/suite-verify.yml.j2 | 6 ++++++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/image/cli/mascli/functions/provision_fyre b/image/cli/mascli/functions/provision_fyre index fe005e9215a..cfca9eed553 100644 --- a/image/cli/mascli/functions/provision_fyre +++ b/image/cli/mascli/functions/provision_fyre @@ -179,6 +179,9 @@ function provision_fyre_noninteractive() { esac done + INSTALL_SERVICE_MESH=true + INSTALL_KIALI=true + # Check all args have been set [[ -z "$FYRE_USERNAME" ]] && provision_fyre_help "FYRE_USERNAME is not set" [[ -z "$FYRE_APIKEY" ]] && provision_fyre_help "FYRE_APIKEY is not set" diff --git a/image/cli/mascli/functions/provision_roks b/image/cli/mascli/functions/provision_roks index 9ecfc353e98..07c7177ae01 100755 --- a/image/cli/mascli/functions/provision_roks +++ b/image/cli/mascli/functions/provision_roks @@ -130,6 +130,9 @@ function provision_roks_noninteractive() { esac done + INSTALL_SERVICE_MESH=true + INSTALL_KIALI=true + # Check all args have been set [[ -z "$IBMCLOUD_APIKEY" ]] && provision_roks_help "IBMCLOUD_APIKEY is not set" [[ -z "$IBMCLOUD_RESOURCEGROUP" ]] && provision_roks_help "IBMCLOUD_RESOURCEGROUP is not set" diff --git a/python/src/mas/cli/install/argParser.py b/python/src/mas/cli/install/argParser.py index 489095d85b7..12196f2d9cc 100644 --- a/python/src/mas/cli/install/argParser.py +++ b/python/src/mas/cli/install/argParser.py @@ -166,6 +166,7 @@ def isValidFile(parser: argparse.ArgumentParser, arg: str) -> str: required=False, help="Configure MAS to use Service Mesh networking (default: false)", choices=["true", "false"], + default="true", ) masAdvancedArgGroup.add_argument( "--manual-routes", @@ -173,7 +174,7 @@ def isValidFile(parser: argparse.ArgumentParser, arg: str) -> str: required=False, action="store_const", const="true", - default="false", + default="true", help="Disable automatic creation of routes.", ) masAdvancedArgGroup.add_argument( diff --git a/tekton/src/tasks/suite-app-install.yml.j2 b/tekton/src/tasks/suite-app-install.yml.j2 index 6ef719ff565..4ddaf2cf082 100644 --- a/tekton/src/tasks/suite-app-install.yml.j2 +++ b/tekton/src/tasks/suite-app-install.yml.j2 @@ -678,6 +678,12 @@ spec: - name: MAS_FACILITIES_SCHEDULERAGENT_DEPLOYMENTMODE value: $(params.mas_ws_facilities_scheduleragent_deploymentmode) + # configure service mesh domain routing + - name: servicemesh-domain-config + command: + - /opt/app-root/src/run-role.sh + - servicemesh_domain_config + - name: app-wait-for-approval # If configmap/approval-app-cfg-$(params.mas_app_id) exists then set STATUS=pending and wait for it to be changed to "approved" command: diff --git a/tekton/src/tasks/suite-verify.yml.j2 b/tekton/src/tasks/suite-verify.yml.j2 index e73798de261..ebaa8b0ebb4 100644 --- a/tekton/src/tasks/suite-verify.yml.j2 +++ b/tekton/src/tasks/suite-verify.yml.j2 @@ -28,6 +28,12 @@ spec: image: "quay.io/ibmmas/cli:latest" imagePullPolicy: $(params.image_pull_policy) + # configure service mesh domain routing + - name: servicemesh-domain-config + command: + - /opt/app-root/src/run-role.sh + - servicemesh_domain_config + # If configmap/approval-suite-verify exists then set STATUS=pending and wait for it to be changed to "approved" - name: suite-wait-for-approval image: "quay.io/ibmmas/cli:latest" From 050e3cd524f9dcab6cdb97b65454c403a829dd2f Mon Sep 17 00:00:00 2001 From: Paul Stone Date: Thu, 25 Jun 2026 08:54:23 +0100 Subject: [PATCH 2/6] [patch] fix modified tekton tasks --- tekton/src/tasks/suite-app-install.yml.j2 | 2 ++ tekton/src/tasks/suite-verify.yml.j2 | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tekton/src/tasks/suite-app-install.yml.j2 b/tekton/src/tasks/suite-app-install.yml.j2 index 4ddaf2cf082..365d1ab80ac 100644 --- a/tekton/src/tasks/suite-app-install.yml.j2 +++ b/tekton/src/tasks/suite-app-install.yml.j2 @@ -683,6 +683,8 @@ spec: command: - /opt/app-root/src/run-role.sh - servicemesh_domain_config + image: "quay.io/ibmmas/cli:latest" + imagePullPolicy: $(params.image_pull_policy) - name: app-wait-for-approval # If configmap/approval-app-cfg-$(params.mas_app_id) exists then set STATUS=pending and wait for it to be changed to "approved" diff --git a/tekton/src/tasks/suite-verify.yml.j2 b/tekton/src/tasks/suite-verify.yml.j2 index ebaa8b0ebb4..5bd55d3f95e 100644 --- a/tekton/src/tasks/suite-verify.yml.j2 +++ b/tekton/src/tasks/suite-verify.yml.j2 @@ -33,6 +33,8 @@ spec: command: - /opt/app-root/src/run-role.sh - servicemesh_domain_config + image: "quay.io/ibmmas/cli:latest" + imagePullPolicy: $(params.image_pull_policy) # If configmap/approval-suite-verify exists then set STATUS=pending and wait for it to be changed to "approved" - name: suite-wait-for-approval From 22af9149b554f7dcd46209c1ae2e938473d67b38 Mon Sep 17 00:00:00 2001 From: Paul Stone Date: Tue, 14 Jul 2026 14:15:17 +0100 Subject: [PATCH 3/6] [patch] include servicemesh resources in mustgather --- python/src/mas/cli/must_gather/app.py | 14 +++- python/src/mas/cli/must_gather/arg_parser.py | 2 +- .../must_gather/dependencies/servicemesh.py | 80 +++++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 python/src/mas/cli/must_gather/dependencies/servicemesh.py diff --git a/python/src/mas/cli/must_gather/app.py b/python/src/mas/cli/must_gather/app.py index d8760acd809..4c22a2a0af6 100644 --- a/python/src/mas/cli/must_gather/app.py +++ b/python/src/mas/cli/must_gather/app.py @@ -281,7 +281,7 @@ def planCollection(self, parsedArgs, outputDir: str): CollectionPlan: Complete plan with all collection tasks organized into groups """ from .collection_plan import CollectionPlan - from .dependencies import kafka, mongodb, grafana, cert_manager, db2, cp4d + from .dependencies import kafka, mongodb, grafana, cert_manager, db2, cp4d, servicemesh # Type assertion: dynClient is guaranteed to be non-None by connect() assert self.dynamicClient is not None, "Kubernetes client must be initialized before planning collection" @@ -380,6 +380,18 @@ def planCollection(self, parsedArgs, outputDir: str): else: logger.debug("Skipping CP4D collection (not in collectors list)") + # Service Mesh + if "servicemesh" in enabledCollectors: + servicemesh.addServiceMeshToCollectionPlan( + plan=plan, + dynClient=self.dynamicClient, + outputDir=outputDir, + noLogs=parsedArgs.no_logs, + ibmCRDs=self.ibmCRDsList, + ) + else: + logger.debug("Skipping ServiceMesh collection (not in collectors list)") + # SLS if "sls" in enabledCollectors: masInstanceIds = parsedArgs.mas_instance_ids.split(",") if parsedArgs.mas_instance_ids else None diff --git a/python/src/mas/cli/must_gather/arg_parser.py b/python/src/mas/cli/must_gather/arg_parser.py index 59ffa250631..43d5ef627d0 100644 --- a/python/src/mas/cli/must_gather/arg_parser.py +++ b/python/src/mas/cli/must_gather/arg_parser.py @@ -72,7 +72,7 @@ def _parse_args_wrapper(args=None, namespace=None): # Define all available collectors -ALL_COLLECTORS = ["ocp", "db2", "kafka", "mongodb", "cp4d", "cert-manager", "grafana", "sls", "mas", "aiservice", "lic"] +ALL_COLLECTORS = ["ocp", "db2", "kafka", "mongodb", "cp4d", "cert-manager", "grafana", "sls", "mas", "aiservice", "lic", "servicemesh"] def validateCollectors(collectorsStr: str) -> str: diff --git a/python/src/mas/cli/must_gather/dependencies/servicemesh.py b/python/src/mas/cli/must_gather/dependencies/servicemesh.py new file mode 100644 index 00000000000..15ec3717013 --- /dev/null +++ b/python/src/mas/cli/must_gather/dependencies/servicemesh.py @@ -0,0 +1,80 @@ +# ***************************************************************************** +# Copyright (c) 2026 IBM Corporation and other Contributors. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# ***************************************************************************** + +"""Service Mesh dependency collector.""" + +import logging +from typing import Set +from kubernetes.dynamic import DynamicClient +from .utils import discoverNamespacesFromCR + +logger = logging.getLogger(__name__) + +# ServiceMesh-specific custom resources to collect (apiVersion, kind) +SERVICEMESH_RESOURCES = [ + ("sailoperator.io/v1", "Istio"), + ("sailoperator.io/v1", "IstioCNI"), + ("networking.istio.io/v1", "Gateway"), + ("networking.istio.io/v1", "VirtualService"), +] + + +def _discoverServiceMeshNamespaces(dynClient: DynamicClient) -> Set[str]: + """Discover namespaces containing ServiceMesh resources. + + Discovers namespaces by finding all ServiceMesh custom resources in the cluster. + + Args: + dynClient (DynamicClient): Kubernetes Dynamic Client for API access + + Returns: + set: Set of namespace names where ServiceMesh CRs exist + """ + namespaces = set() + namespaces.update(discoverNamespacesFromCR(dynClient=dynClient, kind="Gateway", apiVersion="networking.istio.io/v1")) + namespaces.update(discoverNamespacesFromCR(dynClient=dynClient, kind="VirtualService", apiVersion="networking.istio.io/v1")) + + return namespaces + + +def addServiceMeshToCollectionPlan(plan, dynClient: DynamicClient, outputDir: str, noLogs: bool, ibmCRDs: list): + """Add ServiceMesh collection tasks to the collection plan. + + Discovers ServiceMesh namespaces and adds collection groups for each namespace + to the provided collection plan. + + Args: + plan (CollectionPlan): Collection plan to add tasks to + dynClient (DynamicClient): Kubernetes Dynamic Client for API access + outputDir (str): Base output directory for collected resources + noLogs (bool): If True, skip pod log collection + ibmCRDs (list): List of IBM CRD information for collection + """ + from ..common.task_generation import generateNamespaceCollectionTasks + + logger.debug("Discovering ServiceMesh namespaces") + serviceMeshNamespaces = _discoverServiceMeshNamespaces(dynClient) + + if serviceMeshNamespaces: + logger.info(f"Discovered {len(serviceMeshNamespaces)} ServiceMesh namespace(s): {', '.join(sorted(serviceMeshNamespaces))}") + for ns in sorted(serviceMeshNamespaces): + tasks = generateNamespaceCollectionTasks( + dynClient=dynClient, + namespace=ns, + outputDir=outputDir, + noLogs=noLogs, + secretData=False, + customResources=SERVICEMESH_RESOURCES, + ibmCRDs=ibmCRDs, + ) + plan.addGroup(f"ServiceMesh ({ns})", tasks) + logger.debug(f"Added {len(tasks)} ServiceMesh collection tasks for namespace {ns}") + else: + logger.info("No ServiceMesh namespaces discovered") From 8c24631b382d712ea2d568c92c30610ea53046c4 Mon Sep 17 00:00:00 2001 From: Paul Stone Date: Tue, 14 Jul 2026 15:48:10 +0100 Subject: [PATCH 4/6] [patch] include Istio cluster scoped objects in must gather --- .../must_gather/dependencies/servicemesh.py | 37 +++++++++++++++++-- .../tests/unit/must_gather/test_arg_parser.py | 2 +- .../test_collectors_integration.py | 28 ++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/python/src/mas/cli/must_gather/dependencies/servicemesh.py b/python/src/mas/cli/must_gather/dependencies/servicemesh.py index 15ec3717013..8e93d128281 100644 --- a/python/src/mas/cli/must_gather/dependencies/servicemesh.py +++ b/python/src/mas/cli/must_gather/dependencies/servicemesh.py @@ -18,9 +18,11 @@ logger = logging.getLogger(__name__) # ServiceMesh-specific custom resources to collect (apiVersion, kind) -SERVICEMESH_RESOURCES = [ +SERVICEMESH_CLUSTER_RESOURCES = [ ("sailoperator.io/v1", "Istio"), ("sailoperator.io/v1", "IstioCNI"), +] +SERVICEMESH_NS_RESOURCES = [ ("networking.istio.io/v1", "Gateway"), ("networking.istio.io/v1", "VirtualService"), ] @@ -38,8 +40,8 @@ def _discoverServiceMeshNamespaces(dynClient: DynamicClient) -> Set[str]: set: Set of namespace names where ServiceMesh CRs exist """ namespaces = set() - namespaces.update(discoverNamespacesFromCR(dynClient=dynClient, kind="Gateway", apiVersion="networking.istio.io/v1")) - namespaces.update(discoverNamespacesFromCR(dynClient=dynClient, kind="VirtualService", apiVersion="networking.istio.io/v1")) + for apiVersion, kind in SERVICEMESH_NS_RESOURCES: + namespaces.update(discoverNamespacesFromCR(dynClient=dynClient, kind=kind, apiVersion=apiVersion)) return namespaces @@ -58,7 +60,34 @@ def addServiceMeshToCollectionPlan(plan, dynClient: DynamicClient, outputDir: st ibmCRDs (list): List of IBM CRD information for collection """ from ..common.task_generation import generateNamespaceCollectionTasks + from ..common.resources import collectResources + + # Collect cluster-scoped ServiceMesh resources first + logger.debug("Collecting cluster-scoped ServiceMesh resources") + clusterTasks = [] + + # Add Istio and IstioCNI (cluster-scoped) + for apiVersion, kind in SERVICEMESH_CLUSTER_RESOURCES: + # clusterTasks.append(( + # lambda: collectResources(namespace=None, apiVersion=apiVersion, kind=kind, outputDir=outputDir, allNamespaces=False) # None = cluster-scoped + # ) + clusterTasks.append( + ( + kind, + collectResources, + None, # namespace=None for cluster-scoped + apiVersion, + kind, + outputDir, + False, # allNamespaces + ) + ) + + if clusterTasks: + plan.addGroup("ServiceMesh (Cluster)", clusterTasks) + logger.debug(f"Added {len(clusterTasks)} cluster-scoped ServiceMesh tasks") + # Now collect namespace-scoped resources logger.debug("Discovering ServiceMesh namespaces") serviceMeshNamespaces = _discoverServiceMeshNamespaces(dynClient) @@ -71,7 +100,7 @@ def addServiceMeshToCollectionPlan(plan, dynClient: DynamicClient, outputDir: st outputDir=outputDir, noLogs=noLogs, secretData=False, - customResources=SERVICEMESH_RESOURCES, + customResources=SERVICEMESH_NS_RESOURCES, ibmCRDs=ibmCRDs, ) plan.addGroup(f"ServiceMesh ({ns})", tasks) diff --git a/python/tests/unit/must_gather/test_arg_parser.py b/python/tests/unit/must_gather/test_arg_parser.py index cf54988f8e5..6952f72ffd9 100644 --- a/python/tests/unit/must_gather/test_arg_parser.py +++ b/python/tests/unit/must_gather/test_arg_parser.py @@ -294,7 +294,7 @@ def test_parser_collectors_default_all_enabled(self): """ parser = mustGatherArgParser args = parser.parse_args([]) - expected = "ocp,db2,kafka,mongodb,cp4d,cert-manager,grafana,sls,mas,aiservice,lic" + expected = "ocp,db2,kafka,mongodb,cp4d,cert-manager,grafana,sls,mas,aiservice,lic,servicemesh" assert args.collectors == expected def test_parser_collectors_single_collector(self): diff --git a/python/tests/unit/must_gather/test_collectors_integration.py b/python/tests/unit/must_gather/test_collectors_integration.py index f186771c083..4f2bdc2a7cc 100644 --- a/python/tests/unit/must_gather/test_collectors_integration.py +++ b/python/tests/unit/must_gather/test_collectors_integration.py @@ -326,6 +326,34 @@ def test_aiservice_collector_disabled_when_excluded(self, mustGatherApp): mustGatherApp.planCollection(args, "/tmp/test-output") assert "aiservice" not in args.collectors + def test_servicemesh_collector_enabled_by_default(self, mustGatherApp): + """Test that Service Mesh collector is enabled by default. + + GIVEN default collectors configuration + WHEN planCollection is called + THEN ServiceMesh resources are included in collection plan. + """ + parser = mustGatherArgParser + args = parser.parse_args([]) + + with patch.object(mustGatherApp, "_collectMustGather"): + mustGatherApp.planCollection(args, "/tmp/test-output") + assert "servicemesh" in args.collectors + + def test_servicemesh_collector_disabled_when_excluded(self, mustGatherApp): + """Test that Service Mesh collector is disabled when excluded from collectors. + + GIVEN collectors configuration without aiservice + WHEN planCollection is called + THEN AIService resources are not included in collection plan. + """ + parser = mustGatherArgParser + args = parser.parse_args(["--collectors", "ocp,mas"]) + + with patch.object(mustGatherApp, "_collectMustGather"): + mustGatherApp.planCollection(args, "/tmp/test-output") + assert "servicemesh" not in args.collectors + def test_multiple_collectors_combination(self, mustGatherApp): """Test that multiple collectors can be combined. From 29f63e5ce5a78e0afeeedbd915ed03d378fd964e Mon Sep 17 00:00:00 2001 From: Paul Stone Date: Tue, 14 Jul 2026 16:19:31 +0100 Subject: [PATCH 5/6] [patch] adjust to new method signature --- python/src/mas/cli/must_gather/dependencies/servicemesh.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/src/mas/cli/must_gather/dependencies/servicemesh.py b/python/src/mas/cli/must_gather/dependencies/servicemesh.py index 8e93d128281..dea5daae9fe 100644 --- a/python/src/mas/cli/must_gather/dependencies/servicemesh.py +++ b/python/src/mas/cli/must_gather/dependencies/servicemesh.py @@ -99,7 +99,6 @@ def addServiceMeshToCollectionPlan(plan, dynClient: DynamicClient, outputDir: st namespace=ns, outputDir=outputDir, noLogs=noLogs, - secretData=False, customResources=SERVICEMESH_NS_RESOURCES, ibmCRDs=ibmCRDs, ) From fe86b90312b3d2009548e601e7c382e12f017c17 Mon Sep 17 00:00:00 2001 From: Paul Stone Date: Tue, 14 Jul 2026 16:31:07 +0100 Subject: [PATCH 6/6] [patch] fix failing must-gather test --- python/tests/unit/must_gather/test_arg_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tests/unit/must_gather/test_arg_parser.py b/python/tests/unit/must_gather/test_arg_parser.py index 3d0ccf501d5..25e18ec5f3c 100644 --- a/python/tests/unit/must_gather/test_arg_parser.py +++ b/python/tests/unit/must_gather/test_arg_parser.py @@ -283,7 +283,7 @@ def test_parser_collectors_default_all_enabled(self): """ parser = mustGatherArgParser args = parser.parse_args([]) - expected = "ocp,db2,kafka,mongodb,cp4d,cert-manager,grafana,sls,mas,rhoai,aiservice,lic,pipelines,amlen, servicemesh" + expected = "ocp,db2,kafka,mongodb,cp4d,cert-manager,grafana,sls,mas,rhoai,aiservice,lic,pipelines,amlen,servicemesh" assert args.collectors == expected def test_parser_collectors_single_collector(self):