]>
git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/tests/api/test_lvm.py
fe2fe8b577c633ccc325f82e2c334a79e57ba2f3
3 from mock
.mock
import patch
4 from ceph_volume
import process
, exceptions
5 from ceph_volume
.api
import lvm
as api
8 class TestParseTags(object):
10 def test_no_tags_means_empty_dict(self
):
11 result
= api
.parse_tags('')
14 def test_single_tag_gets_parsed(self
):
15 result
= api
.parse_tags('ceph.osd_something=1')
16 assert result
== {'ceph.osd_something': '1'}
18 def test_non_ceph_tags_are_skipped(self
):
19 result
= api
.parse_tags('foo')
22 def test_mixed_non_ceph_tags(self
):
23 result
= api
.parse_tags('foo,ceph.bar=1')
24 assert result
== {'ceph.bar': '1'}
26 def test_multiple_csv_expands_in_dict(self
):
27 result
= api
.parse_tags('ceph.osd_something=1,ceph.foo=2,ceph.fsid=0000')
28 # assert them piecemeal to avoid the un-ordered dict nature
29 assert result
['ceph.osd_something'] == '1'
30 assert result
['ceph.foo'] == '2'
31 assert result
['ceph.fsid'] == '0000'
34 class TestVolume(object):
36 def test_is_ceph_device(self
):
37 lv_tags
= "ceph.type=data,ceph.osd_id=0"
38 osd
= api
.Volume(lv_name
='osd/volume', lv_tags
=lv_tags
)
39 assert api
.is_ceph_device(osd
)
41 @pytest.mark
.parametrize('dev',[
43 api
.VolumeGroup(vg_name
='foo'),
44 api
.Volume(lv_name
='vg/no_osd', lv_tags
='', lv_path
='lv/path'),
45 api
.Volume(lv_name
='vg/no_osd', lv_tags
='ceph.osd_id=null', lv_path
='lv/path'),
48 def test_is_not_ceph_device(self
, dev
):
49 assert not api
.is_ceph_device(dev
)
51 def test_no_empty_lv_name(self
):
52 with pytest
.raises(ValueError):
53 api
.Volume(lv_name
='', lv_tags
='')
56 class TestVolumeGroup(object):
58 def test_volume_group_no_empty_name(self
):
59 with pytest
.raises(ValueError):
60 api
.VolumeGroup(vg_name
='')
63 class TestVolumeGroupFree(object):
65 def test_integer_gets_produced(self
):
66 vg
= api
.VolumeGroup(vg_name
='nosize', vg_free_count
=100, vg_extent_size
=4194304)
67 assert vg
.free
== 100 * 4194304
70 class TestCreateLVs(object):
73 self
.vg
= api
.VolumeGroup(vg_name
='ceph',
74 vg_extent_size
=1073741824,
75 vg_extent_count
=99999999,
78 def test_creates_correct_lv_number_from_parts(self
, monkeypatch
):
79 monkeypatch
.setattr('ceph_volume.api.lvm.create_lv', lambda *a
, **kw
: (a
, kw
))
80 lvs
= api
.create_lvs(self
.vg
, parts
=4)
83 def test_suffixes_the_size_arg(self
, monkeypatch
):
84 monkeypatch
.setattr('ceph_volume.api.lvm.create_lv', lambda *a
, **kw
: (a
, kw
))
85 lvs
= api
.create_lvs(self
.vg
, parts
=4)
86 assert lvs
[0][1]['extents'] == 249
88 def test_only_uses_free_size(self
, monkeypatch
):
89 monkeypatch
.setattr('ceph_volume.api.lvm.create_lv', lambda *a
, **kw
: (a
, kw
))
90 vg
= api
.VolumeGroup(vg_name
='ceph',
91 vg_extent_size
=1073741824,
92 vg_extent_count
=99999999,
94 lvs
= api
.create_lvs(vg
, parts
=4)
95 assert lvs
[0][1]['extents'] == 250
97 def test_null_tags_are_set_by_default(self
, monkeypatch
):
98 monkeypatch
.setattr('ceph_volume.api.lvm.create_lv', lambda *a
, **kw
: (a
, kw
))
99 kwargs
= api
.create_lvs(self
.vg
, parts
=4)[0][1]
100 assert list(kwargs
['tags'].values()) == ['null', 'null', 'null', 'null']
102 def test_fallback_to_one_part(self
, monkeypatch
):
103 monkeypatch
.setattr('ceph_volume.api.lvm.create_lv', lambda *a
, **kw
: (a
, kw
))
104 lvs
= api
.create_lvs(self
.vg
)
108 class TestVolumeGroupSizing(object):
111 self
.vg
= api
.VolumeGroup(vg_name
='ceph',
112 vg_extent_size
=1073741824,
115 def test_parts_and_size_errors(self
):
116 with pytest
.raises(ValueError) as error
:
117 self
.vg
.sizing(parts
=4, size
=10)
118 assert "Cannot process sizing" in str(error
.value
)
120 def test_zero_parts_produces_100_percent(self
):
121 result
= self
.vg
.sizing(parts
=0)
122 assert result
['percentages'] == 100
124 def test_two_parts_produces_50_percent(self
):
125 result
= self
.vg
.sizing(parts
=2)
126 assert result
['percentages'] == 50
128 def test_two_parts_produces_half_size(self
):
129 result
= self
.vg
.sizing(parts
=2)
130 assert result
['sizes'] == 512
132 def test_half_size_produces_round_sizes(self
):
133 result
= self
.vg
.sizing(size
=512)
134 assert result
['sizes'] == 512
135 assert result
['percentages'] == 50
136 assert result
['parts'] == 2
138 def test_bit_more_than_half_size_allocates_full_size(self
):
139 # 513 can't allocate more than 1, so it just fallsback to using the
141 result
= self
.vg
.sizing(size
=513)
142 assert result
['sizes'] == 1024
143 assert result
['percentages'] == 100
144 assert result
['parts'] == 1
146 def test_extents_are_halfed_rounded_down(self
):
147 result
= self
.vg
.sizing(size
=512)
148 assert result
['extents'] == 512
150 def test_bit_less_size_rounds_down(self
):
151 result
= self
.vg
.sizing(size
=129)
152 assert result
['sizes'] == 146
153 assert result
['percentages'] == 14
154 assert result
['parts'] == 7
156 def test_unable_to_allocate_past_free_size(self
):
157 with pytest
.raises(exceptions
.SizeAllocationError
):
158 self
.vg
.sizing(size
=2048)
161 class TestRemoveLV(object):
163 def test_removes_lv(self
, monkeypatch
):
164 def mock_call(cmd
, **kw
):
166 monkeypatch
.setattr(process
, 'call', mock_call
)
167 assert api
.remove_lv("vg/lv")
169 def test_removes_lv_object(self
, fake_call
):
170 foo_volume
= api
.Volume(lv_name
='foo', lv_path
='/path', vg_name
='foo_group', lv_tags
='')
171 api
.remove_lv(foo_volume
)
172 # last argument from the list passed to process.call
173 assert fake_call
.calls
[0]['args'][0][-1] == '/path'
175 def test_fails_to_remove_lv(self
, monkeypatch
):
176 def mock_call(cmd
, **kw
):
178 monkeypatch
.setattr(process
, 'call', mock_call
)
179 with pytest
.raises(RuntimeError):
180 api
.remove_lv("vg/lv")
183 class TestCreateLV(object):
186 self
.foo_volume
= api
.Volume(lv_name
='foo', lv_path
='/path', vg_name
='foo_group', lv_tags
='')
187 self
.foo_group
= api
.VolumeGroup(vg_name
='foo_group',
188 vg_extent_size
=4194304,
191 @patch('ceph_volume.api.lvm.process.run')
192 @patch('ceph_volume.api.lvm.process.call')
193 @patch('ceph_volume.api.lvm.get_first_lv')
194 def test_uses_size(self
, m_get_first_lv
, m_call
, m_run
, monkeypatch
):
195 m_get_first_lv
.return_value
= self
.foo_volume
196 api
.create_lv('foo', 0, vg
=self
.foo_group
, size
=5368709120, tags
={'ceph.type': 'data'})
197 expected
= ['lvcreate', '--yes', '-l', '1280', '-n', 'foo-0', 'foo_group']
198 m_run
.assert_called_with(expected
)
200 @patch('ceph_volume.api.lvm.process.run')
201 @patch('ceph_volume.api.lvm.process.call')
202 @patch('ceph_volume.api.lvm.get_first_lv')
203 def test_uses_extents(self
, m_get_first_lv
, m_call
, m_run
, monkeypatch
):
204 m_get_first_lv
.return_value
= self
.foo_volume
205 api
.create_lv('foo', 0, vg
=self
.foo_group
, extents
='50', tags
={'ceph.type': 'data'})
206 expected
= ['lvcreate', '--yes', '-l', '50', '-n', 'foo-0', 'foo_group']
207 m_run
.assert_called_with(expected
)
209 @pytest.mark
.parametrize("test_input,expected",
212 @patch('ceph_volume.api.lvm.process.run')
213 @patch('ceph_volume.api.lvm.process.call')
214 @patch('ceph_volume.api.lvm.get_first_lv')
215 def test_uses_slots(self
, m_get_first_lv
, m_call
, m_run
, monkeypatch
, test_input
, expected
):
216 m_get_first_lv
.return_value
= self
.foo_volume
217 api
.create_lv('foo', 0, vg
=self
.foo_group
, slots
=test_input
, tags
={'ceph.type': 'data'})
218 expected
= ['lvcreate', '--yes', '-l', str(expected
), '-n', 'foo-0', 'foo_group']
219 m_run
.assert_called_with(expected
)
221 @patch('ceph_volume.api.lvm.process.run')
222 @patch('ceph_volume.api.lvm.process.call')
223 @patch('ceph_volume.api.lvm.get_first_lv')
224 def test_uses_all(self
, m_get_first_lv
, m_call
, m_run
, monkeypatch
):
225 m_get_first_lv
.return_value
= self
.foo_volume
226 api
.create_lv('foo', 0, vg
=self
.foo_group
, tags
={'ceph.type': 'data'})
227 expected
= ['lvcreate', '--yes', '-l', '100%FREE', '-n', 'foo-0', 'foo_group']
228 m_run
.assert_called_with(expected
)
230 @patch('ceph_volume.api.lvm.process.run')
231 @patch('ceph_volume.api.lvm.process.call')
232 @patch('ceph_volume.api.lvm.Volume.set_tags')
233 @patch('ceph_volume.api.lvm.get_first_lv')
234 def test_calls_to_set_tags_default(self
, m_get_first_lv
, m_set_tags
, m_call
, m_run
, monkeypatch
):
235 m_get_first_lv
.return_value
= self
.foo_volume
236 api
.create_lv('foo', 0, vg
=self
.foo_group
)
238 "ceph.osd_id": "null",
240 "ceph.cluster_fsid": "null",
241 "ceph.osd_fsid": "null",
243 m_set_tags
.assert_called_with(tags
)
245 @patch('ceph_volume.api.lvm.process.run')
246 @patch('ceph_volume.api.lvm.process.call')
247 @patch('ceph_volume.api.lvm.Volume.set_tags')
248 @patch('ceph_volume.api.lvm.get_first_lv')
249 def test_calls_to_set_tags_arg(self
, m_get_first_lv
, m_set_tags
, m_call
, m_run
, monkeypatch
):
250 m_get_first_lv
.return_value
= self
.foo_volume
251 api
.create_lv('foo', 0, vg
=self
.foo_group
, tags
={'ceph.type': 'data'})
254 "ceph.data_device": "/path"
256 m_set_tags
.assert_called_with(tags
)
258 @patch('ceph_volume.api.lvm.process.run')
259 @patch('ceph_volume.api.lvm.process.call')
260 @patch('ceph_volume.api.lvm.get_device_vgs')
261 @patch('ceph_volume.api.lvm.create_vg')
262 @patch('ceph_volume.api.lvm.get_first_lv')
263 def test_create_vg(self
, m_get_first_lv
, m_create_vg
, m_get_device_vgs
, m_call
,
265 m_get_first_lv
.return_value
= self
.foo_volume
266 m_get_device_vgs
.return_value
= []
267 api
.create_lv('foo', 0, device
='dev/foo', size
='5G', tags
={'ceph.type': 'data'})
268 m_create_vg
.assert_called_with('dev/foo', name_prefix
='ceph')
271 class TestTags(object):
274 self
.foo_volume_clean
= api
.Volume(lv_name
='foo_clean', lv_path
='/pathclean',
277 self
.foo_volume
= api
.Volume(lv_name
='foo', lv_path
='/path',
279 lv_tags
='ceph.foo0=bar0,ceph.foo1=bar1,ceph.foo2=bar2')
281 def test_set_tag(self
, monkeypatch
, capture
):
282 monkeypatch
.setattr(process
, 'run', capture
)
283 monkeypatch
.setattr(process
, 'call', capture
)
284 self
.foo_volume_clean
.set_tag('foo', 'bar')
285 expected
= ['lvchange', '--addtag', 'foo=bar', '/pathclean']
286 assert capture
.calls
[0]['args'][0] == expected
287 assert self
.foo_volume_clean
.tags
== {'foo': 'bar'}
289 def test_set_clear_tag(self
, monkeypatch
, capture
):
290 monkeypatch
.setattr(process
, 'run', capture
)
291 monkeypatch
.setattr(process
, 'call', capture
)
292 self
.foo_volume_clean
.set_tag('foo', 'bar')
293 assert self
.foo_volume_clean
.tags
== {'foo': 'bar'}
294 self
.foo_volume_clean
.clear_tag('foo')
295 expected
= ['lvchange', '--deltag', 'foo=bar', '/pathclean']
296 assert self
.foo_volume_clean
.tags
== {}
297 assert capture
.calls
[1]['args'][0] == expected
299 def test_set_tags(self
, monkeypatch
, capture
):
300 monkeypatch
.setattr(process
, 'run', capture
)
301 monkeypatch
.setattr(process
, 'call', capture
)
302 tags
= {'ceph.foo0': 'bar0', 'ceph.foo1': 'bar1', 'ceph.foo2': 'bar2'}
303 assert self
.foo_volume
.tags
== tags
305 tags
= {'ceph.foo0': 'bar0', 'ceph.foo1': 'baz1', 'ceph.foo2': 'baz2'}
306 self
.foo_volume
.set_tags(tags
)
307 assert self
.foo_volume
.tags
== tags
309 self
.foo_volume
.set_tag('ceph.foo1', 'other1')
310 tags
['ceph.foo1'] = 'other1'
311 assert self
.foo_volume
.tags
== tags
314 sorted(['lvchange', '--deltag', 'ceph.foo0=bar0', '--deltag',
315 'ceph.foo1=bar1', '--deltag', 'ceph.foo2=bar2', '/path']),
316 sorted(['lvchange', '--deltag', 'ceph.foo1=baz1', '/path']),
317 sorted(['lvchange', '--addtag', 'ceph.foo0=bar0', '--addtag',
318 'ceph.foo1=baz1', '--addtag', 'ceph.foo2=baz2', '/path']),
319 sorted(['lvchange', '--addtag', 'ceph.foo1=other1', '/path']),
321 # The order isn't guaranted
322 for call
in capture
.calls
:
323 assert sorted(call
['args'][0]) in expected
324 assert len(capture
.calls
) == len(expected
)
326 def test_clear_tags(self
, monkeypatch
, capture
):
327 monkeypatch
.setattr(process
, 'run', capture
)
328 monkeypatch
.setattr(process
, 'call', capture
)
329 tags
= {'ceph.foo0': 'bar0', 'ceph.foo1': 'bar1', 'ceph.foo2': 'bar2'}
331 self
.foo_volume_clean
.set_tags(tags
)
332 assert self
.foo_volume_clean
.tags
== tags
333 self
.foo_volume_clean
.clear_tags()
334 assert self
.foo_volume_clean
.tags
== {}
337 sorted(['lvchange', '--addtag', 'ceph.foo0=bar0', '--addtag',
338 'ceph.foo1=bar1', '--addtag', 'ceph.foo2=bar2',
340 sorted(['lvchange', '--deltag', 'ceph.foo0=bar0', '--deltag',
341 'ceph.foo1=bar1', '--deltag', 'ceph.foo2=bar2',
344 # The order isn't guaranted
345 for call
in capture
.calls
:
346 assert sorted(call
['args'][0]) in expected
347 assert len(capture
.calls
) == len(expected
)
350 class TestExtendVG(object):
353 self
.foo_volume
= api
.VolumeGroup(vg_name
='foo', lv_tags
='')
355 def test_uses_single_device_in_list(self
, monkeypatch
, fake_run
):
356 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
357 api
.extend_vg(self
.foo_volume
, ['/dev/sda'])
358 expected
= ['vgextend', '--force', '--yes', 'foo', '/dev/sda']
359 assert fake_run
.calls
[0]['args'][0] == expected
361 def test_uses_single_device(self
, monkeypatch
, fake_run
):
362 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
363 api
.extend_vg(self
.foo_volume
, '/dev/sda')
364 expected
= ['vgextend', '--force', '--yes', 'foo', '/dev/sda']
365 assert fake_run
.calls
[0]['args'][0] == expected
367 def test_uses_multiple_devices(self
, monkeypatch
, fake_run
):
368 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
369 api
.extend_vg(self
.foo_volume
, ['/dev/sda', '/dev/sdb'])
370 expected
= ['vgextend', '--force', '--yes', 'foo', '/dev/sda', '/dev/sdb']
371 assert fake_run
.calls
[0]['args'][0] == expected
374 class TestReduceVG(object):
377 self
.foo_volume
= api
.VolumeGroup(vg_name
='foo', lv_tags
='')
379 def test_uses_single_device_in_list(self
, monkeypatch
, fake_run
):
380 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
381 api
.reduce_vg(self
.foo_volume
, ['/dev/sda'])
382 expected
= ['vgreduce', '--force', '--yes', 'foo', '/dev/sda']
383 assert fake_run
.calls
[0]['args'][0] == expected
385 def test_uses_single_device(self
, monkeypatch
, fake_run
):
386 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
387 api
.reduce_vg(self
.foo_volume
, '/dev/sda')
388 expected
= ['vgreduce', '--force', '--yes', 'foo', '/dev/sda']
389 assert fake_run
.calls
[0]['args'][0] == expected
391 def test_uses_multiple_devices(self
, monkeypatch
, fake_run
):
392 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
393 api
.reduce_vg(self
.foo_volume
, ['/dev/sda', '/dev/sdb'])
394 expected
= ['vgreduce', '--force', '--yes', 'foo', '/dev/sda', '/dev/sdb']
395 assert fake_run
.calls
[0]['args'][0] == expected
398 class TestCreateVG(object):
401 self
.foo_volume
= api
.VolumeGroup(vg_name
='foo', lv_tags
='')
403 def test_no_name(self
, monkeypatch
, fake_run
):
404 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
405 api
.create_vg('/dev/sda')
406 result
= fake_run
.calls
[0]['args'][0]
407 assert '/dev/sda' in result
408 assert result
[-2].startswith('ceph-')
410 def test_devices_list(self
, monkeypatch
, fake_run
):
411 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
412 api
.create_vg(['/dev/sda', '/dev/sdb'], name
='ceph')
413 result
= fake_run
.calls
[0]['args'][0]
414 expected
= ['vgcreate', '--force', '--yes', 'ceph', '/dev/sda', '/dev/sdb']
415 assert result
== expected
417 def test_name_prefix(self
, monkeypatch
, fake_run
):
418 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
419 api
.create_vg('/dev/sda', name_prefix
='master')
420 result
= fake_run
.calls
[0]['args'][0]
421 assert '/dev/sda' in result
422 assert result
[-2].startswith('master-')
424 def test_specific_name(self
, monkeypatch
, fake_run
):
425 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
426 api
.create_vg('/dev/sda', name
='master')
427 result
= fake_run
.calls
[0]['args'][0]
428 assert '/dev/sda' in result
429 assert result
[-2] == 'master'
432 # The following tests are pretty gnarly. VDO detection is very convoluted and
433 # involves correlating information from device mappers, realpaths, slaves of
434 # those mappers, and parents or related mappers. This makes it very hard to
435 # patch nicely or keep tests short and readable. These tests are trying to
436 # ensure correctness, the better approach will be to do some functional testing
442 def disable_kvdo_path(monkeypatch
):
443 monkeypatch
.setattr('os.path.isdir', lambda x
, **kw
: False)
447 def enable_kvdo_path(monkeypatch
):
448 monkeypatch
.setattr('os.path.isdir', lambda x
, **kw
: True)
451 # Stub for os.listdir
454 class ListDir(object):
456 def __init__(self
, paths
):
458 self
._normalize
_paths
()
459 self
.listdir
= os
.listdir
461 def _normalize_paths(self
):
462 for k
, v
in self
.paths
.items():
463 self
.paths
[k
.rstrip('/')] = v
.rstrip('/')
465 def add(self
, original
, fake
):
466 self
.paths
[original
.rstrip('/')] = fake
.rstrip('/')
468 def __call__(self
, path
):
469 return self
.listdir(self
.paths
[path
.rstrip('/')])
472 @pytest.fixture(scope
='function')
473 def listdir(monkeypatch
):
474 def apply(paths
=None, stub
=None):
476 stub
= ListDir(paths
)
478 for original
, fake
in paths
.items():
479 stub
.add(original
, fake
)
481 monkeypatch
.setattr('os.listdir', stub
)
485 @pytest.fixture(scope
='function')
486 def makedirs(tmpdir
):
487 def create(directory
):
488 path
= os
.path
.join(str(tmpdir
), directory
)
491 create
.base
= str(tmpdir
)
495 class TestIsVdo(object):
497 def test_no_vdo_dir(self
, disable_kvdo_path
):
498 assert api
._is
_vdo
('/path') is False
500 def test_exceptions_return_false(self
, monkeypatch
):
503 monkeypatch
.setattr('ceph_volume.api.lvm._is_vdo', throw
)
504 assert api
.is_vdo('/path') == '0'
506 def test_is_vdo_returns_a_string(self
, monkeypatch
):
507 monkeypatch
.setattr('ceph_volume.api.lvm._is_vdo', lambda x
, **kw
: True)
508 assert api
.is_vdo('/path') == '1'
510 def test_kvdo_dir_no_devices(self
, makedirs
, enable_kvdo_path
, listdir
, monkeypatch
):
511 kvdo_path
= makedirs('sys/kvdo')
512 listdir(paths
={'/sys/kvdo': kvdo_path
})
513 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_slaves', lambda x
, **kw
: [])
514 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_parents', lambda x
, **kw
: [])
515 assert api
._is
_vdo
('/dev/mapper/vdo0') is False
517 def test_vdo_slaves_found_and_matched(self
, makedirs
, enable_kvdo_path
, listdir
, monkeypatch
):
518 kvdo_path
= makedirs('sys/kvdo')
519 listdir(paths
={'/sys/kvdo': kvdo_path
})
520 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_slaves', lambda x
, **kw
: ['/dev/dm-3'])
521 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_parents', lambda x
, **kw
: [])
522 assert api
._is
_vdo
('/dev/dm-3') is True
524 def test_vdo_parents_found_and_matched(self
, makedirs
, enable_kvdo_path
, listdir
, monkeypatch
):
525 kvdo_path
= makedirs('sys/kvdo')
526 listdir(paths
={'/sys/kvdo': kvdo_path
})
527 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_slaves', lambda x
, **kw
: [])
528 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_parents', lambda x
, **kw
: ['/dev/dm-4'])
529 assert api
._is
_vdo
('/dev/dm-4') is True
532 class TestVdoSlaves(object):
534 def test_slaves_are_not_found(self
, makedirs
, listdir
, monkeypatch
):
535 slaves_path
= makedirs('sys/block/vdo0/slaves')
536 listdir(paths
={'/sys/block/vdo0/slaves': slaves_path
})
537 monkeypatch
.setattr('ceph_volume.api.lvm.os.path.exists', lambda x
, **kw
: True)
538 result
= sorted(api
._vdo
_slaves
(['vdo0']))
539 assert '/dev/mapper/vdo0' in result
540 assert 'vdo0' in result
542 def test_slaves_are_found(self
, makedirs
, listdir
, monkeypatch
):
543 slaves_path
= makedirs('sys/block/vdo0/slaves')
544 makedirs('sys/block/vdo0/slaves/dm-4')
545 makedirs('dev/mapper/vdo0')
546 listdir(paths
={'/sys/block/vdo0/slaves': slaves_path
})
547 monkeypatch
.setattr('ceph_volume.api.lvm.os.path.exists', lambda x
, **kw
: True)
548 result
= sorted(api
._vdo
_slaves
(['vdo0']))
549 assert '/dev/dm-4' in result
550 assert 'dm-4' in result
553 class TestVDOParents(object):
555 def test_parents_are_found(self
, makedirs
, listdir
):
556 block_path
= makedirs('sys/block')
557 slaves_path
= makedirs('sys/block/dm-4/slaves')
558 makedirs('sys/block/dm-4/slaves/dm-3')
560 '/sys/block/dm-4/slaves': slaves_path
,
561 '/sys/block': block_path
})
562 result
= api
._vdo
_parents
(['dm-3'])
563 assert '/dev/dm-4' in result
564 assert 'dm-4' in result
566 def test_parents_are_not_found(self
, makedirs
, listdir
):
567 block_path
= makedirs('sys/block')
568 slaves_path
= makedirs('sys/block/dm-4/slaves')
569 makedirs('sys/block/dm-4/slaves/dm-5')
571 '/sys/block/dm-4/slaves': slaves_path
,
572 '/sys/block': block_path
})
573 result
= api
._vdo
_parents
(['dm-3'])
577 class TestSplitNameParser(object):
579 def test_keys_are_parsed_without_prefix(self
):
580 line
= ["DM_VG_NAME='/dev/mapper/vg';DM_LV_NAME='lv';DM_LV_LAYER=''"]
581 result
= api
._splitname
_parser
(line
)
582 assert result
['VG_NAME'] == 'vg'
583 assert result
['LV_NAME'] == 'lv'
584 assert result
['LV_LAYER'] == ''
586 def test_vg_name_sans_mapper(self
):
587 line
= ["DM_VG_NAME='/dev/mapper/vg';DM_LV_NAME='lv';DM_LV_LAYER=''"]
588 result
= api
._splitname
_parser
(line
)
589 assert '/dev/mapper' not in result
['VG_NAME']
592 class TestGetDeviceVgs(object):
594 @patch('ceph_volume.process.call')
595 @patch('ceph_volume.api.lvm._output_parser')
596 def test_get_device_vgs_with_empty_pv(self
, patched_output_parser
, pcall
):
597 patched_output_parser
.return_value
= [{'vg_name': ''}]
598 pcall
.return_value
= ('', '', '')
599 vgs
= api
.get_device_vgs('/dev/foo')
602 class TestGetDeviceLvs(object):
604 @patch('ceph_volume.process.call')
605 @patch('ceph_volume.api.lvm._output_parser')
606 def test_get_device_lvs_with_empty_vg(self
, patched_output_parser
, pcall
):
607 patched_output_parser
.return_value
= [{'lv_name': ''}]
608 pcall
.return_value
= ('', '', '')
609 vgs
= api
.get_device_lvs('/dev/foo')
613 # NOTE: api.convert_filters_to_str() and api.convert_tags_to_str() should get
614 # tested automatically while testing api.make_filters_lvmcmd_ready()
615 class TestMakeFiltersLVMCMDReady(object):
617 def test_with_no_filters_and_no_tags(self
):
618 retval
= api
.make_filters_lvmcmd_ready(None, None)
620 assert isinstance(retval
, str)
623 def test_with_filters_and_no_tags(self
):
624 filters
= {'lv_name': 'lv1', 'lv_path': '/dev/sda'}
626 retval
= api
.make_filters_lvmcmd_ready(filters
, None)
628 assert isinstance(retval
, str)
629 for k
, v
in filters
.items():
633 def test_with_no_filters_and_with_tags(self
):
634 tags
= {'ceph.type': 'data', 'ceph.osd_id': '0'}
636 retval
= api
.make_filters_lvmcmd_ready(None, tags
)
638 assert isinstance(retval
, str)
639 assert 'tags' in retval
640 for k
, v
in tags
.items():
643 assert retval
.find('tags') < retval
.find(k
) < retval
.find(v
)
645 def test_with_filters_and_tags(self
):
646 filters
= {'lv_name': 'lv1', 'lv_path': '/dev/sda'}
647 tags
= {'ceph.type': 'data', 'ceph.osd_id': '0'}
649 retval
= api
.make_filters_lvmcmd_ready(filters
, tags
)
651 assert isinstance(retval
, str)
652 for f
, t
in zip(filters
.items(), tags
.items()):
653 assert f
[0] in retval
654 assert f
[1] in retval
655 assert t
[0] in retval
656 assert t
[1] in retval
657 assert retval
.find(f
[0]) < retval
.find(f
[1]) < \
658 retval
.find('tags') < retval
.find(t
[0]) < retval
.find(t
[1])
661 class TestGetPVs(object):
663 def test_get_pvs(self
, monkeypatch
):
664 pv1
= api
.PVolume(pv_name
='/dev/sda', pv_uuid
='0000', pv_tags
={},
666 pv2
= api
.PVolume(pv_name
='/dev/sdb', pv_uuid
='0001', pv_tags
={},
669 stdout
= ['{};{};{};{};;'.format(pv1
.pv_name
, pv1
.pv_tags
, pv1
.pv_uuid
, pv1
.vg_name
),
670 '{};{};{};{};;'.format(pv2
.pv_name
, pv2
.pv_tags
, pv2
.pv_uuid
, pv2
.vg_name
)]
671 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
674 assert len(pvs_
) == len(pvs
)
675 for pv
, pv_
in zip(pvs
, pvs_
):
676 assert pv_
.pv_name
== pv
.pv_name
678 def test_get_pvs_single_pv(self
, monkeypatch
):
679 pv1
= api
.PVolume(pv_name
='/dev/sda', pv_uuid
='0000', pv_tags
={},
682 stdout
= ['{};;;;;;'.format(pv1
.pv_name
)]
683 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
686 assert len(pvs_
) == 1
687 assert pvs_
[0].pv_name
== pvs
[0].pv_name
689 def test_get_pvs_empty(self
, monkeypatch
):
690 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
691 assert api
.get_pvs() == []
694 class TestGetVGs(object):
696 def test_get_vgs(self
, monkeypatch
):
697 vg1
= api
.VolumeGroup(vg_name
='vg1')
698 vg2
= api
.VolumeGroup(vg_name
='vg2')
700 stdout
= ['{};;;;;;'.format(vg1
.vg_name
),
701 '{};;;;;;'.format(vg2
.vg_name
)]
702 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
705 assert len(vgs_
) == len(vgs
)
706 for vg
, vg_
in zip(vgs
, vgs_
):
707 assert vg_
.vg_name
== vg
.vg_name
709 def test_get_vgs_single_vg(self
, monkeypatch
):
710 vg1
= api
.VolumeGroup(vg_name
='vg'); vgs
= [vg1
]
711 stdout
= ['{};;;;;;'.format(vg1
.vg_name
)]
712 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
715 assert len(vgs_
) == 1
716 assert vgs_
[0].vg_name
== vgs
[0].vg_name
718 def test_get_vgs_empty(self
, monkeypatch
):
719 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
720 assert api
.get_vgs() == []
723 class TestGetLVs(object):
725 def test_get_lvs(self
, monkeypatch
):
726 lv1
= api
.Volume(lv_tags
='ceph.type=data', lv_path
='/dev/vg1/lv1',
727 lv_name
='lv1', vg_name
='vg1')
728 lv2
= api
.Volume(lv_tags
='ceph.type=data', lv_path
='/dev/vg2/lv2',
729 lv_name
='lv2', vg_name
='vg2')
731 stdout
= ['{};{};{};{}'.format(lv1
.lv_tags
, lv1
.lv_path
, lv1
.lv_name
,
733 '{};{};{};{}'.format(lv2
.lv_tags
, lv2
.lv_path
, lv2
.lv_name
,
735 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
738 assert len(lvs_
) == len(lvs
)
739 for lv
, lv_
in zip(lvs
, lvs_
):
740 assert lv
.__dict
__ == lv_
.__dict
__
742 def test_get_lvs_single_lv(self
, monkeypatch
):
743 stdout
= ['ceph.type=data;/dev/vg/lv;lv;vg']
744 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
746 lvs
.append((api
.Volume(lv_tags
='ceph.type=data',
747 lv_path
='/dev/vg/lv',
748 lv_name
='lv', vg_name
='vg')))
751 assert len(lvs_
) == len(lvs
)
752 assert lvs
[0].__dict
__ == lvs_
[0].__dict
__
754 def test_get_lvs_empty(self
, monkeypatch
):
755 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
756 assert api
.get_lvs() == []
759 class TestGetFirstPV(object):
761 def test_get_first_pv(self
, monkeypatch
):
762 pv1
= api
.PVolume(pv_name
='/dev/sda', pv_uuid
='0000', pv_tags
={},
764 pv2
= api
.PVolume(pv_name
='/dev/sdb', pv_uuid
='0001', pv_tags
={},
766 stdout
= ['{};{};{};{};;'.format(pv1
.pv_name
, pv1
.pv_tags
, pv1
.pv_uuid
, pv1
.vg_name
),
767 '{};{};{};{};;'.format(pv2
.pv_name
, pv2
.pv_tags
, pv2
.pv_uuid
, pv2
.vg_name
)]
768 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
770 pv_
= api
.get_first_pv()
771 assert isinstance(pv_
, api
.PVolume
)
772 assert pv_
.pv_name
== pv1
.pv_name
774 def test_get_first_pv_single_pv(self
, monkeypatch
):
775 pv
= api
.PVolume(pv_name
='/dev/sda', pv_uuid
='0000', pv_tags
={},
777 stdout
= ['{};;;;;;'.format(pv
.pv_name
)]
778 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
780 pv_
= api
.get_first_pv()
781 assert isinstance(pv_
, api
.PVolume
)
782 assert pv_
.pv_name
== pv
.pv_name
784 def test_get_first_pv_empty(self
, monkeypatch
):
785 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
786 assert api
.get_first_pv() == []
789 class TestGetFirstVG(object):
791 def test_get_first_vg(self
, monkeypatch
):
792 vg1
= api
.VolumeGroup(vg_name
='vg1')
793 vg2
= api
.VolumeGroup(vg_name
='vg2')
794 stdout
= ['{};;;;;;'.format(vg1
.vg_name
), '{};;;;;;'.format(vg2
.vg_name
)]
795 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
797 vg_
= api
.get_first_vg()
798 assert isinstance(vg_
, api
.VolumeGroup
)
799 assert vg_
.vg_name
== vg1
.vg_name
801 def test_get_first_vg_single_vg(self
, monkeypatch
):
802 vg
= api
.VolumeGroup(vg_name
='vg')
803 stdout
= ['{};;;;;;'.format(vg
.vg_name
)]
804 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
806 vg_
= api
.get_first_vg()
807 assert isinstance(vg_
, api
.VolumeGroup
)
808 assert vg_
.vg_name
== vg
.vg_name
810 def test_get_first_vg_empty(self
, monkeypatch
):
811 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
812 vg_
= api
.get_first_vg()
816 class TestGetFirstLV(object):
818 def test_get_first_lv(self
, monkeypatch
):
819 lv1
= api
.Volume(lv_tags
='ceph.type=data', lv_path
='/dev/vg1/lv1',
820 lv_name
='lv1', vg_name
='vg1')
821 lv2
= api
.Volume(lv_tags
='ceph.type=data', lv_path
='/dev/vg2/lv2',
822 lv_name
='lv2', vg_name
='vg2')
823 stdout
= ['{};{};{};{}'.format(lv1
.lv_tags
, lv1
.lv_path
, lv1
.lv_name
,
825 '{};{};{};{}'.format(lv2
.lv_tags
, lv2
.lv_path
, lv2
.lv_name
,
827 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
829 lv_
= api
.get_first_lv()
830 assert isinstance(lv_
, api
.Volume
)
831 assert lv_
.lv_name
== lv1
.lv_name
833 def test_get_first_lv_single_lv(self
, monkeypatch
):
834 stdout
= ['ceph.type=data;/dev/vg/lv;lv;vg']
835 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
836 lv
= api
.Volume(lv_tags
='ceph.type=data',
837 lv_path
='/dev/vg/lv',
838 lv_name
='lv', vg_name
='vg')
840 lv_
= api
.get_first_lv()
841 assert isinstance(lv_
, api
.Volume
)
842 assert lv_
.lv_name
== lv
.lv_name
844 def test_get_first_lv_empty(self
, monkeypatch
):
845 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
846 assert api
.get_lvs() == []