privsep: Convert 'os_brick.caches.opencas'

Change-Id: I4fec9d99911e47d9c3f225d188407af961acc7d3
Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
This commit is contained in:
Stephen Finucane 2021-05-13 16:43:50 +01:00 committed by Stephen Finucane
parent 3369d168f4
commit ab02fe2dfc
6 changed files with 329 additions and 204 deletions

View File

@ -23,6 +23,7 @@ os_brick/local_dev/lvm.py
os_brick/privileged/cryptsetup.py
os_brick/privileged/luks.py
os_brick/privileged/nvmeof.py
os_brick/privileged/opencas.py
os_brick/privileged/rbd.py
os_brick/remotefs/remotefs.py
os_brick/utils.py

View File

@ -12,24 +12,23 @@
import abc
import debtcollector
from oslo_log import log as logging
from oslo_utils import importutils
from os_brick import exception
from os_brick.i18n import _
LOG = logging.getLogger(__name__)
CACHE_ENGINE_TO_CACHE_CLASS_MAP = {
"opencas": 'os_brick.caches.opencas.OpenCASEngine',
}
_NO_ARG_SENTINEL = object()
class CacheEngineBase(object, metaclass=abc.ABCMeta):
def __init__(self, **kwargs):
self._root_helper = kwargs.get('root_helper')
class CacheEngineBase(metaclass=abc.ABCMeta):
@abc.abstractmethod
def is_engine_ready(self, **kwargs):
return
@ -43,20 +42,36 @@ class CacheEngineBase(object, metaclass=abc.ABCMeta):
return
class CacheManager():
class CacheManager:
"""Cache manager for volumes.
This CacheManager uses cache engines to do volume cache.
"""
def __init__(self, root_helper, connection_info,
*args, **kwargs):
def __init__(
self,
root_helper=_NO_ARG_SENTINEL,
connection_info=_NO_ARG_SENTINEL,
*args,
**kwargs,
):
if root_helper != _NO_ARG_SENTINEL:
debtcollector.deprecate(
"The 'root_helper' argument is no longer used and will be "
"removed in a future release: please drop this argument."
)
if connection_info == _NO_ARG_SENTINEL:
raise TypeError('missing connection_info')
data = connection_info['data']
if not data.get('device_path'):
volume_id = data.get('volume_id') or connection_info.get('serial')
raise exception.VolumeLocalCacheNotSupported(
volume_id=volume_id,
volume_type=connection_info.get('driver_volume_type'))
volume_type=connection_info.get('driver_volume_type'),
)
self.ori_device_path = data.get('device_path')
if not data.get('cacheable'):
@ -64,11 +79,9 @@ class CacheManager():
return
self.cacheable = True
self.root_helper = root_helper
self.engine_name = kwargs.get('cache_name')
self.args = args
self.kwargs = kwargs
self.kwargs["root_helper"] = root_helper
self.kwargs["dev_path"] = data.get('device_path')
self.engine = self._get_engine(self.engine_name, **self.kwargs)

View File

