Skip to content

Commit 4e37193

Browse files
committed
Add options for specifying integrity metadata pre-allocations
Signed-off-by: mulhern <[email protected]>
1 parent bb48479 commit 4e37193

File tree

8 files changed

+261
-8
lines changed

8 files changed

+261
-8
lines changed

docs/stratis.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ GLOBAL OPTIONS
3636

3737
COMMANDS
3838
--------
39-
pool create [--key-desc <key_desc>] [--clevis <(nbde|tang|tpm2)> [--tang-url <tang_url>] [<(--thumbprint <thp> | --trust-url)>] [--no-overprovision] <pool_name> <blockdev> [<blockdev>..]::
39+
pool create [--key-desc <key_desc>] [--clevis <(nbde|tang|tpm2)> [--tang-url <tang_url>] [<(--thumbprint <thp> | --trust-url)>] [--no-overprovision] [--integrity <(no,pre-allocate)>] [--journal-size <journal_size>] [--tag-spec <tag_spec>] <pool_name> <blockdev> [<blockdev>..]::
4040
Create a pool from one or more block devices, with the given pool name.
41+
The --tag-spec and --journal-size options are used to configure the amount
42+
of space to reserve for integrity metadata.
4143
pool stop <(--uuid <uuid> |--name <name>)>::
4244
Stop a pool, specifying the pool by its UUID or by its name. Tear down
4345
the storage stack but leave all metadata intact.

src/stratis_cli/_actions/_introspect.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
<arg name="devices" type="as" direction="in" />
1414
<arg name="key_desc" type="(bs)" direction="in" />
1515
<arg name="clevis_info" type="(b(ss))" direction="in" />
16+
<arg name="journal_size" type="(bt)" direction="in" />
17+
<arg name="tag_spec" type="(bs)" direction="in" />
18+
<arg name="allocate_superblock" type="(bb)" direction="in" />
1619
<arg name="result" type="(b(oao))" direction="out" />
1720
<arg name="return_code" type="q" direction="out" />
1821
<arg name="return_string" type="s" direction="out" />

src/stratis_cli/_actions/_pool.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# isort: THIRDPARTY
2626
from justbytes import Range
2727

28-
from .._constants import Id, IdType, UnlockMethod
28+
from .._constants import Id, IdType, IntegrityOption, IntegrityTagSpec, UnlockMethod
2929
from .._error_codes import PoolErrorCode
3030
from .._errors import (
3131
StratisCliEngineError,
@@ -180,6 +180,16 @@ def create_pool(namespace): # pylint: disable=too-many-locals
180180
None if namespace.clevis is None else ClevisInfo.get_info(namespace.clevis)
181181
)
182182

183+
(journal_size, tag_spec, allocate_superblock) = (
184+
((True, 0), (True, IntegrityTagSpec.B0), (True, False))
185+
if namespace.integrity.integrity is IntegrityOption.NO
186+
else (
187+
(True, namespace.integrity.journal_size.magnitude.numerator),
188+
(True, namespace.integrity.tag_spec),
189+
(True, True),
190+
)
191+
)
192+
183193
(
184194
(changed, (pool_object_path, _)),
185195
return_code,
@@ -199,10 +209,13 @@ def create_pool(namespace): # pylint: disable=too-many-locals
199209
if clevis_info is None
200210
else (True, (clevis_info.pin, json.dumps(clevis_info.config)))
201211
),
212+
"journal_size": journal_size,
213+
"tag_spec": tag_spec,
214+
"allocate_superblock": allocate_superblock,
202215
},
203216
)
204217

205-
if return_code != StratisdErrors.OK: # pragma: no cover
218+
if return_code != StratisdErrors.OK:
206219
raise StratisCliEngineError(return_code, message)
207220

208221
if not changed: # pragma: no cover

src/stratis_cli/_constants.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,28 @@ class Clevis(Enum):
116116

117117
def __str__(self):
118118
return self.value
119+
120+
121+
class IntegrityTagSpec(Enum):
122+
"""
123+
Options for specifying integrity tag size.
124+
"""
125+
126+
B0 = "0b"
127+
B32 = "32b"
128+
B512 = "512b"
129+
130+
def __str__(self):
131+
return self.value
132+
133+
134+
class IntegrityOption(Enum):
135+
"""
136+
Options for specifying integrity choices on create.
137+
"""
138+
139+
NO = "no"
140+
PRE_ALLOCATE = "pre-allocate"
141+
142+
def __str__(self):
143+
return self.value

src/stratis_cli/_parser/_pool.py

Lines changed: 138 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,22 @@
2020
from argparse import SUPPRESS, ArgumentTypeError
2121
from uuid import UUID
2222

