1 from typing
import cast
, List
, Dict
, Any
, Optional
, TYPE_CHECKING
2 from os
.path
import isabs
4 from mgr_module
import NFS_GANESHA_SUPPORTED_FSALS
6 from .exception
import NFSInvalidOperation
, FSNotFound
7 from .utils
import check_fs
10 from nfs
.module
import Module
13 def _indentation(depth
: int, size
: int = 4) -> str:
14 return " " * (depth
* size
)
17 def _format_val(block_name
: str, key
: str, val
: str) -> str:
18 if isinstance(val
, list):
19 return ', '.join([_format_val(block_name
, key
, v
) for v
in val
])
20 if isinstance(val
, bool):
21 return str(val
).lower()
22 if isinstance(val
, int) or (block_name
== 'CLIENT'
23 and key
== 'clients'):
24 return '{}'.format(val
)
25 return '"{}"'.format(val
)
28 def _validate_squash(squash
: str) -> None:
30 "root", "root_squash", "rootsquash", "rootid", "root_id_squash",
31 "rootidsquash", "all", "all_squash", "allsquash", "all_anomnymous",
32 "allanonymous", "no_root_squash", "none", "noidsquash",
34 if squash
.lower() not in valid_squash_ls
:
35 raise NFSInvalidOperation(
36 f
"squash {squash} not in valid list {valid_squash_ls}"
40 def _validate_access_type(access_type
: str) -> None:
41 valid_access_types
= ['rw', 'ro', 'none']
42 if not isinstance(access_type
, str) or access_type
.lower() not in valid_access_types
:
43 raise NFSInvalidOperation(
44 f
'{access_type} is invalid, valid access type are'
45 f
'{valid_access_types}'
50 def __init__(self
, block_name
: str, blocks
: List
['RawBlock'] = [], values
: Dict
[str, Any
] = {}):
51 if not values
: # workaround mutable default argument
53 if not blocks
: # workaround mutable default argument
55 self
.block_name
= block_name
59 def __eq__(self
, other
: Any
) -> bool:
60 if not isinstance(other
, RawBlock
):
62 return self
.block_name
== other
.block_name
and \
63 self
.blocks
== other
.blocks
and \
64 self
.values
== other
.values
66 def __repr__(self
) -> str:
67 return f
'RawBlock({self.block_name!r}, {self.blocks!r}, {self.values!r})'
70 class GaneshaConfParser
:
71 def __init__(self
, raw_config
: str):
74 for line
in raw_config
.split("\n"):
77 if line
.startswith("%"):
78 self
.text
+= line
.replace('"', "")
81 self
.text
+= "".join(line
.split())
83 def stream(self
) -> str:
84 return self
.text
[self
.pos
:]
86 def last_context(self
) -> str:
87 return f
'"...{self.text[max(0, self.pos - 30):self.pos]}<here>{self.stream()[:30]}"'
89 def parse_block_name(self
) -> str:
90 idx
= self
.stream().find('{')
92 raise Exception(f
"Cannot find block name at {self.last_context()}")
93 block_name
= self
.stream()[:idx
]
97 def parse_block_or_section(self
) -> RawBlock
:
98 if self
.stream().startswith("%url "):
101 idx
= self
.stream().find('\n')
103 value
= self
.stream()
104 self
.pos
+= len(value
)
106 value
= self
.stream()[:idx
]
108 block_dict
= RawBlock('%url', values
={'value': value
})
111 block_dict
= RawBlock(self
.parse_block_name().upper())
112 self
.parse_block_body(block_dict
)
113 if self
.stream()[0] != '}':
114 raise Exception("No closing bracket '}' found at the end of block")
118 def parse_parameter_value(self
, raw_value
: str) -> Any
:
119 if raw_value
.find(',') != -1:
120 return [self
.parse_parameter_value(v
.strip())
121 for v
in raw_value
.split(',')]
123 return int(raw_value
)
125 if raw_value
== "true":
127 if raw_value
== "false":
129 if raw_value
.find('"') == 0:
130 return raw_value
[1:-1]
133 def parse_stanza(self
, block_dict
: RawBlock
) -> None:
134 equal_idx
= self
.stream().find('=')
136 raise Exception("Malformed stanza: no equal symbol found.")
137 semicolon_idx
= self
.stream().find(';')
138 parameter_name
= self
.stream()[:equal_idx
].lower()
139 parameter_value
= self
.stream()[equal_idx
+ 1:semicolon_idx
]
140 block_dict
.values
[parameter_name
] = self
.parse_parameter_value(parameter_value
)
141 self
.pos
+= semicolon_idx
+ 1
143 def parse_block_body(self
, block_dict
: RawBlock
) -> None:
145 if self
.stream().find('}') == 0:
150 semicolon_idx
= self
.stream().find(';')
151 lbracket_idx
= self
.stream().find('{')
152 is_semicolon
= (semicolon_idx
!= -1)
153 is_lbracket
= (lbracket_idx
!= -1)
154 is_semicolon_lt_lbracket
= (semicolon_idx
< lbracket_idx
)
156 if is_semicolon
and ((is_lbracket
and is_semicolon_lt_lbracket
) or not is_lbracket
):
157 self
.parse_stanza(block_dict
)
158 elif is_lbracket
and ((is_semicolon
and not is_semicolon_lt_lbracket
)
159 or (not is_semicolon
)):
160 block_dict
.blocks
.append(self
.parse_block_or_section())
162 raise Exception("Malformed stanza: no semicolon found.")
164 if last_pos
== self
.pos
:
165 raise Exception("Infinite loop while parsing block content")
167 def parse(self
) -> List
[RawBlock
]:
170 blocks
.append(self
.parse_block_or_section())
175 def __init__(self
, name
: str) -> None:
179 def from_dict(cls
, fsal_dict
: Dict
[str, Any
]) -> 'FSAL':
180 if fsal_dict
.get('name') == NFS_GANESHA_SUPPORTED_FSALS
[0]:
181 return CephFSFSAL
.from_dict(fsal_dict
)
182 if fsal_dict
.get('name') == NFS_GANESHA_SUPPORTED_FSALS
[1]:
183 return RGWFSAL
.from_dict(fsal_dict
)
184 raise NFSInvalidOperation(f
'Unknown FSAL {fsal_dict.get("name")}')
187 def from_fsal_block(cls
, fsal_block
: RawBlock
) -> 'FSAL':
188 if fsal_block
.values
.get('name') == NFS_GANESHA_SUPPORTED_FSALS
[0]:
189 return CephFSFSAL
.from_fsal_block(fsal_block
)
190 if fsal_block
.values
.get('name') == NFS_GANESHA_SUPPORTED_FSALS
[1]:
191 return RGWFSAL
.from_fsal_block(fsal_block
)
192 raise NFSInvalidOperation(f
'Unknown FSAL {fsal_block.values.get("name")}')
194 def to_fsal_block(self
) -> RawBlock
:
195 raise NotImplementedError
197 def to_dict(self
) -> Dict
[str, Any
]:
198 raise NotImplementedError
201 class CephFSFSAL(FSAL
):
204 user_id
: Optional
[str] = None,
205 fs_name
: Optional
[str] = None,
206 sec_label_xattr
: Optional
[str] = None,
207 cephx_key
: Optional
[str] = None) -> None:
208 super().__init
__(name
)
209 assert name
== 'CEPH'
210 self
.fs_name
= fs_name
211 self
.user_id
= user_id
212 self
.sec_label_xattr
= sec_label_xattr
213 self
.cephx_key
= cephx_key
216 def from_fsal_block(cls
, fsal_block
: RawBlock
) -> 'CephFSFSAL':
217 return cls(fsal_block
.values
['name'],
218 fsal_block
.values
.get('user_id'),
219 fsal_block
.values
.get('filesystem'),
220 fsal_block
.values
.get('sec_label_xattr'),
221 fsal_block
.values
.get('secret_access_key'))
223 def to_fsal_block(self
) -> RawBlock
:
224 result
= RawBlock('FSAL', values
={'name': self
.name
})
227 result
.values
['user_id'] = self
.user_id
229 result
.values
['filesystem'] = self
.fs_name
230 if self
.sec_label_xattr
:
231 result
.values
['sec_label_xattr'] = self
.sec_label_xattr
233 result
.values
['secret_access_key'] = self
.cephx_key
237 def from_dict(cls
, fsal_dict
: Dict
[str, Any
]) -> 'CephFSFSAL':
238 return cls(fsal_dict
['name'],
239 fsal_dict
.get('user_id'),
240 fsal_dict
.get('fs_name'),
241 fsal_dict
.get('sec_label_xattr'),
242 fsal_dict
.get('cephx_key'))
244 def to_dict(self
) -> Dict
[str, str]:
245 r
= {'name': self
.name
}
247 r
['user_id'] = self
.user_id
249 r
['fs_name'] = self
.fs_name
250 if self
.sec_label_xattr
:
251 r
['sec_label_xattr'] = self
.sec_label_xattr
258 user_id
: Optional
[str] = None,
259 access_key_id
: Optional
[str] = None,
260 secret_access_key
: Optional
[str] = None
262 super().__init
__(name
)
265 self
.user_id
= user_id
267 self
.access_key_id
= access_key_id
268 self
.secret_access_key
= secret_access_key
271 def from_fsal_block(cls
, fsal_block
: RawBlock
) -> 'RGWFSAL':
272 return cls(fsal_block
.values
['name'],
273 fsal_block
.values
.get('user_id'),
274 fsal_block
.values
.get('access_key_id'),
275 fsal_block
.values
.get('secret_access_key'))
277 def to_fsal_block(self
) -> RawBlock
:
278 result
= RawBlock('FSAL', values
={'name': self
.name
})
281 result
.values
['user_id'] = self
.user_id
282 if self
.access_key_id
:
283 result
.values
['access_key_id'] = self
.access_key_id
284 if self
.secret_access_key
:
285 result
.values
['secret_access_key'] = self
.secret_access_key
289 def from_dict(cls
, fsal_dict
: Dict
[str, str]) -> 'RGWFSAL':
290 return cls(fsal_dict
['name'],
291 fsal_dict
.get('user_id'),
292 fsal_dict
.get('access_key_id'),
293 fsal_dict
.get('secret_access_key'))
295 def to_dict(self
) -> Dict
[str, str]:
296 r
= {'name': self
.name
}
298 r
['user_id'] = self
.user_id
299 if self
.access_key_id
:
300 r
['access_key_id'] = self
.access_key_id
301 if self
.secret_access_key
:
302 r
['secret_access_key'] = self
.secret_access_key
308 addresses
: List
[str],
311 self
.addresses
= addresses
312 self
.access_type
= access_type
316 def from_client_block(cls
, client_block
: RawBlock
) -> 'Client':
317 addresses
= client_block
.values
.get('clients', [])
318 if isinstance(addresses
, str):
319 addresses
= [addresses
]
320 return cls(addresses
,
321 client_block
.values
.get('access_type', None),
322 client_block
.values
.get('squash', None))
324 def to_client_block(self
) -> RawBlock
:
325 result
= RawBlock('CLIENT', values
={'clients': self
.addresses
})
327 result
.values
['access_type'] = self
.access_type
329 result
.values
['squash'] = self
.squash
333 def from_dict(cls
, client_dict
: Dict
[str, Any
]) -> 'Client':
334 return cls(client_dict
['addresses'], client_dict
['access_type'],
335 client_dict
['squash'])
337 def to_dict(self
) -> Dict
[str, Any
]:
339 'addresses': self
.addresses
,
340 'access_type': self
.access_type
,
341 'squash': self
.squash
354 security_label
: bool,
355 protocols
: List
[int],
356 transports
: List
[str],
358 clients
: Optional
[List
[Client
]] = None) -> None:
359 self
.export_id
= export_id
362 self
.cluster_id
= cluster_id
364 self
.access_type
= access_type
366 self
.attr_expiration_time
= 0
367 self
.security_label
= security_label
368 self
.protocols
= protocols
369 self
.transports
= transports
370 self
.clients
: List
[Client
] = clients
or []
373 def from_export_block(cls
, export_block
: RawBlock
, cluster_id
: str) -> 'Export':
374 fsal_blocks
= [b
for b
in export_block
.blocks
375 if b
.block_name
== "FSAL"]
377 client_blocks
= [b
for b
in export_block
.blocks
378 if b
.block_name
== "CLIENT"]
380 protocols
= export_block
.values
.get('protocols')
381 if not isinstance(protocols
, list):
382 protocols
= [protocols
]
384 transports
= export_block
.values
.get('transports')
385 if isinstance(transports
, str):
386 transports
= [transports
]
390 return cls(export_block
.values
['export_id'],
391 export_block
.values
['path'],
393 export_block
.values
['pseudo'],
394 export_block
.values
.get('access_type', 'none'),
395 export_block
.values
.get('squash', 'no_root_squash'),
396 export_block
.values
.get('security_label', True),
399 FSAL
.from_fsal_block(fsal_blocks
[0]),
400 [Client
.from_client_block(client
)
401 for client
in client_blocks
])
403 def to_export_block(self
) -> RawBlock
:
404 result
= RawBlock('EXPORT', values
={
405 'export_id': self
.export_id
,
407 'pseudo': self
.pseudo
,
408 'access_type': self
.access_type
,
409 'squash': self
.squash
,
410 'attr_expiration_time': self
.attr_expiration_time
,
411 'security_label': self
.security_label
,
412 'protocols': self
.protocols
,
413 'transports': self
.transports
,
416 self
.fsal
.to_fsal_block()
418 client
.to_client_block()
419 for client
in self
.clients
424 def from_dict(cls
, export_id
: int, ex_dict
: Dict
[str, Any
]) -> 'Export':
425 return cls(export_id
,
426 ex_dict
.get('path', '/'),
427 ex_dict
['cluster_id'],
429 ex_dict
.get('access_type', 'RO'),
430 ex_dict
.get('squash', 'no_root_squash'),
431 ex_dict
.get('security_label', True),
432 ex_dict
.get('protocols', [4]),
433 ex_dict
.get('transports', ['TCP']),
434 FSAL
.from_dict(ex_dict
.get('fsal', {})),
435 [Client
.from_dict(client
) for client
in ex_dict
.get('clients', [])])
437 def to_dict(self
) -> Dict
[str, Any
]:
439 'export_id': self
.export_id
,
441 'cluster_id': self
.cluster_id
,
442 'pseudo': self
.pseudo
,
443 'access_type': self
.access_type
,
444 'squash': self
.squash
,
445 'security_label': self
.security_label
,
446 'protocols': sorted([p
for p
in self
.protocols
]),
447 'transports': sorted([t
for t
in self
.transports
]),
448 'fsal': self
.fsal
.to_dict(),
449 'clients': [client
.to_dict() for client
in self
.clients
]
452 def validate(self
, mgr
: 'Module') -> None:
453 if not isabs(self
.pseudo
) or self
.pseudo
== "/":
454 raise NFSInvalidOperation(
455 f
"pseudo path {self.pseudo} is invalid. It should be an absolute "
456 "path and it cannot be just '/'."
459 _validate_squash(self
.squash
)
460 _validate_access_type(self
.access_type
)
462 if not isinstance(self
.security_label
, bool):
463 raise NFSInvalidOperation('security_label must be a boolean value')
465 for p
in self
.protocols
:
467 raise NFSInvalidOperation(f
"Invalid protocol {p}")
469 valid_transport
= ["UDP", "TCP"]
470 for trans
in self
.transports
:
471 if trans
.upper() not in valid_transport
:
472 raise NFSInvalidOperation(f
'{trans} is not a valid transport protocol')
474 for client
in self
.clients
:
476 _validate_squash(client
.squash
)
477 if client
.access_type
:
478 _validate_access_type(client
.access_type
)
480 if self
.fsal
.name
== NFS_GANESHA_SUPPORTED_FSALS
[0]:
481 fs
= cast(CephFSFSAL
, self
.fsal
)
482 if not fs
.fs_name
or not check_fs(mgr
, fs
.fs_name
):
483 raise FSNotFound(fs
.fs_name
)
484 elif self
.fsal
.name
== NFS_GANESHA_SUPPORTED_FSALS
[1]:
485 rgw
= cast(RGWFSAL
, self
.fsal
) # noqa
488 raise NFSInvalidOperation('FSAL {self.fsal.name} not supported')
490 def __eq__(self
, other
: Any
) -> bool:
491 if not isinstance(other
, Export
):
493 return self
.to_dict() == other
.to_dict()
496 def _format_block_body(block
: RawBlock
, depth
: int = 0) -> str:
498 for blo
in block
.blocks
:
499 conf_str
+= format_block(blo
, depth
)
501 for key
, val
in block
.values
.items():
503 conf_str
+= _indentation(depth
)
504 fval
= _format_val(block
.block_name
, key
, val
)
505 conf_str
+= '{} = {};\n'.format(key
, fval
)
509 def format_block(block
: RawBlock
, depth
: int = 0) -> str:
510 """Format a raw block object into text suitable as a ganesha configuration
513 if block
.block_name
== "%url":
514 return '%url "{}"\n\n'.format(block
.values
['value'])
517 conf_str
+= _indentation(depth
)
518 conf_str
+= format(block
.block_name
)
520 conf_str
+= _format_block_body(block
, depth
+ 1)
521 conf_str
+= _indentation(depth
)