Secure erase tests
Implement simple secure erase tests. Perform IO that will trigger copying of Data buffers and make sure OCF calls secure erase on them. Signed-off-by: Jan Musial <jan.musial@intel.com>
This commit is contained in:
parent
d4e929140e
commit
75c3948f6d
@ -119,7 +119,7 @@ class Cache:
|
|||||||
metadata_volatile: bool = False,
|
metadata_volatile: bool = False,
|
||||||
max_queue_size: int = DEFAULT_BACKFILL_QUEUE_SIZE,
|
max_queue_size: int = DEFAULT_BACKFILL_QUEUE_SIZE,
|
||||||
queue_unblock_size: int = DEFAULT_BACKFILL_UNBLOCK,
|
queue_unblock_size: int = DEFAULT_BACKFILL_UNBLOCK,
|
||||||
locked: bool = True,
|
locked: bool = False,
|
||||||
pt_unaligned_io: bool = DEFAULT_PT_UNALIGNED_IO,
|
pt_unaligned_io: bool = DEFAULT_PT_UNALIGNED_IO,
|
||||||
use_submit_fast: bool = DEFAULT_USE_SUBMIT_FAST,
|
use_submit_fast: bool = DEFAULT_USE_SUBMIT_FAST,
|
||||||
):
|
):
|
||||||
@ -181,19 +181,26 @@ class Cache:
|
|||||||
def change_cache_mode(self, cache_mode: CacheMode):
|
def change_cache_mode(self, cache_mode: CacheMode):
|
||||||
self.get_and_write_lock()
|
self.get_and_write_lock()
|
||||||
status = self.owner.lib.ocf_mngt_cache_set_mode(self.cache_handle, cache_mode)
|
status = self.owner.lib.ocf_mngt_cache_set_mode(self.cache_handle, cache_mode)
|
||||||
|
|
||||||
if status:
|
if status:
|
||||||
|
self.put_and_write_unlock()
|
||||||
raise OcfError("Error changing cache mode", status)
|
raise OcfError("Error changing cache mode", status)
|
||||||
|
|
||||||
self.put_and_write_unlock()
|
self.put_and_write_unlock()
|
||||||
|
|
||||||
def set_cleaning_policy(self, cleaning_policy: CleaningPolicy):
|
def set_cleaning_policy(self, cleaning_policy: CleaningPolicy):
|
||||||
self.get_and_write_lock()
|
self.get_and_write_lock()
|
||||||
|
|
||||||
status = self.owner.lib.ocf_mngt_cache_cleaning_set_policy(self.cache_handle, cleaning_policy)
|
status = self.owner.lib.ocf_mngt_cache_cleaning_set_policy(self.cache_handle, cleaning_policy)
|
||||||
if status:
|
if status:
|
||||||
|
self.put_and_write_unlock()
|
||||||
raise OcfError("Error changing cleaning policy", status)
|
raise OcfError("Error changing cleaning policy", status)
|
||||||
|
|
||||||
self.put_and_write_unlock()
|
self.put_and_write_unlock()
|
||||||
|
|
||||||
def set_cleaning_policy_param(self, cleaning_policy: CleaningPolicy, param_id, param_value):
|
def set_cleaning_policy_param(self, cleaning_policy: CleaningPolicy, param_id, param_value):
|
||||||
self.get_and_write_lock()
|
self.get_and_write_lock()
|
||||||
|
|
||||||
status = self.owner.lib.ocf_mngt_cache_cleaning_set_param(
|
status = self.owner.lib.ocf_mngt_cache_cleaning_set_param(
|
||||||
self.cache_handle,
|
self.cache_handle,
|
||||||
cleaning_policy,
|
cleaning_policy,
|
||||||
@ -201,14 +208,19 @@ class Cache:
|
|||||||
param_value
|
param_value
|
||||||
)
|
)
|
||||||
if status:
|
if status:
|
||||||
|
self.put_and_write_unlock()
|
||||||
raise OcfError("Error setting cleaning policy param", status)
|
raise OcfError("Error setting cleaning policy param", status)
|
||||||
|
|
||||||
self.put_and_write_unlock()
|
self.put_and_write_unlock()
|
||||||
|
|
||||||
def set_seq_cut_off_policy(self, policy: SeqCutOffPolicy):
|
def set_seq_cut_off_policy(self, policy: SeqCutOffPolicy):
|
||||||
self.get_and_write_lock()
|
self.get_and_write_lock()
|
||||||
|
|
||||||
status = self.owner.lib.ocf_mngt_core_set_seq_cutoff_policy_all(self.cache_handle, policy)
|
status = self.owner.lib.ocf_mngt_core_set_seq_cutoff_policy_all(self.cache_handle, policy)
|
||||||
if status:
|
if status:
|
||||||
|
self.put_and_write_unlock()
|
||||||
raise OcfError("Error setting cache seq cut off policy", status)
|
raise OcfError("Error setting cache seq cut off policy", status)
|
||||||
|
|
||||||
self.put_and_write_unlock()
|
self.put_and_write_unlock()
|
||||||
|
|
||||||
def configure_device(
|
def configure_device(
|
||||||
@ -238,6 +250,7 @@ class Cache:
|
|||||||
self, device, force=False, perform_test=False, cache_line_size=None
|
self, device, force=False, perform_test=False, cache_line_size=None
|
||||||
):
|
):
|
||||||
self.configure_device(device, force, perform_test, cache_line_size)
|
self.configure_device(device, force, perform_test, cache_line_size)
|
||||||
|
self.get_and_write_lock()
|
||||||
|
|
||||||
c = OcfCompletion(
|
c = OcfCompletion(
|
||||||
[("cache", c_void_p), ("priv", c_void_p), ("error", c_int)]
|
[("cache", c_void_p), ("priv", c_void_p), ("error", c_int)]
|
||||||
@ -249,8 +262,11 @@ class Cache:
|
|||||||
|
|
||||||
c.wait()
|
c.wait()
|
||||||
if c.results["error"]:
|
if c.results["error"]:
|
||||||
|
self.put_and_write_unlock()
|
||||||
raise OcfError("Attaching cache device failed", c.results["error"])
|
raise OcfError("Attaching cache device failed", c.results["error"])
|
||||||
|
|
||||||
|
self.put_and_write_unlock()
|
||||||
|
|
||||||
def load_cache(self, device):
|
def load_cache(self, device):
|
||||||
self.configure_device(device)
|
self.configure_device(device)
|
||||||
c = OcfCompletion(
|
c = OcfCompletion(
|
||||||
|
@ -140,9 +140,12 @@ class Core:
|
|||||||
|
|
||||||
def set_seq_cut_off_policy(self, policy: SeqCutOffPolicy):
|
def set_seq_cut_off_policy(self, policy: SeqCutOffPolicy):
|
||||||
self.cache.get_and_write_lock()
|
self.cache.get_and_write_lock()
|
||||||
|
|
||||||
status = self.cache.owner.lib.ocf_mngt_core_set_seq_cutoff_policy(self.handle, policy)
|
status = self.cache.owner.lib.ocf_mngt_core_set_seq_cutoff_policy(self.handle, policy)
|
||||||
if status:
|
if status:
|
||||||
|
self.cache.put_and_write_unlock()
|
||||||
raise OcfError("Error setting core seq cut off policy", status)
|
raise OcfError("Error setting core seq cut off policy", status)
|
||||||
|
|
||||||
self.cache.put_and_write_unlock()
|
self.cache.put_and_write_unlock()
|
||||||
|
|
||||||
def reset_stats(self):
|
def reset_stats(self):
|
||||||
|
@ -87,7 +87,12 @@ class OcfCtx:
|
|||||||
if vol_type:
|
if vol_type:
|
||||||
self.unregister_volume_type(vol_type)
|
self.unregister_volume_type(vol_type)
|
||||||
|
|
||||||
|
def stop_caches(self):
|
||||||
|
for cache in self.caches[:]:
|
||||||
|
cache.stop()
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
|
self.stop_caches()
|
||||||
self.cleanup_volume_types()
|
self.cleanup_volume_types()
|
||||||
|
|
||||||
result = self.lib.ocf_ctx_exit(self.ctx_handle)
|
result = self.lib.ocf_ctx_exit(self.ctx_handle)
|
||||||
|
@ -76,7 +76,7 @@ class Logger(Structure):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ops = LoggerOps(
|
self.ops = LoggerOps(
|
||||||
_open=self._open,
|
_open=self._open,
|
||||||
_printf=cast(OcfLib.getInstance().pyocf_printf_helper, c_void_p),
|
_print=cast(OcfLib.getInstance().pyocf_printf_helper, c_void_p),
|
||||||
_close=self._close,
|
_close=self._close,
|
||||||
)
|
)
|
||||||
self.priv = LoggerPriv(_log=self._log)
|
self.priv = LoggerPriv(_log=self._log)
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
# SPDX-License-Identifier: BSD-3-Clause-Clear
|
# SPDX-License-Identifier: BSD-3-Clause-Clear
|
||||||
#
|
#
|
||||||
|
|
||||||
from ctypes import c_void_p, c_int, Structure, CFUNCTYPE
|
from ctypes import c_void_p, c_int, c_uint32, Structure, CFUNCTYPE
|
||||||
from .shared import SharedOcfObject
|
from threading import Thread, Event
|
||||||
|
|
||||||
|
from ..ocf import OcfLib
|
||||||
|
|
||||||
|
|
||||||
class MetadataUpdaterOps(Structure):
|
class MetadataUpdaterOps(Structure):
|
||||||
@ -15,14 +17,42 @@ class MetadataUpdaterOps(Structure):
|
|||||||
_fields_ = [("_init", INIT), ("_kick", KICK), ("_stop", STOP)]
|
_fields_ = [("_init", INIT), ("_kick", KICK), ("_stop", STOP)]
|
||||||
|
|
||||||
|
|
||||||
class MetadataUpdater(SharedOcfObject):
|
class MetadataUpdater:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def mu_run(*, mu: MetadataUpdater, kick: Event, stop: Event):
|
||||||
|
while True:
|
||||||
|
kick.clear()
|
||||||
|
|
||||||
|
if OcfLib.getInstance().ocf_metadata_updater_run(mu):
|
||||||
|
continue
|
||||||
|
|
||||||
|
kick.wait()
|
||||||
|
if stop.is_set():
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataUpdater:
|
||||||
_instances_ = {}
|
_instances_ = {}
|
||||||
_fields_ = [("mu", c_void_p)]
|
|
||||||
ops = None
|
ops = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, ref):
|
||||||
self._as_parameter_ = self.mu
|
self._as_parameter_ = ref
|
||||||
super().__init__()
|
MetadataUpdater._instances_[ref] = self
|
||||||
|
self.kick_event = Event()
|
||||||
|
self.stop_event = Event()
|
||||||
|
|
||||||
|
lib = OcfLib.getInstance()
|
||||||
|
self.thread = Thread(
|
||||||
|
group=None,
|
||||||
|
target=mu_run,
|
||||||
|
name="mu-{}".format(
|
||||||
|
lib.ocf_cache_get_name(lib.ocf_metadata_updater_get_cache(self))
|
||||||
|
),
|
||||||
|
kwargs={"mu": self, "kick": self.kick_event, "stop": self.stop_event},
|
||||||
|
)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_ops(cls):
|
def get_ops(cls):
|
||||||
@ -32,17 +62,41 @@ class MetadataUpdater(SharedOcfObject):
|
|||||||
)
|
)
|
||||||
return cls.ops
|
return cls.ops
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_instance(cls, ref):
|
||||||
|
return cls._instances_[ref]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def del_instance(cls, ref):
|
||||||
|
del cls._instances_[ref]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@MetadataUpdaterOps.INIT
|
@MetadataUpdaterOps.INIT
|
||||||
def _init(ref):
|
def _init(ref):
|
||||||
|
m = MetadataUpdater(ref)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@MetadataUpdaterOps.KICK
|
@MetadataUpdaterOps.KICK
|
||||||
def _kick(ref):
|
def _kick(ref):
|
||||||
pass
|
MetadataUpdater.get_instance(ref).kick()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@MetadataUpdaterOps.STOP
|
@MetadataUpdaterOps.STOP
|
||||||
def _stop(ref):
|
def _stop(ref):
|
||||||
pass
|
MetadataUpdater.get_instance(ref).stop()
|
||||||
|
del MetadataUpdater._instances_[ref]
|
||||||
|
|
||||||
|
def kick(self):
|
||||||
|
self.kick_event.set()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.stop_event.set()
|
||||||
|
self.kick_event.set()
|
||||||
|
|
||||||
|
|
||||||
|
lib = OcfLib.getInstance()
|
||||||
|
lib.ocf_metadata_updater_get_cache.argtypes = [c_void_p]
|
||||||
|
lib.ocf_metadata_updater_get_cache.restype = c_void_p
|
||||||
|
lib.ocf_metadata_updater_run.argtypes = [c_void_p]
|
||||||
|
lib.ocf_metadata_updater_run.restype = c_uint32
|
||||||
|
@ -62,6 +62,9 @@ class Size:
|
|||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.bytes
|
return self.bytes
|
||||||
|
|
||||||
|
def __index__(self):
|
||||||
|
return self.bytes
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_B(cls, value):
|
def from_B(cls, value):
|
||||||
return cls(value)
|
return cls(value)
|
||||||
|
@ -23,8 +23,6 @@ def pyocf_ctx():
|
|||||||
c.register_volume_type(Volume)
|
c.register_volume_type(Volume)
|
||||||
c.register_volume_type(ErrorDevice)
|
c.register_volume_type(ErrorDevice)
|
||||||
yield c
|
yield c
|
||||||
for cache in c.caches[:]:
|
|
||||||
cache.stop()
|
|
||||||
c.exit()
|
c.exit()
|
||||||
|
|
||||||
|
|
||||||
@ -35,5 +33,4 @@ def pyocf_ctx_log_buffer():
|
|||||||
c.register_volume_type(Volume)
|
c.register_volume_type(Volume)
|
||||||
c.register_volume_type(ErrorDevice)
|
c.register_volume_type(ErrorDevice)
|
||||||
yield logger
|
yield logger
|
||||||
for cache in c.caches:
|
c.exit()
|
||||||
cache.stop()
|
|
||||||
|
197
tests/functional/tests/security/test_secure_erase.py
Normal file
197
tests/functional/tests/security/test_secure_erase.py
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
#
|
||||||
|
# Copyright(c) 2019 Intel Corporation
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause-Clear
|
||||||
|
#
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ctypes import c_int
|
||||||
|
|
||||||
|
from pyocf.types.cache import Cache, CacheMode
|
||||||
|
from pyocf.types.core import Core
|
||||||
|
from pyocf.types.volume import Volume
|
||||||
|
from pyocf.utils import Size as S
|
||||||
|
from pyocf.types.data import Data, DataOps
|
||||||
|
from pyocf.types.ctx import OcfCtx
|
||||||
|
from pyocf.types.logger import DefaultLogger, LogLevel
|
||||||
|
from pyocf.ocf import OcfLib
|
||||||
|
from pyocf.types.metadata_updater import MetadataUpdater
|
||||||
|
from pyocf.types.cleaner import Cleaner
|
||||||
|
from pyocf.types.io import IoDir
|
||||||
|
from pyocf.types.shared import OcfCompletion
|
||||||
|
|
||||||
|
|
||||||
|
class DataCopyTracer(Data):
|
||||||
|
"""
|
||||||
|
This class enables tracking whether each copied over Data instance is
|
||||||
|
then securely erased.
|
||||||
|
"""
|
||||||
|
|
||||||
|
needs_erase = set()
|
||||||
|
locked_instances = set()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@DataOps.ALLOC
|
||||||
|
def _alloc(pages):
|
||||||
|
data = DataCopyTracer.pages(pages)
|
||||||
|
Data._ocf_instances_.append(data)
|
||||||
|
|
||||||
|
return data.handle.value
|
||||||
|
|
||||||
|
def mlock(self):
|
||||||
|
DataCopyTracer.locked_instances.add(self)
|
||||||
|
DataCopyTracer.needs_erase.add(self)
|
||||||
|
return super().mlock()
|
||||||
|
|
||||||
|
def munlock(self):
|
||||||
|
if self in DataCopyTracer.needs_erase:
|
||||||
|
assert 0, "Erase should be called first on locked Data!"
|
||||||
|
|
||||||
|
DataCopyTracer.locked_instances.remove(self)
|
||||||
|
return super().munlock()
|
||||||
|
|
||||||
|
def secure_erase(self):
|
||||||
|
DataCopyTracer.needs_erase.remove(self)
|
||||||
|
return super().secure_erase()
|
||||||
|
|
||||||
|
def copy(self, src, end, start, size):
|
||||||
|
DataCopyTracer.needs_erase.add(self)
|
||||||
|
return super().copy(src, end, start, size)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.security
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"cache_mode", [CacheMode.WT, CacheMode.WB, CacheMode.WA, CacheMode.WI]
|
||||||
|
)
|
||||||
|
def test_secure_erase_simple_io_read_misses(cache_mode):
|
||||||
|
"""
|
||||||
|
Perform simple IO which will trigger read misses, which in turn should
|
||||||
|
trigger backfill. Track all the data locked/copied for backfill and make
|
||||||
|
sure OCF calls secure erase and unlock on them.
|
||||||
|
"""
|
||||||
|
ctx = OcfCtx(
|
||||||
|
OcfLib.getInstance(),
|
||||||
|
b"Security tests ctx",
|
||||||
|
DefaultLogger(LogLevel.WARN),
|
||||||
|
DataCopyTracer,
|
||||||
|
MetadataUpdater,
|
||||||
|
Cleaner,
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.register_volume_type(Volume)
|
||||||
|
|
||||||
|
cache_device = Volume(S.from_MiB(30))
|
||||||
|
cache = Cache.start_on_device(cache_device, cache_mode=cache_mode)
|
||||||
|
|
||||||
|
core_device = Volume(S.from_MiB(50))
|
||||||
|
core = Core.using_device(core_device)
|
||||||
|
cache.add_core(core)
|
||||||
|
|
||||||
|
write_data = Data.from_string("This is test data")
|
||||||
|
io = core.new_io()
|
||||||
|
io.set_data(write_data)
|
||||||
|
io.configure(20, write_data.size, IoDir.WRITE, 0, 0)
|
||||||
|
io.set_queue(cache.get_default_queue())
|
||||||
|
|
||||||
|
cmpl = OcfCompletion([("err", c_int)])
|
||||||
|
io.callback = cmpl.callback
|
||||||
|
io.submit()
|
||||||
|
cmpl.wait()
|
||||||
|
|
||||||
|
cmpls = []
|
||||||
|
for i in range(100):
|
||||||
|
read_data = Data(500)
|
||||||
|
io = core.new_io()
|
||||||
|
io.set_data(read_data)
|
||||||
|
io.configure(
|
||||||
|
(i * 1259) % int(core_device.size), read_data.size, IoDir.READ, 0, 0
|
||||||
|
)
|
||||||
|
io.set_queue(cache.get_default_queue())
|
||||||
|
|
||||||
|
cmpl = OcfCompletion([("err", c_int)])
|
||||||
|
io.callback = cmpl.callback
|
||||||
|
cmpls.append(cmpl)
|
||||||
|
io.submit()
|
||||||
|
|
||||||
|
for c in cmpls:
|
||||||
|
c.wait()
|
||||||
|
|
||||||
|
write_data = Data.from_string("TEST DATA" * 100)
|
||||||
|
io = core.new_io()
|
||||||
|
io.set_data(write_data)
|
||||||
|
io.configure(500, write_data.size, IoDir.WRITE, 0, 0)
|
||||||
|
io.set_queue(cache.get_default_queue())
|
||||||
|
|
||||||
|
cmpl = OcfCompletion([("err", c_int)])
|
||||||
|
io.callback = cmpl.callback
|
||||||
|
io.submit()
|
||||||
|
cmpl.wait()
|
||||||
|
|
||||||
|
stats = cache.get_stats()
|
||||||
|
|
||||||
|
ctx.exit()
|
||||||
|
|
||||||
|
assert (
|
||||||
|
len(DataCopyTracer.needs_erase) == 0
|
||||||
|
), "Not all locked Data instances were secure erased!"
|
||||||
|
assert (
|
||||||
|
len(DataCopyTracer.locked_instances) == 0
|
||||||
|
), "Not all locked Data instances were unlocked!"
|
||||||
|
assert (
|
||||||
|
stats["req"]["rd_partial_misses"]["value"]
|
||||||
|
+ stats["req"]["rd_full_misses"]["value"]
|
||||||
|
) > 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.security
|
||||||
|
def test_secure_erase_simple_io_cleaning():
|
||||||
|
"""
|
||||||
|
Perform simple IO which will trigger WB cleaning. Track all the data from
|
||||||
|
cleaner (locked) and make sure they are erased and unlocked after use.
|
||||||
|
"""
|
||||||
|
ctx = OcfCtx(
|
||||||
|
OcfLib.getInstance(),
|
||||||
|
b"Security tests ctx",
|
||||||
|
DefaultLogger(LogLevel.WARN),
|
||||||
|
DataCopyTracer,
|
||||||
|
MetadataUpdater,
|
||||||
|
Cleaner,
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.register_volume_type(Volume)
|
||||||
|
|
||||||
|
cache_device = Volume(S.from_MiB(30))
|
||||||
|
cache = Cache.start_on_device(cache_device, cache_mode=CacheMode.WB)
|
||||||
|
|
||||||
|
core_device = Volume(S.from_MiB(100))
|
||||||
|
core = Core.using_device(core_device)
|
||||||
|
cache.add_core(core)
|
||||||
|
|
||||||
|
cmpls = []
|
||||||
|
for i in range(10000):
|
||||||
|
read_data = Data(S.from_KiB(120))
|
||||||
|
io = core.new_io()
|
||||||
|
io.set_data(read_data)
|
||||||
|
io.configure(
|
||||||
|
(i * 1259) % int(core_device.size), read_data.size, IoDir.WRITE, 0, 0
|
||||||
|
)
|
||||||
|
io.set_queue(cache.get_default_queue())
|
||||||
|
|
||||||
|
cmpl = OcfCompletion([("err", c_int)])
|
||||||
|
io.callback = cmpl.callback
|
||||||
|
cmpls.append(cmpl)
|
||||||
|
io.submit()
|
||||||
|
|
||||||
|
for c in cmpls:
|
||||||
|
c.wait()
|
||||||
|
|
||||||
|
stats = cache.get_stats()
|
||||||
|
|
||||||
|
ctx.exit()
|
||||||
|
|
||||||
|
assert (
|
||||||
|
len(DataCopyTracer.needs_erase) == 0
|
||||||
|
), "Not all locked Data instances were secure erased!"
|
||||||
|
assert (
|
||||||
|
len(DataCopyTracer.locked_instances) == 0
|
||||||
|
), "Not all locked Data instances were unlocked!"
|
||||||
|
assert (stats["usage"]["clean"]["value"]) > 0, "Cleaner didn't run!"
|
Loading…
Reference in New Issue
Block a user