From: Sebastian Wagner Date: Tue, 13 Feb 2018 16:22:26 +0000 (+0100) Subject: mgr/dashboard_v2: Added tcmu iSCSI controller. X-Git-Tag: v13.0.2~84^2~56 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=2c7844fabbbb4670a896f31c9358841ef81d508f;p=ceph.git mgr/dashboard_v2: Added tcmu iSCSI controller. * Based on `mgr/dashboard/rbd_iscsi.py`. * Fixed Python3 compatibility issues. * Added test. Signed-off-by: Sebastian Wagner --- diff --git a/src/pybind/mgr/dashboard_v2/controllers/tcmu_iscsi.py b/src/pybind/mgr/dashboard_v2/controllers/tcmu_iscsi.py new file mode 100644 index 0000000000000..db1c1460f176a --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/controllers/tcmu_iscsi.py @@ -0,0 +1,76 @@ +# -*- 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']), + } diff --git a/src/pybind/mgr/dashboard_v2/tests/test_tcmu_iscsi.py b/src/pybind/mgr/dashboard_v2/tests/test_tcmu_iscsi.py new file mode 100644 index 0000000000000..a8b48dba6af0e --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/tests/test_tcmu_iscsi.py @@ -0,0 +1,87 @@ +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} + }] + })