From 3a7f85809a99acb1dca760da9dd501ade963b517 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Stephan=20M=C3=BCller?= Date: Mon, 17 Sep 2018 09:54:11 +0200 Subject: [PATCH] mgr/dashboard: Adds ECP info endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The new info endpoint will provide the frontend with the necessary information it needs to create new profiles. Fixes: https://tracker.ceph.com/issues/25156 Signed-off-by: Stephan Müller --- .../dashboard/test_erasure_code_profile.py | 60 +++++++++++-------- .../controllers/erasure_code_profile.py | 40 +++++++++---- 2 files changed, 62 insertions(+), 38 deletions(-) diff --git a/qa/tasks/mgr/dashboard/test_erasure_code_profile.py b/qa/tasks/mgr/dashboard/test_erasure_code_profile.py index cf5012f8dde..2a9bd2e5a0a 100644 --- a/qa/tasks/mgr/dashboard/test_erasure_code_profile.py +++ b/qa/tasks/mgr/dashboard/test_erasure_code_profile.py @@ -2,9 +2,9 @@ from __future__ import absolute_import -import unittest +import six -from .helper import DashboardTestCase +from .helper import DashboardTestCase, JObj, JList class ECPTest(DashboardTestCase): @@ -13,12 +13,12 @@ class ECPTest(DashboardTestCase): @DashboardTestCase.RunAs('test', 'test', ['block-manager']) def test_read_access_permissions(self): - self._get("/api/erasure_code_profile") + self._get('/api/erasure_code_profile') self.assertStatus(403) @DashboardTestCase.RunAs('test', 'test', ['read-only']) def test_write_access_permissions(self): - self._get("/api/erasure_code_profile") + self._get('/api/erasure_code_profile') self.assertStatus(200) data = {'name': 'ecp32', 'k': 3, 'm': 2} self._post('/api/erasure_code_profile', data) @@ -40,10 +40,10 @@ class ECPTest(DashboardTestCase): if default: default_ecp = { 'k': 2, - 'technique': "reed_sol_van", + 'technique': 'reed_sol_van', 'm': 1, - 'name': "default", - 'plugin': "jerasure" + 'name': 'default', + 'plugin': 'jerasure' } if 'crush-failure-domain' in default[0]: default_ecp['crush-failure-domain'] = default[0]['crush-failure-domain'] @@ -59,16 +59,16 @@ class ECPTest(DashboardTestCase): self._get('/api/erasure_code_profile/ecp32') self.assertJsonBody({ - "crush-device-class": "", - "crush-failure-domain": "osd", - "crush-root": "default", - "jerasure-per-chunk-alignment": "false", - "k": 3, - "m": 2, - "name": "ecp32", - "plugin": "jerasure", - "technique": "reed_sol_van", - "w": "8" + 'crush-device-class': '', + 'crush-failure-domain': 'osd', + 'crush-root': 'default', + 'jerasure-per-chunk-alignment': 'false', + 'k': 3, + 'm': 2, + 'name': 'ecp32', + 'plugin': 'jerasure', + 'technique': 'reed_sol_van', + 'w': '8' }) self.assertStatus(200) @@ -84,14 +84,14 @@ class ECPTest(DashboardTestCase): self._get('/api/erasure_code_profile/lrc') self.assertJsonBody({ - "crush-device-class": "", - "crush-failure-domain": "host", - "crush-root": "default", - "k": 2, - "l": "2", - "m": 2, - "name": "lrc", - "plugin": "lrc" + 'crush-device-class': '', + 'crush-failure-domain': 'host', + 'crush-root': 'default', + 'k': 2, + 'l': '2', + 'm': 2, + 'name': 'lrc', + 'plugin': 'lrc' }) self.assertStatus(200) @@ -99,3 +99,13 @@ class ECPTest(DashboardTestCase): self._delete('/api/erasure_code_profile/lrc') self.assertStatus(204) + def test_ecp_info(self): + self._get('/api/erasure_code_profile/_info') + self.assertSchemaBody(JObj({ + 'names': JList(six.string_types), + 'failure_domains': JList(six.string_types), + 'plugins': JList(six.string_types), + 'devices': JList(six.string_types), + 'directory': six.string_types, + })) + diff --git a/src/pybind/mgr/dashboard/controllers/erasure_code_profile.py b/src/pybind/mgr/dashboard/controllers/erasure_code_profile.py index 688fbf78354..742604beb63 100644 --- a/src/pybind/mgr/dashboard/controllers/erasure_code_profile.py +++ b/src/pybind/mgr/dashboard/controllers/erasure_code_profile.py @@ -3,25 +3,30 @@ from __future__ import absolute_import from cherrypy import NotFound -from . import ApiController, RESTController +from . import ApiController, RESTController, Endpoint, ReadPermission from ..security import Scope from ..services.ceph_service import CephService from .. import mgr def _serialize_ecp(name, ecp): + def serialize_numbers(key): + value = ecp.get(key) + if value is not None: + ecp[key] = int(value) + ecp['name'] = name - ecp['k'] = int(ecp['k']) - ecp['m'] = int(ecp['m']) + serialize_numbers('k') + serialize_numbers('m') return ecp @ApiController('/erasure_code_profile', Scope.POOL) class ErasureCodeProfile(RESTController): - """ + ''' create() supports additional key-value arguments that are passed to the ECP plugin. - """ + ''' def list(self): ret = [] @@ -36,17 +41,26 @@ class ErasureCodeProfile(RESTController): except KeyError: raise NotFound('No such erasure code profile') - def create(self, name, k, m, plugin=None, ruleset_failure_domain=None, **kwargs): - kwargs['k'] = k - kwargs['m'] = m - if plugin: - kwargs['plugin'] = plugin - if ruleset_failure_domain: - kwargs['ruleset_failure_domain'] = ruleset_failure_domain - + def create(self, name, **kwargs): profile = ['{}={}'.format(key, value) for key, value in kwargs.items()] CephService.send_command('mon', 'osd erasure-code-profile set', name=name, profile=profile) def delete(self, name): CephService.send_command('mon', 'osd erasure-code-profile rm', name=name) + + @Endpoint() + @ReadPermission + def _info(self): + '''Used for profile creation and editing''' + config = mgr.get('config') + osd_map_crush = mgr.get('osd_map_crush') + return { + # Because 'shec' is experimental it's not included + 'plugins': config['osd_erasure_code_plugins'].split() + ['shec'], + 'directory': config['erasure_code_dir'], + 'devices': list(set([device['class'] for device in osd_map_crush['devices']])), + 'failure_domains': [domain['name'] for domain in osd_map_crush['types']], + 'names': [name for name, _ in + mgr.get('osd_map').get('erasure_code_profiles', {}).items()] + } -- 2.39.5