@ -9,54 +9,35 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from oslo_concurrency import processutils as putils
import debtcollector
from oslo_log import log as logging
from os_brick import caches
from os_brick import exception
from os_brick import executor
import os_brick.privileged.opencas
LOG = logging.getLogger(__name__)
class OpenCASEngine(executor.Executor, caches.CacheEngineBase):
class OpenCASEngine(caches.CacheEngineBase):
def __init__(self, **kwargs):
super(OpenCASEngine, self).__init__(**kwargs)
if 'root_helper' in kwargs:
debtcollector.deprecate(
"The 'root_helper' argument is no longer used and will be "
"removed in a future release: please drop this argument."
)
kwargs.pop('root_helper')
self.cache_id = kwargs.get('opencas_cache_id')
def os_execute(self, *cmd, **kwargs):
LOG.debug('os_execute: cmd: %s, args: %s', cmd, kwargs)
try:
out, err = self._execute(*cmd, **kwargs)
except putils.ProcessExecutionError as err:
LOG.exception('os_execute error')
LOG.error('Cmd :%s', err.cmd)
LOG.error('StdOut :%s', err.stdout)
LOG.error('StdErr :%s', err.stderr)
raise
return out, err
def is_engine_ready(self, **kwargs):
"""'casadm -L' will print like:
return os_brick.privileged.opencas.is_engine_ready(self.cache_id)
type id disk status write policy device
cache 1 /dev/nvme0n1 Running wt -
"""
cmd = ['casadm', '-L']
kwargs = dict(run_as_root=True,
root_helper=self._root_helper)
out, err = self.os_execute(*cmd, **kwargs)
for line in out.splitlines():
fields = line.split()
if str(self.cache_id) == fields[1] and 'Running' == fields[3]:
return True
return False
def _map_casdisk(self, core_id):
return os_brick.privileged.opencas.map_device(self.cache_id, core_id)
def attach_volume(self, **kwargs):
core = kwargs.get('dev_path')
@ -66,54 +47,16 @@ class OpenCASEngine(executor.Executor, caches.CacheEngineBase):
core = os.path.realpath(core)
return self._map_casdisk(core)
def _unmap_casdisk(self, core_id):
return os_brick.privileged.opencas.unmap_device(self.cache_id, core_id)
def detach_volume(self, **kwargs):
casdev = kwargs.get('dev_path')
if casdev is None:
dev_path = kwargs.get('dev_path')
if dev_path is None:
LOG.error('dev_path is not specified')
raise exception.VolumePathsNotFound()
coreid, coredev = self._get_mapped_coredev(casdev)
coreid, coredev = os_brick.privileged.opencas.get_mapping(dev_path)
LOG.info("opencas: coreid=%s,coredev=%s", coreid, coredev)
self._unmap_casdisk(coreid)
return coredev
def _get_mapped_casdev(self, core):
cmd = ['casadm', '-L']
kwargs = dict(run_as_root=True,
root_helper=self._root_helper)
out, err = self.os_execute(*cmd, **kwargs)
for line in out.splitlines():
if line.find(core) < 0:
continue
fields = line.split()
return fields[5]
raise exception.BrickException('Cannot find emulated device.')
def _get_mapped_coredev(self, casdev):
cmd = ['casadm', '-L']
kwargs = dict(run_as_root=True,
root_helper=self._root_helper)
out, err = self.os_execute(*cmd, **kwargs)
for line in out.splitlines():
if line.find(casdev) < 0:
continue
fields = line.split()
return (fields[1], fields[2])
raise exception.BrickException('Cannot find core device.')
def _map_casdisk(self, core):
cmd = ['casadm', '-A', '-i', self.cache_id, '-d', core]
kwargs = dict(run_as_root=True,
root_helper=self._root_helper)
out, err = self.os_execute(*cmd, **kwargs)
return self._get_mapped_casdev(core)
def _unmap_casdisk(self, coreid):
cmd = ['casadm', '-R', '-f', '-i', self.cache_id, '-j', coreid]
kwargs = dict(run_as_root=True,
root_helper=self._root_helper)
out, err = self.os_execute(*cmd, **kwargs)

View File

