From: Venky Shankar Date: Tue, 14 Oct 2025 09:10:40 +0000 (+0000) Subject: pybind/nfs: support custom "log" block X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fheads%2Fwip-fs-suite-ganesha-tests-x7;p=ceph-ci.git pybind/nfs: support custom "log" block Signed-off-by: Venky Shankar --- diff --git a/src/pybind/mgr/nfs/export.py b/src/pybind/mgr/nfs/export.py index 1f0d4b26132..83b8bf0f7be 100644 --- a/src/pybind/mgr/nfs/export.py +++ b/src/pybind/mgr/nfs/export.py @@ -29,6 +29,7 @@ from .ganesha_conf import ( RGWFSAL, RawBlock, CephBlock, + LogBlock, format_block) from .exception import NFSException, NFSInvalidOperation, FSNotFound, NFSObjectNotFound from .utils import ( @@ -218,12 +219,14 @@ class AppliedExportResults: return self.status class GaneshaExport: - # currently, EXPORT and CEPH block. + # EXPORT, CEPH and LOG block. def __init__(self, export: Export, - ceph_block: Optional[CephBlock] = None) -> None: + ceph_block: Optional[CephBlock] = None, + log_block: Optional[LogBlock] = None) -> None: self.export = export self.ceph_block = ceph_block + self.log_block = log_block # frequently uesd properties so that much of the code that now # has moved to using this class can still continue to acess via @@ -250,18 +253,21 @@ class GaneshaExport: def to_dict(self, full=False) -> Dict[str, Any]: export_dict = self.export.to_dict() - if not full or not self.ceph_block: + if not full or (not self.ceph_block and not self.log_block): return export_dict - ge_dict = { - 'export': export_dict, - 'ceph': self.ceph_block.to_dict() - } + ge_dict = {'export': export_dict} + if self.ceph_block: + ge_dict['ceph'] = self.ceph_block.to_dict() + if self.log_block: + ge_dict['log'] = self.log_block.to_dict() return ge_dict def to_export_block(self): block_str = format_block(self.export.to_export_block()) if self.ceph_block: block_str += format_block(self.ceph_block.to_ceph_block()) + if self.log_block: + block_str += format_block(self.log_block.to_log_block()) return block_str def __eq__(self, other: Any) -> bool: @@ -379,8 +385,10 @@ class ExportMgr: break return nid - def _has_ceph_block(raw_config_parsed: List) -> bool: - return len(raw_config_parsed) > 1 + def _has_ceph_block(raw_config_parsed: Dict) -> bool: + return 'CEPH' in raw_config_parsed.keys() + def _has_log_block(raw_config_parsed: Dict) -> bool: + return 'LOG' in raw_config_parsed.keys() def _read_raw_config(self, rados_namespace: str) -> None: with self.mgr.rados.open_ioctx(self.rados_pool) as ioctx: @@ -396,16 +404,19 @@ class ExportMgr: log.debug(f'raw_config: {raw_config}') raw_config_parsed = GaneshaConfParser(raw_config).parse() log.debug(f'raw_config_parsed: {raw_config_parsed}') - export_block = raw_config_parsed[0] - # do we have a ceph block? + # mandatory export block + export_block = raw_config_parsed['EXPORT'] + # do we have a ceph/log block? (optional) + ceph_block = None + log_block = None if _has_ceph_block(raw_config_parsed): - ceph_block = raw_config_parsed[1] - self.export_conf_objs.append( + ceph_block = raw_config_parsed['CEPH'] + if _has_log_block(raw_config_parsed): + log_block = raw_config_parsed['LOG'] + self.export_conf_objs.append( GaneshaExport(Export.from_export_block(export_block, rados_namespace), - CephBlock.from_ceph_block(ceph_block))) - else: - self.export_conf_objs.append( - GaneshaExport(Export.from_export_block(export_block, rados_namespace))) + CephBlock.from_ceph_block(ceph_block), + LogBlock.from_log_block(log_block))) def _save_export(self, cluster_id: str, ganesha_export: GaneshaExport) -> None: log.debug('in _save_export') @@ -462,14 +473,18 @@ class ExportMgr: log.debug(f'raw_config: {raw_config}') raw_config_parsed = GaneshaConfParser(raw_config).parse() log.debug(f'raw_config_parsed: {raw_config_parsed}') - export_block = raw_config_parsed[0] - # do we have a ceph block? + export_block = raw_config_parsed['EXPORT'] + # do we have a ceph/log block? (optional) + ceph_block = None + log_block = None if _has_ceph_block(raw_config_parsed): - ceph_block = raw_config_parsed[1] - export = GaneshaExport(Export.from_export_block(export_block, cluster_id), - CephBlock.from_ceph_block(ceph_block)) - else: - export = GaneshaExport(Export.from_export_block(export_block, cluster_id)) + ceph_block = raw_config_parsed['CEPH'] + if _has_log_block(raw_config_parsed): + log_block = raw_config_parsed['LOG'] + self.export_conf_objs.append( + GaneshaExport(Export.from_export_block(export_block, rados_namespace), + CephBlock.from_ceph_block(ceph_block), + LogBlock.from_log_block(log_block))) log.debug(f'export: {export}') return export except ObjectNotFound: @@ -661,20 +676,24 @@ class ExportMgr: def _change_export(self, cluster_id: str, export: Dict, earmark_resolver: Optional[CephFSEarmarkResolver] = None) -> Dict[str, Any]: - # if the export json has a ceph section (key), extract it from the export + # if the export json has a ceph/log section (key), extract it from the export # json to preserver backward compatability. ceph_dict = {} + log_dict = {} if "ceph" in export.keys(): ceph_dict = export.pop("ceph") - if not "export" in export.keys(): - raise Exception('\'export\' key missing in export json') + if "log" in export.keys(): + log_dict = export.pop("log") + if "export" in export.keys(): export = export.pop("export") msg = f'export_dict: {export}' log.exception(msg) msg = f'ceph_dict: {ceph_dict}' log.exception(msg) + msg = f'log_dict: {log_dict}' + log.exception(msg) try: - return self._apply_export(cluster_id, export, earmark_resolver, ceph_dict) + return self._apply_export(cluster_id, export, earmark_resolver, ceph_dict, log_dict) except NotImplementedError as e: # in theory, the NotImplementedError here may be raised by a hook back to # an orchestration module. If the orchestration module supports it the NFS @@ -926,7 +945,8 @@ class ExportMgr: cluster_id: str, new_export_dict: Dict, earmark_resolver: Optional[CephFSEarmarkResolver] = None, - ceph_dict: Optional[Dict] = {}) -> Dict[str, str]: + ceph_dict: Optional[Dict] = {}, + log_dict: Optional[Dict] = {}) -> Dict[str, str]: for k in ['path', 'pseudo']: if k not in new_export_dict: raise NFSInvalidOperation(f'Export missing required field {k}') @@ -972,9 +992,13 @@ class ExportMgr: log.debug(f'ceph_dict: {ceph_dict}') if ceph_dict: ceph_block = CephBlock.from_dict(ceph_dict) + log_block = None + log.debug(f'log_dict: {log_dict}') + if log_dict: + log_block = LogBlock.from_dict(log_dict) # use @ganesha_export in place of @new_export here onwards - ganesha_export = GaneshaExport(new_export, ceph_block) + ganesha_export = GaneshaExport(new_export, ceph_block, log_block) if not old_export: if new_export.fsal.name == NFS_GANESHA_SUPPORTED_FSALS[1]: # only for RGW @@ -997,7 +1021,8 @@ class ExportMgr: and old_fsal.fs_name == new_fsal.fs_name and old_export.path == new_export.path and old_export.pseudo == new_export.pseudo - and old_export.ceph_block == ganesha_export.ceph_block) + and old_export.ceph_block == ganesha_export.ceph_block + and old_export.log_block == ganesha_export.log_block) if old_export.fsal.name == NFS_GANESHA_SUPPORTED_FSALS[1]: old_rgw_fsal = cast(RGWFSAL, old_export.fsal) diff --git a/src/pybind/mgr/nfs/ganesha_conf.py b/src/pybind/mgr/nfs/ganesha_conf.py index 6946297cd75..bd2cef02b3d 100644 --- a/src/pybind/mgr/nfs/ganesha_conf.py +++ b/src/pybind/mgr/nfs/ganesha_conf.py @@ -113,14 +113,15 @@ class GaneshaConfParser: value = self.stream()[:idx] self.pos += idx + 1 block_dict = RawBlock('%url', values={'value': value}) - return block_dict + return ('%url', block_dict) - block_dict = RawBlock(self.parse_block_name().upper()) + block_name = self.parse_block_name().upper() + block_dict = RawBlock(block_name) self.parse_block_body(block_dict) if self.stream()[0] != '}': raise Exception("No closing bracket '}' found at the end of block") self.pos += 1 - return block_dict + return (block_name, block_dict) def parse_parameter_value(self, raw_value: str) -> Any: if raw_value.find(',') != -1: @@ -164,7 +165,7 @@ class GaneshaConfParser: self.parse_stanza(block_dict) elif is_lbracket and ((is_semicolon and not is_semicolon_lt_lbracket) or (not is_semicolon)): - block_dict.blocks.append(self.parse_block_or_section()) + block_dict.blocks.append(self.parse_block_or_section()[1]) else: raise Exception("Malformed stanza: no semicolon found.") @@ -172,9 +173,10 @@ class GaneshaConfParser: raise Exception("Infinite loop while parsing block content") def parse(self) -> List[RawBlock]: - blocks = [] + blocks = {} while self.stream(): - blocks.append(self.parse_block_or_section()) + (block_name, block) = self.parse_block_or_section() + blocks[block_name] = block return blocks @@ -381,7 +383,7 @@ class CephBlock: return result @classmethod - def from_dict(cls, ex_dict: Dict[str, Any]) -> 'Export': + def from_dict(cls, ex_dict: Dict[str, Any]) -> 'CephBlock': return cls(ex_dict.get('async', False), ex_dict.get('zerocopy', False)) @@ -397,6 +399,117 @@ class CephBlock: return False return self.to_dict() == other.to_dict() +class Facility: + def __init(self, + name: str, + destination: str, + enable: str): + self.name = name + self.destination = destination + self.enable = enable + + @classmethod + def from_facility_block(cls, facility: RawBlock) -> 'Facility': + return cls(facility.values['name'], + facility.values['destination'], facility.values['enable']) + + def to_facility_block(self) -> RawBlock: + result = RawBlock("FACILITY", values={'name': self.name, + 'destination': self.destination, + 'enable': self.enable}) + return result + + @classmethod + def from_dict(cls, ex_dict: Dict[str, Any]) -> 'Facility': + return cls(ex_dict['name'], ex_dict['destination'], ex_dict['enable']) + + def to_dict(self) -> Dict[str, Any]: + values = { + 'name': self.name, + 'destination': self.destination, + 'enable': self.enable + } + return values + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Facility): + return False + return self.to_dict() == other.to_dict() + +class Components: + def __init(self, + fsal: str, + nfsv4: str): + self.fsal = fsal + self.nfsv4 = nfsv4 + + @classmethod + def from_components_block(cls, components: RawBlock) -> 'Components': + return cls(components.values['fsal'], components.values['nfsv4']) + + def to_components_block(self) -> RawBlock: + result = RawBlock("COMPONENTS", values={'fsal': self.fsal, 'nfsv4': self.nfsv4}) + return result + + @classmethod + def from_dict(cls, ex_dict: Dict[str, Any]) -> 'Components': + return cls(ex_dict['fsal'], ex_dict['nfsv4']) + + def to_dict(self) -> Dict[str, Any]: + values = { + 'fsal': self.fsal, + 'nfsv4': self.nfsv4 + } + return values + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Components): + return False + return self.to_dict() == other.to_dict() + +class LogBlock: + def __init__(self, + default_log_level: str, + components: Components, + facility: Facility): + self.default_log_level = default_log_level + self.components = components + self.facility = facility + + @classmethod + def from_log_block(cls, log_block: RawBlock) -> 'LogBlock': + return cls(log_block.values.get('default_log_level', None), + Components.from_components_block(self.components), + Facility.from_facility_block(self.facility)) + + def to_log_block(self) -> RawBlock: + result = RawBlock("LOG", values={'default_log_level': self.default_log_level}) + result.blocks = [ + self.components.to_components_block() + ] + [ + self.facility.to_facility_block() + ] + return result + + @classmethod + def from_dict(cls, ex_dict: Dict[str, Any]) -> 'LogBlock': + return cls(ex_dict['default_log_level'], + Components.from_dict(ex_dict['components']), + Facility.fron_dict(ex_dict['facility'])) + + def to_dict(self) -> Dict[str, Any]: + values = { + 'default_log_level': self.default_log_level, + 'components': self.components.to_dict(), + 'facility': self.facility.to_dict() + } + return values + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, LogBlock): + return False + return self.to_dict() == other.to_dict() + class Export: def __init__( self,