Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mgr/dashboard: Add ErasureCodeProfile controller #20920

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 88 additions & 0 deletions qa/tasks/mgr/dashboard/test_erasure_code_profile.py
@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-

from __future__ import absolute_import

import unittest

from .helper import DashboardTestCase, authenticate


class ECPTest(DashboardTestCase):


@classmethod
def tearDownClass(cls):
super(ECPTest, cls).tearDownClass()
cls._ceph_cmd(['osd', 'erasure-code-profile', 'rm', 'ecp32'])
cls._ceph_cmd(['osd', 'erasure-code-profile', 'rm', 'lrc'])

@authenticate
def test_list(self):
data = self._get('/api/erasure_code_profile')
self.assertStatus(200)

default = [p for p in data if p['name'] == 'default']
if default:
default_ecp = {
'k': 2,
'technique': "reed_sol_van",
'm': 1,
'name': "default",
'plugin': "jerasure"
}
if 'crush-failure-domain' in default[0]:
default_ecp['crush-failure-domain'] = default[0]['crush-failure-domain']
self.assertEqual(default[0], default_ecp)
get_data = self._get('/api/erasure_code_profile/default')
self.assertEqual(get_data, default[0])


@authenticate
def test_create(self):
data = {'name': 'ecp32', 'k': 3, 'm': 2}
self._post('/api/erasure_code_profile', data)
self.assertStatus(201)

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"
})

self.assertStatus(200)

self._delete('/api/erasure_code_profile/ecp32')
self.assertStatus(204)

@authenticate
def test_create_plugin(self):
data = {'name': 'lrc', 'k': '2', 'm': '2', 'l': '2', 'plugin': 'lrc'}
self._post('/api/erasure_code_profile', data)
self.assertJsonBody(None)
self.assertStatus(201)

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"
})

self.assertStatus(200)

self._delete('/api/erasure_code_profile/lrc')
self.assertStatus(204)

8 changes: 5 additions & 3 deletions qa/tasks/mgr/dashboard/test_pool.py
Expand Up @@ -146,13 +146,15 @@ def test_pool_info(self):
info_data = self._get("/api/pool/_info")
self.assertEqual(set(info_data),
{'pool_names', 'crush_rules_replicated', 'crush_rules_erasure',
'is_all_bluestore', 'compression_algorithms', 'compression_modes'})
'is_all_bluestore', 'compression_algorithms', 'compression_modes',
'osd_count'})
self.assertTrue(all(isinstance(n, six.string_types) for n in info_data['pool_names']))
self.assertTrue(
all(isinstance(n, six.string_types) for n in info_data['crush_rules_replicated']))
all(isinstance(n, dict) for n in info_data['crush_rules_replicated']))
self.assertTrue(
all(isinstance(n, six.string_types) for n in info_data['crush_rules_erasure']))
all(isinstance(n, dict) for n in info_data['crush_rules_erasure']))
self.assertIsInstance(info_data['is_all_bluestore'], bool)
self.assertIsInstance(info_data['osd_count'], int)
self.assertTrue(
all(isinstance(n, six.string_types) for n in info_data['compression_algorithms']))
self.assertTrue(
Expand Down
54 changes: 54 additions & 0 deletions src/pybind/mgr/dashboard/controllers/erasure_code_profile.py
@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import

from cherrypy import NotFound

from . import ApiController, AuthRequired, RESTController
from ..services.ceph_service import CephService
from .. import mgr


def _serialize_ecp(name, ecp):
ecp['name'] = name
ecp['k'] = int(ecp['k'])
ecp['m'] = int(ecp['m'])
return ecp


@ApiController('erasure_code_profile')
@AuthRequired()
class ErasureCodeProfile(RESTController):
"""
create() supports additional key-value arguments that are passed to the
ECP plugin.
"""

def list(self):
ret = []
for name, ecp in mgr.get('osd_map').get('erasure_code_profiles', {}).items():
ret.append(_serialize_ecp(name, ecp))
return ret

def get(self, name):
try:
ecp = mgr.get('osd_map')['erasure_code_profiles'][name]
return _serialize_ecp(name, ecp)
except KeyError:
raise NotFound('No such erasure code profile')

# pylint: disable=too-many-arguments
@RESTController.args_from_json
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

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)
3 changes: 2 additions & 1 deletion src/pybind/mgr/dashboard/controllers/pool.py
Expand Up @@ -84,7 +84,7 @@ def create(self, pool, pg_num, pool_type, erasure_code_profile=None, flags=None,
def _info(self):
"""Used by the create-pool dialog"""
def rules(pool_type):
return [r["rule_name"]
return [r
for r in mgr.get('osd_map_crush')['rules']
if r['type'] == pool_type]

Expand All @@ -101,6 +101,7 @@ def compression_enum(conf_name):
"crush_rules_replicated": rules(1),
"crush_rules_erasure": rules(3),
"is_all_bluestore": all_bluestore(),
"osd_count": len(mgr.get('osd_map')['osds']),
"compression_algorithms": compression_enum('bluestore_compression_algorithm'),
"compression_modes": compression_enum('bluestore_compression_mode'),
}