--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+from ..tools import ApiController, AuthRequired, RESTController
+
+SERVICE_TYPE = 'tcmu-runner'
+
+
+@ApiController('tcmuiscsi')
+@AuthRequired()
+class TcmuIscsi(RESTController):
+ # pylint: disable=too-many-locals,too-many-nested-blocks
+ def list(self): # pylint: disable=unused-argument
+ daemons = {}
+ images = {}
+ for server in self.mgr.list_servers():
+ for service in server['services']:
+ if service['type'] == SERVICE_TYPE:
+ metadata = self.mgr.get_metadata(SERVICE_TYPE,
+ service['id'])
+ status = self.mgr.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
+
+ service_id = service['id']
+ device_id = service_id.split(':')[-1]
+ image = images.get(device_id)
+ if image is None:
+ image = {
+ 'device_id': device_id,
+ 'pool_name': metadata['pool_name'],
+ 'name': metadata['image_name'],
+ 'id': metadata.get('image_id', None),
+ 'optimized_paths': [],
+ 'non_optimized_paths': []
+ }
+ images[device_id] = image
+ if status.get('lock_owner', 'false') == 'true':
+ daemon['optimized_paths'] += 1
+ image['optimized_paths'].append(server['hostname'])
+
+ perf_key_prefix = "librbd-{id}-{pool}-{name}.".format(
+ id=metadata.get('image_id', ''),
+ pool=metadata['pool_name'],
+ name=metadata['image_name'])
+ perf_key = "{}lock_acquired_time".format(perf_key_prefix)
+ lock_acquired_time = (self.mgr.get_counter(
+ 'tcmu-runner', service_id, perf_key)[perf_key] or
+ [[0, 0]])[-1][1] / 1000000000
+ if lock_acquired_time > image.get('optimized_since', 0):
+ image['optimized_since'] = lock_acquired_time
+ image['stats'] = {}
+ image['stats_history'] = {}
+ for s in ['rd', 'wr', 'rd_bytes', 'wr_bytes']:
+ perf_key = "{}{}".format(perf_key_prefix, s)
+ image['stats'][s] = self.mgr.get_rate(
+ 'tcmu-runner', service_id, perf_key)
+ image['stats_history'][s] = self.mgr.get_counter(
+ 'tcmu-runner', service_id, perf_key)[perf_key]
+ else:
+ daemon['non_optimized_paths'] += 1
+ image['non_optimized_paths'].append(server['hostname'])
+
+ return {
+ 'daemons': sorted(daemons.values(), key=lambda d: d['server_hostname']),
+ 'images': sorted(images.values(), key=lambda i: ['id']),
+ }
--- /dev/null
+from cherrypy.test.helper import CPWebCase
+import cherrypy
+import mock
+
+from ..controllers.auth import Auth
+from ..tools import SessionExpireAtBrowserCloseTool
+from ..controllers.tcmu_iscsi import TcmuIscsi
+from .helper import ControllerTestCase
+
+mocked_servers = [{
+ 'ceph_version': 'ceph version 13.0.0-5083- () mimic (dev)',
+ 'hostname': 'ceph-dev',
+ 'services': [{'id': 'a:b', 'type': 'tcmu-runner'}]
+}]
+
+mocked_metadata = {
+ 'ceph_version': 'ceph version 13.0.0-5083- () mimic (dev)',
+ 'pool_name': 'pool1',
+ 'image_name': 'image1',
+ 'image_id': '42',
+ 'optimized_since': 100.0,
+}
+
+mocked_get_daemon_status = {
+ 'lock_owner': 'true',
+}
+
+mocked_get_counter = {
+ 'librbd-42-pool1-image1.lock_acquired_time': [[10000.0, 10000.0]],
+ 'librbd-42-pool1-image1.rd': 43,
+ 'librbd-42-pool1-image1.wr': 44,
+ 'librbd-42-pool1-image1.rd_bytes': 45,
+ 'librbd-42-pool1-image1.wr_bytes': 46,
+}
+
+mocked_get_rate = 47
+
+
+class TcmuIscsiControllerTest(ControllerTestCase, CPWebCase):
+
+ @classmethod
+ def setup_server(cls):
+ # Initialize custom handlers.
+ cherrypy.tools.authenticate = cherrypy.Tool('before_handler', Auth.check_auth)
+ cherrypy.tools.session_expire_at_browser_close = SessionExpireAtBrowserCloseTool()
+
+ cls._mgr_module = mock.Mock()
+ cls.setup_test()
+
+ @classmethod
+ def setup_test(cls):
+ mgr_mock = mock.Mock()
+ mgr_mock.list_servers.return_value = mocked_servers
+ mgr_mock.get_metadata.return_value = mocked_metadata
+ mgr_mock.get_daemon_status.return_value = mocked_get_daemon_status
+ mgr_mock.get_counter.return_value = mocked_get_counter
+ mgr_mock.get_rate.return_value = mocked_get_rate
+ mgr_mock.url_prefix = ''
+ TcmuIscsi.mgr = mgr_mock
+ TcmuIscsi._cp_config['tools.authenticate.on'] = False # pylint: disable=protected-access
+
+ cherrypy.tree.mount(TcmuIscsi(), "/api/test/tcmu")
+
+ def __init__(self, *args, **kwargs):
+ super(TcmuIscsiControllerTest, self).__init__(*args, dashboard_port=54583, **kwargs)
+
+ def test_list(self):
+ self._post("/api/auth", {'username': 'admin', 'password': 'admin'})
+ self._get('/api/test/tcmu')
+ self.assertStatus(200)
+ self.assertJsonBody({
+ 'daemons': [{
+ 'server_hostname': 'ceph-dev',
+ 'version': 'ceph version 13.0.0-5083- () mimic (dev)',
+ 'optimized_paths': 1, 'non_optimized_paths': 0}],
+ 'images': [{
+ 'device_id': 'b',
+ 'pool_name': 'pool1',
+ 'name': 'image1',
+ 'id': '42', 'optimized_paths': ['ceph-dev'],
+ 'non_optimized_paths': [],
+ 'optimized_since': 1e-05,
+ 'stats': {'rd': 47, 'rd_bytes': 47, 'wr': 47, 'wr_bytes': 47},
+ 'stats_history': {
+ 'rd': 43, 'wr': 44, 'rd_bytes': 45, 'wr_bytes': 46}
+ }]
+ })