]>
Commit | Line | Data |
---|---|---|
a4b75251 TL |
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 | |
1d09f67e | 15 | from nfs.export import ExportMgr, normalize_path |
a4b75251 TL |
16 | from nfs.export_utils 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.export_utils.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.export_utils.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) | |
1d09f67e TL |
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 |