]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/orchestrator_cli: module for calling into orchestators
authorJohn Spray <john.spray@redhat.com>
Wed, 2 May 2018 10:58:45 +0000 (11:58 +0100)
committerJohn Spray <john.spray@redhat.com>
Mon, 6 Aug 2018 15:22:29 +0000 (16:22 +0100)
This is going to be pretty useful for development: calls
into the same places that the dashboard would call into.

Signed-off-by: John Spray <john.spray@redhat.com>
src/pybind/mgr/orchestrator_cli/__init__.py [new file with mode: 0644]
src/pybind/mgr/orchestrator_cli/module.py [new file with mode: 0644]

diff --git a/src/pybind/mgr/orchestrator_cli/__init__.py b/src/pybind/mgr/orchestrator_cli/__init__.py
new file mode 100644 (file)
index 0000000..86b0c90
--- /dev/null
@@ -0,0 +1,2 @@
+
+from module import OrchestratorCli
diff --git a/src/pybind/mgr/orchestrator_cli/module.py b/src/pybind/mgr/orchestrator_cli/module.py
new file mode 100644 (file)
index 0000000..5e1bcf0
--- /dev/null
@@ -0,0 +1,245 @@
+import errno
+import time
+from mgr_module import MgrModule
+
+import orchestrator
+
+
+class NoOrchestrator(Exception):
+    pass
+
+
+class OrchestratorCli(MgrModule):
+    OPTIONS = [
+        {'name': 'orchestrator'}
+    ]
+    COMMANDS = [
+        {
+            'cmd': "orchestrator device ls "
+                   "name=node,type=CephString,req=false",
+            "desc": "List devices on a node",
+            "perm": "r"
+        },
+        {
+            'cmd': "orchestrator service status "
+                   "name=svc_type,type=CephString "
+                   "name=svc_id,type=CephString ",
+            "desc": "Get orchestrator state for Ceph service",
+            "perm": "r"
+        },
+        {
+            'cmd': "orchestrator service add "
+                   "name=svc_type,type=CephString "
+                   "name=svc_arg,type=CephString ",
+            "desc": "Create a service of any type",
+            "perm": "rw"
+        },
+        {
+            'cmd': "orchestrator set backend "
+                   "name=module,type=CephString,req=true",
+            "desc": "Select orchestrator module backend",
+            "perm": "rw"
+        },
+    ]
+
+    def _select_orchestrator(self):
+        o = self.get_config("orchestrator")
+        if o is None:
+            raise NoOrchestrator()
+
+        return o
+
+    def _oremote(self, *args, **kwargs):
+        """
+        Helper for invoking `remote` on whichever orchestrator is enabled
+        """
+        return self.remote(self._select_orchestrator(),
+                           *args, **kwargs)
+
+    def _wait(self, completions):
+        """
+        Helper to wait for completions to complete (reads) or
+        become persistent (writes).
+
+        Waits for writes to be *persistent* but not *effective*.
+        """
+        done = False
+
+        while done is False:
+            done = self._oremote("wait", completions)
+
+            if not done:
+                any_nonpersistent = False
+                for c in completions:
+                    if c.is_read:
+                        if not c.is_complete:
+                            any_nonpersistent = True
+                            break
+                    else:
+                        if not c.is_persistent:
+                            any_nonpersistent = True
+                            break
+
+                if any_nonpersistent:
+                    time.sleep(5)
+                else:
+                    done = True
+
+    def _list_devices(self, cmd):
+        node = cmd.get('node', None)
+
+        if node:
+            nf = orchestrator.InventoryFilter()
+            nf.nodes = [node]
+        else:
+            nf = None
+
+        completion = self._oremote("get_inventory", node_filter=nf)
+
+        self._wait([completion])
+
+        # Spit out a human readable version
+        result = ""
+
+        for inventory_node in completion.result:
+            result += "{0}:\n".format(inventory_node.name)
+            for d in inventory_node.devices:
+                result += "  {0} ({1}, {2}b)\n".format(
+                    d.id, d.type, d.size)
+            result += "\n"
+
+        return 0, result, ""
+
+    def _service_status(self, cmd):
+        svc_type = cmd['svc_type']
+        svc_id = cmd['svc_id']
+
+        # XXX this is kind of confusing for people because in the orchestrator
+        # context the service ID for MDS is the filesystem ID, not the daemon ID
+
+        completion = self._oremote("describe_service", svc_type, svc_id)
+
+        self._wait([completion])
+
+        service_description = completion.result
+        #assert isinstance(service_description, orchestrator.ServiceDescription)
+
+        if len(service_description.locations) == 0:
+            return 0, "", "No locations reported"
+        else:
+            lines = []
+            for l in service_description.locations:
+                lines.append("{0}.{1} {2} {3}".format(
+                    svc_type,
+                    l.daemon_name,
+                    l.nodename,
+                    l.container_id))
+
+            return 0, "\n".join(lines), ""
+
+    def _service_add(self, cmd):
+        svc_type = cmd['svc_type']
+        if svc_type == "osd":
+            device_spec = cmd['svc_arg']
+            try:
+                node_name, block_device = device_spec.split(":")
+            except TypeError:
+                return -errno.EINVAL, "", "Invalid device spec, should be <node>:<device>"
+
+            spec = orchestrator.OsdCreationSpec()
+            spec.node = node_name
+            spec.format = "bluestore"
+            spec.drive_group = orchestrator.DriveGroupSpec([block_device])
+
+            completion = self._oremote("create_osds", spec)
+            self._wait([completion])
+
+            return 0, "", "Success."
+
+        elif svc_type == "mds":
+            fs_name = cmd['svc_arg']
+
+            spec = orchestrator.StatelessServiceSpec()
+            spec.name = fs_name
+
+            completion = self._oremote(
+                "add_stateless_service",
+                svc_type,
+                spec
+            )
+            self._wait([completion])
+
+            return 0, "", "Success."
+        else:
+            raise NotImplementedError(svc_type)
+
+    def _set_backend(self, cmd):
+        """
+        We implement a setter command instead of just having the user
+        modify the setting directly, so that we can validate they're setting
+        it to a module that really exists and is enabled.
+
+        There isn't a mechanism for ensuring they don't *disable* the module
+        later, but this is better than nothing.
+        """
+
+        mgr_map = self.get("mgr_map")
+        module_name = cmd['module']
+
+        if module_name == "":
+            self.set_config("orchestrator", None)
+            return 0, "", ""
+
+        for module in mgr_map['available_modules']:
+            if module['name'] != module_name:
+                continue
+
+            if not module['can_run']:
+                continue
+
+            enabled = module['name'] in mgr_map['modules']
+            if not enabled:
+                return -errno.EINVAL, "", "Module '{0}' is not enabled".format(
+                    module_name
+                )
+
+            try:
+                is_orchestrator = self.remote(module_name,
+                                              "is_orchestrator_module")
+            except NameError:
+                is_orchestrator = False
+
+            if not is_orchestrator:
+                return -errno.EINVAL, "",\
+                       "'{0}' is not an orchestrator module".format(
+                           module_name)
+
+            self.set_config("orchestrator", module_name)
+
+            return 0, "", ""
+
+        return -errno.ENOENT, "", "Module '{0}' not found".format(
+            module_name
+        )
+
+    def handle_command(self, inbuf, cmd):
+        try:
+            return self._handle_command(inbuf, cmd)
+        except NoOrchestrator:
+            return -errno.ENODEV, "", "No orchestrator configured"
+        except ImportError as e:
+            return -errno.ENOENT, "", e.message
+        except NotImplementedError:
+            return -errno.EINVAL, "", "Command not found"
+
+    def _handle_command(self, _, cmd):
+        if cmd['prefix'] == "orchestrator device ls":
+            return self._list_devices(cmd)
+        elif cmd['prefix'] == "orchestrator service status":
+            return self._service_status(cmd)
+        elif cmd['prefix'] == "orchestrator service add":
+            return self._service_add(cmd)
+        if cmd['prefix'] == "orchestrator set backend":
+            return self._set_backend(cmd)
+        else:
+            raise NotImplementedError()