]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: initial RBD iSCSI integration 16547/head
authorJason Dillaman <dillaman@redhat.com>
Tue, 25 Jul 2017 01:08:36 +0000 (21:08 -0400)
committerJason Dillaman <dillaman@redhat.com>
Tue, 25 Jul 2017 02:00:21 +0000 (22:00 -0400)
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/pybind/mgr/dashboard/base.html
src/pybind/mgr/dashboard/module.py
src/pybind/mgr/dashboard/rbd_iscsi.html [new file with mode: 0644]
src/pybind/mgr/dashboard/rbd_iscsi.py [new file with mode: 0644]

index 270491a49779fbf41460d3fdb61057cf93bf4b31..1d4947c26de3f5b22ce982777603196f47ea9895 100644 (file)
                 </span>
               </a>
             </li>
+            <li>
+              <a href="/rbd_iscsi">
+                <i class="fa fa-upload"></i> iSCSI
+                <span class="pull-right-container" />
+              </a>
+            </li>
             <li class="treeview{%if path_info.startswith('/rbd_pool')%} active menu-open{%endif%}">
               <a href="#">
                 <i class="fa fa-dot-circle-o"></i> <span>Pools</span>
index 7e2a4260a03e6d0455eef9348a1de159a78fe4c6..23b297ab195a2715b99788f631791607e0cc43bb 100644 (file)
@@ -32,6 +32,7 @@ from types import OsdMap, NotFound, Config, FsMap, MonMap, \
     PgSummary, Health, MonStatus
 
 import rados
+import rbd_iscsi
 import rbd_mirroring
 from rbd_ls import RbdLs, RbdPoolLs
 from cephfs_clients import CephFSClients
@@ -83,6 +84,9 @@ class Module(MgrModule):
         # pools
         self.rbd_pool_ls = RbdPoolLs(self)
 
+        # Stateful instance of RbdISCSI
+        self.rbd_iscsi = rbd_iscsi.Controller(self)
+
         # Stateful instance of RbdMirroring, hold cached results.
         self.rbd_mirroring = rbd_mirroring.Controller(self)
 
@@ -624,6 +628,32 @@ class Module(MgrModule):
             def rbd_mirroring_data(self):
                 return self._rbd_mirroring()
 
+            def _rbd_iscsi(self):
+                status, data = global_instance().rbd_iscsi.content_data.get()
+                if data is None:
+                    log.warning("Failed to get RBD iSCSI status")
+                    return {}
+                return data
+
+            @cherrypy.expose
+            def rbd_iscsi(self):
+                template = env.get_template("rbd_iscsi.html")
+
+                toplevel_data = self._toplevel_data()
+                content_data = self._rbd_iscsi()
+
+                return template.render(
+                    ceph_version=global_instance().version,
+                    path_info=cherrypy.request.path_info,
+                    toplevel_data=json.dumps(toplevel_data, indent=2),
+                    content_data=json.dumps(content_data, indent=2)
+                )
+
+            @cherrypy.expose
+            @cherrypy.tools.json_out()
+            def rbd_iscsi_data(self):
+                return self._rbd_iscsi()
+
             @cherrypy.expose
             def health(self):
                 template = env.get_template("health.html")
