]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/cephadm: allow nvmeof group assignment for NVMe-oF services 68747/head
authorKushal Deb <Kushal.Deb@ibm.com>
Tue, 5 May 2026 05:30:12 +0000 (11:00 +0530)
committerKushal Deb <Kushal.Deb@ibm.com>
Tue, 5 May 2026 07:36:35 +0000 (13:06 +0530)
NVMe-oF services created in older releases may have a service_id that does
not include the gateway group name, because those services could be created
without a group.
The current validation requires an NVMe-oF service_id to end with
the configured group name when spec.group is set. This is correct for new
services, but it blocks the documented upgrade flow for existing legacy
services where the user exports the existing spec, adds spec.group, and
applies it back.

Allow this narrow update path when the service already exists, the stored
spec has no group, and the incoming spec keeps the same service_id while
adding a non-empty group. Keep the existing validation for new services and
for services that already have a group, so duplicate or misleading group
configurations are still rejected.

Signed-off-by: Kushal Deb <Kushal.Deb@ibm.com>
src/pybind/mgr/cephadm/module.py

index e6704e8a023c2d4dd194164cf0f335c9f26c31bc..8658c64e22324725e93be1a05d18c5e2587c30af 100644 (file)
@@ -3875,6 +3875,30 @@ Then run the following:
                 )
         return cert_warning
 
+    def _is_adding_nvmeof_group_to_existing_service(self, nvmeof_spec: NvmeofServiceSpec) -> bool:
+        """
+        Check whether this spec is assigning a group to an existing NVMe-oF
+        service that does not have a group yet.
+
+        This allows an existing service to keep its current service_id while adding
+        a group for the first time. New services and services that already have a
+        group must still follow the normal service_id/group validation.
+        """
+        existing_spec = self.spec_store.active_specs.get(nvmeof_spec.service_name())
+        if existing_spec is None:
+            return False
+
+        if existing_spec.service_type != 'nvmeof':
+            return False
+
+        existing_nvmeof_spec = cast(NvmeofServiceSpec, existing_spec)
+
+        return (
+            existing_nvmeof_spec.service_id == nvmeof_spec.service_id
+            and not existing_nvmeof_spec.group
+            and bool(nvmeof_spec.group)
+        )
+
     def _apply_service_spec(self, spec: ServiceSpec) -> str:
         if spec.placement.is_empty():
             # fill in default placement
@@ -3932,11 +3956,13 @@ Then run the following:
             except OrchestratorError as e:
                 self.log.debug(f"{e}")
                 raise
-            nvmeof_spec = cast(NvmeofServiceSpec, spec)
             assert nvmeof_spec.service_id is not None  # for mypy
             if nvmeof_spec.group and not nvmeof_spec.service_id.endswith(nvmeof_spec.group):
-                raise OrchestratorError("The 'nvmeof' service id/name must end with '.<nvmeof-group-name>'. Found "
-                                        f"group name '{nvmeof_spec.group}' and service id '{nvmeof_spec.service_id}'")
+                if not self._is_adding_nvmeof_group_to_existing_service(nvmeof_spec):
+                    raise OrchestratorError(
+                        "The 'nvmeof' service id/name must end with '.<nvmeof-group-name>'. Found "
+                        f"group name '{nvmeof_spec.group}' and service id '{nvmeof_spec.service_id}'"
+                    )
             for sspec in [s.spec for s in self.spec_store.get_by_service_type('nvmeof')]:
                 nspec = cast(NvmeofServiceSpec, sspec)
                 if nvmeof_spec.group == nspec.group and nvmeof_spec.service_id != nspec.service_id: