</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>
PgSummary, Health, MonStatus
import rados
+import rbd_iscsi
import rbd_mirroring
from rbd_ls import RbdLs, RbdPoolLs
from cephfs_clients import CephFSClients
# 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)
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")
--- /dev/null
+{% 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 %}
--- /dev/null
+
+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)