@ -0,0 +1,113 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import typing as ty
from oslo_concurrency import processutils
from oslo_log import log as logging
from os_brick import exception
import os_brick.privileged
LOG = logging.getLogger(__name__)
# NOTE(stephenfin): This is purposefully not wrapped in an entrypoint since it
# shouldn't be called directly
def _list_caches() -> ty.Tuple[str, str]:
"""Displays each cache instance with relevant details.
'casadm -L' will print output like::
type id disk status write policy device
cache 1 /dev/nvme0n1 Running wt -
It's the responsibility of the caller to parse this.
:returns: output from command
"""
cmd = ['casadm', '-L']
return processutils.execute(*cmd)
@os_brick.privileged.default.entrypoint
def is_engine_ready(cache_id: int) -> bool:
"""Determine if the specified cache is active.
:param cache_id: ID of cache to query status for
:returns: True if cache is enabled and running, else False
"""
out, err = _list_caches()
for line in out.splitlines():
fields = line.split()
if str(cache_id) == fields[1] and 'Running' == fields[3]:
return True
return False
@os_brick.privileged.default.entrypoint
def get_mapping(dev_path: str) -> ty.Tuple[str, str]:
"""Get core mapping for the specified device.
:param dev_path: path to device
:returns: A tuple of core ID and core device
:raises: exception.BrickException if no matching device found
"""
out, err = _list_caches()
for line in out.splitlines():
if line.find(dev_path) < 0:
continue
fields = line.split()
return fields[1], fields[2]
raise exception.BrickException('Cannot find core device')
@os_brick.privileged.default.entrypoint
def map_device(cache_id: int, core_device: str) -> str:
"""Map a core device to the framework associated with a given cache.
:param cache_id: Unique identifier for cache in range 1 to 16384
:param core_device: Location of the HDD storage/core device. This must
be a complete device path in the /dev directory, for example /dev/sdb
:returns: The created mapping name
"""
cmd = ['casadm', '-A', '-i', cache_id, '-d', core_device]
processutils.execute(*cmd)
out, err = _list_caches()
for line in out.splitlines():
if line.find(core_device) < 0:
continue
fields = line.split()
return fields[5]
raise exception.BrickException('Cannot find emulated device')
@os_brick.privileged.default.entrypoint
def unmap_device(cache_id: int, core_id: str) -> None:
"""Unmap a core device from the framework associated with a given cache.
:param cache_id: Unique identifier for cache in range 1 to 16384
:param core_device: Location of the HDD storage/core device. This must
be a complete device path in the /dev directory, for example /dev/sdb
:returns: None
"""
cmd = ['casadm', '-R', '-f', '-i', cache_id, '-j', core_id]
processutils.execute(*cmd)

View File

