From: Varsha Rao Date: Fri, 22 Nov 2019 05:34:01 +0000 (+0530) Subject: mgr: Add command to create exports for nfs-ganesha X-Git-Tag: v16.1.0~2626^2~30 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=960688169f26b0cdbbcd90d3e7cefe9a176d17dc;p=ceph.git mgr: Add command to create exports for nfs-ganesha 'ceph fs nfs create': This command creates export objects for nfs-ganesha. vstart ganesha daemon fetches the export block from rados object. Fixes: https://tracker.ceph.com/issues/44193 Signed-off-by: Varsha Rao --- diff --git a/src/pybind/mgr/volumes/fs/nfs.py b/src/pybind/mgr/volumes/fs/nfs.py new file mode 100644 index 00000000000..9ef45aa57a7 --- /dev/null +++ b/src/pybind/mgr/volumes/fs/nfs.py @@ -0,0 +1,186 @@ +import json +import logging +import cephfs +import orchestrator +from dashboard.services.cephx import CephX +from dashboard.services.ganesha import Ganesha, NFSException, Export, GaneshaConfParser + +log = logging.getLogger(__name__) + +class GaneshaConf(object): + # pylint: disable=R0902 + + def __init__(self, cluster_id, rados_pool, rados_namespace, mgr): + self.mgr = mgr + self.cephx_key = "" + self.cluster_id = cluster_id + self.rados_pool = rados_pool + self.rados_namespace = rados_namespace + self.export_conf_blocks = [] + self.daemons_conf_blocks = {} + self._defaults = {} + self.exports = {} + + self._read_raw_config() + + # load defaults + def_block = [b for b in self.export_conf_blocks + if b['block_name'] == "EXPORT_DEFAULTS"] + self.export_defaults = def_block[0] if def_block else {} + self._defaults = self.ganesha_defaults(self.export_defaults) + + for export_block in [block for block in self.export_conf_blocks + if block['block_name'] == "EXPORT"]: + export = Export.from_export_block(export_block, cluster_id, + self._defaults) + self.exports[export.export_id] = export + + # link daemons to exports + for daemon_id, daemon_blocks in self.daemons_conf_blocks.items(): + for block in daemon_blocks: + if block['block_name'] == "%url": + rados_url = block['value'] + _, _, obj = Ganesha.parse_rados_url(rados_url) + if obj.startswith("export-"): + export_id = int(obj[obj.find('-')+1:]) + self.exports[export_id].daemons.add(daemon_id) + + def _read_raw_config(self): + with self.mgr.rados.open_ioctx(self.rados_pool) as ioctx: + if self.rados_namespace: + ioctx.set_namespace(self.rados_namespace) + objs = ioctx.list_objects() + for obj in objs: + if obj.key.startswith("export-"): + size, _ = obj.stat() + raw_config = obj.read(size) + raw_config = raw_config.decode("utf-8") + log.debug("read export configuration from rados " + "object %s/%s/%s:\n%s", self.rados_pool, + self.rados_namespace, obj.key, raw_config) + self.export_conf_blocks.extend( + GaneshaConfParser(raw_config).parse()) + elif obj.key.startswith("conf-"): + size, _ = obj.stat() + raw_config = obj.read(size) + raw_config = raw_config.decode("utf-8") + log.debug("read daemon configuration from rados " + "object %s/%s/%s:\n%s", self.rados_pool, + self.rados_namespace, obj.key, raw_config) + + idx = obj.key.find('-') + self.daemons_conf_blocks[obj.key[idx+1:]] = \ + GaneshaConfParser(raw_config).parse() + + def _write_raw_config(self, conf_block, obj): + raw_config = GaneshaConfParser.write_conf(conf_block) + with self.mgr.rados.open_ioctx(self.rados_pool) as ioctx: + if self.rados_namespace: + ioctx.set_namespace(self.rados_namespace) + ioctx.write_full(obj, raw_config.encode('utf-8')) + log.debug( + "write configuration into rados object %s/%s/%s:\n%s", + self.rados_pool, self.rados_namespace, obj, raw_config) + + + @classmethod + def ganesha_defaults(cls, export_defaults): + """ + According to + https://github.com/nfs-ganesha/nfs-ganesha/blob/next/src/config_samples/export.txt + """ + return { + 'access_type': export_defaults.get('access_type', 'NONE'), + 'protocols': export_defaults.get('protocols', [3, 4]), + 'transports': export_defaults.get('transports', ['TCP', 'UDP']), + 'squash': export_defaults.get('squash', 'root_squash') + } + + def _gen_export_id(self): + exports = sorted(self.exports) + nid = 1 + for e_id in exports: + if e_id == nid: + nid += 1 + else: + break + return nid + + def fill_keys(self, export): + r, auth_dump, outs = self.mgr.mon_command({'prefix':"auth list", "format":"json"}) + auth_dump_ls = json.loads(auth_dump) + result = {} + entity_type = "client" + for auth_entry in auth_dump_ls['auth_dump']: + entity = auth_entry['entity'] + if not entity_type or entity.startswith('{}.'.format(entity_type)): + entity_id = entity[entity.find('.')+1:] + result[entity_id] = auth_entry + self.cephx_key = result["admin"]["key"] + export.fsal.cephx_key = self.cephx_key + + def _persist_daemon_configuration(self): + daemon_map = {} + """ + for daemon_id in self.list_daemons(): + daemon_map[daemon_id] = [] + """ + daemon_map["ganesha.a"] = [] + + for _, ex in self.exports.items(): + for daemon in ex.daemons: + daemon_map[daemon].append({ + 'block_name': "%url", + 'value': Ganesha.make_rados_url( + self.rados_pool, self.rados_namespace, + "export-{}".format(ex.export_id)) + }) + for daemon_id, conf_blocks in daemon_map.items(): + self._write_raw_config(conf_blocks, "conf-{}".format(daemon_id)) + + + def _save_export(self, export): + self.fill_keys(export) + self.exports[export.export_id] = export + conf_block = export.to_export_block(self.export_defaults) + self._write_raw_config(conf_block, "export-{}".format(export.export_id)) + self._persist_daemon_configuration() + + def create_export(self, ex_dict): + ex_id = self._gen_export_id() + export = Export.from_dict(ex_id, ex_dict) + self._save_export(export) + return ex_id + + def list_daemons(self): + return [daemon_id for daemon_id in self.daemons_conf_blocks] + + +def create_instance(orch): + return GaneshaConf("a", "nfs-ganesha", "ganesha", orch) + +def create_export(ganesha_conf): + ex_id = ganesha_conf.create_export({ + 'path': "/", + 'pseudo': "/cephfs", + 'cluster_id': "a", + 'daemons': ["ganesha.a"], + 'tag': "", + 'access_type': "RW", + 'squash': "no_root_squash", + 'security_label': True, + 'protocols': [4], + 'transports': ["TCP"], + 'fsal': {"name": "CEPH", "user_id":"admin", "fs_name": "a", "sec_label_xattr": ""}, + 'clients': [] + }) + + log.info("Export ID is {}".format(ex_id)) + return 0, "", "" + +def check_fsal_valid(fs_map): + fsmap_res = [{'id': fs['id'], 'name': fs['mdsmap']['fs_name']} + for fs in fs_map['filesystems']] + + #return 0, json.dumps(fsmap_res, indent=2), "" + return fsmap_res diff --git a/src/pybind/mgr/volumes/module.py b/src/pybind/mgr/volumes/module.py index 0d8015bf48f..45a86884ede 100644 --- a/src/pybind/mgr/volumes/module.py +++ b/src/pybind/mgr/volumes/module.py @@ -5,6 +5,7 @@ from mgr_module import MgrModule import orchestrator from .fs.volume import VolumeClient +from .fs.nfs import check_fsal_valid, create_instance, create_export class Module(orchestrator.OrchestratorClientMixin, MgrModule): COMMANDS = [ @@ -212,6 +213,11 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): 'desc': "Cancel an pending or ongoing clone operation.", 'perm': 'r' }, + { + 'cmd': 'fs nfs create', + 'desc': "Create dummy exports", + 'perm': 'rw' + }, # volume ls [recursive] # subvolume ls @@ -376,3 +382,8 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): def _cmd_fs_clone_cancel(self, inbuf, cmd): return self.vc.clone_cancel( vol_name=cmd['vol_name'], clone_name=cmd['clone_name'], group_name=cmd.get('group_name', None)) + + def _cmd_fs_nfs_create(self, inbuf, cmd): + if check_fsal_valid(self.vc.mgr.get('fs_map')): + instance = create_instance(self) + return create_export(instance) diff --git a/src/vstart.sh b/src/vstart.sh index 31c67ee45de..017b7f3e5a8 100755 --- a/src/vstart.sh +++ b/src/vstart.sh @@ -1108,24 +1108,17 @@ NFSv4 { Minor_Versions = 1, 2; } -EXPORT { - Export_Id = 100; - Transports = TCP; - Path = /; - Pseudo = /ceph/; - Protocols = 4; - Access_Type = RW; - Attr_Expiration_Time = 0; - Squash = None; - FSAL { - Name = CEPH; - } +CEPH { + Ceph_Conf = $conf_fn; } -CEPH { - Ceph_Conf = $conf_fn; +RADOS_URLS { + ceph_conf = $conf_fn; + userid = "admin"; } +%url rados://nfs-ganesha/ganesha/export-1 + RADOS_KV { Ceph_Conf = $conf_fn; pool = 'nfs-ganesha'; @@ -1149,6 +1142,7 @@ EOF prun ceph_adm osd pool application enable nfs-ganesha nfs fi + prun ceph_adm fs nfs create prun ganesha-rados-grace -p nfs-ganesha -n ganesha add $name prun ganesha-rados-grace -p nfs-ganesha -n ganesha