23+
# isort: THIRDPARTY
24+
from justbytes import MiB, Range
25+
2326
from .._actions import BindActions, PoolActions
24-
from .._constants import Clevis, EncryptionMethod, UnlockMethod, YesOrNo
27+
from .._constants import (
28+
Clevis,
29+
EncryptionMethod,
30+
IntegrityOption,
31+
IntegrityTagSpec,
32+
UnlockMethod,
33+
YesOrNo,
34+
)
2535
from .._error_codes import PoolErrorCode
2636
from ._bind import BIND_SUBCMDS, REBIND_SUBCMDS
2737
from ._debug import POOL_DEBUG_SUBCMDS
28-
from ._range import RejectAction
38+
from ._range import DefaultAction, RejectAction, parse_range
2939

3040

3141
class ClevisEncryptionOptions: # pylint: disable=too-few-public-methods
@@ -81,6 +91,62 @@ def verify(self, namespace, parser):
8191
namespace.clevis = None if self.clevis is None else self
8292

8393

94+
class IntegrityOptions: # pylint: disable=too-few-public-methods
95+
"""
96+
Gathers and verifies integrity options.
97+
"""
98+
99+
def __init__(self, namespace):
100+
self.integrity = copy.copy(namespace.integrity)
101+
del namespace.integrity
102+
103+
self.journal_size = copy.copy(namespace.journal_size)
104+
del namespace.journal_size
105+
106+
self.journal_size_default = getattr(namespace, "journal_size_default", True)
107+
108+
self.tag_spec = copy.copy(namespace.tag_spec)
109+
del namespace.tag_spec
110+
111+
self.tag_spec_default = getattr(namespace, "tag_spec_default", True)
112+
113+
def verify(self, namespace, parser):
114+
"""
115+
Do supplementary parsing of conditional arguments.
116+
"""
117+
118+
if self.integrity is IntegrityOption.NO and not self.journal_size_default:
119+
parser.error(
120+
f'--integrity value "{IntegrityOption.NO}" and --journal-size '
121+
"option can not be specified together"
122+
)
123+
124+
if self.integrity is IntegrityOption.NO and not self.tag_spec_default:
125+
parser.error(
126+
f'--integrity value "{IntegrityOption.NO}" and --tag-spec '
127+
"option can not be specified together"
128+
)
129+
130+
namespace.integrity = self
131+
132+
133+
class CreateOptions: # pylint: disable=too-few-public-methods
134+
"""
135+
Gathers and verifies options specified on pool create.
136+
"""
137+
138+
def __init__(self, namespace):
139+
self.clevis_encryption_options = ClevisEncryptionOptions(namespace)
140+
self.integrity_options = IntegrityOptions(namespace)
141+
142+
def verify(self, namespace, parser):
143+
"""
144+
Verify that create command-line is formed correctly.
145+
"""
146+
self.clevis_encryption_options.verify(namespace, parser)
147+
self.integrity_options.verify(namespace, parser)
148+
149+
84150
def _ensure_nat(arg):
85151
"""
86152
Raise error if argument is not an natural number.
@@ -110,7 +176,7 @@ def _ensure_nat(arg):
110176
"--post-parser",
111177
{
112178
"action": RejectAction,
113-
"default": ClevisEncryptionOptions,
179+
"default": CreateOptions,
114180
"help": SUPPRESS,
115181
"nargs": "?",
116182
},
@@ -163,7 +229,75 @@ def _ensure_nat(arg):
163229
)
164230
],
165231
},
166-
)
232+
),
233+
(
234+
"integrity",
235+
{
236+
"description": (
237+
"Optional parameters for configuring integrity "
238+
"metadata pre-allocation"
239+
),
240+
"args": [
241+
(
242+
"--integrity",
243+
{
244+
"help": (
245+
"Integrity options for this pool. If "
246+
f'"{IntegrityOption.NO}" no space will '
247+
"be allocated for integrity metadata "
248+
"and it will never be possible to turn "
249+
"on integrity functionality for this "
250+
"pool. "
251+
f'If "{IntegrityOption.PRE_ALLOCATE}" '
252+
"then space will be allocated for "
253+
"integrity metadata and it will be "
254+
"possible to switch on integrity "
255+
"functionality in future. The default "
256+
'is "%(default)s".'
257+
),
258+
"choices": list(IntegrityOption),
259+
"default": IntegrityOption.PRE_ALLOCATE,
260+
"type": IntegrityOption,
261+
},
262+
),
263+
(
264+
"--journal-size",
265+
{
266+
"help": (
267+
"Size of integrity device's journal. "
268+
"Each block is written to this journal "
269+
"before being written to its address. "
270+
"The default is %(default)s."
271+
),
272+
"action": DefaultAction,
273+
"default": Range(128, MiB),
274+
"type": parse_range,
275+
},
276+
),
277+
(
278+
"--tag-spec",
279+
{
280+
"help": (
281+
"Integrity tag specification defining "
282+
"the size of the tag used to store a "
283+
"checksum or other value for each "
284+
"block on a device. All size "
285+
"specifications are in bits. The "
286+
"default is %(default)s."
287+
),
288+
"action": DefaultAction,
289+
"default": IntegrityTagSpec.B512,
290+
"choices": [
291+
x
292+
for x in list(IntegrityTagSpec)
293+
if x is not IntegrityTagSpec.B0
294+
],
295+
"type": IntegrityTagSpec,
296+
},
297+
),
298+
],
299+
},
300+
),
167301
],
168302
"args": [
169303
("pool_name", {"help": "Name of new pool"}),

src/stratis_cli/_parser/_range.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,13 @@ def __call__(self, parser, namespace, values, option_string=None):
8888
raise argparse.ArgumentError(
8989
self, f"Option {option_string} can not be assigned to or set."
9090
)
91+
92+
93+
class DefaultAction(argparse.Action):
94+
"""
95+
Detect if the default value was set.
96+
"""
97+
98+
def __call__(self, parser, namespace, values, option_string=None):
99+
setattr(namespace, self.dest, values)
100+
setattr(namespace, self.dest + "_default", False)

tests/whitebox/integration/pool/test_create.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# isort: LOCAL
1919
from stratis_cli import StratisCliErrorCodes
2020
from stratis_cli._errors import (
21+
StratisCliEngineError,
2122
StratisCliInUseSameTierError,
2223
StratisCliNameConflictError,
2324
)
@@ -165,3 +166,22 @@ def test_no_overprovision(self):
165166
self._MENU + [self._POOLNAME] + self._DEVICES + ["--no-overprovision"]
166167
)
167168
TEST_RUNNER(command_line)
169+
170+
171+
class Create7TestCase(SimTestCase):
172+
"""
173+
Test create with integrity options.
174+
"""
175+
176+
_MENU = ["--propagate", "pool", "create"]
177+
_DEVICES = _DEVICE_STRATEGY()
178+
_POOLNAME = "thispool"
179+
180+
def test_invalid_journal_size(self):
181+
"""
182+
Test creating with an invalid journal size.
183+
"""
184+
command_line = (
185+
self._MENU + [self._POOLNAME] + self._DEVICES + ["--journal-size=131079KiB"]
186+
)
187+
self.check_error(StratisCliEngineError, command_line, _ERROR)

tests/whitebox/integration/test_parser.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
_PARSE_ERROR = StratisCliErrorCodes.PARSE_ERROR
2424

2525

26-
class ParserTestCase(RunTestCase):
26+
class ParserTestCase(RunTestCase): # pylint: disable=too-many-public-methods
2727
"""
2828
Test parser behavior. The behavior should be identical, regardless of
2929
whether the "--propagate" flag is set. That is, stratis should never produce
@@ -221,6 +221,52 @@ def test_create_with_post_parser_set(self):
221221
for prefix in [[], ["--propagate"]]:
222222
self.check_system_exit(prefix + command_line, _PARSE_ERROR)
223223

