Repository: ceph/ceph
Pull request #61781: mgr/dashboard: fix lifecycle get request
Author: login: Pegonzal
Labels: name: build/ops
name: pybind
name: mgr
name: dashboard
name: ui-tested
name: wip-pedro-testing
Created: 2025-02-12T19:25:25Z | Merged: 2025-03-14T14:25:47Z

Fixes lifecycle get request, imports xmltodict and fixes change detection issue when parsing json to xml in frontend.

[screen-capture (69).webm](https://github.com/user-attachments/assets/8214f089-aecd-4836-b61a-a991b680c77c)



<!--
  - Please give your pull request a title like

      [component]: [short description]

  - Please use this format for each git commit message:

      [component]: [short description]

      [A longer multiline description]

      Fixes: [ticket URL on tracker.ceph.com, create one if necessary]
      Signed-off-by: [Your Name] <[your email]>

    For examples, use "git log".
-->

## Contribution Guidelines
- To sign and title your commits, please refer to [Submitting Patches to Ceph](https://github.com/ceph/ceph/blob/main/SubmittingPatches.rst).

- If you are submitting a fix for a stable branch (e.g. "quincy"), please refer to [Submitting Patches to Ceph - Backports](https://github.com/ceph/ceph/blob/master/SubmittingPatches-backports.rst) for the proper workflow.

- When filling out the below checklist, you may click boxes directly in the GitHub web UI.  When entering or editing the entire PR message in the GitHub web UI editor, you may also select a checklist item by adding an `x` between the brackets: `[x]`.  Spaces and capitalization matter when checking off items this way.

## Checklist
- Tracker (select at least one)
  - [x] References tracker ticket
  - [ ] Very recent bug; references commit where it was introduced
  - [ ] New feature (ticket optional)
  - [ ] Doc update (no ticket needed)
  - [ ] Code cleanup (no ticket needed)
- Component impact
  - [ ] Affects [Dashboard](https://tracker.ceph.com/projects/dashboard/issues/new), opened tracker ticket
  - [ ] Affects [Orchestrator](https://tracker.ceph.com/projects/orchestrator/issues/new), opened tracker ticket
  - [ ] No impact that needs to be tracked
- Documentation (select at least one)
  - [ ] Updates relevant documentation
  - [ ] No doc update is appropriate
- Tests (select at least one)
  - [ ] Includes [unit test(s)](https://docs.ceph.com/en/latest/dev/developer_guide/tests-unit-tests/)
  - [ ] Includes [integration test(s)](https://docs.ceph.com/en/latest/dev/developer_guide/testing_integration_tests/)
  - [ ] Includes bug reproducer
  - [ ] No tests

<details>
<summary>Show available Jenkins commands</summary>

- `jenkins retest this please`
- `jenkins test classic perf`
- `jenkins test crimson perf`
- `jenkins test signed`
- `jenkins test make check`
- `jenkins test make check arm64`
- `jenkins test submodules`
- `jenkins test dashboard`
- `jenkins test dashboard cephadm`
- `jenkins test api`
- `jenkins test docs`
- `jenkins render docs`
- `jenkins test ceph-volume all`
- `jenkins test ceph-volume tox`
- `jenkins test windows`
- `jenkins test rook e2e`
</details>



Diff:
diff --git a/ceph.spec.in b/ceph.spec.in
index d72b5abcc6222..8c022125ebabc 100644
--- a/ceph.spec.in
+++ b/ceph.spec.in
@@ -695,6 +695,7 @@ Requires:       ceph-prometheus-alerts = %{_epoch_prefix}%{version}-%{release}
 Requires:       python%{python3_pkgversion}-grpcio
 Requires:       python%{python3_pkgversion}-grpcio-tools
 Requires:       python%{python3_pkgversion}-jmespath
+Requires:       python%{python3_pkgversion}-xmltodict
 %endif
 %if 0%{?fedora} || 0%{?rhel} || 0%{?openEuler}
 Requires:       python%{python3_pkgversion}-cherrypy
diff --git a/debian/control b/debian/control
index 88c4c92238f0f..6d3fa3263050a 100644
--- a/debian/control
+++ b/debian/control
@@ -100,6 +100,7 @@ Build-Depends: automake,
                python3-dateutil <pkg.ceph.check>,
                python3-grpcio <pkg.ceph.check>,
                python3-jmespath (>=0.10) <pkg.ceph.check>,
+               python3-xmltodict <pkg.ceph.check>,
                python3-openssl <pkg.ceph.check>,
                python3-prettytable <pkg.ceph.check>,
                python3-requests <pkg.ceph.check>,
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html
index 2c22cf80c609e..49d4f4255c7af 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html
@@ -152,13 +152,13 @@
                     <button type="button"
                             class="btn btn-light"
                             [ngClass]="{'active': lifecycleFormat === 'json'}"
-                            (click)="lifecycleFormat = 'json'">
+                            (click)="updateLifecycleFormatTo('json')">
                           JSON
                     </button>
                     <button type="button"
                             class="btn btn-light"
                             [ngClass]="{'active': lifecycleFormat === 'xml'}"
-                            (click)="lifecycleFormat = 'xml'">
+                            (click)="updateLifecycleFormatTo('xml')">
                           XML
                     </button>
                   </div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts
index b95faa3140eb6..fe8ab3e6a3279 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnChanges } from '@angular/core';
+import { ChangeDetectorRef, Component, Input, OnChanges } from '@angular/core';
 
 import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
 
@@ -24,7 +24,7 @@ export class RgwBucketDetailsComponent implements OnChanges {
   replicationStatus = $localize`Disabled`;
   bucketRateLimit: RgwRateLimitConfig;
 
-  constructor(private rgwBucketService: RgwBucketService) {}
+  constructor(private rgwBucketService: RgwBucketService, private cd: ChangeDetectorRef) {}
 
   ngOnChanges() {
     this.updateBucketDetails(this.extraxtDetailsfromResponse.bind(this));
@@ -102,4 +102,9 @@ export class RgwBucketDetailsComponent implements OnChanges {
       );
     }
   }
+
+  updateLifecycleFormatTo(format: 'json' | 'xml'): void {
+    this.lifecycleFormat = format;
+    this.cd.detectChanges();
+  }
 }
diff --git a/src/pybind/mgr/dashboard/requirements.txt b/src/pybind/mgr/dashboard/requirements.txt
index be0bc1efb022e..6f95477cb1299 100644
--- a/src/pybind/mgr/dashboard/requirements.txt
+++ b/src/pybind/mgr/dashboard/requirements.txt
@@ -15,3 +15,4 @@ grpcio==1.46.5
 grpcio-tools==1.46.5
 jmespath
 lxml==4.8.0  # to fix https://github.com/xmlsec/python-xmlsec/issues/320
+xmltodict
diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py
index f29e4766d6156..f9423da564e7e 100755
--- a/src/pybind/mgr/dashboard/services/rgw_client.py
+++ b/src/pybind/mgr/dashboard/services/rgw_client.py
@@ -16,6 +16,12 @@
 from urllib.parse import urlparse
 
 import requests
+
+try:
+    import xmltodict
+except ModuleNotFoundError:
+    logging.error("Module 'xmltodict' is not installed.")
+
 from mgr_util import build_url, name_to_config_section
 
 from .. import mgr
@@ -715,23 +721,30 @@ def _handle_rules(pairs):
     @RestClient.api_get('/{bucket_name}?lifecycle')
     def get_lifecycle(self, bucket_name, request=None):
         # pylint: disable=unused-argument
+
+        # xmltodict parser will prepend namespace to json keys as {ns0:key}
+        def remove_namespace(xml: str):
+            """Remove namespace in given xml string."""
+            root = ET.fromstring(xml)
+            for elem in root.iter():
+                tag_elements = elem.tag.split("}")  # tag: {ns}tagname
+                # Removing namespaces and attributes
+                elem.tag = tag_elements[1]
+                elem.attrib.clear()
+
+            return ET.tostring(root)
+
         try:
-            decoded_request = request(raw_content=True).decode("utf-8")  # type: ignore
-            result = {
-                'LifecycleConfiguration':
-                json.loads(
-                    decoded_request,
-                    object_pairs_hook=RgwClient._handle_rules
-                )
-            }
+            result = request(
+                raw_content=True, headers={'Accept': 'text/xml'}).decode()  # type: ignore
+            return xmltodict.parse(remove_namespace(result), process_namespaces=False)
         except RequestException as e:
             if e.content:
-                content = json_str_to_object(e.content)
-                if content.get(
-                        'Code') == 'NoSuchLifecycleConfiguration':
+                root = ET.fromstring(e.content)
+                code = root.find('Code')
+                if code is not None and code.text == 'NoSuchLifecycleConfiguration':
                     return None
             raise DashboardException(msg=str(e), component='rgw')
-        return result
 
     @staticmethod
     def dict_to_xml(data):
diff --git a/src/pybind/mgr/tox.ini b/src/pybind/mgr/tox.ini
index 366dc1ff87121..c2deb627261ec 100644
--- a/src/pybind/mgr/tox.ini
+++ b/src/pybind/mgr/tox.ini
@@ -82,6 +82,7 @@ deps =
     types-PyYAML
     types-jwt
     types-jmespath
+    types-xmltodict
 commands =
     mypy --config-file=../../mypy.ini \
            -m alerts \


Reviews:
user: login: nmunet
body: Thanks @Pegonzal for finding out time to work on this,
Works as expected, returning correct XML
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T09:52:38Z
user: login: nizamial09
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-25T13:35:08Z
user: login: Pegonzal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T10:41:01Z
user: login: Pegonzal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T10:42:19Z
user: login: Pegonzal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T10:42:48Z
user: login: nizamial09
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-26T11:27:34Z
user: login: nizamial09
body: once the shaman passes, we can go ahead with it.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-02-26T11:28:12Z
user: login: nizamial09
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T17:30:22Z
user: login: Pegonzal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T17:33:50Z
user: login: nizamial09
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T17:36:03Z
user: login: Pegonzal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T17:38:21Z

Review comments:
diff_hunk: @@ -13,3 +13,4 @@ setuptools
 jsonpatch
 grpcio
 grpcio-tools
+xmltodict
path: src/pybind/mgr/dashboard/requirements.txt
user: login: nizamial09
body: new line?
created_at: 2025-02-25T13:20:07Z
author_association: MEMBER
diff_hunk: @@ -69,4 +69,9 @@ export class RgwBucketDetailsComponent implements OnChanges {
     });
     return data;
   }
+
+  switchLifecycleFormat(): void {
+    this.lifecycleFormat = this.lifecycleFormat === 'json' ? 'xml' : 'json';
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts
user: login: nizamial09
body: this is actually confusing.. i see what you are trying to do but it took me a while to actually digest that. why not just simplify it and get the format as input and then assign it to the lifecycleFormat?

```suggestion
  switchLifecycleFormat(format: string): void {
    this.lifecycleFormat = format;
```
created_at: 2025-02-25T13:24:22Z
author_association: MEMBER
diff_hunk: @@ -715,23 +716,28 @@ def _handle_rules(pairs):
     @RestClient.api_get('/{bucket_name}?lifecycle')
     def get_lifecycle(self, bucket_name, request=None):
         # pylint: disable=unused-argument
+
+        def remove_namespace(xml: str):
+            """Remove namespace in given xml string."""
+            root = ET.fromstring(xml)
+            for elem in root.iter():
+                tag_elements = elem.tag.split("}")
+                # Removing name spaces and attributes
path: src/pybind/mgr/dashboard/services/rgw_client.py
user: login: nizamial09
body: ```suggestion
                # Removing namespaces and attributes
```
created_at: 2025-02-25T13:25:03Z
author_association: MEMBER
diff_hunk: @@ -715,23 +716,28 @@ def _handle_rules(pairs):
     @RestClient.api_get('/{bucket_name}?lifecycle')
     def get_lifecycle(self, bucket_name, request=None):
         # pylint: disable=unused-argument
+
+        def remove_namespace(xml: str):
path: src/pybind/mgr/dashboard/services/rgw_client.py
user: login: nizamial09
body: might be nice to add a short comment saying why exactly are we removing the namepace. 
created_at: 2025-02-25T13:25:29Z
author_association: MEMBER
diff_hunk: @@ -715,23 +716,28 @@ def _handle_rules(pairs):
     @RestClient.api_get('/{bucket_name}?lifecycle')
     def get_lifecycle(self, bucket_name, request=None):
         # pylint: disable=unused-argument
+
+        def remove_namespace(xml: str):
path: src/pybind/mgr/dashboard/services/rgw_client.py
user: login: Pegonzal
body: The xml document includes a namespace that we do not need, and if you parse from xml with namespaces to json it will prefixes the namespace to each json key, like so:
https://www.ietf.org/archive/id/draft-saintandre-json-namespaces-00.html#rfc.section.2

I've included some comments, hope it's more clear now
created_at: 2025-02-26T10:41:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -69,4 +69,9 @@ export class RgwBucketDetailsComponent implements OnChanges {
     });
     return data;
   }
+
+  switchLifecycleFormat(): void {
+    this.lifecycleFormat = this.lifecycleFormat === 'json' ? 'xml' : 'json';
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts
user: login: Pegonzal
body: I wanted to have a toggle like behaviour but updated it to: `updateLifecycleFormatTo(format: 'json' | 'xml')` to favour readability

created_at: 2025-02-26T10:42:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -715,23 +716,28 @@ def _handle_rules(pairs):
     @RestClient.api_get('/{bucket_name}?lifecycle')
     def get_lifecycle(self, bucket_name, request=None):
         # pylint: disable=unused-argument
+
+        def remove_namespace(xml: str):
path: src/pybind/mgr/dashboard/services/rgw_client.py
user: login: Pegonzal
body: @nizamial09 

created_at: 2025-02-26T10:42:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -715,23 +716,28 @@ def _handle_rules(pairs):
     @RestClient.api_get('/{bucket_name}?lifecycle')
     def get_lifecycle(self, bucket_name, request=None):
         # pylint: disable=unused-argument
+
+        def remove_namespace(xml: str):
path: src/pybind/mgr/dashboard/services/rgw_client.py
user: login: nizamial09
body: it is, thanks
created_at: 2025-02-26T11:27:34Z
author_association: MEMBER
diff_hunk: @@ -704,12 +705,14 @@ Recommends:     python%{python3_pkgversion}-saml
 %if 0%{?fedora} || 0%{?rhel} <= 8
 Recommends:     python%{python3_pkgversion}-grpcio
 Recommends:     python%{python3_pkgversion}-grpcio-tools
+Requires:       python%{python3_pkgversion}-xmltodict
path: ceph.spec.in
user: login: nizamial09
body: Just noticed this. Why are we adding it to 3 different places? I think the first one should have been enough isnt it
created_at: 2025-03-13T17:30:22Z
author_association: MEMBER
diff_hunk: @@ -704,12 +705,14 @@ Recommends:     python%{python3_pkgversion}-saml
 %if 0%{?fedora} || 0%{?rhel} <= 8
 Recommends:     python%{python3_pkgversion}-grpcio
 Recommends:     python%{python3_pkgversion}-grpcio-tools
+Requires:       python%{python3_pkgversion}-xmltodict
path: ceph.spec.in
user: login: Pegonzal
body: I wasn't sure to be honest, so just added it for all versions
Should I leave it only for `%if 0%{?fedora} || 0%{?rhel} >= 9`?
created_at: 2025-03-13T17:33:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -704,12 +705,14 @@ Recommends:     python%{python3_pkgversion}-saml
 %if 0%{?fedora} || 0%{?rhel} <= 8
 Recommends:     python%{python3_pkgversion}-grpcio
 Recommends:     python%{python3_pkgversion}-grpcio-tools
+Requires:       python%{python3_pkgversion}-xmltodict
path: ceph.spec.in
user: login: nizamial09
body: You can keep the one that is below the jmespath. 
created_at: 2025-03-13T17:36:02Z
author_association: MEMBER
diff_hunk: @@ -704,12 +705,14 @@ Recommends:     python%{python3_pkgversion}-saml
 %if 0%{?fedora} || 0%{?rhel} <= 8
 Recommends:     python%{python3_pkgversion}-grpcio
 Recommends:     python%{python3_pkgversion}-grpcio-tools
+Requires:       python%{python3_pkgversion}-xmltodict
path: ceph.spec.in
user: login: Pegonzal
body: done

created_at: 2025-03-13T17:38:21Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-02-13T12:25:02Z
body: This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-02-25T04:57:59Z
body: This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved
author_association: NONE
user: login: Pegonzal
created_at: 2025-02-25T10:46:34Z
body: https://shaman.ceph.com/builds/ceph/wip-pedro-xml-fix/
author_association: CONTRIBUTOR
user: login: nizamial09
created_at: 2025-02-28T07:22:25Z
body: ```
dashboard/controllers/rgw.py:18: note: In module imported here,
dashboard/plugins/feature_toggles.py:16: note: ... from here,
dashboard/module.py:54: note: ... from here,
dashboard/__init__.py:60: note: ... from here:
dashboard/services/rgw_client.py:19: error: Library stubs not installed for "xmltodict"  [import-untyped]
dashboard/services/rgw_client.py:19: note: Hint: "python3 -m pip install types-xmltodict"
dashboard/services/rgw_client.py:19: note: (or run "mypy --install-types" to install all missing stub packages)
dashboard/services/rgw_client.py:19: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
dashboard/services/rgw_client.py: note: In member "get_lifecycle" of class "RgwClient":
dashboard/services/rgw_client.py:739: error: Item "None" of "Element | None" has no attribute "text"  [union-attr]
Found 2 errors in 1 file (checked 32 source files)
```
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-03-04T19:25:36Z
body: This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved
author_association: NONE
user: login: nizamial09
created_at: 2025-03-10T06:22:19Z
body: @Pegonzal shall we update this and retrigger shaman again?
author_association: MEMBER
user: login: Pegonzal
created_at: 2025-03-10T10:18:58Z
body: jenkins test api
author_association: CONTRIBUTOR
user: login: Pegonzal
created_at: 2025-03-10T10:19:24Z
body: jenkins test make check arm64
author_association: CONTRIBUTOR
user: login: Pegonzal
created_at: 2025-03-10T12:00:28Z
body: jenkins test api
author_association: CONTRIBUTOR
user: login: Pegonzal
created_at: 2025-03-10T12:01:12Z
body: https://shaman.ceph.com/builds/ceph/wip-pedro-xml-fix/
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-11T14:47:11Z
body: This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved
author_association: NONE
user: login: Pegonzal
created_at: 2025-03-13T17:03:46Z
body: jenkins test make check arm64
author_association: CONTRIBUTOR
user: login: Pegonzal
created_at: 2025-03-13T17:04:42Z
body: jenkins test docs
author_association: CONTRIBUTOR
user: login: Pegonzal
created_at: 2025-03-14T04:07:46Z
body: Jenkins test make check
author_association: CONTRIBUTOR
user: login: Pegonzal
created_at: 2025-03-14T04:08:11Z
body: Jenkins test make check arm64
author_association: CONTRIBUTOR
user: login: nizamial09
created_at: 2025-03-14T05:51:22Z
body: 3 [dashboard e2e failures](https://jenkins.ceph.com/job/ceph-dashboard-pull-requests/18164/)
```
Running:  ui/notification.e2e-spec.ts                                                   (32 of 36)
  Estimated: 29 seconds


  Notification page
    1) "before all" hook for "should open notification sidebar"
    2) "after all" hook for "should open notification sidebar"
```
which is flaky for a long time. maybe we should consider disabling it..

```
  Running:  ui/login.e2e-spec.ts                                                          (30 of 36)
  Estimated: 9 seconds


  Login page
    ✓ should login and navigate to dashboard page
    1) "after each" hook for "should logout when clicking the button"


  1 passing (11s)
  1 failing

  1) Login page
       "after each" hook for "should logout when clicking the button":
     HttpErrorResponse: The following error originated from your application code, not from Cypress.
```
should be fixed through https://github.com/ceph/ceph/pull/62194

```

  Muti-cluster management page
    ✓ should authenticate the second cluster (10458ms)
    1) should switch to the second cluster and back to hub
    2) should reconnect the second cluster
    3) should edit the second cluster
    4) should disconnect the second cluster
```

it's also started being flaky now. 

And for [cephadm e2e](https://jenkins.ceph.com/job/ceph-dashboard-cephadm-e2e/15130/)

[Test Result](https://jenkins.ceph.com/job/ceph-dashboard-cephadm-e2e/15130/testReport/) (3 failures / -7)
[should drain, remove and add the host back.Host Page should drain, remove and add the host back](https://jenkins.ceph.com/job/ceph-dashboard-cephadm-e2e/15130/testReport/junit/(root)/should%20drain,%20remove%20and%20add%20the%20host%20back/Host_Page_should_drain__remove_and_add_the_host_back/)
[should create and delete snmp-gateway service with version V3.Services page should create and delete snmp-gateway service with version V3](https://jenkins.ceph.com/job/ceph-dashboard-cephadm-e2e/15130/testReport/junit/(root)/should%20create%20and%20delete%20snmp-gateway%20service%20with%20version%20V3/Services_page_should_create_and_delete_snmp_gateway_service_with_version_V3/)
[should create and delete snmp-gateway service with version V3 and w/o privacy protocol.Services page should create and delete snmp-gateway service with version V3 and w/o privacy protocol](https://jenkins.ceph.com/job/ceph-dashboard-cephadm-e2e/15130/testReport/junit/(root)/should%20create%20and%20delete%20snmp-gateway%20service%20with%20version%20V3%20and%20w_o%20privacy%20protocol/Services_page_should_create_and_delete_snmp_gateway_service_with_version_V3_and_w_o_privacy_protocol/)

I think we need to increase the timeout for those test cases. Because I am seeing some performance deterioration in the F40 VM even in my local.


But nothing to block this PR
author_association: MEMBER
user: login: nizamial09
created_at: 2025-03-14T06:20:58Z
body: jenkins test make check arm64
author_association: MEMBER
user: login: Pegonzal
created_at: 2025-03-14T08:50:01Z
body: jenkins test make check arm64
author_association: CONTRIBUTOR
user: login: Pegonzal
created_at: 2025-03-14T13:00:53Z
body: jenkins test make check arm64
author_association: CONTRIBUTOR