diff --git a/src/pybind/mgr/dashboard/rbd_iscsi.html b/src/pybind/mgr/dashboard/rbd_iscsi.html
new file mode 100644 (file)
index 0000000..105f5de
--- /dev/null
@@ -0,0 +1,104 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+<script>
+        $(document).ready(function(){
+            // Pre-populated initial data at page load
+            var content_data = {{ content_data }};
+
+            var refresh = function() {
+                $.get("/rbd_iscsi_data", function(data) {
+                    _.extend(content_data, data);
+                    setTimeout(refresh, 30000);
+                });
+            };
+
+            console.log(content_data);
+
+            rivets.bind($("div#content"), content_data);
+            setTimeout(refresh, 30000);
+
+            $('#daemons').DataTable({
+                'paging'      : true,
+                'pageLength'  : 5,
+                'lengthChange': false,
+                'info'        : false,
+                'autoWidth'   : false,
+                'searching'   : false
+            });
+
+            $('#images').DataTable({
+                'paging'      : true,
+                'pageLength'  : 10,
+                'lengthChange': false,
+                'searching'   : true,
+                'ordering'    : true,
+                'info'        : false
+            });
+        });
+</script>
+
+
+<section class="content-header">
+    <h1>
+        Block iSCSI
+    </h1>
+</section>
+
+<section class="content">
+    <div class="box">
+        <div class="box-header">
+            <h3 class="box-title">Daemons</h3>
+        </div>
+        <div class="box-body">
+            <table id="daemons" class="table table-condensed">
+                <thead>
+                <tr>
+                    <th>Hostname</th>
+                    <th># Active/Optimized</th>
+                    <th># Active/Non-Optimized</th>
+                    <th>Version</th>
+                </tr>
+                </thead>
+                <tbody>
+                    <tr rv-each-daemon="daemons">
+                        <td>{daemon.server_hostname}</td>
+                        <td>{daemon.optimized_paths}</td>
+                        <td>{daemon.non_optimized_paths}</td>
+                        <td>{daemon.version | short_version}</td>
+                    </tr>
+                </tbody>
+            </table>
+        </div>
+    </div>
+
+    <div class="box">
+        <div class="box-header">
+            <h3 class="box-title">Images</h3>
+        </div>
+        <div class="box-body">
+            <table id="images" class="table table-condensed">
+                <thead>
+                <tr>
+                    <th>Pool</th>
+                    <th>Image</th>
+                    <th>Active/Optimized</th>
+                    <th>Active/Non-Optimized</th>
+                </tr>
+                </thead>
+                <tbody>
+                    <tr rv-each-image="images">
+                        <td>{image.pool_name}</td>
+                        <td>{image.name}</td>
+                        <td>{image.optimized_paths}</td>
+                        <td>{image.non_optimized_paths}</td>
+                    </tr>
+                </tbody>
+            </table>
+        </div>
+    </div>
+</section>
+<!-- /.content -->
+
+{% endblock %}
diff --git a/src/pybind/mgr/dashboard/rbd_iscsi.py b/src/pybind/mgr/dashboard/rbd_iscsi.py
new file mode 100644 (file)
index 0000000..9e0fda6
--- /dev/null
@@ -0,0 +1,54 @@
+
+import rados
+import rbd
+from remote_view_cache import RemoteViewCache
+
+SERVICE_TYPE = 'tcmu-runner'
+
+class DaemonsAndImages(RemoteViewCache):
+    def _get(self):
+        daemons = {}
+        images = {}
+        for server in self._module.list_servers():
+            for service in server['services']:
+                if service['type'] == SERVICE_TYPE:
+                    metadata = self._module.get_metadata(SERVICE_TYPE,
+                                                         service['id'])
+                    status = self._module.get_daemon_status(SERVICE_TYPE,
+                                                            service['id'])
+
+                    daemon = daemons.get(server['hostname'], None)
+                    if daemon is None:
+                        daemon = {
+                            'server_hostname': server['hostname'],
+                            'version': metadata['ceph_version'],
+                            'optimized_paths': 0,
+                            'non_optimized_paths': 0
+                        }
+                        daemons[server['hostname']] = daemon
+
+                    image = images.get(service['id'])
+                    if image is None:
+                        image = {
+                            'id': service['id'],
+                            'pool_name': metadata['pool_name'],
+                            'name': metadata['image_name'],
+                            'optimized_paths': [],
+                            'non_optimized_paths': []
+                        }
+                        if status.get('lock_owner', 'false') == 'true':
+                            daemon['optimized_paths'] += 1
+                            image['optimized_paths'].append(server['hostname'])
+                        else:
+                            daemon['non_optimized_paths'] += 1
+                            image['non_optimized_paths'].append(server['hostname'])
+                        images[service['id']] = image
+
+        return {
+            'daemons': [daemons[k] for k in sorted(daemons, key=daemons.get)],
+            'images': [images[k] for k in sorted(images, key=images.get)]
+        }
+
+class Controller:
+    def __init__(self, module_inst):
+        self.content_data = DaemonsAndImages(module_inst)