]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: Handle always-on Ceph Manager modules correctly
authorVolker Theile <vtheile@suse.com>
Thu, 5 Sep 2019 14:06:57 +0000 (16:06 +0200)
committerVolker Theile <vtheile@suse.com>
Mon, 9 Sep 2019 09:12:55 +0000 (11:12 +0200)
Currently always-on modules are not marked as enabled in the WebUI and can be disabled. This PR will fix that.
Note, this PR will NOT implement code that will prevent a developer from trying to disable an always-on module through the REST API. The Mgr Python extension will throw an adequate exception.

This PR will also do:
* Remove old code fragments from a previous Mgr Module management UI that is obsolete now.
* Cleanup code in BaseMgrModule code.

Fixes: https://tracker.ceph.com/issues/41648
Signed-off-by: Volker Theile <vtheile@suse.com>
src/mgr/BaseMgrModule.cc
src/pybind/mgr/dashboard/controllers/mgr_modules.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/mgr-modules/telemetry/telemetry.component.html [deleted file]
src/pybind/mgr/mgr_module.py

index e69d28c3cfda0822048c35210f8836bd27fd8c83..812e23d9fa67cdbd815ee1bb609707db5b6a876a 100644 (file)
@@ -574,7 +574,13 @@ ceph_get_version(BaseMgrModule *self, PyObject *args)
 }
 
 static PyObject *
-ceph_get_context(BaseMgrModule *self, PyObject *args)
+ceph_get_release_name(BaseMgrModule *self, PyObject *args)
+{
+  return PyString_FromString(ceph_release_to_str());
+}
+
+static PyObject *
+ceph_get_context(BaseMgrModule *self)
 {
   return self->py_modules->get_context();
 }
@@ -1064,9 +1070,12 @@ PyMethodDef BaseMgrModule_methods[] = {
   {"_ceph_cluster_log", (PyCFunction)ceph_cluster_log, METH_VARARGS,
    "Emit a cluster log message"},
 
-  {"_ceph_get_version", (PyCFunction)ceph_get_version, METH_VARARGS,
+  {"_ceph_get_version", (PyCFunction)ceph_get_version, METH_NOARGS,
    "Get the ceph version of this process"},
 
+  {"_ceph_get_release_name", (PyCFunction)ceph_get_release_name, METH_NOARGS,
+   "Get the ceph release name of this process"},
+
   {"_ceph_get_context", (PyCFunction)ceph_get_context, METH_NOARGS,
     "Get a CephContext* in a python capsule"},
 
index 358ae8be56abe1045441c16f97e4626843ab11ad..6e3cb5a3ee18013853ef8d3e4916dd6c769aaab2 100644 (file)
@@ -21,18 +21,19 @@ class MgrModules(RESTController):
         """
         result = []
         mgr_map = mgr.get('mgr_map')
+        always_on_modules = mgr_map['always_on_modules'][mgr.release_name]
         for module_config in mgr_map['available_modules']:
-            if module_config['name'] not in self.ignore_modules:
+            module_name = module_config['name']
+            if module_name not in self.ignore_modules:
+                always_on = module_name in always_on_modules
+                enabled = module_name in mgr_map['modules'] or always_on
                 result.append({
-                    'name': module_config['name'],
-                    'enabled': False,
+                    'name': module_name,
+                    'enabled': enabled,
+                    'always_on': always_on,
                     'options': self._convert_module_options(
                         module_config['module_options'])
                 })
-        for name in mgr_map['modules']:
-            if name not in self.ignore_modules:
-                obj = find_object_in_list('name', name, result)
-                obj['enabled'] = True
         return result
 
     def get(self, module_name):
index 7c10a1700ed889785038ecbd8c9681b74d2a40c9..15cc3034909a86072816e7dd9afe15ec8fb94b01 100644 (file)
@@ -99,7 +99,8 @@ describe('MgrModuleListComponent', () => {
       spyOn(mgrModuleService, 'list').and.returnValues(observableThrowError('z'), observableOf([]));
       component.selection.selected.push({
         name: 'foo',
-        enabled: false
+        enabled: false,
+        always_on: false
       });
       component.selection.update();
       component.updateModuleState();
@@ -118,7 +119,8 @@ describe('MgrModuleListComponent', () => {
       spyOn(mgrModuleService, 'list').and.returnValue(observableOf([]));
       component.selection.selected.push({
         name: 'bar',
-        enabled: true
+        enabled: true,
+        always_on: false
       });
       component.selection.update();
       component.updateModuleState();
@@ -130,5 +132,26 @@ describe('MgrModuleListComponent', () => {
       expect(component.blockUI.stop).toHaveBeenCalled();
       expect(component.table.refreshBtn).toHaveBeenCalled();
     }));
+
+    it('should not disable module (1)', () => {
+      component.selection.selected = [
+        {
+          name: 'dashboard'
+        }
+      ];
+      component.selection.update();
+      expect(component.isTableActionDisabled('enabled')).toBeTruthy();
+    });
+
+    it('should not disable module (2)', () => {
+      component.selection.selected = [
+        {
+          name: 'bar',
+          always_on: true
+        }
+      ];
+      component.selection.update();
+      expect(component.isTableActionDisabled('enabled')).toBeTruthy();
+    });
   });
 });
index 892df1f52ad401229931af438b4680701804173c..47635095d2cef58548b799ae9309991e136699d6 100644 (file)
@@ -82,6 +82,7 @@ export class MgrModuleListComponent {
         permission: 'update',
         click: () => this.updateModuleState(),
         disable: () => this.isTableActionDisabled('disabled'),
+        disableDesc: () => this.getTableActionDisabledDesc(),
         icon: Icons.stop
       }
     ];
@@ -112,17 +113,31 @@ export class MgrModuleListComponent {
     if (!this.selection.hasSelection) {
       return true;
     }
+    const selected = this.selection.first();
     // Make sure the user can't modify the run state of the 'Dashboard' module.
     // This check is only done in the UI because the REST API should still be
     // able to do so.
-    if (this.selection.first().name === 'dashboard') {
+    if (selected.name === 'dashboard') {
+      return true;
+    }
+    // Always-on modules can't be disabled.
+    if (selected.always_on) {
       return true;
     }
     switch (state) {
       case 'enabled':
-        return this.selection.first().enabled;
+        return selected.enabled;
       case 'disabled':
-        return !this.selection.first().enabled;
+        return !selected.enabled;
+    }
+  }
+
+  getTableActionDisabledDesc(): string | undefined {
+    if (this.selection.hasSelection) {
+      const selected = this.selection.first();
+      if (selected.always_on) {
+        return this.i18n('This Manager module is always on.');
+      }
     }
   }
 
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/mgr-modules/telemetry/telemetry.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/mgr-modules/telemetry/telemetry.component.html
deleted file mode 100644 (file)
index e69de29..0000000
index b34bc523c926f541c024c3c31d53e19ac3c1b200..e6b74ef72f7331d443f6bc66ad81417fbddf6722 100644 (file)
@@ -584,6 +584,15 @@ class MgrModule(ceph_module.BaseMgrModule):
     def version(self):
         return self._version
 
+    @property
+    def release_name(self):
+        """
+        Get the release name of the Ceph version, e.g. 'nautilus' or 'octopus'.
+        :return: Returns the release name of the Ceph version in lower case.
+        :rtype: str
+        """
+        return self._ceph_get_release_name()
+
     def get_context(self):
         """
         :return: a Python capsule containing a C++ CephContext pointer