]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/nfs/tests/test_nfs.py
1fca67249e0d459710f521de349bd04db2be06bd
[ceph.git] / ceph / src / pybind / mgr / nfs / tests / test_nfs.py
1 # flake8: noqa
2 import json
3 import pytest
4 from typing import Optional, Tuple, Iterator, List, Any
5
6 from contextlib import contextmanager
7 from unittest import mock
8 from unittest.mock import MagicMock
9 from mgr_module import MgrModule, NFS_POOL_NAME
10
11 from rados import ObjectNotFound
12
13 from ceph.deployment.service_spec import NFSServiceSpec
14 from nfs import Module
15 from nfs.export import ExportMgr, normalize_path
16 from nfs.ganesha_conf import GaneshaConfParser, Export, RawBlock
17 from nfs.cluster import NFSCluster
18 from orchestrator import ServiceDescription, DaemonDescription, OrchResult
19
20
21 class TestNFS:
22 cluster_id = "foo"
23 export_1 = """
24 EXPORT {
25 Export_ID=1;
26 Protocols = 4;
27 Path = /;
28 Pseudo = /cephfs_a/;
29 Access_Type = RW;
30 Protocols = 4;
31 Attr_Expiration_Time = 0;
32 # Squash = root;
33
34 FSAL {
35 Name = CEPH;
36 Filesystem = "a";
37 User_Id = "ganesha";
38 # Secret_Access_Key = "YOUR SECRET KEY HERE";
39 }
40
41 CLIENT
42 {
43 Clients = 192.168.0.10, 192.168.1.0/8;
44 Squash = None;
45 }
46
47 CLIENT
48 {
49 Clients = 192.168.0.0/16;
50 Squash = All;
51 Access_Type = RO;
52 }
53 }
54 """
55
56 export_2 = """
57 EXPORT
58 {
59 Export_ID=2;
60 Path = "/";
61 Pseudo = "/rgw";
62 Access_Type = RW;
63 squash = AllAnonymous;
64 Protocols = 4, 3;
65 Transports = TCP, UDP;
66
67 FSAL {
68 Name = RGW;
69 User_Id = "nfs.foo.bucket";
70 Access_Key_Id ="the_access_key";
71 Secret_Access_Key = "the_secret_key";
72 }
73 }
74 """
75 export_3 = """
76 EXPORT {
77 FSAL {
78 name = "CEPH";
79 user_id = "nfs.foo.1";
80 filesystem = "a";
81 secret_access_key = "AQCjU+hgjyReLBAAddJa0Dza/ZHqjX5+JiePMA==";
82 }
83 export_id = 1;
84 path = "/";
85 pseudo = "/a";
86 access_type = "RW";
87 squash = "none";
88 attr_expiration_time = 0;
89 security_label = true;
90 protocols = 4;
91 transports = "TCP";
92 }
93 """
94
95 conf_nfs_foo = f'''
96 %url "rados://{NFS_POOL_NAME}/{cluster_id}/export-1"
97
98 %url "rados://{NFS_POOL_NAME}/{cluster_id}/export-2"'''
99
100 class RObject(object):
101 def __init__(self, key: str, raw: str) -> None:
102 self.key = key
103 self.raw = raw
104
105 def read(self, _: Optional[int]) -> bytes:
106 return self.raw.encode('utf-8')
107
108 def stat(self) -> Tuple[int, None]:
109 return len(self.raw), None
110
111 def _ioctx_write_full_mock(self, key: str, content: bytes) -> None:
112 if key not in self.temp_store[self.temp_store_namespace]:
113 self.temp_store[self.temp_store_namespace][key] = \
114 TestNFS.RObject(key, content.decode('utf-8'))
115 else:
116 self.temp_store[self.temp_store_namespace][key].raw = content.decode('utf-8')
117
118 def _ioctx_remove_mock(self, key: str) -> None:
119 del self.temp_store[self.temp_store_namespace][key]
120
121 def _ioctx_list_objects_mock(self) -> List['TestNFS.RObject']:
122 r = [obj for _, obj in self.temp_store[self.temp_store_namespace].items()]
123 return r
124
125 def _ioctl_stat_mock(self, key):
126 return self.temp_store[self.temp_store_namespace][key].stat()
127
128 def _ioctl_read_mock(self, key: str, size: Optional[Any] = None) -> bytes:
129 if key not in self.temp_store[self.temp_store_namespace]:
130 raise ObjectNotFound
131 return self.temp_store[self.temp_store_namespace][key].read(size)
132
133 def _ioctx_set_namespace_mock(self, namespace: str) -> None:
134 self.temp_store_namespace = namespace
135
136 def _reset_temp_store(self) -> None:
137 self.temp_store_namespace = None
138 self.temp_store = {
139 'foo': {
140 'export-1': TestNFS.RObject("export-1", self.export_1),
141 'export-2': TestNFS.RObject("export-2", self.export_2),
142 'conf-nfs.foo': TestNFS.RObject("conf-nfs.foo", self.conf_nfs_foo)
143 }
144 }
145
146 @contextmanager
147 def _mock_orchestrator(self, enable: bool) -> Iterator:
148 self.io_mock = MagicMock()
149 self.io_mock.set_namespace.side_effect = self._ioctx_set_namespace_mock
150 self.io_mock.read = self._ioctl_read_mock
151 self.io_mock.stat = self._ioctl_stat_mock
152 self.io_mock.list_objects.side_effect = self._ioctx_list_objects_mock
153 self.io_mock.write_full.side_effect = self._ioctx_write_full_mock
154 self.io_mock.remove_object.side_effect = self._ioctx_remove_mock
155
156 # mock nfs services
157 orch_nfs_services = [
158 ServiceDescription(spec=NFSServiceSpec(service_id=self.cluster_id))
159 ] if enable else []
160
161 orch_nfs_daemons = [
162 DaemonDescription('nfs', 'foo.mydaemon', 'myhostname')
163 ] if enable else []
164
165 def mock_exec(cls, args):
166 if args[1:3] == ['bucket', 'stats']:
167 bucket_info = {
168 "owner": "bucket_owner_user",
169 }
170 return 0, json.dumps(bucket_info), ''
171 u = {
172 "user_id": "abc",
173 "display_name": "foo",
174 "email": "",
175 "suspended": 0,
176 "max_buckets": 1000,
177 "subusers": [],
178 "keys": [
179 {
180 "user": "abc",
181 "access_key": "the_access_key",
182 "secret_key": "the_secret_key"
183 }
184 ],
185 "swift_keys": [],
186 "caps": [],
187 "op_mask": "read, write, delete",
188 "default_placement": "",
189 "default_storage_class": "",
190 "placement_tags": [],
191 "bucket_quota": {
192 "enabled": False,
193 "check_on_raw": False,
194 "max_size": -1,
195 "max_size_kb": 0,
196 "max_objects": -1
197 },
198 "user_quota": {
199 "enabled": False,
200 "check_on_raw": False,
201 "max_size": -1,
202 "max_size_kb": 0,
203 "max_objects": -1
204 },
205 "temp_url_keys": [],
206 "type": "rgw",
207 "mfa_ids": []
208 }
209 if args[2] == 'list':
210 return 0, json.dumps([u]), ''
211 return 0, json.dumps(u), ''
212
213 def mock_describe_service(cls, *args, **kwargs):
214 if kwargs['service_type'] == 'nfs':
215 return OrchResult(orch_nfs_services)
216 return OrchResult([])
217
218 def mock_list_daemons(cls, *args, **kwargs):
219 if kwargs['daemon_type'] == 'nfs':
220 return OrchResult(orch_nfs_daemons)
221 return OrchResult([])
222
223 with mock.patch('nfs.module.Module.describe_service', mock_describe_service) as describe_service, \
224 mock.patch('nfs.module.Module.list_daemons', mock_list_daemons) as list_daemons, \
225 mock.patch('nfs.module.Module.rados') as rados, \
226 mock.patch('nfs.export.available_clusters',
227 return_value=[self.cluster_id]), \
228 mock.patch('nfs.export.restart_nfs_service'), \
229 mock.patch('nfs.cluster.restart_nfs_service'), \
230 mock.patch.object(MgrModule, 'tool_exec', mock_exec), \
231 mock.patch('nfs.export.check_fs', return_value=True), \
232 mock.patch('nfs.ganesha_conf.check_fs', return_value=True), \
233 mock.patch('nfs.export.ExportMgr._create_user_key',
234 return_value='thekeyforclientabc'):
235
236 rados.open_ioctx.return_value.__enter__.return_value = self.io_mock
237 rados.open_ioctx.return_value.__exit__ = mock.Mock(return_value=None)
238
239 self._reset_temp_store()
240
241 yield
242
243 def test_parse_daemon_raw_config(self) -> None:
244 expected_daemon_config = [
245 RawBlock('NFS_CORE_PARAM', values={
246 "enable_nlm": False,
247 "enable_rquota": False,
248 "protocols": 4,
249 "nfs_port": 14000
250 }),
251 RawBlock('MDCACHE', values={
252 "dir_chunk": 0
253 }),
254 RawBlock('NFSV4', values={
255 "recoverybackend": "rados_cluster",
256 "minor_versions": [1, 2]
257 }),
258 RawBlock('RADOS_KV', values={
259 "pool": NFS_POOL_NAME,
260 "namespace": "vstart",
261 "userid": "vstart",
262 "nodeid": "a"
263 }),
264 RawBlock('RADOS_URLS', values={
265 "userid": "vstart",
266 "watch_url": f"'rados://{NFS_POOL_NAME}/vstart/conf-nfs.vstart'"
267 }),
268 RawBlock('%url', values={
269 "value": f"rados://{NFS_POOL_NAME}/vstart/conf-nfs.vstart"
270 })
271 ]
272 daemon_raw_config = """
273 NFS_CORE_PARAM {
274 Enable_NLM = false;
275 Enable_RQUOTA = false;
276 Protocols = 4;
277 NFS_Port = 14000;
278 }
279
280 MDCACHE {
281 Dir_Chunk = 0;
282 }
283
284 NFSv4 {
285 RecoveryBackend = rados_cluster;
286 Minor_Versions = 1, 2;
287 }
288
289 RADOS_KV {
290 pool = {};
291 namespace = vstart;
292 UserId = vstart;
293 nodeid = a;
294 }
295
296 RADOS_URLS {
297 Userid = vstart;
298 watch_url = 'rados://{}/vstart/conf-nfs.vstart';
299 }
300
301 %url rados://{}/vstart/conf-nfs.vstart
302 """.replace('{}', NFS_POOL_NAME)
303 daemon_config = GaneshaConfParser(daemon_raw_config).parse()
304 assert daemon_config == expected_daemon_config
305
306 def _validate_export_1(self, export: Export):
307 assert export.export_id == 1
308 assert export.path == "/"
309 assert export.pseudo == "/cephfs_a/"
310 assert export.access_type == "RW"
311 # assert export.squash == "root_squash" # probably correct value
312 assert export.squash == "no_root_squash"
313 assert export.protocols == [4]
314 # assert export.transports == {"TCP", "UDP"}
315 assert export.fsal.name == "CEPH"
316 assert export.fsal.user_id == "ganesha"
317 assert export.fsal.fs_name == "a"
318 assert export.fsal.sec_label_xattr == None
319 assert len(export.clients) == 2
320 assert export.clients[0].addresses == \
321 ["192.168.0.10", "192.168.1.0/8"]
322 # assert export.clients[0].squash == "no_root_squash" # probably correct value
323 assert export.clients[0].squash == "None"
324 assert export.clients[0].access_type is None
325 assert export.clients[1].addresses == ["192.168.0.0/16"]
326 # assert export.clients[1].squash == "all_squash" # probably correct value
327 assert export.clients[1].squash == "All"
328 assert export.clients[1].access_type == "RO"
329 assert export.cluster_id == 'foo'
330 assert export.attr_expiration_time == 0
331 # assert export.security_label == False # probably correct value
332 assert export.security_label == True
333
334 def test_export_parser_1(self) -> None:
335 blocks = GaneshaConfParser(self.export_1).parse()
336 assert isinstance(blocks, list)
337 assert len(blocks) == 1
338 export = Export.from_export_block(blocks[0], self.cluster_id)
339 self._validate_export_1(export)
340
341 def _validate_export_2(self, export: Export):
342 assert export.export_id == 2
343 assert export.path == "/"
344 assert export.pseudo == "/rgw"
345 assert export.access_type == "RW"
346 # assert export.squash == "all_squash" # probably correct value
347 assert export.squash == "AllAnonymous"
348 assert export.protocols == [4, 3]
349 assert set(export.transports) == {"TCP", "UDP"}
350 assert export.fsal.name == "RGW"
351 assert export.fsal.user_id == "nfs.foo.bucket"
352 assert export.fsal.access_key_id == "the_access_key"
353 assert export.fsal.secret_access_key == "the_secret_key"
354 assert len(export.clients) == 0
355 assert export.cluster_id == 'foo'
356
357 def test_export_parser_2(self) -> None:
358 blocks = GaneshaConfParser(self.export_2).parse()
359 assert isinstance(blocks, list)
360 assert len(blocks) == 1
361 export = Export.from_export_block(blocks[0], self.cluster_id)
362 self._validate_export_2(export)
363
364 def test_daemon_conf_parser(self) -> None:
365 blocks = GaneshaConfParser(self.conf_nfs_foo).parse()
366 assert isinstance(blocks, list)
367 assert len(blocks) == 2
368 assert blocks[0].block_name == "%url"
369 assert blocks[0].values['value'] == f"rados://{NFS_POOL_NAME}/{self.cluster_id}/export-1"
370 assert blocks[1].block_name == "%url"
371 assert blocks[1].values['value'] == f"rados://{NFS_POOL_NAME}/{self.cluster_id}/export-2"
372
373 def _do_mock_test(self, func) -> None:
374 with self._mock_orchestrator(True):
375 func()
376 self._reset_temp_store()
377
378 def test_ganesha_conf(self) -> None:
379 self._do_mock_test(self._do_test_ganesha_conf)
380
381 def _do_test_ganesha_conf(self) -> None:
382 nfs_mod = Module('nfs', '', '')
383 ganesha_conf = ExportMgr(nfs_mod)
384 exports = ganesha_conf.exports[self.cluster_id]
385
386 assert len(exports) == 2
387
388 self._validate_export_1([e for e in exports if e.export_id == 1][0])
389 self._validate_export_2([e for e in exports if e.export_id == 2][0])
390
391 def test_config_dict(self) -> None:
392 self._do_mock_test(self._do_test_config_dict)
393
394 def _do_test_config_dict(self) -> None:
395 nfs_mod = Module('nfs', '', '')
396 conf = ExportMgr(nfs_mod)
397 export = [e for e in conf.exports['foo'] if e.export_id == 1][0]
398 ex_dict = export.to_dict()
399
400 assert ex_dict == {'access_type': 'RW',
401 'clients': [{'access_type': None,
402 'addresses': ['192.168.0.10', '192.168.1.0/8'],
403 'squash': 'None'},
404 {'access_type': 'RO',
405 'addresses': ['192.168.0.0/16'],
406 'squash': 'All'}],
407 'cluster_id': self.cluster_id,
408 'export_id': 1,
409 'fsal': {'fs_name': 'a', 'name': 'CEPH', 'user_id': 'ganesha'},
410 'path': '/',
411 'protocols': [4],
412 'pseudo': '/cephfs_a/',
413 'security_label': True,
414 'squash': 'no_root_squash',
415 'transports': []}
416
417 export = [e for e in conf.exports['foo'] if e.export_id == 2][0]
418 ex_dict = export.to_dict()
419 assert ex_dict == {'access_type': 'RW',
420 'clients': [],
421 'cluster_id': self.cluster_id,
422 'export_id': 2,
423 'fsal': {'name': 'RGW',
424 'access_key_id': 'the_access_key',
425 'secret_access_key': 'the_secret_key',
426 'user_id': 'nfs.foo.bucket'},
427 'path': '/',
428 'protocols': [3, 4],
429 'pseudo': '/rgw',
430 'security_label': True,
431 'squash': 'AllAnonymous',
432 'transports': ['TCP', 'UDP']}
433
434 def test_config_from_dict(self) -> None:
435 self._do_mock_test(self._do_test_config_from_dict)
436
437 def _do_test_config_from_dict(self) -> None:
438 export = Export.from_dict(1, {
439 'export_id': 1,
440 'path': '/',
441 'cluster_id': self.cluster_id,
442 'pseudo': '/cephfs_a',
443 'access_type': 'RW',
444 'squash': 'root_squash',
445 'security_label': True,
446 'protocols': [4],
447 'transports': ['TCP', 'UDP'],
448 'clients': [{
449 'addresses': ["192.168.0.10", "192.168.1.0/8"],
450 'access_type': None,
451 'squash': 'no_root_squash'
452 }, {
453 'addresses': ["192.168.0.0/16"],
454 'access_type': 'RO',
455 'squash': 'all_squash'
456 }],
457 'fsal': {
458 'name': 'CEPH',
459 'user_id': 'ganesha',
460 'fs_name': 'a',
461 'sec_label_xattr': 'security.selinux'
462 }
463 })
464
465 assert export.export_id == 1
466 assert export.path == "/"
467 assert export.pseudo == "/cephfs_a"
468 assert export.access_type == "RW"
469 assert export.squash == "root_squash"
470 assert set(export.protocols) == {4}
471 assert set(export.transports) == {"TCP", "UDP"}
472 assert export.fsal.name == "CEPH"
473 assert export.fsal.user_id == "ganesha"
474 assert export.fsal.fs_name == "a"
475 assert export.fsal.sec_label_xattr == 'security.selinux'
476 assert len(export.clients) == 2
477 assert export.clients[0].addresses == \
478 ["192.168.0.10", "192.168.1.0/8"]
479 assert export.clients[0].squash == "no_root_squash"
480 assert export.clients[0].access_type is None
481 assert export.clients[1].addresses == ["192.168.0.0/16"]
482 assert export.clients[1].squash == "all_squash"
483 assert export.clients[1].access_type == "RO"
484 assert export.cluster_id == self.cluster_id
485 assert export.attr_expiration_time == 0
486 assert export.security_label
487
488 export = Export.from_dict(2, {
489 'export_id': 2,
490 'path': 'bucket',
491 'pseudo': '/rgw',
492 'cluster_id': self.cluster_id,
493 'access_type': 'RW',
494 'squash': 'all_squash',
495 'security_label': False,
496 'protocols': [4, 3],
497 'transports': ['TCP', 'UDP'],
498 'clients': [],
499 'fsal': {
500 'name': 'RGW',
501 'user_id': 'rgw.foo.bucket',
502 'access_key_id': 'the_access_key',
503 'secret_access_key': 'the_secret_key'
504 }
505 })
506
507 assert export.export_id == 2
508 assert export.path == "bucket"
509 assert export.pseudo == "/rgw"
510 assert export.access_type == "RW"
511 assert export.squash == "all_squash"
512 assert set(export.protocols) == {4, 3}
513 assert set(export.transports) == {"TCP", "UDP"}
514 assert export.fsal.name == "RGW"
515 assert export.fsal.user_id == "rgw.foo.bucket"
516 assert export.fsal.access_key_id == "the_access_key"
517 assert export.fsal.secret_access_key == "the_secret_key"
518 assert len(export.clients) == 0
519 assert export.cluster_id == self.cluster_id
520
521 @pytest.mark.parametrize(
522 "block",
523 [
524 export_1,
525 export_2,
526 ]
527 )
528 def test_export_from_to_export_block(self, block):
529 blocks = GaneshaConfParser(block).parse()
530 export = Export.from_export_block(blocks[0], self.cluster_id)
531 newblock = export.to_export_block()
532 export2 = Export.from_export_block(newblock, self.cluster_id)
533 newblock2 = export2.to_export_block()
534 assert newblock == newblock2
535
536 @pytest.mark.parametrize(
537 "block",
538 [
539 export_1,
540 export_2,
541 ]
542 )
543 def test_export_from_to_dict(self, block):
544 blocks = GaneshaConfParser(block).parse()
545 export = Export.from_export_block(blocks[0], self.cluster_id)
546 j = export.to_dict()
547 export2 = Export.from_dict(j['export_id'], j)
548 j2 = export2.to_dict()
549 assert j == j2
550
551 @pytest.mark.parametrize(
552 "block",
553 [
554 export_1,
555 export_2,
556 ]
557 )
558 def test_export_validate(self, block):
559 blocks = GaneshaConfParser(block).parse()
560 export = Export.from_export_block(blocks[0], self.cluster_id)
561 nfs_mod = Module('nfs', '', '')
562 with mock.patch('nfs.ganesha_conf.check_fs', return_value=True):
563 export.validate(nfs_mod)
564
565 def test_update_export(self):
566 self._do_mock_test(self._do_test_update_export)
567
568 def _do_test_update_export(self):
569 nfs_mod = Module('nfs', '', '')
570 conf = ExportMgr(nfs_mod)
571 r = conf.apply_export(self.cluster_id, json.dumps({
572 'export_id': 2,
573 'path': 'bucket',
574 'pseudo': '/rgw/bucket',
575 'cluster_id': self.cluster_id,
576 'access_type': 'RW',
577 'squash': 'all_squash',
578 'security_label': False,
579 'protocols': [4, 3],
580 'transports': ['TCP', 'UDP'],
581 'clients': [{
582 'addresses': ["192.168.0.0/16"],
583 'access_type': None,
584 'squash': None
585 }],
586 'fsal': {
587 'name': 'RGW',
588 'user_id': 'nfs.foo.bucket',
589 'access_key_id': 'the_access_key',
590 'secret_access_key': 'the_secret_key',
591 }
592 }))
593 assert r[0] == 0
594
595 export = conf._fetch_export('foo', '/rgw/bucket')
596 assert export.export_id == 2
597 assert export.path == "bucket"
598 assert export.pseudo == "/rgw/bucket"
599 assert export.access_type == "RW"
600 assert export.squash == "all_squash"
601 assert export.protocols == [4, 3]
602 assert export.transports == ["TCP", "UDP"]
603 assert export.fsal.name == "RGW"
604 assert export.fsal.access_key_id == "the_access_key"
605 assert export.fsal.secret_access_key == "the_secret_key"
606 assert len(export.clients) == 1
607 assert export.clients[0].squash is None
608 assert export.clients[0].access_type is None
609 assert export.cluster_id == self.cluster_id
610
611 # do it again, with changes
612 r = conf.apply_export(self.cluster_id, json.dumps({
613 'export_id': 2,
614 'path': 'newbucket',
615 'pseudo': '/rgw/bucket',
616 'cluster_id': self.cluster_id,
617 'access_type': 'RO',
618 'squash': 'root',
619 'security_label': False,
620 'protocols': [4],
621 'transports': ['TCP'],
622 'clients': [{
623 'addresses': ["192.168.10.0/16"],
624 'access_type': None,
625 'squash': None
626 }],
627 'fsal': {
628 'name': 'RGW',
629 'user_id': 'nfs.foo.newbucket',
630 'access_key_id': 'the_access_key',
631 'secret_access_key': 'the_secret_key',
632 }
633 }))
634 assert r[0] == 0
635
636 export = conf._fetch_export('foo', '/rgw/bucket')
637 assert export.export_id == 2
638 assert export.path == "newbucket"
639 assert export.pseudo == "/rgw/bucket"
640 assert export.access_type == "RO"
641 assert export.squash == "root"
642 assert export.protocols == [4]
643 assert export.transports == ["TCP"]
644 assert export.fsal.name == "RGW"
645 assert export.fsal.access_key_id == "the_access_key"
646 assert export.fsal.secret_access_key == "the_secret_key"
647 assert len(export.clients) == 1
648 assert export.clients[0].squash is None
649 assert export.clients[0].access_type is None
650 assert export.cluster_id == self.cluster_id
651
652 # again, but without export_id
653 r = conf.apply_export(self.cluster_id, json.dumps({
654 'path': 'newestbucket',
655 'pseudo': '/rgw/bucket',
656 'cluster_id': self.cluster_id,
657 'access_type': 'RW',
658 'squash': 'root',
659 'security_label': False,
660 'protocols': [4],
661 'transports': ['TCP'],
662 'clients': [{
663 'addresses': ["192.168.10.0/16"],
664 'access_type': None,
665 'squash': None
666 }],
667 'fsal': {
668 'name': 'RGW',
669 'user_id': 'nfs.foo.newestbucket',
670 'access_key_id': 'the_access_key',
671 'secret_access_key': 'the_secret_key',
672 }
673 }))
674 assert r[0] == 0
675
676 export = conf._fetch_export(self.cluster_id, '/rgw/bucket')
677 assert export.export_id == 2
678 assert export.path == "newestbucket"
679 assert export.pseudo == "/rgw/bucket"
680 assert export.access_type == "RW"
681 assert export.squash == "root"
682 assert export.protocols == [4]
683 assert export.transports == ["TCP"]
684 assert export.fsal.name == "RGW"
685 assert export.fsal.access_key_id == "the_access_key"
686 assert export.fsal.secret_access_key == "the_secret_key"
687 assert len(export.clients) == 1
688 assert export.clients[0].squash is None
689 assert export.clients[0].access_type is None
690 assert export.cluster_id == self.cluster_id
691
692 def test_update_export_with_ganesha_conf(self):
693 self._do_mock_test(self._do_test_update_export_with_ganesha_conf)
694
695 def _do_test_update_export_with_ganesha_conf(self):
696 nfs_mod = Module('nfs', '', '')
697 conf = ExportMgr(nfs_mod)
698 r = conf.apply_export(self.cluster_id, self.export_3)
699 assert r[0] == 0
700
701 def test_update_export_with_list(self):
702 self._do_mock_test(self._do_test_update_export_with_list)
703
704 def _do_test_update_export_with_list(self):
705 nfs_mod = Module('nfs', '', '')
706 conf = ExportMgr(nfs_mod)
707 r = conf.apply_export(self.cluster_id, json.dumps([
708 {
709 'path': 'bucket',
710 'pseudo': '/rgw/bucket',
711 'cluster_id': self.cluster_id,
712 'access_type': 'RW',
713 'squash': 'root',
714 'security_label': False,
715 'protocols': [4],
716 'transports': ['TCP'],
717 'clients': [{
718 'addresses': ["192.168.0.0/16"],
719 'access_type': None,
720 'squash': None
721 }],
722 'fsal': {
723 'name': 'RGW',
724 'user_id': 'nfs.foo.bucket',
725 'access_key_id': 'the_access_key',
726 'secret_access_key': 'the_secret_key',
727 }
728 },
729 {
730 'path': 'bucket2',
731 'pseudo': '/rgw/bucket2',
732 'cluster_id': self.cluster_id,
733 'access_type': 'RO',
734 'squash': 'root',
735 'security_label': False,
736 'protocols': [4],
737 'transports': ['TCP'],
738 'clients': [{
739 'addresses': ["192.168.0.0/16"],
740 'access_type': None,
741 'squash': None
742 }],
743 'fsal': {
744 'name': 'RGW',
745 'user_id': 'nfs.foo.bucket2',
746 'access_key_id': 'the_access_key',
747 'secret_access_key': 'the_secret_key',
748 }
749 },
750 ]))
751 assert r[0] == 0
752
753 export = conf._fetch_export('foo', '/rgw/bucket')
754 assert export.export_id == 3
755 assert export.path == "bucket"
756 assert export.pseudo == "/rgw/bucket"
757 assert export.access_type == "RW"
758 assert export.squash == "root"
759 assert export.protocols == [4]
760 assert export.transports == ["TCP"]
761 assert export.fsal.name == "RGW"
762 assert export.fsal.access_key_id == "the_access_key"
763 assert export.fsal.secret_access_key == "the_secret_key"
764 assert len(export.clients) == 1
765 assert export.clients[0].squash is None
766 assert export.clients[0].access_type is None
767 assert export.cluster_id == self.cluster_id
768
769 export = conf._fetch_export('foo', '/rgw/bucket2')
770 assert export.export_id == 4
771 assert export.path == "bucket2"
772 assert export.pseudo == "/rgw/bucket2"
773 assert export.access_type == "RO"
774 assert export.squash == "root"
775 assert export.protocols == [4]
776 assert export.transports == ["TCP"]
777 assert export.fsal.name == "RGW"
778 assert export.fsal.access_key_id == "the_access_key"
779 assert export.fsal.secret_access_key == "the_secret_key"
780 assert len(export.clients) == 1
781 assert export.clients[0].squash is None
782 assert export.clients[0].access_type is None
783 assert export.cluster_id == self.cluster_id
784
785 def test_remove_export(self) -> None:
786 self._do_mock_test(self._do_test_remove_export)
787
788 def _do_test_remove_export(self) -> None:
789 nfs_mod = Module('nfs', '', '')
790 conf = ExportMgr(nfs_mod)
791 assert len(conf.exports[self.cluster_id]) == 2
792 assert conf.delete_export(cluster_id=self.cluster_id,
793 pseudo_path="/rgw") == (0, "Successfully deleted export", "")
794 exports = conf.exports[self.cluster_id]
795 assert len(exports) == 1
796 assert exports[0].export_id == 1
797
798 def test_create_export_rgw_bucket(self):
799 self._do_mock_test(self._do_test_create_export_rgw_bucket)
800
801 def _do_test_create_export_rgw_bucket(self):
802 nfs_mod = Module('nfs', '', '')
803 conf = ExportMgr(nfs_mod)
804
805 exports = conf.list_exports(cluster_id=self.cluster_id)
806 ls = json.loads(exports[1])
807 assert len(ls) == 2
808
809 r = conf.create_export(
810 fsal_type='rgw',
811 cluster_id=self.cluster_id,
812 bucket='bucket',
813 pseudo_path='/mybucket',
814 read_only=False,
815 squash='root',
816 addr=["192.168.0.0/16"]
817 )
818 assert r[0] == 0
819
820 exports = conf.list_exports(cluster_id=self.cluster_id)
821 ls = json.loads(exports[1])
822 assert len(ls) == 3
823
824 export = conf._fetch_export('foo', '/mybucket')
825 assert export.export_id
826 assert export.path == "bucket"
827 assert export.pseudo == "/mybucket"
828 assert export.access_type == "none"
829 assert export.squash == "none"
830 assert export.protocols == [4]
831 assert export.transports == ["TCP"]
832 assert export.fsal.name == "RGW"
833 assert export.fsal.user_id == "bucket_owner_user"
834 assert export.fsal.access_key_id == "the_access_key"
835 assert export.fsal.secret_access_key == "the_secret_key"
836 assert len(export.clients) == 1
837 assert export.clients[0].squash == 'root'
838 assert export.clients[0].access_type == 'rw'
839 assert export.clients[0].addresses == ["192.168.0.0/16"]
840 assert export.cluster_id == self.cluster_id
841
842 def test_create_export_rgw_bucket_user(self):
843 self._do_mock_test(self._do_test_create_export_rgw_bucket_user)
844
845 def _do_test_create_export_rgw_bucket_user(self):
846 nfs_mod = Module('nfs', '', '')
847 conf = ExportMgr(nfs_mod)
848
849 exports = conf.list_exports(cluster_id=self.cluster_id)
850 ls = json.loads(exports[1])
851 assert len(ls) == 2
852
853 r = conf.create_export(
854 fsal_type='rgw',
855 cluster_id=self.cluster_id,
856 bucket='bucket',
857 user_id='other_user',
858 pseudo_path='/mybucket',
859 read_only=False,
860 squash='root',
861 addr=["192.168.0.0/16"]
862 )
863 assert r[0] == 0
864
865 exports = conf.list_exports(cluster_id=self.cluster_id)
866 ls = json.loads(exports[1])
867 assert len(ls) == 3
868
869 export = conf._fetch_export('foo', '/mybucket')
870 assert export.export_id
871 assert export.path == "bucket"
872 assert export.pseudo == "/mybucket"
873 assert export.access_type == "none"
874 assert export.squash == "none"
875 assert export.protocols == [4]
876 assert export.transports == ["TCP"]
877 assert export.fsal.name == "RGW"
878 assert export.fsal.access_key_id == "the_access_key"
879 assert export.fsal.secret_access_key == "the_secret_key"
880 assert len(export.clients) == 1
881 assert export.clients[0].squash == 'root'
882 assert export.fsal.user_id == "other_user"
883 assert export.clients[0].access_type == 'rw'
884 assert export.clients[0].addresses == ["192.168.0.0/16"]
885 assert export.cluster_id == self.cluster_id
886
887 def test_create_export_rgw_user(self):
888 self._do_mock_test(self._do_test_create_export_rgw_user)
889
890 def _do_test_create_export_rgw_user(self):
891 nfs_mod = Module('nfs', '', '')
892 conf = ExportMgr(nfs_mod)
893
894 exports = conf.list_exports(cluster_id=self.cluster_id)
895 ls = json.loads(exports[1])
896 assert len(ls) == 2
897
898 r = conf.create_export(
899 fsal_type='rgw',
900 cluster_id=self.cluster_id,
901 user_id='some_user',
902 pseudo_path='/mybucket',
903 read_only=False,
904 squash='root',
905 addr=["192.168.0.0/16"]
906 )
907 assert r[0] == 0
908
909 exports = conf.list_exports(cluster_id=self.cluster_id)
910 ls = json.loads(exports[1])
911 assert len(ls) == 3
912
913 export = conf._fetch_export('foo', '/mybucket')
914 assert export.export_id
915 assert export.path == "/"
916 assert export.pseudo == "/mybucket"
917 assert export.access_type == "none"
918 assert export.squash == "none"
919 assert export.protocols == [4]
920 assert export.transports == ["TCP"]
921 assert export.fsal.name == "RGW"
922 assert export.fsal.access_key_id == "the_access_key"
923 assert export.fsal.secret_access_key == "the_secret_key"
924 assert len(export.clients) == 1
925 assert export.clients[0].squash == 'root'
926 assert export.fsal.user_id == "some_user"
927 assert export.clients[0].access_type == 'rw'
928 assert export.clients[0].addresses == ["192.168.0.0/16"]
929 assert export.cluster_id == self.cluster_id
930
931 def test_create_export_cephfs(self):
932 self._do_mock_test(self._do_test_create_export_cephfs)
933
934 def _do_test_create_export_cephfs(self):
935 nfs_mod = Module('nfs', '', '')
936 conf = ExportMgr(nfs_mod)
937
938 exports = conf.list_exports(cluster_id=self.cluster_id)
939 ls = json.loads(exports[1])
940 assert len(ls) == 2
941
942 r = conf.create_export(
943 fsal_type='cephfs',
944 cluster_id=self.cluster_id,
945 fs_name='myfs',
946 path='/',
947 pseudo_path='/cephfs2',
948 read_only=False,
949 squash='root',
950 addr=["192.168.1.0/8"],
951 )
952 assert r[0] == 0
953
954 exports = conf.list_exports(cluster_id=self.cluster_id)
955 ls = json.loads(exports[1])
956 assert len(ls) == 3
957
958 export = conf._fetch_export('foo', '/cephfs2')
959 assert export.export_id
960 assert export.path == "/"
961 assert export.pseudo == "/cephfs2"
962 assert export.access_type == "none"
963 assert export.squash == "none"
964 assert export.protocols == [4]
965 assert export.transports == ["TCP"]
966 assert export.fsal.name == "CEPH"
967 assert export.fsal.user_id == "nfs.foo.3"
968 assert export.fsal.cephx_key == "thekeyforclientabc"
969 assert len(export.clients) == 1
970 assert export.clients[0].squash == 'root'
971 assert export.clients[0].access_type == 'rw'
972 assert export.clients[0].addresses == ["192.168.1.0/8"]
973 assert export.cluster_id == self.cluster_id
974
975 def _do_test_cluster_ls(self):
976 nfs_mod = Module('nfs', '', '')
977 cluster = NFSCluster(nfs_mod)
978
979 rc, out, err = cluster.list_nfs_cluster()
980 assert rc == 0
981 assert out == self.cluster_id
982
983 def test_cluster_ls(self):
984 self._do_mock_test(self._do_test_cluster_ls)
985
986 def _do_test_cluster_info(self):
987 nfs_mod = Module('nfs', '', '')
988 cluster = NFSCluster(nfs_mod)
989
990 rc, out, err = cluster.show_nfs_cluster_info(self.cluster_id)
991 assert rc == 0
992 assert json.loads(out) == {"foo": {"virtual_ip": None, "backend": []}}
993
994 def test_cluster_info(self):
995 self._do_mock_test(self._do_test_cluster_info)
996
997 def _do_test_cluster_config(self):
998 nfs_mod = Module('nfs', '', '')
999 cluster = NFSCluster(nfs_mod)
1000
1001 rc, out, err = cluster.get_nfs_cluster_config(self.cluster_id)
1002 assert rc == 0
1003 assert out == ""
1004
1005 rc, out, err = cluster.set_nfs_cluster_config(self.cluster_id, '# foo\n')
1006 assert rc == 0
1007
1008 rc, out, err = cluster.get_nfs_cluster_config(self.cluster_id)
1009 assert rc == 0
1010 assert out == "# foo\n"
1011
1012 rc, out, err = cluster.reset_nfs_cluster_config(self.cluster_id)
1013 assert rc == 0
1014
1015 rc, out, err = cluster.get_nfs_cluster_config(self.cluster_id)
1016 assert rc == 0
1017 assert out == ""
1018
1019 def test_cluster_config(self):
1020 self._do_mock_test(self._do_test_cluster_config)
1021
1022
1023 @pytest.mark.parametrize(
1024 "path,expected",
1025 [
1026 ("/foo/bar/baz", "/foo/bar/baz"),
1027 ("/foo/bar/baz/", "/foo/bar/baz"),
1028 ("/foo/bar/baz ", "/foo/bar/baz"),
1029 ("/foo/./bar/baz", "/foo/bar/baz"),
1030 ("/foo/bar/baz/..", "/foo/bar"),
1031 ("//foo/bar/baz", "/foo/bar/baz"),
1032 ("", ""),
1033 ]
1034 )
1035 def test_normalize_path(path, expected):
1036 assert normalize_path(path) == expected
1037
1038
1039 def test_ganesha_validate_squash():
1040 """Check error handling of internal validation function for squash value."""
1041 from nfs.ganesha_conf import _validate_squash
1042 from nfs.exception import NFSInvalidOperation
1043
1044 _validate_squash("root")
1045 with pytest.raises(NFSInvalidOperation):
1046 _validate_squash("toot")
1047
1048
1049 def test_ganesha_validate_access_type():
1050 """Check error handling of internal validation function for access type value."""
1051 from nfs.ganesha_conf import _validate_access_type
1052 from nfs.exception import NFSInvalidOperation
1053
1054 for ok in ("rw", "ro", "none"):
1055 _validate_access_type(ok)
1056 with pytest.raises(NFSInvalidOperation):
1057 _validate_access_type("any")