@ -12,9 +12,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
from oslo_concurrency import processutils as putils
from unittest import mock
from os_brick.caches import opencas
from os_brick import exception
@ -23,7 +22,7 @@ from os_brick.tests import base
class OpenCASEngineTestCase(base.TestCase):
def setUp(self):
super(OpenCASEngineTestCase, self).setUp()
super().setUp()
self.connection_info = {
"data": {
"device_path": "/dev/disk/by-path/"
@ -31,133 +30,61 @@ class OpenCASEngineTestCase(base.TestCase):
":volume-fake_uuid-lun-1",
},
}
self.root_helper = None
@mock.patch('os_brick.executor.Executor._execute')
def test_os_execute_exception(self, mock_execute):
raise_err = [
putils.ProcessExecutionError(exit_code=1),
mock.DEFAULT,
]
@mock.patch(
'os_brick.privileged.opencas.is_engine_ready',
return_value=True,
)
def test_is_engine_ready(self, mock_is_engine_ready):
engine = opencas.OpenCASEngine(opencas_cache_id=42)
self.assertTrue(engine.is_engine_ready())
mock_is_engine_ready.assert_called_once_with(42)
@mock.patch('os_brick.privileged.opencas.map_device')
def test_map_casdisk(self, mock_unmap_device):
engine = opencas.OpenCASEngine(root_helper=None, opencas_cache_id=1)
mock_execute.side_effect = raise_err
self.assertRaises(putils.ProcessExecutionError,
engine.os_execute, 'cmd', 'param')
mock_execute.side_effect = raise_err
self.assertRaises(putils.ProcessExecutionError,
engine.is_engine_ready)
mock_execute.side_effect = raise_err
self.assertRaises(putils.ProcessExecutionError,
engine._get_mapped_casdev, 'path')
mock_execute.side_effect = raise_err
self.assertRaises(putils.ProcessExecutionError,
engine._get_mapped_coredev, 'path')
mock_execute.side_effect = raise_err
self.assertRaises(putils.ProcessExecutionError,
engine._map_casdisk, 'path')
mock_execute.side_effect = raise_err
self.assertRaises(putils.ProcessExecutionError,
engine._unmap_casdisk, 'path')
@mock.patch('os_brick.executor.Executor._execute')
def test_is_engine_ready(self, moc_exec):
out_ready = """type id disk status write policy device
cache 1 /dev/nvme0n1 Running wt -"""
out_not_ready = 'type id disk status write policy device'
err = ''
engine = opencas.OpenCASEngine(root_helper=None, opencas_cache_id=1)
moc_exec.return_value = (out_ready, err)
ret = engine.is_engine_ready()
self.assertTrue(ret)
moc_exec.return_value = (out_not_ready, err)
ret = engine.is_engine_ready()
self.assertFalse(ret)
moc_exec.assert_has_calls([
mock.call('casadm', '-L', run_as_root=True, root_helper=None)
])
@mock.patch('os_brick.executor.Executor._execute')
def test_get_mapped_casdev(self, moc_exec):
out_ready = """type id disk status write policy device
cache 1 /dev/nvme0n1 Running wt -
core 1 /dev/sdd Active - /dev/cas1-1"""
err = ''
engine = opencas.OpenCASEngine(root_helper=None, opencas_cache_id=1)
moc_exec.return_value = (out_ready, err)
ret1 = engine._get_mapped_casdev('/dev/sdd')
self.assertEqual('/dev/cas1-1', ret1)
@mock.patch('os_brick.executor.Executor._execute')
def test_get_mapped_coredev(self, moc_exec):
out_ready = """type id disk status write policy device
cache 1 /dev/nvme0n1 Running wt -
core 1 /dev/sdd Active - /dev/cas1-1"""
err = ''
engine = opencas.OpenCASEngine(root_helper=None, opencas_cache_id=1)
moc_exec.return_value = (out_ready, err)
ret1, ret2 = engine._get_mapped_coredev('/dev/cas1-1')
self.assertEqual('1', ret1)
self.assertEqual('/dev/sdd', ret2)
@mock.patch('os_brick.executor.Executor._execute')
@mock.patch('os_brick.caches.opencas.OpenCASEngine._get_mapped_casdev')
def test_map_casdisk(self, moc_get_mapped_casdev, moc_exec):
engine = opencas.OpenCASEngine(root_helper=None, opencas_cache_id=1)
moc_get_mapped_casdev.return_value = ''
moc_exec.return_value = ('', '')
engine._map_casdisk('/dev/sdd')
moc_exec.assert_has_calls([
mock.call('casadm', '-A', '-i', 1, '-d', '/dev/sdd',
run_as_root=True, root_helper=None)
])
@mock.patch('os_brick.executor.Executor._execute')
def test_unmap_casdisk(self, moc_exec):
engine = opencas.OpenCASEngine(root_helper=None, opencas_cache_id=1)
moc_exec.return_value = ('', '')
engine._unmap_casdisk('1')
moc_exec.assert_has_calls([
mock.call('casadm', '-R', '-f', '-i', 1, '-j', '1',
run_as_root=True, root_helper=None)
])
engine._map_casdisk('1')
mock_unmap_device.assert_called_once_with(1, '1')
@mock.patch('os_brick.caches.opencas.OpenCASEngine._map_casdisk')
def test_attach_volume(self, moc_map):
def test_attach_volume(self, mock_map_casdisk):
engine = opencas.OpenCASEngine(root_helper=None, opencas_cache_id=1)
moc_map.return_value = ''
args = {'no_dev_path': 'path'}
self.assertRaises(exception.VolumePathsNotFound,
engine.attach_volume, **args)
# Exception raised if we don't pass a dev_path or the dev_path is None
self.assertRaises(exception.VolumePathsNotFound, engine.attach_volume)
self.assertRaises(
exception.VolumePathsNotFound,
engine.attach_volume,
dev_path=None,
)
# No exception if dev_path set correctly
args = {'dev_path': 'path'}
engine.attach_volume(**args)
engine.attach_volume(dev_path='path')
mock_map_casdisk.assert_called_once_with(mock.ANY)
@mock.patch('os_brick.executor.Executor._execute')
def test_detach_volume(self, moc_exec):
out_ready = """type id disk status write policy device
cache 1 /dev/nvme0n1 Running wt -
core 1 /dev/sdd Active - /dev/cas1-1"""
err = ''
@mock.patch('os_brick.privileged.opencas.unmap_device')
def test_unmap_casdisk(self, mock_unmap_device):
engine = opencas.OpenCASEngine(root_helper=None, opencas_cache_id=1)
moc_exec.return_value = (out_ready, err)
engine._unmap_casdisk('1')
mock_unmap_device.assert_called_once_with(1, '1')
args = {'no_dev_path': 'path'}
self.assertRaises(exception.VolumePathsNotFound,
engine.detach_volume, **args)
@mock.patch('os_brick.privileged.opencas.get_mapping')
@mock.patch('os_brick.caches.opencas.OpenCASEngine._unmap_casdisk')
def test_detach_volume(self, mock_unmap_casdisk, mock_get_mapping):
mock_get_mapping.return_value = ('1', '/dev/sdd')
engine = opencas.OpenCASEngine(root_helper=None, opencas_cache_id=1)
# Exception raised if we don't pass a dev_path or the dev_path is None
self.assertRaises(exception.VolumePathsNotFound, engine.detach_volume)
self.assertRaises(
exception.VolumePathsNotFound,
engine.detach_volume,
dev_path=None,
)
# No exception if dev_path set correctly
args = {'dev_path': '/dev/cas1-1'}
engine.detach_volume(**args)
engine.detach_volume(dev_path='/dev/cas1-1')
mock_unmap_casdisk.assert_called_once_with('1')

