@@ -8,6 +8,7 @@ Doxygen/
*.o
*.lo
*.la
+src/domainroot/fedfs-domainroot
src/fedfsd/fedfsd
src/nsdbparams/nsdbparams
src/nfsref/nfsref
@@ -183,6 +183,7 @@ AC_CONFIG_FILES([Makefile
doc/man/Makefile
doc/rpcl/Makefile
src/Makefile
+ src/domainroot/Makefile
src/fedfsc/Makefile
src/fedfsd/Makefile
src/include/Makefile
@@ -197,5 +198,6 @@ AC_CONFIG_FILES([Makefile
src/nsdbc/Makefile
src/nsdbparams/Makefile
src/PyFedfs/Makefile
+ src/PyFedfs/domainroot/Makefile
src/plug-ins/Makefile])
AC_OUTPUT
@@ -38,7 +38,8 @@ NSDB_CLIENT_CMDS = nsdb-create-fsl.8 nsdb-create-fsn.8 \
dist_man7_MANS = fedfs.7 nsdb-parameters.7
dist_man8_MANS = rpc.fedfsd.8 mount.fedfs.8 fedfs-map-nfs4.8 nfsref.8 \
- nsdbparams.8 $(FEDFS_CLIENT_CMDS) $(NSDB_CLIENT_CMDS)
+ nsdbparams.8 fedfs-domainroot.8 \
+ $(FEDFS_CLIENT_CMDS) $(NSDB_CLIENT_CMDS)
CLEANFILES = cscope.in.out cscope.out cscope.po.out *~
DISTCLEANFILES = Makefile.in
new file mode 100644
@@ -0,0 +1,305 @@
+.\"@(#)fedfs-domainroot.8"
+.\"
+.\" @file doc/man/fedfs-domainroot.8
+.\" @brief man page for fedfs-domainroot tool
+.\"
+
+.\"
+.\" Copyright 2013 Oracle. All rights reserved.
+.\"
+.\" This file is part of fedfs-utils.
+.\"
+.\" fedfs-utils is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License version 2.0 as
+.\" published by the Free Software Foundation.
+.\"
+.\" fedfs-utils is distributed in the hope that it will be useful, but
+.\" WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+.\" GNU General Public License version 2.0 for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" version 2.0 along with fedfs-utils. If not, see:
+.\"
+.\" http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+.\"
+.TH FEDFS-DOMAINROOT 8 "@publication-date@"
+.SH NAME
+fedfs-domainroot \- set up FedFS domain root infrastructure
+.SH SYNOPSIS
+.B fedfs-domainroot
+.RB [ \-h ", " \-\-help ]
+.RB [ \-\-version ]
+.P
+.B fedfs-domainroot
+.RB [ \-\-silent ]
+.RB [ \-\-statedir =
+.IR statedir ]
+.B add
+.I domainname
+.P
+.B fedfs-domainroot
+.RB [ \-\-silent ]
+.RB [ \-\-statedir =
+.IR statedir ]
+.B remove
+.I domainname
+.RB [ \-\-force ]
+.P
+.B fedfs-domainroot
+.RB [ \-\-silent ]
+.RB [ \-\-statedir =
+.IR statedir ]
+.B status
+.P
+.B fedfs-domainroot
+.RB [ \-\-silent ]
+.RB [ \-\-statedir =
+.IR statedir ]
+.B clean
+.RB [ \-\-force ]
+.SH INTRODUCTION
+RFC 5716 introduces the Federated File System (FedFS, for short).
+FedFS is an extensible standardized mechanism
+by which system administrators construct
+a coherent namespace across multiple file servers using
+.IR "file system referrals" .
+For further details, see
+.BR fedfs (7).
+.P
+The top directory of a FedFS domain namespace is known as a
+.I domain root
+directory.
+FedFS-enabled clients discover the fileserver that exports
+a FedFS domain's root directory using a DNS SRV query.
+Using a well-known export path,
+clients then mount the domain root directory
+on that fileserver in the normal fashion.
+.P
+After a filesystem client mounts a domain's root directory,
+applications on that client descend into the domain's name space
+starting in that directory,
+and are directed transparently to exports on other fileservers.
+.P
+Further information about domain roots is available in
+.BR fedfs (7).
+.P
+.SH DESCRIPTION
+A single fileserver may host domain root directories
+for one or more FedFS domains.
+The
+.BR fedfs-domainroot (8)
+command is a convenient way to securely manage domain root exports
+on a Linux NFS fileserver.
+FedFS itself is agnostic about the underlying file-access protocol,
+but the
+.BR fedfs-domainroot (8)
+command supports only NFS at this time.
+.P
+FedFS domain root directories are exported using a standard well-known
+pathname to make it simple for clients to find them.
+The first component of the domain root's export pathname is always
+.IR /.domainroot .
+The second component is a FedFS domain name.
+.P
+For instance, the export pathname of the domain root of the
+.I example.net
+FedFS domain is
+.IR /.domainroot/example.net .
+.SS Operation
+The
+.B add
+subcommand creates a directory under
+.I @statedir@/domainroots
+where the contents of the domain root directory reside.
+A directory is also set up under
+.I /.domainroot
+for each doman root directory.
+.BR fedfs-domainroot (8)
+bind-mounts the domain root directory under
+.I @statedir@/domainroots,
+then exports the directory under
+.IR /.domainroot .
+.P
+In this way, each domain root directory is exported via a well-known pathname,
+and can have its own export settings separate from other domain root directories,
+including security settings and client and network designations.
+These can be modified by editing
+.I /etc/exports
+after the
+domain root export is created.
+.P
+The final step of setting up a FedFS domain is adding a set of DNS SRV
+records that direct FedFS-enabled clients to the fileserver
+where the domain's root directory resides.
+Adding DNS SRV records is outside the scope of the
+.BR fedfs-domainroot (8)
+command.
+.P
+The
+.BR fedfs-domainroot (8)
+command must run as root in order to create and remove NFS exports
+and entries in
+.IR /etc/fstab .
+.SS Subcommands
+Valid
+.BR fedfs-domainroot (8)
+subcommands are:
+.IP "\fBclean\fP"
+Remove the
+.I /.domainroot
+directory and other infrastructure (as long as it is empty).
+The user is asked to confirm before action is taken.
+.IP
+By default, this process stops when a step encounters an error.
+Adding the
+.B \-\-force
+option forces the process to try each step even if an error occurs,
+and bypasses the confirmation request.
+.IP "\fBstatus\fP"
+Display the status of the domain root infrastructure on the local system.
+This includes whether NFSD is running, and what domain root directories
+are currently configured and exported.
+This subcommand takes no arguments.
+.IP "\fBadd\fP"
+Create a new FedFS domain root directory under
+.I /.domainroot
+and export it.
+This subcommand takes a FedFS domain name as an argument.
+.IP "\fBremove\fP"
+Remove an existing FedFS domain root directory from
+.IR /.domainroot .
+This subcommand takes a FedFS domain name as an argument.
+The user is asked to confirm before action is taken.
+.IP
+By default, this process stops when a step encounters an error.
+Adding the
+.B \-\-force
+option forces the process to try each step even if an error occurs,
+and bypasses the confirmation request.
+.SS Command line options
+The following options are specified before the subcommand on the command line.
+.IP "\fB\-h, \-\-help"
+Display usage and copyright information, then exit.
+.IP "\fB\-\-version"
+Display fedfs-utils version information, then exit.
+.IP "\fB\-\-silent"
+Process quietly.
+.IP "\fB\-\-statedir=\fIstate-directory\fP"
+Find FedFS domain root directories on the local system in the
+.I domainroots
+subdirectory of the specified directory.
+By default, the state directory is
+.IR @statedir .
+.SH EXIT CODES
+The
+.BR fedfs-domainroot (8)
+command returns one of two values upon exit.
+.TP
+.B 0
+The requested subcommand succeeded.
+.TP
+.B 1
+The requested subcommand failed.
+.SH EXAMPLES
+Suppose you are the FedFS administrator of the
+.I example.net
+FedFS domain.
+After you have chosen a reliable NFS fileserver to serve your
+FedFS domain root directory, log in on that fileserver as root
+and ensure that NFSD is running.
+.P
+To create a new FedFS domain root for the
+.I example.net
+domain, use:
+.RS
+.sp
+# fedfs-domainroot --silent add example.net
+.br
+Added domain root for FedFS domain "example.net"
+.br
+#
+.sp
+.RE
+To populate the new domain root, change your current directory to
+.IR /.domainroot/example.net ,
+then add junctions with the
+.BR nfsref (8)
+command on the fileserver.
+.P
+You can list the domain roots that are currently exported
+by your fileserver with:
+.RS
+.sp
+# fedfs-domainroot --silent status
+.br
+FedFS domain roots:
+.br
+ example.net is exported with options
+ *(ro,subtree_check,mp,insecure,sec=sys:none)
+.br
+#
+.sp
+.RE
+When you want to remove this domain root (say, because you have
+moved it to another fileserver), remove it's contents, then use:
+.RS
+.sp
+# fedfs-domainroot remove example.net
+.br
+Removed domain root for FedFS domain "example.net"
+.br
+#
+.RE
+.SH SECURITY
+FedFS domain root exports created by
+.BR fedfs-domainroot (8)
+are exported with
+.BR *(ro,insecure,subtree_check,sec=sys:none) .
+FedFS standards recommend that
+FedFS domain root directories should be globally readable.
+Specific access restrictions typically occur lower in a domain's name space.
+.P
+However, fileserver administrators can alter
+a domain root export's security settings
+by editing a domain root export's entry in
+.IR /etc/exports ,
+and then refreshing the kernel's export cache with
+.BR "exportfs -r" .
+.P
+For example, if the domain root fileserver has Kerberos configured,
+an administrator might change a domain root export's
+.B sec=
+option to
+.BR sec=krb5p:krb5i:krb5:sys:none .
+Or, to restrict the range of clients that can access the
+domain root, an administrator might replace the leading
+.B *
+with a specific netgroup or IP network designation.
+.P
+It is recommended to keep the
+.B subtree_check
+export option.
+Refer to
+.BR exports (5)
+for details.
+.SH FILES
+.TP
+.I @statedir@/domainroots
+directory containing domain root directories
+.TP
+.I /.domainroot
+directory containing domain root exports
+.SH "SEE ALSO"
+.BR fedfs (7),
+.BR nfsref (8),
+.BR rpc.fedfsd (8),
+.BR exportfs (8),
+.BR exports (5)
+.SH COLOPHON
+This page is part of the fedfs-utils package.
+A description of the project and information about reporting bugs
+can be found at
+.IR http://wiki.linux-nfs.org/wiki/index.php/FedFsUtilsProject .
+.SH "AUTHOR"
+Chuck Lever <chuck.lever@oracle.com>
@@ -26,7 +26,7 @@
SUBDIRS = include libxlog libadmin libnsdb libjunction \
libparser libsi \
fedfsc fedfsd mount nfsref nsdbc nsdbparams \
- plug-ins PyFedfs
+ plug-ins PyFedfs domainroot
CLEANFILES = cscope.in.out cscope.out cscope.po.out *~
DISTCLEANFILES = Makefile.in
@@ -23,6 +23,7 @@
## http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
##
+SUBDIRS = domainroot
pyfedfs_PYTHON = __init__.py run.py userinput.py utilities.py
pyfedfsdir = $(pythondir)/PyFedfs
new file mode 100644
@@ -0,0 +1,31 @@
+##
+## @file src/PyFedfs/domainroot/Makefile.am
+## @brief Process this file with automake to produce src/domainroot/Makefile.in
+##
+
+##
+## Copyright 2013 Oracle. All rights reserved.
+##
+## This file is part of fedfs-utils.
+##
+## fedfs-utils is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License version 2.0 as
+## published by the Free Software Foundation.
+##
+## fedfs-utils is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License version 2.0 for more details.
+##
+## You should have received a copy of the GNU General Public License
+## version 2.0 along with fedfs-utils. If not, see:
+##
+## http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+##
+
+pyfedfs_PYTHON = __init__.py addremove.py clean.py status.py \
+ exports.py mounts.py paths.py parse_file.py
+pyfedfsdir = $(pythondir)/PyFedfs/domainroot
+
+CLEANFILES = cscope.in.out cscope.out cscope.po.out *~
+DISTCLEANFILES = Makefile.in
new file mode 100644
@@ -0,0 +1,23 @@
+"""
+PyFedfs
+
+This module contains components of the fedfs-domainroot command.
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
new file mode 100644
@@ -0,0 +1,175 @@
+"""
+Set up FedFS domain root infrastructure on an NFS server
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+try:
+ import sys
+ import os
+ import logging as log
+
+ from PyFedfs.domainroot.paths import DOMAINROOT_PATH
+ from PyFedfs.domainroot.paths import DOMAINROOTS_DIR_PATH
+ from PyFedfs.domainroot.mounts import add_mount, remove_mount
+ from PyFedfs.domainroot.exports import add_export, remove_export
+
+ from PyFedfs.run import EXIT_SUCCESS, EXIT_FAILURE
+ from PyFedfs.run import check_for_daemon
+ from PyFedfs.userinput import confirm
+ from PyFedfs.utilities import make_directory, list_directory
+ from PyFedfs.utilities import remove_directory
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+def prepare_domainroot(domainroots_dir_path):
+ """
+ Ensure domainroot infrastructure is set up
+
+ Returns a shell exit status value
+ """
+
+ try:
+ os.mkdir(DOMAINROOT_PATH)
+ except OSError as inst:
+ if inst.errno != 17:
+ log.exception('Failed to make directory "%s"',
+ DOMAINROOT_PATH)
+ return EXIT_FAILURE
+
+ parent = os.path.dirname(domainroots_dir_path)
+ if not os.path.isdir(parent):
+ log.error('"%s" does not exist', parent)
+ return EXIT_FAILURE
+
+ try:
+ os.mkdir(domainroots_dir_path)
+ except OSError as inst:
+ if inst.errno != 17:
+ log.exception('Failed to make directory "%s"',
+ domainroots_dir_path)
+ return EXIT_FAILURE
+
+ return EXIT_SUCCESS
+
+
+def do_add(export_pathname, bind_pathname):
+ """
+ Add a domain root directory
+
+ Returns a shell exit status value
+ """
+ ret = make_directory(bind_pathname, 0755)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = add_mount(bind_pathname, export_pathname, 'bind')
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return add_export(export_pathname)
+
+
+def do_remove(export_pathname, bind_pathname, force):
+ """
+ Remove a domain root directory
+
+ Returns a shell exit status value
+ """
+ ret = remove_export(export_pathname, force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = remove_mount(export_pathname, force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = remove_directory(bind_pathname, force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return EXIT_SUCCESS
+
+
+# No verification of the domain argument is done. A DNS SRV lookup
+# would be surest, but that would require the SRV record exist a
+# priori... Otherwise we'd need to regular expression of some kind
+# that can match both ASCII and IDNA hostnames but not IP addresses.
+def subcmd_add(args):
+ """
+ The 'add domain' subcommand
+
+ Returns a shell exit status value
+ """
+ domainroots_dir_path = os.path.join(args.statedir, DOMAINROOTS_DIR_PATH)
+
+ ret = prepare_domainroot(domainroots_dir_path)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ if not check_for_daemon('nfsd'):
+ log.warn('NFSD is not running')
+
+ export_pathname = os.path.join(DOMAINROOT_PATH, args.domainname)
+ bind_pathname = os.path.join(domainroots_dir_path, args.domainname)
+
+ ret = do_add(export_pathname, bind_pathname)
+ if ret != EXIT_SUCCESS:
+ do_remove(export_pathname, bind_pathname, True)
+ return ret
+
+ print('Added domain root for FedFS domain "%s"' % args.domainname)
+ return EXIT_SUCCESS
+
+
+def subcmd_remove(args):
+ """
+ The 'remove domain' subcommand
+
+ Returns a shell exit status value
+ """
+ domainroots_dir_path = os.path.join(args.statedir, DOMAINROOTS_DIR_PATH)
+
+ export_pathname = os.path.join(DOMAINROOT_PATH, args.domainname)
+ bind_pathname = os.path.join(domainroots_dir_path, args.domainname)
+
+ if not args.force:
+ items = list_directory(bind_pathname)
+ if len(items):
+ log.error('The domain root directory is not empty')
+ return EXIT_FAILURE
+ print('This tool will remove the domain root directory ' \
+ 'for the "%s" domain' % args.domainname)
+ if not confirm('Do you want to continue? [y/N] '):
+ print('Command aborted')
+ return EXIT_FAILURE
+
+ ret = do_remove(export_pathname, bind_pathname, args.force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ if not args.force:
+ print('Removed domain root for FedFS domain "%s"' % args.domainname)
+ return EXIT_SUCCESS
+
+
+__all__ = ['subcmd_add', 'subcmd_remove']
new file mode 100644
@@ -0,0 +1,86 @@
+"""
+Set up FedFS domain root infrastructure on an NFS server
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+
+try:
+ import sys
+ import os
+ import logging as log
+
+ from PyFedfs.domainroot.paths import DOMAINROOT_PATH
+ from PyFedfs.domainroot.paths import DOMAINROOTS_DIR_PATH
+
+ from PyFedfs.run import EXIT_SUCCESS, EXIT_FAILURE
+ from PyFedfs.userinput import confirm
+ from PyFedfs.utilities import list_directory, remove_directory
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+def do_clean(domainroots_dir_path, force):
+ """
+ Remove FedFS domainroot infrastructure
+
+ Returns a shell exit status value
+ """
+ ret = remove_directory(DOMAINROOT_PATH, force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = remove_directory(domainroots_dir_path, force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return EXIT_SUCCESS
+
+
+def subcmd_clean(args):
+ """
+ The 'clean infrastructure' subcommand
+
+ Returns a shell exit status value
+ """
+
+ if not args.force:
+ items = list_directory(DOMAINROOT_PATH)
+ if len(items):
+ log.error('There still exist some domain root directories')
+ return EXIT_FAILURE
+ print('This tool will remove all FedFS domain root infrastructure')
+ if not confirm('Do you want to continue? [y/N] '):
+ print('Command aborted')
+ return EXIT_FAILURE
+
+ domainroots_dir_path = os.path.join(args.statedir, DOMAINROOTS_DIR_PATH)
+
+ ret = do_clean(domainroots_dir_path, args.force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ if not args.force:
+ print('Successfully cleaned FedFS domain root infrastructure')
+ return EXIT_SUCCESS
+
+
+__all__ = ['subcmd_clean']
new file mode 100644
@@ -0,0 +1,173 @@
+"""
+Manage NFS exports
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+try:
+ import sys
+ import logging as log
+ import augeas
+
+ from PyFedfs.domainroot.parse_file import parse_file
+
+ from PyFedfs.run import EXIT_SUCCESS, EXIT_FAILURE
+ from PyFedfs.run import run_command
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+def filesystem_is_exported(pathname):
+ """
+ Predicate: is filesystem exported?
+
+ Returns True if "pathname" is exported, otherwise returns False
+ """
+ exports = parse_file('/var/lib/nfs/etab')
+ for export in exports:
+ if export[0] == pathname:
+ return True
+ return False
+
+
+def add_exports_entry(pathname):
+ """
+ Add entry to /etc/exports
+
+ Returns a shell exit status value
+ """
+ log.debug('Adding entry for "%s" to /etc/exports...', pathname)
+
+ config = augeas.augeas()
+
+ config.set('/files/etc/exports/dir[last()+1]', pathname)
+ config.set('/files/etc/exports/dir[last()]/client[1]', '*')
+ config.set('/files/etc/exports/dir[last()]/client[1]/option[1]',
+ 'ro')
+ config.set('/files/etc/exports/dir[last()]/client[1]/option[2]',
+ 'mp')
+ config.set('/files/etc/exports/dir[last()]/client[1]/option[3]',
+ 'subtree_check')
+ config.set('/files/etc/exports/dir[last()]/client[1]/option[4]',
+ 'insecure')
+ config.set('/files/etc/exports/dir[last()]/client[1]/option[5]',
+ 'sec=sys:none')
+
+ ret = EXIT_SUCCESS
+ try:
+ config.save()
+ except IOError:
+ log.exception('Failed to save /etc/exports')
+ ret = EXIT_FAILURE
+
+ config.close()
+
+ return ret
+
+
+def exports_contains_entry(pathname):
+ """
+ Predicate: does /etc/exports contain an entry for "pathname" ?
+
+ Returns True if /etc/exports contains an entry for "pathname"
+ otherwise returns False
+ """
+ config = augeas.augeas()
+ path = '/files/etc/exports/dir[.="' + pathname + '"]'
+ exports = config.match(path)
+ config.close()
+ if len(exports):
+ return True
+ return False
+
+
+def remove_exports_entry(pathname, force):
+ """
+ Remove an entry from /etc/exports
+
+ Returns a shell exit status value
+ """
+ log.debug('Removing entry for "%s" from /etc/exports...', pathname)
+
+ ret = EXIT_FAILURE
+ config = augeas.augeas()
+
+ path = '/files/etc/exports/dir[.="' + pathname + '"]'
+ matches = config.match(path)
+ if len(matches) == 1:
+ config.remove(matches[0])
+ config.save()
+ ret = EXIT_SUCCESS
+ else:
+ if not force:
+ log.error('No entry for "%s" in /etc/exports', pathname)
+
+ config.close()
+
+ if force:
+ return EXIT_SUCCESS
+ return ret
+
+
+def add_export(pathname):
+ """
+ Add an export
+
+ Returns a shell exit status value
+ """
+ ret = add_exports_entry(pathname)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return run_command(['exportfs', '*:' + pathname], False)
+
+
+def display_export(pathname):
+ """
+ Display the status of a domain root export
+
+ Returns no value
+ """
+ exports = parse_file('/var/lib/nfs/etab')
+ for export in exports:
+ if export[0] == pathname:
+ print('\t"%s" is exported with options' % export[0])
+ print('\t\t%s' % export[1])
+ break
+ else:
+ print('\t"%s" is not exported' % pathname)
+
+
+def remove_export(pathname, force):
+ """
+ Remove an export
+
+ Returns a shell exit status value
+ """
+ ret = run_command(['exportfs', '-u', '*:' + pathname], force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = remove_exports_entry(pathname, force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return EXIT_SUCCESS
new file mode 100644
@@ -0,0 +1,168 @@
+"""
+Manage local mounts
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+try:
+ import sys
+ import logging as log
+ import augeas
+
+ from PyFedfs.domainroot.parse_file import parse_file
+
+ from PyFedfs.run import EXIT_SUCCESS, EXIT_FAILURE
+ from PyFedfs.run import run_command
+ from PyFedfs.utilities import change_mode
+ from PyFedfs.utilities import make_directory, remove_directory
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+def filesystem_is_mounted(pathname):
+ """
+ Predicate: is filesystem mounted?
+
+ Returns True if "pathname" is mounted, otherwise returns False
+ """
+ mounts = parse_file('/proc/mounts')
+ for mount in mounts:
+ if mount[1] == pathname:
+ return True
+ return False
+
+
+def add_fstab_entry(devicepath, mounted_on, fs_type):
+ """
+ Add entry to /etc/fstab
+
+ Returns a shell exit status value
+ """
+ log.debug('Adding entry for "%s" to /etc/fstab...', devicepath)
+
+ config = augeas.augeas()
+
+ config.set('/files/etc/fstab/01/spec', devicepath)
+ config.set('/files/etc/fstab/01/file', mounted_on)
+ config.set('/files/etc/fstab/01/vfstype', fs_type)
+ if fs_type == 'bind':
+ config.set('/files/etc/fstab/01/opt', 'bind')
+ else:
+ config.set('/files/etc/fstab/01/opt', 'defaults')
+ config.set('/files/etc/fstab/01/dump', '1')
+ config.set('/files/etc/fstab/01/passno', '2')
+
+ ret = EXIT_SUCCESS
+ try:
+ config.save()
+ except IOError:
+ log.exception('Failed to save /etc/fstab')
+ ret = EXIT_FAILURE
+
+ config.close()
+
+ return ret
+
+
+def fstab_contains_entry(pathname):
+ """
+ Predicate: does /etc/fstab contain an entry for "pathname" ?
+
+ Returns True if /etc/fstab contains an entry for "pathname"
+ otherwise returns False
+ """
+ config = augeas.augeas()
+ path = '/files/etc/fstab/*[file="' + pathname + '"]'
+ fstab = config.match(path)
+ config.close()
+ if len(fstab):
+ return True
+ return False
+
+
+def remove_fstab_entry(pathname, force):
+ """
+ Remove an entry from /etc/fstab
+
+ Returns a shell exit status value
+ """
+ log.debug('Removing entry for "%s" from /etc/fstab...', pathname)
+
+ ret = EXIT_FAILURE
+ config = augeas.augeas()
+
+ path = '/files/etc/fstab/*[file="' + pathname + '"]'
+ matches = config.match(path)
+ if len(matches) == 1:
+ config.remove(matches[0])
+ config.save()
+ ret = EXIT_SUCCESS
+ else:
+ if not force:
+ log.error('No entry for "%s" in /etc/fstab', pathname)
+
+ config.close()
+
+ if force:
+ return EXIT_SUCCESS
+ return ret
+
+
+def add_mount(pathname, mounted_on, fs_type):
+ """
+ Add a mount point
+
+ Returns a shell exit status value
+ """
+ ret = add_fstab_entry(pathname, mounted_on, fs_type)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = make_directory(mounted_on, 0755)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = run_command(['mount', mounted_on], False)
+ if ret != EXIT_SUCCESS:
+ return EXIT_FAILURE
+
+ return change_mode(mounted_on, 0755)
+
+
+def remove_mount(pathname, force):
+ """
+ Remove a mount
+
+ Returns a shell exit status value
+ """
+ ret = run_command(['umount', pathname], force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = remove_directory(pathname, force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = remove_fstab_entry(pathname, force)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return EXIT_SUCCESS
new file mode 100644
@@ -0,0 +1,54 @@
+"""
+Parse a text file into a list of lists
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+import logging as log
+
+
+def parse_file(pathname):
+ """
+ Read a configuration file into a list of lists
+
+ I could use the csv module for this, but some files may contain
+ interspersed blanks and tabs, which csv does not handle.
+ Can't use augeas, as some files aren't under /etc.
+
+ Returns a list containing each line in "pathname".
+ Each line is parsed into a list of the line's fields.
+ """
+ try:
+ file_object = open(pathname, 'r')
+ except OSError as inst:
+ log.debug('Failed to open "%s": %s', pathname, inst)
+ return []
+
+ ret = []
+ try:
+ for line in file_object:
+ stripped = line.strip()
+ if len(stripped):
+ items = stripped.split()
+ if items[0][0] != '#':
+ ret.append(items)
+ finally:
+ file_object.close()
+
+ return ret
new file mode 100644
@@ -0,0 +1,24 @@
+"""
+Common directory pathnames for fedfs-domainroot command
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+DOMAINROOT_PATH = '/.domainroot'
+DOMAINROOTS_DIR_PATH = 'domainroots'
new file mode 100644
@@ -0,0 +1,61 @@
+"""
+Set up FedFS domain root infrastructure on an NFS server
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+try:
+ import sys
+ import os
+
+ from PyFedfs.domainroot.paths import DOMAINROOT_PATH
+ from PyFedfs.domainroot.exports import display_export
+
+ from PyFedfs.run import EXIT_SUCCESS
+ from PyFedfs.run import check_for_daemon
+ from PyFedfs.utilities import list_directory
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+# pylint: disable-msg=W0613
+def subcmd_status(args):
+ """
+ The 'display status' subcommand
+
+ Returns a shell exit status value
+ """
+ if not check_for_daemon('nfsd'):
+ print('NFSD is not running')
+ return EXIT_SUCCESS
+
+ output = list_directory(DOMAINROOT_PATH)
+ if len(output):
+ print('FedFS domain roots:')
+ for item in output:
+ display_export(os.path.join(DOMAINROOT_PATH, item))
+ else:
+ print('FedFS domain roots: None')
+
+ return EXIT_SUCCESS
+
+
+__all__ = ['subcmd_status']
new file mode 100644
@@ -0,0 +1,40 @@
+##
+## @file src/domainroot/Makefile.am
+## @brief Process this file with automake to produce src/domainroot/Makefile.in
+##
+
+##
+## Copyright 2013 Oracle. All rights reserved.
+##
+## This file is part of fedfs-utils.
+##
+## fedfs-utils is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License version 2.0 as
+## published by the Free Software Foundation.
+##
+## fedfs-utils is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License version 2.0 for more details.
+##
+## You should have received a copy of the GNU General Public License
+## version 2.0 along with fedfs-utils. If not, see:
+##
+## http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+##
+
+
+bin_SCRIPTS = fedfs-domainroot
+EXTRA_DIST = fedfs-domainroot.in
+
+do_substitution = $(SED) -e 's,[@]pythondir[@],$(pythondir),g' \
+ -e 's,[@]PACKAGE[@],$(PACKAGE),g' \
+ -e 's,[@]VERSION[@],$(VERSION),g' \
+ -e 's,[@]STATEDIR[@],$(statedir),g'
+
+fedfs-domainroot: fedfs-domainroot.in Makefile
+ $(do_substitution) < $(srcdir)/fedfs-domainroot.in > fedfs-domainroot
+ chmod +x fedfs-domainroot
+
+CLEANFILES = $(bin_SCRIPTS) cscope.in.out cscope.out cscope.po.out *~
+DISTCLEANFILES = Makefile.in
new file mode 100644
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+
+"""
+Run the domainroot administration tool
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+import sys
+import os
+import argparse
+import logging as log
+
+sys.path.insert(1, '@pythondir@')
+
+try:
+ from PyFedfs.run import EXIT_FAILURE
+ from PyFedfs.domainroot.addremove import subcmd_add, subcmd_remove
+ from PyFedfs.domainroot.clean import subcmd_clean
+ from PyFedfs.domainroot.status import subcmd_status
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+
+def main():
+ """
+ Domainroot helper main program
+
+ Returns a shell exit status value
+ """
+ if os.getegid() != 0:
+ print >> sys.stderr, 'You must be root to run fedfs-domainroot.'
+ return EXIT_FAILURE
+
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description='Set up FedFS domain root infrastructure',
+ epilog='''\
+Copyright 2013 Oracle. All rights reserved.
+
+License GPLv2: <http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt>
+This is free software. You are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.''')
+ parser.add_argument('--version',
+ help='Display the version of this command',
+ action='version',
+ version='@PACKAGE@ @VERSION@')
+ parser.add_argument('--silent',
+ help='Display less output',
+ action='store_true')
+ parser.add_argument('--statedir',
+ help='FedFS state dir (default @STATEDIR@)',
+ default='@STATEDIR@')
+ subparsers = parser.add_subparsers(title='Sub-commands')
+
+ add_parser = subparsers.add_parser('add',
+ help='Add a new domain root directory')
+ add_parser.add_argument('domainname',
+ help='Name of FedFS domain to add')
+ add_parser.set_defaults(func=subcmd_add)
+
+ remove_parser = subparsers.add_parser('remove',
+ help='Remove a domain root '
+ 'directory')
+ remove_parser.add_argument('--force',
+ help='Ignore errors', action='store_true')
+ remove_parser.add_argument('domainname',
+ help='Name of FedFS domain to remove')
+ remove_parser.set_defaults(func=subcmd_remove)
+
+ status_parser = subparsers.add_parser('status',
+ help='Display current FedFS domain '
+ 'root configuration')
+ status_parser.set_defaults(func=subcmd_status)
+
+ clean_parser = subparsers.add_parser('clean',
+ help='Remove domain root '
+ 'infrastructure')
+ clean_parser.add_argument('--force',
+ help='Ignore errors', action='store_true')
+ clean_parser.set_defaults(func=subcmd_clean)
+
+ args = parser.parse_args()
+
+ if args.silent:
+ log.basicConfig(format='%(levelname)s: %(message)s')
+ else:
+ log.basicConfig(format='%(levelname)s: %(message)s',
+ level=log.DEBUG)
+
+ return args.func(args)
+
+
+try:
+ if __name__ == '__main__':
+ sys.exit(main())
+except (SystemExit, KeyboardInterrupt, RuntimeError):
+ sys.exit(1)
Simplify the process of setting up FedFS domain roots by introducing a tool called "fedfs-domainroot" that can handle the details. Sub-commands: {clean,status,add,remove} clean Remove FedFS domain root infrastructure status Display current FedFS domain root configuration add Add a new domain root directory remove Remove a domain root directory Signed-off-by: Chuck Lever <chuck.lever@oracle.com> --- .gitignore | 1 configure.ac | 2 doc/man/Makefile.am | 3 doc/man/fedfs-domainroot.8 | 305 ++++++++++++++++++++++++++++++++++ src/Makefile.am | 2 src/PyFedfs/Makefile.am | 1 src/PyFedfs/domainroot/Makefile.am | 31 +++ src/PyFedfs/domainroot/__init__.py | 23 +++ src/PyFedfs/domainroot/addremove.py | 175 ++++++++++++++++++++ src/PyFedfs/domainroot/clean.py | 86 ++++++++++ src/PyFedfs/domainroot/exports.py | 173 +++++++++++++++++++ src/PyFedfs/domainroot/mounts.py | 168 +++++++++++++++++++ src/PyFedfs/domainroot/parse_file.py | 54 ++++++ src/PyFedfs/domainroot/paths.py | 24 +++ src/PyFedfs/domainroot/status.py | 61 +++++++ src/domainroot/Makefile.am | 40 ++++ src/domainroot/fedfs-domainroot.in | 117 +++++++++++++ 17 files changed, 1264 insertions(+), 2 deletions(-) create mode 100644 doc/man/fedfs-domainroot.8 create mode 100644 src/PyFedfs/domainroot/Makefile.am create mode 100644 src/PyFedfs/domainroot/__init__.py create mode 100644 src/PyFedfs/domainroot/addremove.py create mode 100644 src/PyFedfs/domainroot/clean.py create mode 100644 src/PyFedfs/domainroot/exports.py create mode 100644 src/PyFedfs/domainroot/mounts.py create mode 100644 src/PyFedfs/domainroot/parse_file.py create mode 100644 src/PyFedfs/domainroot/paths.py create mode 100644 src/PyFedfs/domainroot/status.py create mode 100644 src/domainroot/Makefile.am create mode 100644 src/domainroot/fedfs-domainroot.in