]>
git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/tests/api/test_lvm.py
cd2f9d9cb42ef5542a448fd380c4e4896711b095
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,
192 @patch('ceph_volume.api.lvm.process.run')
193 @patch('ceph_volume.api.lvm.process.call')
194 @patch('ceph_volume.api.lvm.get_first_lv')
195 def test_uses_size(self
, m_get_first_lv
, m_call
, m_run
, monkeypatch
):
196 m_get_first_lv
.return_value
= self
.foo_volume
197 api
.create_lv('foo', 0, vg
=self
.foo_group
, size
=5368709120, tags
={'ceph.type': 'data'})
198 expected
= ['lvcreate', '--yes', '-l', '1280', '-n', 'foo-0', 'foo_group']
199 m_run
.assert_called_with(expected
)
201 @patch('ceph_volume.api.lvm.process.run')
202 @patch('ceph_volume.api.lvm.process.call')
203 @patch('ceph_volume.api.lvm.get_first_lv')
204 def test_uses_extents(self
, m_get_first_lv
, m_call
, m_run
, monkeypatch
):
205 m_get_first_lv
.return_value
= self
.foo_volume
206 api
.create_lv('foo', 0, vg
=self
.foo_group
, extents
='50', tags
={'ceph.type': 'data'})
207 expected
= ['lvcreate', '--yes', '-l', '50', '-n', 'foo-0', 'foo_group']
208 m_run
.assert_called_with(expected
)
210 @pytest.mark
.parametrize("test_input,expected",
213 @patch('ceph_volume.api.lvm.process.run')
214 @patch('ceph_volume.api.lvm.process.call')
215 @patch('ceph_volume.api.lvm.get_first_lv')
216 def test_uses_slots(self
, m_get_first_lv
, m_call
, m_run
, monkeypatch
, test_input
, expected
):
217 m_get_first_lv
.return_value
= self
.foo_volume
218 api
.create_lv('foo', 0, vg
=self
.foo_group
, slots
=test_input
, tags
={'ceph.type': 'data'})
219 expected
= ['lvcreate', '--yes', '-l', str(expected
), '-n', 'foo-0', 'foo_group']
220 m_run
.assert_called_with(expected
)
222 @patch('ceph_volume.api.lvm.process.run')
223 @patch('ceph_volume.api.lvm.process.call')
224 @patch('ceph_volume.api.lvm.get_first_lv')
225 def test_uses_all(self
, m_get_first_lv
, m_call
, m_run
, monkeypatch
):
226 m_get_first_lv
.return_value
= self
.foo_volume
227 api
.create_lv('foo', 0, vg
=self
.foo_group
, tags
={'ceph.type': 'data'})
228 expected
= ['lvcreate', '--yes', '-l', '100%FREE', '-n', 'foo-0', 'foo_group']
229 m_run
.assert_called_with(expected
)
231 @patch('ceph_volume.api.lvm.process.run')
232 @patch('ceph_volume.api.lvm.process.call')
233 @patch('ceph_volume.api.lvm.Volume.set_tags')
234 @patch('ceph_volume.api.lvm.get_first_lv')
235 def test_calls_to_set_tags_default(self
, m_get_first_lv
, m_set_tags
, m_call
, m_run
, monkeypatch
):
236 m_get_first_lv
.return_value
= self
.foo_volume
237 api
.create_lv('foo', 0, vg
=self
.foo_group
)
239 "ceph.osd_id": "null",
241 "ceph.cluster_fsid": "null",
242 "ceph.osd_fsid": "null",
244 m_set_tags
.assert_called_with(tags
)
246 @patch('ceph_volume.api.lvm.process.run')
247 @patch('ceph_volume.api.lvm.process.call')
248 @patch('ceph_volume.api.lvm.Volume.set_tags')
249 @patch('ceph_volume.api.lvm.get_first_lv')
250 def test_calls_to_set_tags_arg(self
, m_get_first_lv
, m_set_tags
, m_call
, m_run
, monkeypatch
):
251 m_get_first_lv
.return_value
= self
.foo_volume
252 api
.create_lv('foo', 0, vg
=self
.foo_group
, tags
={'ceph.type': 'data'})
255 "ceph.data_device": "/path"
257 m_set_tags
.assert_called_with(tags
)
259 @patch('ceph_volume.api.lvm.process.run')
260 @patch('ceph_volume.api.lvm.process.call')
261 @patch('ceph_volume.api.lvm.get_device_vgs')
262 @patch('ceph_volume.api.lvm.create_vg')
263 @patch('ceph_volume.api.lvm.get_first_lv')
264 def test_create_vg(self
, m_get_first_lv
, m_create_vg
, m_get_device_vgs
, m_call
,
266 m_get_first_lv
.return_value
= self
.foo_volume
267 m_get_device_vgs
.return_value
= []
268 api
.create_lv('foo', 0, device
='dev/foo', size
='5G', tags
={'ceph.type': 'data'})
269 m_create_vg
.assert_called_with('dev/foo', name_prefix
='ceph')
272 class TestTags(object):
275 self
.foo_volume_clean
= api
.Volume(lv_name
='foo_clean', lv_path
='/pathclean',
278 self
.foo_volume
= api
.Volume(lv_name
='foo', lv_path
='/path',
280 lv_tags
='ceph.foo0=bar0,ceph.foo1=bar1,ceph.foo2=bar2')
282 def test_set_tag(self
, monkeypatch
, capture
):
283 monkeypatch
.setattr(process
, 'run', capture
)
284 monkeypatch
.setattr(process
, 'call', capture
)
285 self
.foo_volume_clean
.set_tag('foo', 'bar')
286 expected
= ['lvchange', '--addtag', 'foo=bar', '/pathclean']
287 assert capture
.calls
[0]['args'][0] == expected
288 assert self
.foo_volume_clean
.tags
== {'foo': 'bar'}
290 def test_set_clear_tag(self
, monkeypatch
, capture
):
291 monkeypatch
.setattr(process
, 'run', capture
)
292 monkeypatch
.setattr(process
, 'call', capture
)
293 self
.foo_volume_clean
.set_tag('foo', 'bar')
294 assert self
.foo_volume_clean
.tags
== {'foo': 'bar'}
295 self
.foo_volume_clean
.clear_tag('foo')
296 expected
= ['lvchange', '--deltag', 'foo=bar', '/pathclean']
297 assert self
.foo_volume_clean
.tags
== {}
298 assert capture
.calls
[1]['args'][0] == expected
300 def test_set_tags(self
, monkeypatch
, capture
):
301 monkeypatch
.setattr(process
, 'run', capture
)
302 monkeypatch
.setattr(process
, 'call', capture
)
303 tags
= {'ceph.foo0': 'bar0', 'ceph.foo1': 'bar1', 'ceph.foo2': 'bar2'}
304 assert self
.foo_volume
.tags
== tags
306 tags
= {'ceph.foo0': 'bar0', 'ceph.foo1': 'baz1', 'ceph.foo2': 'baz2'}
307 self
.foo_volume
.set_tags(tags
)
308 assert self
.foo_volume
.tags
== tags
310 self
.foo_volume
.set_tag('ceph.foo1', 'other1')
311 tags
['ceph.foo1'] = 'other1'
312 assert self
.foo_volume
.tags
== tags
315 sorted(['lvchange', '--deltag', 'ceph.foo0=bar0', '--deltag',
316 'ceph.foo1=bar1', '--deltag', 'ceph.foo2=bar2', '/path']),
317 sorted(['lvchange', '--deltag', 'ceph.foo1=baz1', '/path']),
318 sorted(['lvchange', '--addtag', 'ceph.foo0=bar0', '--addtag',
319 'ceph.foo1=baz1', '--addtag', 'ceph.foo2=baz2', '/path']),
320 sorted(['lvchange', '--addtag', 'ceph.foo1=other1', '/path']),
322 # The order isn't guaranted
323 for call
in capture
.calls
:
324 assert sorted(call
['args'][0]) in expected
325 assert len(capture
.calls
) == len(expected
)
327 def test_clear_tags(self
, monkeypatch
, capture
):
328 monkeypatch
.setattr(process
, 'run', capture
)
329 monkeypatch
.setattr(process
, 'call', capture
)
330 tags
= {'ceph.foo0': 'bar0', 'ceph.foo1': 'bar1', 'ceph.foo2': 'bar2'}
332 self
.foo_volume_clean
.set_tags(tags
)
333 assert self
.foo_volume_clean
.tags
== tags
334 self
.foo_volume_clean
.clear_tags()
335 assert self
.foo_volume_clean
.tags
== {}
338 sorted(['lvchange', '--addtag', 'ceph.foo0=bar0', '--addtag',
339 'ceph.foo1=bar1', '--addtag', 'ceph.foo2=bar2',
341 sorted(['lvchange', '--deltag', 'ceph.foo0=bar0', '--deltag',
342 'ceph.foo1=bar1', '--deltag', 'ceph.foo2=bar2',
345 # The order isn't guaranted
346 for call
in capture
.calls
:
347 assert sorted(call
['args'][0]) in expected
348 assert len(capture
.calls
) == len(expected
)
351 class TestExtendVG(object):
354 self
.foo_volume
= api
.VolumeGroup(vg_name
='foo', lv_tags
='')
356 def test_uses_single_device_in_list(self
, monkeypatch
, fake_run
):
357 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
358 api
.extend_vg(self
.foo_volume
, ['/dev/sda'])
359 expected
= ['vgextend', '--force', '--yes', 'foo', '/dev/sda']
360 assert fake_run
.calls
[0]['args'][0] == expected
362 def test_uses_single_device(self
, monkeypatch
, fake_run
):
363 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
364 api
.extend_vg(self
.foo_volume
, '/dev/sda')
365 expected
= ['vgextend', '--force', '--yes', 'foo', '/dev/sda']
366 assert fake_run
.calls
[0]['args'][0] == expected
368 def test_uses_multiple_devices(self
, monkeypatch
, fake_run
):
369 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
370 api
.extend_vg(self
.foo_volume
, ['/dev/sda', '/dev/sdb'])
371 expected
= ['vgextend', '--force', '--yes', 'foo', '/dev/sda', '/dev/sdb']
372 assert fake_run
.calls
[0]['args'][0] == expected
375 class TestReduceVG(object):
378 self
.foo_volume
= api
.VolumeGroup(vg_name
='foo', lv_tags
='')
380 def test_uses_single_device_in_list(self
, monkeypatch
, fake_run
):
381 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
382 api
.reduce_vg(self
.foo_volume
, ['/dev/sda'])
383 expected
= ['vgreduce', '--force', '--yes', 'foo', '/dev/sda']
384 assert fake_run
.calls
[0]['args'][0] == expected
386 def test_uses_single_device(self
, monkeypatch
, fake_run
):
387 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
388 api
.reduce_vg(self
.foo_volume
, '/dev/sda')
389 expected
= ['vgreduce', '--force', '--yes', 'foo', '/dev/sda']
390 assert fake_run
.calls
[0]['args'][0] == expected
392 def test_uses_multiple_devices(self
, monkeypatch
, fake_run
):
393 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
394 api
.reduce_vg(self
.foo_volume
, ['/dev/sda', '/dev/sdb'])
395 expected
= ['vgreduce', '--force', '--yes', 'foo', '/dev/sda', '/dev/sdb']
396 assert fake_run
.calls
[0]['args'][0] == expected
399 class TestCreateVG(object):
402 self
.foo_volume
= api
.VolumeGroup(vg_name
='foo', lv_tags
='')
404 def test_no_name(self
, monkeypatch
, fake_run
):
405 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
406 api
.create_vg('/dev/sda')
407 result
= fake_run
.calls
[0]['args'][0]
408 assert '/dev/sda' in result
409 assert result
[-2].startswith('ceph-')
411 def test_devices_list(self
, monkeypatch
, fake_run
):
412 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
413 api
.create_vg(['/dev/sda', '/dev/sdb'], name
='ceph')
414 result
= fake_run
.calls
[0]['args'][0]
415 expected
= ['vgcreate', '--force', '--yes', 'ceph', '/dev/sda', '/dev/sdb']
416 assert result
== expected
418 def test_name_prefix(self
, monkeypatch
, fake_run
):
419 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
420 api
.create_vg('/dev/sda', name_prefix
='master')
421 result
= fake_run
.calls
[0]['args'][0]
422 assert '/dev/sda' in result
423 assert result
[-2].startswith('master-')
425 def test_specific_name(self
, monkeypatch
, fake_run
):
426 monkeypatch
.setattr(api
, 'get_first_vg', lambda **kw
: True)
427 api
.create_vg('/dev/sda', name
='master')
428 result
= fake_run
.calls
[0]['args'][0]
429 assert '/dev/sda' in result
430 assert result
[-2] == 'master'
433 # The following tests are pretty gnarly. VDO detection is very convoluted and
434 # involves correlating information from device mappers, realpaths, slaves of
435 # those mappers, and parents or related mappers. This makes it very hard to
436 # patch nicely or keep tests short and readable. These tests are trying to
437 # ensure correctness, the better approach will be to do some functional testing
443 def disable_kvdo_path(monkeypatch
):
444 monkeypatch
.setattr('os.path.isdir', lambda x
, **kw
: False)
448 def enable_kvdo_path(monkeypatch
):
449 monkeypatch
.setattr('os.path.isdir', lambda x
, **kw
: True)
452 # Stub for os.listdir
455 class ListDir(object):
457 def __init__(self
, paths
):
459 self
._normalize
_paths
()
460 self
.listdir
= os
.listdir
462 def _normalize_paths(self
):
463 for k
, v
in self
.paths
.items():
464 self
.paths
[k
.rstrip('/')] = v
.rstrip('/')
466 def add(self
, original
, fake
):
467 self
.paths
[original
.rstrip('/')] = fake
.rstrip('/')
469 def __call__(self
, path
):
470 return self
.listdir(self
.paths
[path
.rstrip('/')])
473 @pytest.fixture(scope
='function')
474 def listdir(monkeypatch
):
475 def apply(paths
=None, stub
=None):
477 stub
= ListDir(paths
)
479 for original
, fake
in paths
.items():
480 stub
.add(original
, fake
)
482 monkeypatch
.setattr('os.listdir', stub
)
486 @pytest.fixture(scope
='function')
487 def makedirs(tmpdir
):
488 def create(directory
):
489 path
= os
.path
.join(str(tmpdir
), directory
)
492 create
.base
= str(tmpdir
)
496 class TestIsVdo(object):
498 def test_no_vdo_dir(self
, disable_kvdo_path
):
499 assert api
._is
_vdo
('/path') is False
501 def test_exceptions_return_false(self
, monkeypatch
):
504 monkeypatch
.setattr('ceph_volume.api.lvm._is_vdo', throw
)
505 assert api
.is_vdo('/path') == '0'
507 def test_is_vdo_returns_a_string(self
, monkeypatch
):
508 monkeypatch
.setattr('ceph_volume.api.lvm._is_vdo', lambda x
, **kw
: True)
509 assert api
.is_vdo('/path') == '1'
511 def test_kvdo_dir_no_devices(self
, makedirs
, enable_kvdo_path
, listdir
, monkeypatch
):
512 kvdo_path
= makedirs('sys/kvdo')
513 listdir(paths
={'/sys/kvdo': kvdo_path
})
514 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_slaves', lambda x
, **kw
: [])
515 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_parents', lambda x
, **kw
: [])
516 assert api
._is
_vdo
('/dev/mapper/vdo0') is False
518 def test_vdo_slaves_found_and_matched(self
, makedirs
, enable_kvdo_path
, listdir
, monkeypatch
):
519 kvdo_path
= makedirs('sys/kvdo')
520 listdir(paths
={'/sys/kvdo': kvdo_path
})
521 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_slaves', lambda x
, **kw
: ['/dev/dm-3'])
522 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_parents', lambda x
, **kw
: [])
523 assert api
._is
_vdo
('/dev/dm-3') is True
525 def test_vdo_parents_found_and_matched(self
, makedirs
, enable_kvdo_path
, listdir
, monkeypatch
):
526 kvdo_path
= makedirs('sys/kvdo')
527 listdir(paths
={'/sys/kvdo': kvdo_path
})
528 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_slaves', lambda x
, **kw
: [])
529 monkeypatch
.setattr('ceph_volume.api.lvm._vdo_parents', lambda x
, **kw
: ['/dev/dm-4'])
530 assert api
._is
_vdo
('/dev/dm-4') is True
533 class TestVdoSlaves(object):
535 def test_slaves_are_not_found(self
, makedirs
, listdir
, monkeypatch
):
536 slaves_path
= makedirs('sys/block/vdo0/slaves')
537 listdir(paths
={'/sys/block/vdo0/slaves': slaves_path
})
538 monkeypatch
.setattr('ceph_volume.api.lvm.os.path.exists', lambda x
, **kw
: True)
539 result
= sorted(api
._vdo
_slaves
(['vdo0']))
540 assert '/dev/mapper/vdo0' in result
541 assert 'vdo0' in result
543 def test_slaves_are_found(self
, makedirs
, listdir
, monkeypatch
):
544 slaves_path
= makedirs('sys/block/vdo0/slaves')
545 makedirs('sys/block/vdo0/slaves/dm-4')
546 makedirs('dev/mapper/vdo0')
547 listdir(paths
={'/sys/block/vdo0/slaves': slaves_path
})
548 monkeypatch
.setattr('ceph_volume.api.lvm.os.path.exists', lambda x
, **kw
: True)
549 result
= sorted(api
._vdo
_slaves
(['vdo0']))
550 assert '/dev/dm-4' in result
551 assert 'dm-4' in result
554 class TestVDOParents(object):
556 def test_parents_are_found(self
, makedirs
, listdir
):
557 block_path
= makedirs('sys/block')
558 slaves_path
= makedirs('sys/block/dm-4/slaves')
559 makedirs('sys/block/dm-4/slaves/dm-3')
561 '/sys/block/dm-4/slaves': slaves_path
,
562 '/sys/block': block_path
})
563 result
= api
._vdo
_parents
(['dm-3'])
564 assert '/dev/dm-4' in result
565 assert 'dm-4' in result
567 def test_parents_are_not_found(self
, makedirs
, listdir
):
568 block_path
= makedirs('sys/block')
569 slaves_path
= makedirs('sys/block/dm-4/slaves')
570 makedirs('sys/block/dm-4/slaves/dm-5')
572 '/sys/block/dm-4/slaves': slaves_path
,
573 '/sys/block': block_path
})
574 result
= api
._vdo
_parents
(['dm-3'])
578 class TestSplitNameParser(object):
580 def test_keys_are_parsed_without_prefix(self
):
581 line
= ["DM_VG_NAME='/dev/mapper/vg';DM_LV_NAME='lv';DM_LV_LAYER=''"]
582 result
= api
._splitname
_parser
(line
)
583 assert result
['VG_NAME'] == 'vg'
584 assert result
['LV_NAME'] == 'lv'
585 assert result
['LV_LAYER'] == ''
587 def test_vg_name_sans_mapper(self
):
588 line
= ["DM_VG_NAME='/dev/mapper/vg';DM_LV_NAME='lv';DM_LV_LAYER=''"]
589 result
= api
._splitname
_parser
(line
)
590 assert '/dev/mapper' not in result
['VG_NAME']
593 class TestGetDeviceVgs(object):
595 @patch('ceph_volume.process.call')
596 @patch('ceph_volume.api.lvm._output_parser')
597 def test_get_device_vgs_with_empty_pv(self
, patched_output_parser
, pcall
):
598 patched_output_parser
.return_value
= [{'vg_name': ''}]
599 pcall
.return_value
= ('', '', '')
600 vgs
= api
.get_device_vgs('/dev/foo')
603 class TestGetDeviceLvs(object):
605 @patch('ceph_volume.process.call')
606 @patch('ceph_volume.api.lvm._output_parser')
607 def test_get_device_lvs_with_empty_vg(self
, patched_output_parser
, pcall
):
608 patched_output_parser
.return_value
= [{'lv_name': ''}]
609 pcall
.return_value
= ('', '', '')
610 vgs
= api
.get_device_lvs('/dev/foo')
614 # NOTE: api.convert_filters_to_str() and api.convert_tags_to_str() should get
615 # tested automatically while testing api.make_filters_lvmcmd_ready()
616 class TestMakeFiltersLVMCMDReady(object):
618 def test_with_no_filters_and_no_tags(self
):
619 retval
= api
.make_filters_lvmcmd_ready(None, None)
621 assert isinstance(retval
, str)
624 def test_with_filters_and_no_tags(self
):
625 filters
= {'lv_name': 'lv1', 'lv_path': '/dev/sda'}
627 retval
= api
.make_filters_lvmcmd_ready(filters
, None)
629 assert isinstance(retval
, str)
630 for k
, v
in filters
.items():
634 def test_with_no_filters_and_with_tags(self
):
635 tags
= {'ceph.type': 'data', 'ceph.osd_id': '0'}
637 retval
= api
.make_filters_lvmcmd_ready(None, tags
)
639 assert isinstance(retval
, str)
640 assert 'tags' in retval
641 for k
, v
in tags
.items():
644 assert retval
.find('tags') < retval
.find(k
) < retval
.find(v
)
646 def test_with_filters_and_tags(self
):
647 filters
= {'lv_name': 'lv1', 'lv_path': '/dev/sda'}
648 tags
= {'ceph.type': 'data', 'ceph.osd_id': '0'}
650 retval
= api
.make_filters_lvmcmd_ready(filters
, tags
)
652 assert isinstance(retval
, str)
653 for f
, t
in zip(filters
.items(), tags
.items()):
654 assert f
[0] in retval
655 assert f
[1] in retval
656 assert t
[0] in retval
657 assert t
[1] in retval
658 assert retval
.find(f
[0]) < retval
.find(f
[1]) < \
659 retval
.find('tags') < retval
.find(t
[0]) < retval
.find(t
[1])
662 class TestGetPVs(object):
664 def test_get_pvs(self
, monkeypatch
):
665 pv1
= api
.PVolume(pv_name
='/dev/sda', pv_uuid
='0000', pv_tags
={},
667 pv2
= api
.PVolume(pv_name
='/dev/sdb', pv_uuid
='0001', pv_tags
={},
670 stdout
= ['{};{};{};{};;'.format(pv1
.pv_name
, pv1
.pv_tags
, pv1
.pv_uuid
, pv1
.vg_name
),
671 '{};{};{};{};;'.format(pv2
.pv_name
, pv2
.pv_tags
, pv2
.pv_uuid
, pv2
.vg_name
)]
672 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
675 assert len(pvs_
) == len(pvs
)
676 for pv
, pv_
in zip(pvs
, pvs_
):
677 assert pv_
.pv_name
== pv
.pv_name
679 def test_get_pvs_single_pv(self
, monkeypatch
):
680 pv1
= api
.PVolume(pv_name
='/dev/sda', pv_uuid
='0000', pv_tags
={},
683 stdout
= ['{};;;;;;'.format(pv1
.pv_name
)]
684 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
687 assert len(pvs_
) == 1
688 assert pvs_
[0].pv_name
== pvs
[0].pv_name
690 def test_get_pvs_empty(self
, monkeypatch
):
691 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
692 assert api
.get_pvs() == []
695 class TestGetVGs(object):
697 def test_get_vgs(self
, monkeypatch
):
698 vg1
= api
.VolumeGroup(vg_name
='vg1')
699 vg2
= api
.VolumeGroup(vg_name
='vg2')
701 stdout
= ['{};;;;;;'.format(vg1
.vg_name
),
702 '{};;;;;;'.format(vg2
.vg_name
)]
703 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
706 assert len(vgs_
) == len(vgs
)
707 for vg
, vg_
in zip(vgs
, vgs_
):
708 assert vg_
.vg_name
== vg
.vg_name
710 def test_get_vgs_single_vg(self
, monkeypatch
):
711 vg1
= api
.VolumeGroup(vg_name
='vg'); vgs
= [vg1
]
712 stdout
= ['{};;;;;;'.format(vg1
.vg_name
)]
713 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
716 assert len(vgs_
) == 1
717 assert vgs_
[0].vg_name
== vgs
[0].vg_name
719 def test_get_vgs_empty(self
, monkeypatch
):
720 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
721 assert api
.get_vgs() == []
724 class TestGetLVs(object):
726 def test_get_lvs(self
, monkeypatch
):
727 lv1
= api
.Volume(lv_tags
='ceph.type=data', lv_path
='/dev/vg1/lv1',
728 lv_name
='lv1', vg_name
='vg1')
729 lv2
= api
.Volume(lv_tags
='ceph.type=data', lv_path
='/dev/vg2/lv2',
730 lv_name
='lv2', vg_name
='vg2')
732 stdout
= ['{};{};{};{}'.format(lv1
.lv_tags
, lv1
.lv_path
, lv1
.lv_name
,
734 '{};{};{};{}'.format(lv2
.lv_tags
, lv2
.lv_path
, lv2
.lv_name
,
736 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
739 assert len(lvs_
) == len(lvs
)
740 for lv
, lv_
in zip(lvs
, lvs_
):
741 assert lv
.__dict
__ == lv_
.__dict
__
743 def test_get_lvs_single_lv(self
, monkeypatch
):
744 stdout
= ['ceph.type=data;/dev/vg/lv;lv;vg']
745 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
747 lvs
.append((api
.Volume(lv_tags
='ceph.type=data',
748 lv_path
='/dev/vg/lv',
749 lv_name
='lv', vg_name
='vg')))
752 assert len(lvs_
) == len(lvs
)
753 assert lvs
[0].__dict
__ == lvs_
[0].__dict
__
755 def test_get_lvs_empty(self
, monkeypatch
):
756 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
757 assert api
.get_lvs() == []
760 class TestGetFirstPV(object):
762 def test_get_first_pv(self
, monkeypatch
):
763 pv1
= api
.PVolume(pv_name
='/dev/sda', pv_uuid
='0000', pv_tags
={},
765 pv2
= api
.PVolume(pv_name
='/dev/sdb', pv_uuid
='0001', pv_tags
={},
767 stdout
= ['{};{};{};{};;'.format(pv1
.pv_name
, pv1
.pv_tags
, pv1
.pv_uuid
, pv1
.vg_name
),
768 '{};{};{};{};;'.format(pv2
.pv_name
, pv2
.pv_tags
, pv2
.pv_uuid
, pv2
.vg_name
)]
769 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
771 pv_
= api
.get_first_pv()
772 assert isinstance(pv_
, api
.PVolume
)
773 assert pv_
.pv_name
== pv1
.pv_name
775 def test_get_first_pv_single_pv(self
, monkeypatch
):
776 pv
= api
.PVolume(pv_name
='/dev/sda', pv_uuid
='0000', pv_tags
={},
778 stdout
= ['{};;;;;;'.format(pv
.pv_name
)]
779 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
781 pv_
= api
.get_first_pv()
782 assert isinstance(pv_
, api
.PVolume
)
783 assert pv_
.pv_name
== pv
.pv_name
785 def test_get_first_pv_empty(self
, monkeypatch
):
786 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
787 assert api
.get_first_pv() == []
790 class TestGetFirstVG(object):
792 def test_get_first_vg(self
, monkeypatch
):
793 vg1
= api
.VolumeGroup(vg_name
='vg1')
794 vg2
= api
.VolumeGroup(vg_name
='vg2')
795 stdout
= ['{};;;;;;'.format(vg1
.vg_name
), '{};;;;;;'.format(vg2
.vg_name
)]
796 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
798 vg_
= api
.get_first_vg()
799 assert isinstance(vg_
, api
.VolumeGroup
)
800 assert vg_
.vg_name
== vg1
.vg_name
802 def test_get_first_vg_single_vg(self
, monkeypatch
):
803 vg
= api
.VolumeGroup(vg_name
='vg')
804 stdout
= ['{};;;;;;'.format(vg
.vg_name
)]
805 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
807 vg_
= api
.get_first_vg()
808 assert isinstance(vg_
, api
.VolumeGroup
)
809 assert vg_
.vg_name
== vg
.vg_name
811 def test_get_first_vg_empty(self
, monkeypatch
):
812 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
813 vg_
= api
.get_first_vg()
817 class TestGetFirstLV(object):
819 def test_get_first_lv(self
, monkeypatch
):
820 lv1
= api
.Volume(lv_tags
='ceph.type=data', lv_path
='/dev/vg1/lv1',
821 lv_name
='lv1', vg_name
='vg1')
822 lv2
= api
.Volume(lv_tags
='ceph.type=data', lv_path
='/dev/vg2/lv2',
823 lv_name
='lv2', vg_name
='vg2')
824 stdout
= ['{};{};{};{}'.format(lv1
.lv_tags
, lv1
.lv_path
, lv1
.lv_name
,
826 '{};{};{};{}'.format(lv2
.lv_tags
, lv2
.lv_path
, lv2
.lv_name
,
828 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
830 lv_
= api
.get_first_lv()
831 assert isinstance(lv_
, api
.Volume
)
832 assert lv_
.lv_name
== lv1
.lv_name
834 def test_get_first_lv_single_lv(self
, monkeypatch
):
835 stdout
= ['ceph.type=data;/dev/vg/lv;lv;vg']
836 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: (stdout
, '', 0))
837 lv
= api
.Volume(lv_tags
='ceph.type=data',
838 lv_path
='/dev/vg/lv',
839 lv_name
='lv', vg_name
='vg')
841 lv_
= api
.get_first_lv()
842 assert isinstance(lv_
, api
.Volume
)
843 assert lv_
.lv_name
== lv
.lv_name
845 def test_get_first_lv_empty(self
, monkeypatch
):
846 monkeypatch
.setattr(api
.process
, 'call', lambda x
,**kw
: ('', '', 0))
847 assert api
.get_lvs() == []