View File

@ -0,0 +1,128 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import textwrap
from unittest import mock
from os_brick import exception
import os_brick.privileged as privsep_brick
import os_brick.privileged.opencas
from os_brick.tests import base
FAKE_LIST_CACHES = (
textwrap.dedent(
"""
type id disk status write policy device
cache 1 /dev/nvme0n1 Running wt /dev/cas1-1
cache 2 /dev/sdc1 Inactive wt /dev/cas1-2
""".strip()
),
None,
)
class PrivOpenCASTestCase(base.TestCase):
def setUp(self):
super().setUp()
# Disable privsep server/client mode
privsep_brick.default.set_client_mode(False)
self.addCleanup(privsep_brick.default.set_client_mode, True)
@mock.patch('oslo_concurrency.processutils.execute')
def test_is_engine_ready(self, mock_execute):
mock_execute.return_value = FAKE_LIST_CACHES
self.assertTrue(os_brick.privileged.opencas.is_engine_ready(1))
self.assertFalse(os_brick.privileged.opencas.is_engine_ready(2))
# called twice
mock_execute.assert_has_calls(
[
mock.call('casadm', '-L'),
mock.call('casadm', '-L'),
]
)
@mock.patch('oslo_concurrency.processutils.execute')
def test_get_mapping(self, mock_execute):
mock_execute.return_value = FAKE_LIST_CACHES
self.assertEqual(
('2', '/dev/sdc1'),
os_brick.privileged.opencas.get_mapping('/dev/cas1-2'),
)
mock_execute.assert_called_once_with('casadm', '-L')
@mock.patch('oslo_concurrency.processutils.execute')
def test_get_mapping__missing(self, mock_execute):
mock_execute.return_value = ('', '')
exc = self.assertRaises(
exception.BrickException,
os_brick.privileged.opencas.get_mapping,
'/dev/cas1-2',
)
self.assertIn('Cannot find core device', str(exc))
@mock.patch('oslo_concurrency.processutils.execute')
def test_map_device(self, mock_execute):
mock_execute.side_effect = [
None,
FAKE_LIST_CACHES,
]
self.assertEqual(
'/dev/cas1-2',
os_brick.privileged.opencas.map_device('2', '/dev/sdc1'),
)
mock_execute.assert_has_calls(
[
mock.call('casadm', '-A', '-i', '2', '-d', '/dev/sdc1'),
mock.call('casadm', '-L'),
]
)
@mock.patch('oslo_concurrency.processutils.execute')
def test_map_device__missing(self, mock_execute):
mock_execute.side_effect = [
None,
('', ''),
]
exc = self.assertRaises(
exception.BrickException,
os_brick.privileged.opencas.map_device,
'2',
'/dev/sdc1',
)
self.assertIn('Cannot find emulated device', str(exc))
@mock.patch('oslo_concurrency.processutils.execute')
def test_unmap_device(self, mock_execute):
self.assertIsNone(
os_brick.privileged.opencas.unmap_device('2', '/dev/sdc1'),
)
mock_execute.assert_called_once_with(
'casadm',
'-R',
'-f',
'-i',
'2',
'-j',
'/dev/sdc1',
)