]> git.proxmox.com Git - ceph.git/blame - ceph/src/python-common/ceph/tests/test_disk_selector.py
import ceph quincy 17.2.6
[ceph.git] / ceph / src / python-common / ceph / tests / test_disk_selector.py
CommitLineData
9f95a23c
TL
1# flake8: noqa
2import pytest
3
20effc67 4from ceph.deployment.drive_selection.matchers import _MatchInvalid
9f95a23c
TL
5from ceph.deployment.inventory import Devices, Device
6
f91f0fd5
TL
7from ceph.deployment.drive_group import DriveGroupSpec, DeviceSelection, \
8 DriveGroupValidationError
9f95a23c
TL
9
10from ceph.deployment import drive_selection
11from ceph.deployment.service_spec import PlacementSpec
12from ceph.tests.factories import InventoryFactory
13from ceph.tests.utils import _mk_inventory, _mk_device
14
15
16class TestMatcher(object):
17 """ Test Matcher base class
18 """
19
20 def test_get_disk_key_3(self):
21 """
22 virtual is False
23 key is found
24 retrun value of key is expected
25 """
26 disk_map = Device(path='/dev/vdb', sys_api={'foo': 'bar'})
27 ret = drive_selection.Matcher('foo', 'bar')._get_disk_key(disk_map)
28 assert ret is disk_map.sys_api.get('foo')
29
30 def test_get_disk_key_4(self):
31 """
32 virtual is False
33 key is not found
34 expect raise Exception
35 """
36 disk_map = Device(path='/dev/vdb')
37 with pytest.raises(Exception):
38 drive_selection.Matcher('bar', 'foo')._get_disk_key(disk_map)
39 pytest.fail("No disk_key found for foo or None")
40
41
42class TestSubstringMatcher(object):
43 def test_compare(self):
44 disk_dict = Device(path='/dev/vdb', sys_api=dict(model='samsung'))
45 matcher = drive_selection.SubstringMatcher('model', 'samsung')
46 ret = matcher.compare(disk_dict)
47 assert ret is True
48
49 def test_compare_false(self):
50 disk_dict = Device(path='/dev/vdb', sys_api=dict(model='nothing_matching'))
51 matcher = drive_selection.SubstringMatcher('model', 'samsung')
52 ret = matcher.compare(disk_dict)
53 assert ret is False
54
55
56class TestEqualityMatcher(object):
57 def test_compare(self):
58 disk_dict = Device(path='/dev/vdb', sys_api=dict(rotates='1'))
59 matcher = drive_selection.EqualityMatcher('rotates', '1')
60 ret = matcher.compare(disk_dict)
61 assert ret is True
62
63 def test_compare_false(self):
64 disk_dict = Device(path='/dev/vdb', sys_api=dict(rotates='1'))
65 matcher = drive_selection.EqualityMatcher('rotates', '0')
66 ret = matcher.compare(disk_dict)
67 assert ret is False
68
69
70class TestAllMatcher(object):
71 def test_compare(self):
72 disk_dict = Device(path='/dev/vdb')
73 matcher = drive_selection.AllMatcher('all', 'True')
74 ret = matcher.compare(disk_dict)
75 assert ret is True
76
77 def test_compare_value_not_true(self):
78 disk_dict = Device(path='/dev/vdb')
79 matcher = drive_selection.AllMatcher('all', 'False')
80 ret = matcher.compare(disk_dict)
81 assert ret is True
82
83
84class TestSizeMatcher(object):
85 def test_parse_filter_exact(self):
86 """ Testing exact notation with 20G """
87 matcher = drive_selection.SizeMatcher('size', '20G')
88 assert isinstance(matcher.exact, tuple)
89 assert matcher.exact[0] == '20'
90 assert matcher.exact[1] == 'GB'
91
92 def test_parse_filter_exact_GB_G(self):
93 """ Testing exact notation with 20G """
94 matcher = drive_selection.SizeMatcher('size', '20GB')
95 assert isinstance(matcher.exact, tuple)
96 assert matcher.exact[0] == '20'
97 assert matcher.exact[1] == 'GB'
98
99 def test_parse_filter_high_low(self):
100 """ Testing high-low notation with 20G:50G """
101
102 matcher = drive_selection.SizeMatcher('size', '20G:50G')
103 assert isinstance(matcher.exact, tuple)
104 assert matcher.low[0] == '20'
105 assert matcher.high[0] == '50'
106 assert matcher.low[1] == 'GB'
107 assert matcher.high[1] == 'GB'
108
109 def test_parse_filter_max_high(self):
110 """ Testing high notation with :50G """
111
112 matcher = drive_selection.SizeMatcher('size', ':50G')
113 assert isinstance(matcher.exact, tuple)
114 assert matcher.high[0] == '50'
115 assert matcher.high[1] == 'GB'
116
117 def test_parse_filter_min_low(self):
118 """ Testing low notation with 20G: """
119
120 matcher = drive_selection.SizeMatcher('size', '50G:')
121 assert isinstance(matcher.exact, tuple)
122 assert matcher.low[0] == '50'
123 assert matcher.low[1] == 'GB'
124
39ae355f
TL
125 def test_to_byte_KB(self):
126 """ I doubt anyone ever thought we'd need to understand KB """
127
128 ret = drive_selection.SizeMatcher('size', '4K').to_byte(('4', 'KB'))
129 assert ret == 4 * 1e+3
130
9f95a23c
TL
131 def test_to_byte_GB(self):
132 """ Pretty nonesense test.."""
133
134 ret = drive_selection.SizeMatcher('size', '10G').to_byte(('10', 'GB'))
135 assert ret == 10 * 1e+9
136
137 def test_to_byte_MB(self):
138 """ Pretty nonesense test.."""
139
140 ret = drive_selection.SizeMatcher('size', '10M').to_byte(('10', 'MB'))
141 assert ret == 10 * 1e+6
142
143 def test_to_byte_TB(self):
144 """ Pretty nonesense test.."""
145
146 ret = drive_selection.SizeMatcher('size', '10T').to_byte(('10', 'TB'))
147 assert ret == 10 * 1e+12
148
149 def test_to_byte_PB(self):
150 """ Expect to raise """
151
20effc67 152 with pytest.raises(_MatchInvalid):
9f95a23c
TL
153 drive_selection.SizeMatcher('size', '10P').to_byte(('10', 'PB'))
154 assert 'Unit \'P\' is not supported'
155
156 def test_compare_exact(self):
157
158 matcher = drive_selection.SizeMatcher('size', '20GB')
159 disk_dict = Device(path='/dev/vdb', sys_api=dict(size='20.00 GB'))
160 ret = matcher.compare(disk_dict)
161 assert ret is True
162
b3b6e05e
TL
163 def test_compare_exact_decimal(self):
164
165 matcher = drive_selection.SizeMatcher('size', '20.12GB')
166 disk_dict = Device(path='/dev/vdb', sys_api=dict(size='20.12 GB'))
167 ret = matcher.compare(disk_dict)
168 assert ret is True
169
9f95a23c
TL
170 @pytest.mark.parametrize("test_input,expected", [
171 ("1.00 GB", False),
172 ("20.00 GB", True),
173 ("50.00 GB", True),
174 ("100.00 GB", True),
175 ("101.00 GB", False),
176 ("1101.00 GB", False),
177 ])
178 def test_compare_high_low(self, test_input, expected):
179
180 matcher = drive_selection.SizeMatcher('size', '20GB:100GB')
181 disk_dict = Device(path='/dev/vdb', sys_api=dict(size=test_input))
182 ret = matcher.compare(disk_dict)
183 assert ret is expected
184
185 @pytest.mark.parametrize("test_input,expected", [
186 ("1.00 GB", True),
187 ("20.00 GB", True),
188 ("50.00 GB", True),
189 ("100.00 GB", False),
190 ("101.00 GB", False),
191 ("1101.00 GB", False),
192 ])
193 def test_compare_high(self, test_input, expected):
194
195 matcher = drive_selection.SizeMatcher('size', ':50GB')
196 disk_dict = Device(path='/dev/vdb', sys_api=dict(size=test_input))
197 ret = matcher.compare(disk_dict)
198 assert ret is expected
199
200 @pytest.mark.parametrize("test_input,expected", [
201 ("1.00 GB", False),
202 ("20.00 GB", False),
203 ("50.00 GB", True),
204 ("100.00 GB", True),
205 ("101.00 GB", True),
206 ("1101.00 GB", True),
207 ])
208 def test_compare_low(self, test_input, expected):
209
210 matcher = drive_selection.SizeMatcher('size', '50GB:')
211 disk_dict = Device(path='/dev/vdb', sys_api=dict(size=test_input))
212 ret = matcher.compare(disk_dict)
213 assert ret is expected
214
215 @pytest.mark.parametrize("test_input,expected", [
216 ("1.00 GB", False),
217 ("20.00 GB", False),
218 ("50.00 GB", False),
219 ("100.00 GB", False),
220 ("101.00 GB", False),
221 ("1101.00 GB", True),
222 ("9.10 TB", True),
223 ])
224 def test_compare_at_least_1TB(self, test_input, expected):
225
226 matcher = drive_selection.SizeMatcher('size', '1TB:')
227 disk_dict = Device(path='/dev/sdz', sys_api=dict(size=test_input))
228 ret = matcher.compare(disk_dict)
229 assert ret is expected
230
231 def test_compare_raise(self):
232
233 matcher = drive_selection.SizeMatcher('size', 'None')
234 disk_dict = Device(path='/dev/vdb', sys_api=dict(size='20.00 GB'))
235 with pytest.raises(Exception):
236 matcher.compare(disk_dict)
237 pytest.fail("Couldn't parse size")
238
239 @pytest.mark.parametrize("test_input,expected", [
240 ("10G", ('10', 'GB')),
241 ("20GB", ('20', 'GB')),
242 ("10g", ('10', 'GB')),
243 ("20gb", ('20', 'GB')),
244 ])
245 def test_get_k_v(self, test_input, expected):
246 assert drive_selection.SizeMatcher('size', '10G')._get_k_v(test_input) == expected
247
248 @pytest.mark.parametrize("test_input,expected", [
249 ("10G", ('GB')),
250 ("10g", ('GB')),
251 ("20GB", ('GB')),
252 ("20gb", ('GB')),
253 ("20TB", ('TB')),
254 ("20tb", ('TB')),
255 ("20T", ('TB')),
256 ("20t", ('TB')),
257 ("20MB", ('MB')),
258 ("20mb", ('MB')),
259 ("20M", ('MB')),
260 ("20m", ('MB')),
261 ])
262 def test_parse_suffix(self, test_input, expected):
263 assert drive_selection.SizeMatcher('size', '10G')._parse_suffix(test_input) == expected
264
265 @pytest.mark.parametrize("test_input,expected", [
266 ("G", 'GB'),
267 ("GB", 'GB'),
268 ("TB", 'TB'),
269 ("T", 'TB'),
270 ("MB", 'MB'),
271 ("M", 'MB'),
272 ])
273 def test_normalize_suffix(self, test_input, expected):
274
275 assert drive_selection.SizeMatcher('10G', 'size')._normalize_suffix(test_input) == expected
276
277 def test_normalize_suffix_raises(self):
278
20effc67 279 with pytest.raises(_MatchInvalid):
9f95a23c
TL
280 drive_selection.SizeMatcher('10P', 'size')._normalize_suffix("P")
281 pytest.fail("Unit 'P' not supported")
282
283
284class TestDriveGroup(object):
285 @pytest.fixture(scope='class')
286 def test_fix(self, empty=None):
287 def make_sample_data(empty=empty,
288 data_limit=0,
289 wal_limit=0,
290 db_limit=0,
291 osds_per_device='',
292 disk_format='bluestore'):
293 raw_sample_bluestore = {
294 'service_type': 'osd',
f91f0fd5 295 'service_id': 'foo',
9f95a23c
TL
296 'placement': {'host_pattern': 'data*'},
297 'data_devices': {
298 'size': '30G:50G',
299 'model': '42-RGB',
300 'vendor': 'samsung',
301 'limit': data_limit
302 },
303 'wal_devices': {
304 'model': 'fast',
305 'limit': wal_limit
306 },
307 'db_devices': {
308 'size': ':20G',
309 'limit': db_limit
310 },
311 'db_slots': 5,
312 'wal_slots': 5,
313 'block_wal_size': '5G',
314 'block_db_size': '10G',
315 'objectstore': disk_format,
316 'osds_per_device': osds_per_device,
317 'encrypted': True,
318 }
319 raw_sample_filestore = {
320 'service_type': 'osd',
f91f0fd5 321 'service_id': 'foo',
9f95a23c
TL
322 'placement': {'host_pattern': 'data*'},
323 'objectstore': 'filestore',
324 'data_devices': {
325 'size': '30G:50G',
326 'model': 'foo',
327 'vendor': '1x',
328 'limit': data_limit
329 },
330 'journal_devices': {
331 'size': ':20G'
332 },
333 'journal_size': '5G',
334 'osds_per_device': osds_per_device,
335 'encrypted': True,
336 }
337 if disk_format == 'filestore':
338 raw_sample = raw_sample_filestore
339 else:
340 raw_sample = raw_sample_bluestore
341
342 if empty:
343 raw_sample = {
344 'service_type': 'osd',
f91f0fd5 345 'service_id': 'foo',
f6b5b4d7
TL
346 'placement': {'host_pattern': 'data*'},
347 'data_devices': {
348 'all': True
349 },
9f95a23c
TL
350 }
351
352 dgo = DriveGroupSpec.from_json(raw_sample)
353 return dgo
354
355 return make_sample_data
356
357 def test_encryption_prop(self, test_fix):
358 test_fix = test_fix()
359 assert test_fix.encrypted is True
360
361 def test_encryption_prop_empty(self, test_fix):
362 test_fix = test_fix(empty=True)
363 assert test_fix.encrypted is False
364
365 def test_db_slots_prop(self, test_fix):
366 test_fix = test_fix()
367 assert test_fix.db_slots == 5
368
369 def test_db_slots_prop_empty(self, test_fix):
370 test_fix = test_fix(empty=True)
371 assert test_fix.db_slots is None
372
373 def test_wal_slots_prop(self, test_fix):
374 test_fix = test_fix()
375 assert test_fix.wal_slots == 5
376
377 def test_wal_slots_prop_empty(self, test_fix):
378 test_fix = test_fix(empty=True)
379 assert test_fix.wal_slots is None
380
381 def test_block_wal_size_prop(self, test_fix):
382 test_fix = test_fix()
f91f0fd5 383 assert test_fix.block_wal_size == '5G'
9f95a23c
TL
384
385 def test_block_wal_size_prop_empty(self, test_fix):
386 test_fix = test_fix(empty=True)
387 assert test_fix.block_wal_size is None
388
389 def test_block_db_size_prop(self, test_fix):
390 test_fix = test_fix()
f91f0fd5 391 assert test_fix.block_db_size == '10G'
9f95a23c
TL
392
393 def test_block_db_size_prop_empty(self, test_fix):
394 test_fix = test_fix(empty=True)
395 assert test_fix.block_db_size is None
396
397 def test_data_devices_prop(self, test_fix):
398 test_fix = test_fix()
399 assert test_fix.data_devices == DeviceSelection(
400 model='42-RGB',
401 size='30G:50G',
402 vendor='samsung',
403 limit=0,
404 )
405
406 def test_data_devices_prop_empty(self, test_fix):
407 test_fix = test_fix(empty=True)
f6b5b4d7 408 assert test_fix.db_devices is None
9f95a23c
TL
409
410 def test_db_devices_prop(self, test_fix):
411 test_fix = test_fix()
412 assert test_fix.db_devices == DeviceSelection(
413 size=':20G',
414 limit=0,
415 )
416
417 def test_db_devices_prop_empty(self, test_fix):
418 test_fix = test_fix(empty=True)
419 assert test_fix.db_devices is None
420
421 def test_wal_device_prop(self, test_fix):
422 test_fix = test_fix()
423 assert test_fix.wal_devices == DeviceSelection(
424 model='fast',
425 limit=0,
426 )
427
9f95a23c
TL
428 def test_wal_device_prop_empty(self, test_fix):
429 test_fix = test_fix(empty=True)
430 assert test_fix.wal_devices is None
431
9f95a23c
TL
432 def test_bluestore_format_prop(self, test_fix):
433 test_fix = test_fix(disk_format='bluestore')
434 assert test_fix.objectstore == 'bluestore'
435
436 def test_default_format_prop(self, test_fix):
437 test_fix = test_fix(empty=True)
438 assert test_fix.objectstore == 'bluestore'
439
9f95a23c
TL
440 def test_osds_per_device(self, test_fix):
441 test_fix = test_fix(osds_per_device='3')
442 assert test_fix.osds_per_device == '3'
443
444 def test_osds_per_device_default(self, test_fix):
445 test_fix = test_fix()
446 assert test_fix.osds_per_device == ''
447
448 def test_journal_size_empty(self, test_fix):
449 test_fix = test_fix(empty=True)
450 assert test_fix.journal_size is None
451
452 @pytest.fixture
453 def inventory(self, available=True):
454 def make_sample_data(available=available,
455 data_devices=10,
456 wal_devices=0,
457 db_devices=2,
458 human_readable_size_data='50.00 GB',
459 human_readable_size_wal='20.00 GB',
460 size=5368709121,
461 human_readable_size_db='20.00 GB'):
462 factory = InventoryFactory()
463 inventory_sample = []
464 data_disks = factory.produce(
465 pieces=data_devices,
466 available=available,
467 size=size,
468 human_readable_size=human_readable_size_data)
469 wal_disks = factory.produce(
470 pieces=wal_devices,
471 human_readable_size=human_readable_size_wal,
472 rotational='0',
473 model='ssd_type_model',
474 size=size,
475 available=available)
476 db_disks = factory.produce(
477 pieces=db_devices,
478 human_readable_size=human_readable_size_db,
479 rotational='0',
480 size=size,
481 model='ssd_type_model',
482 available=available)
483 inventory_sample.extend(data_disks)
484 inventory_sample.extend(wal_disks)
485 inventory_sample.extend(db_disks)
486
487 return Devices(devices=inventory_sample)
488
489 return make_sample_data
490
491
492class TestDriveSelection(object):
493
494 testdata = [
495 (
f91f0fd5
TL
496 DriveGroupSpec(
497 placement=PlacementSpec(host_pattern='*'),
498 service_id='foobar',
499 data_devices=DeviceSelection(all=True)),
9f95a23c
TL
500 _mk_inventory(_mk_device() * 5),
501 ['/dev/sda', '/dev/sdb', '/dev/sdc', '/dev/sdd', '/dev/sde'], []
502 ),
503 (
504 DriveGroupSpec(
505 placement=PlacementSpec(host_pattern='*'),
f91f0fd5 506 service_id='foobar',
9f95a23c
TL
507 data_devices=DeviceSelection(all=True, limit=3),
508 db_devices=DeviceSelection(all=True)
509 ),
510 _mk_inventory(_mk_device() * 5),
511 ['/dev/sda', '/dev/sdb', '/dev/sdc'], ['/dev/sdd', '/dev/sde']
512 ),
513 (
514 DriveGroupSpec(
515 placement=PlacementSpec(host_pattern='*'),
f91f0fd5 516 service_id='foobar',
9f95a23c
TL
517 data_devices=DeviceSelection(rotational=True),
518 db_devices=DeviceSelection(rotational=False)
519 ),
520 _mk_inventory(_mk_device(rotational=False) + _mk_device(rotational=True)),
521 ['/dev/sdb'], ['/dev/sda']
522 ),
523 (
524 DriveGroupSpec(
525 placement=PlacementSpec(host_pattern='*'),
f91f0fd5 526 service_id='foobar',
9f95a23c
TL
527 data_devices=DeviceSelection(rotational=True),
528 db_devices=DeviceSelection(rotational=False)
529 ),
530 _mk_inventory(_mk_device(rotational=True)*2 + _mk_device(rotational=False)),
531 ['/dev/sda', '/dev/sdb'], ['/dev/sdc']
532 ),
533 (
534 DriveGroupSpec(
535 placement=PlacementSpec(host_pattern='*'),
f91f0fd5 536 service_id='foobar',
9f95a23c
TL
537 data_devices=DeviceSelection(rotational=True),
538 db_devices=DeviceSelection(rotational=False)
539 ),
540 _mk_inventory(_mk_device(rotational=True)*2),
541 ['/dev/sda', '/dev/sdb'], []
542 ),
543 ]
544
545 @pytest.mark.parametrize("spec,inventory,expected_data,expected_db", testdata)
546 def test_disk_selection(self, spec, inventory, expected_data, expected_db):
547 sel = drive_selection.DriveSelection(spec, inventory)
548 assert [d.path for d in sel.data_devices()] == expected_data
549 assert [d.path for d in sel.db_devices()] == expected_db
20effc67
TL
550
551 def test_disk_selection_raise(self):
552 spec = DriveGroupSpec(
553 placement=PlacementSpec(host_pattern='*'),
554 service_id='foobar',
555 data_devices=DeviceSelection(size='wrong'),
556 )
557 inventory = _mk_inventory(_mk_device(rotational=True)*2)
558 m = 'Failed to validate OSD spec "foobar.data_devices": No filters applied'
559 with pytest.raises(DriveGroupValidationError, match=m):
560 drive_selection.DriveSelection(spec, inventory)