From patchwork Tue Oct 29 19:42:19 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Chuck Lever X-Patchwork-Id: 287043 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from aserp1040.oracle.com (aserp1040.oracle.com [141.146.126.69]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client CN "aserp1040.oracle.com", Issuer "VeriSign Class 3 International Server CA - G3" (not verified)) by ozlabs.org (Postfix) with ESMTPS id 266082C0369 for ; Wed, 30 Oct 2013 06:42:42 +1100 (EST) Received: from acsinet21.oracle.com (acsinet21.oracle.com [141.146.126.237]) by aserp1040.oracle.com (Sentrion-MTA-4.3.1/Sentrion-MTA-4.3.1) with ESMTP id r9TJgew8015850 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Tue, 29 Oct 2013 19:42:40 GMT Received: from oss.oracle.com (oss-external.oracle.com [137.254.96.51]) by acsinet21.oracle.com (8.14.4+Sun/8.14.4) with ESMTP id r9TJgdL8022168 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Tue, 29 Oct 2013 19:42:40 GMT Received: from localhost ([127.0.0.1] helo=oss.oracle.com) by oss.oracle.com with esmtp (Exim 4.63) (envelope-from ) id 1VbFBX-0005R2-J2; Tue, 29 Oct 2013 12:42:39 -0700 Received: from acsinet22.oracle.com ([141.146.126.238]) by oss.oracle.com with esmtp (Exim 4.63) (envelope-from ) id 1VbFBP-0005Qr-6B for fedfs-utils-devel@oss.oracle.com; Tue, 29 Oct 2013 12:42:31 -0700 Received: from aserp1030.oracle.com (aserp1030.oracle.com [141.146.126.68]) by acsinet22.oracle.com (8.14.4+Sun/8.14.4) with ESMTP id r9TJgUCV023300 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Tue, 29 Oct 2013 19:42:30 GMT Received: from mail-oa0-f53.google.com (mail-oa0-f53.google.com [209.85.219.53]) by aserp1030.oracle.com (Sentrion-MTA-4.3.1/Sentrion-MTA-4.3.1) with ESMTP id r9TJgTbK011026 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=OK) for ; Tue, 29 Oct 2013 19:42:30 GMT Received: by mail-oa0-f53.google.com with SMTP id n12so409984oag.26 for ; Tue, 29 Oct 2013 12:42:29 -0700 (PDT) X-Received: by 10.182.101.134 with SMTP id fg6mr994351obb.30.1383075749459; Tue, 29 Oct 2013 12:42:29 -0700 (PDT) Received: from seurat.1015granger.net (c-68-32-80-121.hsd1.mi.comcast.net. [68.32.80.121]) by mx.google.com with ESMTPSA id d8sm46439314oeu.6.2013.10.29.12.42.26 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 29 Oct 2013 12:42:29 -0700 (PDT) To: fedfs-utils-devel@oss.oracle.com From: Chuck Lever Date: Tue, 29 Oct 2013 15:42:19 -0400 Message-ID: <20131029194219.19294.47664.stgit@seurat.1015granger.net> In-Reply-To: <20131029192303.19294.65282.stgit@seurat.1015granger.net> References: <20131029192303.19294.65282.stgit@seurat.1015granger.net> User-Agent: StGit/0.16 MIME-Version: 1.0 X-Flow-Control-Info: class=Pass-to-MM reputation=ipRisk-All ip=209.85.219.53 ct-class=R5 ct-vol1=0 ct-vol2=8 ct-vol3=8 ct-risk=50 ct-spam1=80 ct-spam2=7 ct-bulk=6 rcpts=1 size=45453 X-SPF-Info: PASS::mail-oa0-f53.google.com X-Sendmail-CM-Score: 0.00% X-Sendmail-CM-Analysis: v=2.1 cv=LqSrlBtc c=1 sm=1 tr=0 a=k+UpYAzc8REPnh4cOiqX8g==:117 a=lHl1VeN+WkZht9Pe5WE6vg==:17 a=dzsqy3y4QnMA:10 a=Q76tbfDxsnMA:10 a=dPGociXpb70A:10 a=IkcTkHD0fZMA:10 a=yPCof4ZbAAAA:8 a=Lb1rMZzfAAAA:8 a=1XWaLZrsAAAA:8 a=C_IRinGWAAAA:8 a=8j W6NUQqCxAA:10 a=mDV3o1hIAAAA:8 a=OK-8mIdLAAAA:8 a=P6JkxrBpAAAA:8 a=qex1LihbTeKr_wqb7F8A:9 a=QEXdDO2ut3YA:10 a=u37mErDvIGIA:10 a=nvz6EU7xhngA:10 a=7DSvI1NPTFQA:10 a=MQEq1R4Qs-cA:10 X-Sendmail-CT-Classification: not spam X-Sendmail-CT-RefID: str=0001.0A090203.52700FA6.009E, ss=1, re=-6.400, recu=0.000, reip=0.000, cl=1, cld=1, fgs=0 Subject: [fedfs-utils] [PATCH 4/5] Add a script to configure domain roots X-BeenThere: fedfs-utils-devel@oss.oracle.com X-Mailman-Version: 2.1.9 Precedence: list Reply-To: fedfs-utils Developers List-Id: fedfs-utils Developers List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: fedfs-utils-devel-bounces@oss.oracle.com Errors-To: fedfs-utils-devel-bounces@oss.oracle.com X-Source-IP: acsinet21.oracle.com [141.146.126.237] 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 --- .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 diff --git a/.gitignore b/.gitignore index ab759f1..407e013 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ Doxygen/ *.o *.lo *.la +src/domainroot/fedfs-domainroot src/fedfsd/fedfsd src/nsdbparams/nsdbparams src/nfsref/nfsref diff --git a/configure.ac b/configure.ac index fd70d92..7b83a2c 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am index 19230b1..65e9e9c 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -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 diff --git a/doc/man/fedfs-domainroot.8 b/doc/man/fedfs-domainroot.8 new file mode 100644 index 0000000..359a416 --- /dev/null +++ b/doc/man/fedfs-domainroot.8 @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index c7dff86..9f84ebc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/PyFedfs/Makefile.am b/src/PyFedfs/Makefile.am index be67d2a..bef9d11 100644 --- a/src/PyFedfs/Makefile.am +++ b/src/PyFedfs/Makefile.am @@ -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 diff --git a/src/PyFedfs/domainroot/Makefile.am b/src/PyFedfs/domainroot/Makefile.am new file mode 100644 index 0000000..a36a4c0 --- /dev/null +++ b/src/PyFedfs/domainroot/Makefile.am @@ -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 diff --git a/src/PyFedfs/domainroot/__init__.py b/src/PyFedfs/domainroot/__init__.py new file mode 100644 index 0000000..5513e49 --- /dev/null +++ b/src/PyFedfs/domainroot/__init__.py @@ -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 +""" diff --git a/src/PyFedfs/domainroot/addremove.py b/src/PyFedfs/domainroot/addremove.py new file mode 100644 index 0000000..2363350 --- /dev/null +++ b/src/PyFedfs/domainroot/addremove.py @@ -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'] diff --git a/src/PyFedfs/domainroot/clean.py b/src/PyFedfs/domainroot/clean.py new file mode 100644 index 0000000..eb08db6 --- /dev/null +++ b/src/PyFedfs/domainroot/clean.py @@ -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'] diff --git a/src/PyFedfs/domainroot/exports.py b/src/PyFedfs/domainroot/exports.py new file mode 100644 index 0000000..20597dc --- /dev/null +++ b/src/PyFedfs/domainroot/exports.py @@ -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 diff --git a/src/PyFedfs/domainroot/mounts.py b/src/PyFedfs/domainroot/mounts.py new file mode 100644 index 0000000..a17f2e1 --- /dev/null +++ b/src/PyFedfs/domainroot/mounts.py @@ -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 diff --git a/src/PyFedfs/domainroot/parse_file.py b/src/PyFedfs/domainroot/parse_file.py new file mode 100644 index 0000000..b92d9e6 --- /dev/null +++ b/src/PyFedfs/domainroot/parse_file.py @@ -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 diff --git a/src/PyFedfs/domainroot/paths.py b/src/PyFedfs/domainroot/paths.py new file mode 100644 index 0000000..f53bff8 --- /dev/null +++ b/src/PyFedfs/domainroot/paths.py @@ -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' diff --git a/src/PyFedfs/domainroot/status.py b/src/PyFedfs/domainroot/status.py new file mode 100644 index 0000000..1dd9a45 --- /dev/null +++ b/src/PyFedfs/domainroot/status.py @@ -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'] diff --git a/src/domainroot/Makefile.am b/src/domainroot/Makefile.am new file mode 100644 index 0000000..26a3c2e --- /dev/null +++ b/src/domainroot/Makefile.am @@ -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 diff --git a/src/domainroot/fedfs-domainroot.in b/src/domainroot/fedfs-domainroot.in new file mode 100644 index 0000000..e8878de --- /dev/null +++ b/src/domainroot/fedfs-domainroot.in @@ -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: +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)