224+
def test_create_with_bad_tag_value(self):
225+
"""
226+
Verify that an unrecognized tag value causes an error.
227+
"""
228+
command_line = [
229+
"pool",
230+
"create",
231+
"pn",
232+
"/dev/n",
233+
"--tag-spec=512",
234+
]
235+
for prefix in [[], ["--propagate"]]:
236+
self.check_system_exit(prefix + command_line, _PARSE_ERROR)
237+
238+
def test_create_with_integrity_no_journal_size(self):
239+
"""
240+
Verify that creating with integrity = no plus good journal-size
241+
results in a parse error.
242+
"""
243+
command_line = [
244+
"pool",
245+
"create",
246+
"pn",
247+
"/dev/n",
248+
"--integrity=no",
249+
"--journal-size=128MiB",
250+
]
251+
for prefix in [[], ["--propagate"]]:
252+
self.check_system_exit(prefix + command_line, _PARSE_ERROR)
253+
254+
def test_create_with_integrity_no_tag_spec(self):
255+
"""
256+
Verify that creating with integrity = no plus good tag-size
257+
results in a parse error.
258+
"""
259+
command_line = [
260+
"pool",
261+
"create",
262+
"pn",
263+
"/dev/n",
264+
"--integrity=no",
265+
"--tag-spec=32b",
266+
]
267+
for prefix in [[], ["--propagate"]]:
268+
self.check_system_exit(prefix + command_line, _PARSE_ERROR)
269+
224270
def test_stratis_list_filesystem_with_name_no_pool(self):
225271
"""
226272
We want to get a parse error if filesystem UUID is specified but no

0 commit comments

Comments
 (0)