Add helper for listing detailed pool information

To support automation of pool creation on a secondary Ceph cluster
in a RBD Mirror setup the actor will require additional information
about pools on the source cluster.

Change-Id: Id88d268d42da6e453879f850ae76575ac3b0adbf
This commit is contained in:
Frode Nordahl 2019-02-19 10:27:49 +01:00
parent c0d3f4d246
commit 03685ca4a6
No known key found for this signature in database
GPG Key ID: 6A5D59A3BA48373F
2 changed files with 212 additions and 7 deletions

View File

@ -2520,18 +2520,20 @@ def update_owner(path, recurse_dirs=True):
secs=elapsed_time.total_seconds(), path=path), DEBUG)
def list_pools(service):
def list_pools(client='admin'):
"""This will list the current pools that Ceph has
:param service: String service id to run under
:returns: list. Returns a list of the ceph pools.
:raises: CalledProcessError if the subprocess fails to run.
:param client: (Optional) client id for ceph key to use
Defaults to ``admin``
:type cilent: str
:returns: Returns a list of available pools.
:rtype: list
:raises: subprocess.CalledProcessError if the subprocess fails to run.
"""
try:
pool_list = []
pools = str(subprocess
.check_output(['rados', '--id', service, 'lspools'])
.decode('UTF-8'))
pools = subprocess.check_output(['rados', '--id', client, 'lspools'],
universal_newlines=True)
for pool in pools.splitlines():
pool_list.append(pool)
return pool_list
@ -2540,6 +2542,113 @@ def list_pools(service):
raise
def get_pool_param(pool, param, client='admin'):
"""Get parameter from pool.
:param pool: Name of pool to get variable from
:type pool: str
:param param: Name of variable to get
:type param: str
:param client: (Optional) client id for ceph key to use
Defaults to ``admin``
:type cilent: str
:returns: Value of variable on pool or None
:rtype: str or None
:raises: subprocess.CalledProcessError
"""
try:
output = subprocess.check_output(
['ceph', '--id', client, 'osd', 'pool', 'get',
pool, param], universal_newlines=True)
except subprocess.CalledProcessError as cp:
if cp.returncode == 2 and 'ENOENT: option' in cp.output:
return None
raise
if ':' in output:
return output.split(':')[1].lstrip().rstrip()
def get_pool_quota(pool, client='admin'):
"""Get pool quota.
:param pool: Name of pool to get variable from
:type pool: str
:param client: (Optional) client id for ceph key to use
Defaults to ``admin``
:type cilent: str
:returns: Dictionary with quota variables
:rtype: dict
:raises: subprocess.CalledProcessError
"""
output = subprocess.check_output(
['ceph', '--id', client, 'osd', 'pool', 'get-quota', pool],
universal_newlines=True)
rc = re.compile(r'\s+max\s+(\S+)\s*:\s+(\d+)')
result = {}
for line in output.splitlines():
m = rc.match(line)
if m:
result.update({'max_{}'.format(m.group(1)): m.group(2)})
return result
def get_pool_applications(pool='', client='admin'):
"""Get pool applications.
:param pool: (Optional) Name of pool to get applications for
Defaults to get for all pools
:type pool: str
:param client: (Optional) client id for ceph key to use
Defaults to ``admin``
:type cilent: str
:returns: Dictionary with pool name as key
:rtype: dict
:raises: subprocess.CalledProcessError
"""
cmd = ['ceph', '--id', client, 'osd', 'pool', 'application', 'get']
if pool:
cmd.append(pool)
try:
output = subprocess.check_output(cmd, universal_newlines=True)
except subprocess.CalledProcessError as cp:
if cp.returncode == 2 and 'ENOENT' in cp.output:
return {}
raise
return json.loads(output)
def list_pools_detail():
"""Get detailed information about pools.
Structure:
{'pool_name_1': {'applications': {'application': {}},
'parameters': {'pg_num': '42', 'size': '42'},
'quota': {'max_bytes': '1000',
'max_objects': '10'},
},
'pool_name_2': ...
}
:returns: Dictionary with detailed pool information.
:rtype: dict
:raises: subproces.CalledProcessError
"""
get_params = ['pg_num', 'size']
result = {}
applications = get_pool_applications()
for pool in list_pools():
result[pool] = {
'applications': applications.get(pool, {}),
'parameters': {},
'quota': get_pool_quota(pool),
}
for param in get_params:
result[pool]['parameters'].update({
param: get_pool_param(pool, param)})
return result
def dirs_need_ownership_update(service):
"""Determines if directories still need change of ownership.

View File

@ -816,6 +816,102 @@ class CephTestCase(unittest.TestCase):
])
)
@patch.object(utils.subprocess, 'check_output')
def test_list_pools(self, _check_output):
_check_output.return_value = 'poola\npoolb\n'
self.assertEqual(utils.list_pools('someuser'), ['poola', 'poolb'])
_check_output.assert_called_with(['rados', '--id', 'someuser',
'lspools'], universal_newlines=True)
self.assertEqual(utils.list_pools(client='someotheruser'),
['poola', 'poolb'])
_check_output.assert_called_with(['rados', '--id', 'someotheruser',
'lspools'], universal_newlines=True)
self.assertEqual(utils.list_pools(),
['poola', 'poolb'])
_check_output.assert_called_with(['rados', '--id', 'admin',
'lspools'], universal_newlines=True)
@patch.object(utils.subprocess, 'check_output')
def test_get_pool_param(self, _check_output):
_check_output.return_value = 'size: 3\n'
self.assertEqual(utils.get_pool_param('rbd', 'size'), '3')
_check_output.assert_called_with(['ceph', '--id', 'admin', 'osd',
'pool', 'get', 'rbd', 'size'],
universal_newlines=True)
@patch.object(utils.subprocess, 'check_output')
def test_get_pool_quota(self, _check_output):
_check_output.return_value = (
"quotas for pool 'rbd':\n"
" max objects: N/A\n"
" max bytes : N/A\n")
self.assertEqual(utils.get_pool_quota('rbd'),
{})
_check_output.assert_called_with(['ceph', '--id', 'admin', 'osd',
'pool', 'get-quota', 'rbd'],
universal_newlines=True)
_check_output.return_value = (
"quotas for pool 'rbd':\n"
" max objects: 10\n"
" max bytes : N/A\n")
self.assertEqual(utils.get_pool_quota('rbd'), {'max_objects': '10'})
_check_output.return_value = (
"quotas for pool 'rbd':\n"
" max objects: N/A\n"
" max bytes : 1000B\n")
self.assertEqual(utils.get_pool_quota('rbd'), {'max_bytes': '1000'})
_check_output.return_value = (
"quotas for pool 'rbd':\n"
" max objects: 10\n"
" max bytes : 1000B\n")
self.assertEqual(utils.get_pool_quota('rbd'),
{'max_objects': '10', 'max_bytes': '1000'})
@patch.object(utils.subprocess, 'check_output')
def test_get_pool_applications(self, _check_output):
_check_output.return_value = (
'{\n'
' "pool": {\n'
' "application": {}\n'
' }\n'
'}\n')
self.assertEqual(utils.get_pool_applications(),
{'pool': {'application': {}}})
_check_output.assert_called_with(['ceph', '--id', 'admin', 'osd',
'pool', 'application', 'get'],
universal_newlines=True)
utils.get_pool_applications('42')
_check_output.assert_called_with(['ceph', '--id', 'admin', 'osd',
'pool', 'application', 'get', '42'],
universal_newlines=True)
@patch.object(utils, 'get_pool_param')
@patch.object(utils, 'get_pool_quota')
@patch.object(utils, 'list_pools')
@patch.object(utils, 'get_pool_applications')
def test_list_pools_detail(self, _get_pool_applications, _list_pools,
_get_pool_quota, _get_pool_param):
self.assertEqual(utils.list_pools_detail(), {})
_get_pool_applications.return_value = {'pool': {'application': {}}}
_list_pools.return_value = ['pool', 'pool2']
_get_pool_quota.return_value = {'max_objects': '10',
'max_bytes': '1000'}
_get_pool_param.return_value = '42'
self.assertEqual(utils.list_pools_detail(),
{'pool': {'applications': {'application': {}},
'parameters': {'pg_num': '42',
'size': '42'},
'quota': {'max_bytes': '1000',
'max_objects': '10'},
},
'pool2': {'applications': {},
'parameters': {'pg_num': '42',
'size': '42'},
'quota': {'max_bytes': '1000',
'max_objects': '10'},
},
})
class CephVolumeSizeCalculatorTestCase(unittest.TestCase):