Patchwork [3/5] src: Introduce Python FedFS module

login
register
mail settings
Submitter Chuck Lever
Date Oct. 29, 2013, 7:42 p.m.
Message ID <20131029194211.19294.10860.stgit@seurat.1015granger.net>
Download mbox | patch
Permalink /patch/287042/
State Accepted
Headers show

Comments

Chuck Lever - Oct. 29, 2013, 7:42 p.m.
To support upcoming tools based on Python, start building a module
of utility functions that can be imported to keep the new tools
small.

Eventually this could become a FedFS implementation in Python.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 .gitignore               |    1 
 configure.ac             |    2 
 src/Makefile.am          |    2 
 src/PyFedfs/Makefile.am  |   30 +++++
 src/PyFedfs/__init__.py  |   25 ++++
 src/PyFedfs/run.py       |  299 ++++++++++++++++++++++++++++++++++++++++++++++
 src/PyFedfs/userinput.py |  105 ++++++++++++++++
 src/PyFedfs/utilities.py |  134 +++++++++++++++++++++
 8 files changed, 597 insertions(+), 1 deletion(-)
 create mode 100644 src/PyFedfs/Makefile.am
 create mode 100644 src/PyFedfs/__init__.py
 create mode 100644 src/PyFedfs/run.py
 create mode 100644 src/PyFedfs/userinput.py
 create mode 100644 src/PyFedfs/utilities.py

Patch

diff --git a/.gitignore b/.gitignore
index 379a95e..ab759f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,3 +71,4 @@  depcomp
 .deps/
 .libs/
 .stgit*
+py-compile
diff --git a/configure.ac b/configure.ac
index e07f108..fd70d92 100644
--- a/configure.ac
+++ b/configure.ac
@@ -36,6 +36,7 @@  AC_CONFIG_MACRO_DIR([m4])
 m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
 LT_INIT
 AM_INIT_AUTOMAKE([-Wall -Werror silent-rules])
+AM_PATH_PYTHON([2.7])
 
 # configure command line options
 AC_ARG_WITH([fedfsuser],
@@ -195,5 +196,6 @@  AC_CONFIG_FILES([Makefile
                  src/nfsref/Makefile
                  src/nsdbc/Makefile
                  src/nsdbparams/Makefile
+                 src/PyFedfs/Makefile
                  src/plug-ins/Makefile])
 AC_OUTPUT
diff --git a/src/Makefile.am b/src/Makefile.am
index 003ff8e..c7dff86 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
+			  plug-ins PyFedfs
 
 CLEANFILES		= cscope.in.out cscope.out cscope.po.out *~
 DISTCLEANFILES		= Makefile.in
diff --git a/src/PyFedfs/Makefile.am b/src/PyFedfs/Makefile.am
new file mode 100644
index 0000000..be67d2a
--- /dev/null
+++ b/src/PyFedfs/Makefile.am
@@ -0,0 +1,30 @@ 
+##
+## @file src/PyFedfs/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 run.py userinput.py utilities.py
+pyfedfsdir		= $(pythondir)/PyFedfs
+
+CLEANFILES		= cscope.in.out cscope.out cscope.po.out *~
+DISTCLEANFILES		= Makefile.in
diff --git a/src/PyFedfs/__init__.py b/src/PyFedfs/__init__.py
new file mode 100644
index 0000000..5f76b5a
--- /dev/null
+++ b/src/PyFedfs/__init__.py
@@ -0,0 +1,25 @@ 
+"""
+PyFedfs
+
+This module contains an evolving implementation of FedFS
+in Python.
+
+"""
+
+__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/run.py b/src/PyFedfs/run.py
new file mode 100644
index 0000000..dc04b92
--- /dev/null
+++ b/src/PyFedfs/run.py
@@ -0,0 +1,299 @@ 
+"""
+run - utilities for running commands and managing daemons
+
+Part of the PyFedfs module
+"""
+
+__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
+"""
+
+# Standard Unix shell exit values
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+import os
+import pwd
+import logging as log
+
+from subprocess import Popen, PIPE
+
+
+def __run(command):
+    """
+    Run a command, ignore all command output, but return exit status
+
+    Returns a shell exit status value
+    """
+    try:
+        process = Popen(command, stdout=PIPE, stderr=PIPE, shell=False)
+    except OSError:
+        log.error('"%s" command did not execute', ' '.join(command))
+        return None
+    except ValueError:
+        log.error('"%s": bad arguments to Popen', ' '.join(command))
+        return None
+    return process
+
+
+def run_command(command, force=False):
+    """
+    Run a command, ignore all command output, but return exit status
+
+    Returns a shell exit status value
+    """
+    log.debug('Running the "%s" command...', command[0])
+
+    process = __run(command)
+    if process is None:
+        return EXIT_FAILURE
+
+    # pylint: disable-msg=E1101
+    process.wait()
+    # pylint: disable-msg=E1101
+    if process.returncode != 0:
+        if not force:
+            log.error('"%s" returned %d', command[0], process.returncode)
+            return EXIT_FAILURE
+    return EXIT_SUCCESS
+
+
+def __ut_run_command():
+    """
+    Unit tests for run_command
+    """
+    result = run_command(['ls', '-l'])
+    if result == EXIT_SUCCESS:
+        print('run_command("ls -l") succeeded')
+    elif result == EXIT_FAILURE:
+        print('run_command("ls -l") failed')
+    else:
+        print('run_command("ls -l"): %d' % result)
+
+
+def demote(user_uid, user_gid):
+    """
+    Returns a function that changes the UID and GID of a process
+    """
+    def result():
+        """
+        Change the UID and GID of a process
+        """
+        os.setgid(user_gid)
+        os.setuid(user_uid)
+
+    return result
+
+
+def run_as_user(username, command):
+    """
+    Run a command as a different user
+
+    Returns a shell exit status value
+    """
+    log.debug('Running the "%s" command as %s...', command[0], username)
+
+    try:
+        user_uid = pwd.getpwnam(username).pw_uid
+        user_gid = pwd.getpwnam(username).pw_gid
+    except KeyError:
+        log.error('"%s" is not a valid user', username)
+        return EXIT_FAILURE
+
+    try:
+        process = Popen(command, preexec_fn=demote(user_uid, user_gid),
+                        stdout=PIPE, stderr=PIPE, shell=False)
+    except OSError:
+        log.error('"%s" command did not execute', ' '.join(command))
+        return EXIT_FAILURE
+    except ValueError:
+        log.error('"%s": bad arguments to Popen', ' '.join(command))
+        return EXIT_FAILURE
+
+    # pylint: disable-msg=E1101
+    process.wait()
+    # pylint: disable-msg=E1101
+    if process.returncode != 0:
+        log.error('"%s" returned %d', command[0], process.returncode)
+        return EXIT_FAILURE
+    return EXIT_SUCCESS
+
+
+def __ut_run_as_user():
+    """
+    Unit tests for run_as_user()
+    """
+    result = run_as_user('ldap', ['echo', 'this is a test'])
+    if result == EXIT_SUCCESS:
+        print('run_as_user("ldap", "echo this is a test") succeeded')
+    elif result == EXIT_FAILURE:
+        print('run_as_user("ldap", "echo this is a test") failed')
+    else:
+        print('run_as_user("ldap", "echo this is a test"): %d' % result)
+
+    result = run_as_user('bogus', ['id'])
+    if result == EXIT_SUCCESS:
+        print('run_as_user("bogus", "id") succeeded')
+    elif result == EXIT_FAILURE:
+        print('run_as_user("bogus", "id") failed')
+    else:
+        print('run_as_user("bogus", "id"): %d' % result)
+
+
+def check_for_daemon(name):
+    """
+    Predicate: is a process named "name" running on the system?
+
+    Returns True if "name" is running, otherwise returns False
+    """
+    process = __run(['pgrep', name])
+    if process is None:
+        return False
+
+    # pylint: disable-msg=E1101
+    process.wait()
+    # pylint: disable-msg=E1101
+    if process.returncode != 0:
+        return False
+    return True
+
+
+def __ut_check_for_daemon():
+    """
+    Unit tests for check_for_daemon()
+    """
+    if check_for_daemon('dbus'):
+        print('dbus is running')
+    else:
+        print('dbus is not running')
+
+    if check_for_daemon('bogus'):
+        print('bogus is running')
+    else:
+        print('bogus is not running')
+
+
+def systemctl(command, service):
+    """
+    Try a systemctl command, report failure
+
+    Returns a shell exit status value
+    """
+    log.debug('Trying to %s the %s service...', command, service)
+
+    process = __run(['systemctl', command, service + '.service'])
+    if process is None:
+        return EXIT_FAILURE
+
+    # pylint: disable-msg=E1101
+    process.wait()
+    # pylint: disable-msg=E1101
+    if process.returncode != 0:
+        log.error('systemctl %s %s.service failed: %d',
+                  command, service, process.returncode)
+        return EXIT_FAILURE
+    return EXIT_SUCCESS
+
+
+def __ut_systemctl():
+    """
+    Unit tests for run.py module
+    """
+    result = systemctl('status', 'network')
+    if result == EXIT_SUCCESS:
+        print('systemctl result: succeeded')
+    elif result == EXIT_FAILURE:
+        print('systemctl result: failed')
+    else:
+        print('systemctl result: %d' % result)
+
+    result = systemctl('status', 'bogus')
+    if result == EXIT_SUCCESS:
+        print('systemctl result: succeeded')
+    elif result == EXIT_FAILURE:
+        print('systemctl result: failed')
+    else:
+        print('systemctl result: %d' % result)
+
+
+def stop_service(service):
+    """
+    Stop a server
+
+    Returns a shell exit status value
+    """
+    if systemctl('status', service) != 0:
+        log.debug('"%s" is not running', service)
+        return EXIT_SUCCESS
+
+    ret = systemctl('stop', service)
+    if ret != EXIT_SUCCESS:
+        return ret
+
+    log.debug('The "%s" service was stopped successfully', service)
+    return EXIT_SUCCESS
+
+
+def start_service(service):
+    """
+    Start a server
+
+    Returns a shell exit status value
+    """
+    ret = systemctl('start', service)
+    if ret != EXIT_SUCCESS:
+        return ret
+
+    log.debug('The "%s" service was started successfully', service)
+    return EXIT_SUCCESS
+
+
+def enable_and_start_service(service):
+    """
+    Enable and start a server
+
+    Returns a shell exit status value
+    """
+    ret = systemctl('enable', service)
+    if ret != EXIT_SUCCESS:
+        return ret
+
+    return start_service(service)
+
+
+def restart_service(service):
+    """
+    Restart a server
+
+    Returns a shell exit status value
+    """
+    return systemctl('restart', service)
+
+
+__all__ = ['EXIT_SUCCESS', 'EXIT_FAILURE',
+           'run_command', 'run_as_user',
+           'check_for_daemon',
+           'stop_service', 'start_service',
+           'restart_service', 'enable_and_start_service']
+
+if __name__ == '__main__':
+    log.basicConfig(format='%(levelname)s: %(message)s', level=log.DEBUG)
+
+    __ut_check_for_daemon()
+    __ut_run_command()
+    __ut_run_as_user()
+    __ut_systemctl()
diff --git a/src/PyFedfs/userinput.py b/src/PyFedfs/userinput.py
new file mode 100644
index 0000000..285e903
--- /dev/null
+++ b/src/PyFedfs/userinput.py
@@ -0,0 +1,105 @@ 
+"""
+userinput - utilities to get user input
+
+Part of the PyFedfs module
+"""
+
+__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
+from getpass import getpass
+from subprocess import check_output, CalledProcessError
+
+
+def confirm(question):
+    """
+    Confirm user would like to proceed with some operation
+
+    Returns True if user gives a "yes" answer, False if user
+    gives a "no" answer or hits return.
+    """
+    valid = {'yes': True, 'ye': True, 'y': True, 'no': False, 'n': False}
+
+    while True:
+        sys.stdout.write(question + ' [y/N] ')
+        choice = raw_input().lower()
+        if choice == '':
+            return False
+        elif choice in valid:
+            return valid[choice]
+        else:
+            sys.stdout.write('Respond "yes" or "no"\n')
+
+
+def __ut_confirm():
+    """
+    Unit tests for confirm()
+    """
+    if confirm('Answer this question: '):
+        print('You answered "yes"')
+    else:
+        print('You answered "no"')
+
+
+def get_password(prompt):
+    """
+    Ask user for a password; use input blanking
+
+    Returns a string
+    """
+    print(prompt)
+    while True:
+        try:
+            password1 = getpass('New password: ')
+            password2 = getpass('Re-enter new password: ')
+        except KeyboardInterrupt:
+            return ''
+
+        if password1 == '':
+            print('Empty password, try again')
+        elif password1 != password2:
+            print('Password values do not match, try again')
+        else:
+            break
+
+    try:
+        result = check_output(['slappasswd', '-n', '-s', password1])
+    except CalledProcessError:
+        return ''
+    return result
+
+
+def __ut_get_password():
+    """
+    Unit tests for get_password()
+    """
+    password = get_password('Enter a strong password: ')
+    if password == '':
+        print('An empty password was returned')
+    else:
+        print('The password you entered was "%s"' % password)
+
+
+__all__ = ['confirm', 'get_password']
+
+# unit tests
+if __name__ == '__main__':
+    __ut_confirm()
+    __ut_get_password()
diff --git a/src/PyFedfs/utilities.py b/src/PyFedfs/utilities.py
new file mode 100644
index 0000000..cf36f4f
--- /dev/null
+++ b/src/PyFedfs/utilities.py
@@ -0,0 +1,134 @@ 
+"""
+Utility functions for PyFedfs
+"""
+
+__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.run import EXIT_SUCCESS, EXIT_FAILURE
+except ImportError:
+    print >> sys.stderr, \
+        'Could not import a required Python module:', sys.exc_value
+    sys.exit(1)
+
+
+def change_mode(pathname, mode):
+    """
+    Change permission on a local file
+
+    Returns a shell exit status value
+    """
+    log.debug('Changing mode bits on "%s"...', pathname)
+
+    ret = EXIT_FAILURE
+    try:
+        os.chmod(pathname, mode)
+        ret = EXIT_SUCCESS
+    except OSError:
+        log.error('Failed to chmod "%s"', pathname)
+
+    return ret
+
+
+def list_directory(pathname):
+    """
+    List all entries but 'lost+found'
+
+    Returns a list containing one entry for each item in "pathname"
+    """
+    log.debug('Listing directory "' + pathname + '"...')
+
+    try:
+        output = os.listdir(pathname)
+    except OSError as inst:
+        log.error('Failed to list "%s": %s', pathname, inst)
+        return []
+
+    return [x for x in output if x != 'lost+found']
+
+
+def remove_directory(pathname, force=False):
+    """
+    Remove a local directory
+
+    Returns a shell exit status value
+    """
+    log.debug('Removing directory "%s"...', pathname)
+
+    ret = EXIT_FAILURE
+    try:
+        os.rmdir(pathname)
+        ret = EXIT_SUCCESS
+    except OSError:
+        if not force:
+            log.error('Failed to remove "%s"', pathname)
+
+    if force:
+        return EXIT_SUCCESS
+    return ret
+
+
+def make_directory(pathname, mode):
+    """
+    Create a local directory
+
+    Returns a shell exit status value
+    """
+    if os.path.isdir(pathname):
+        log.debug('Directory "%s" exists', pathname)
+        return EXIT_SUCCESS
+
+    log.debug('Creating directory "%s"...', pathname)
+
+    try:
+        os.mkdir(pathname)
+    except OSError:
+        log.error('Failed to create "%s"', pathname)
+        return EXIT_FAILURE
+
+    ret = change_mode(pathname, mode)
+    if ret != EXIT_SUCCESS:
+        log.error('Failed to chmod "%s"', pathname)
+        os.rmdir(pathname)
+        return EXIT_FAILURE
+
+    return EXIT_SUCCESS
+
+
+__all__ = ['make_directory', 'list_directory', 'remove_directory',
+           'change_mode']
+
+if __name__ == '__main__':
+    log.basicConfig(format='%(levelname)s: %(message)s', level=log.DEBUG)
+
+    list_directory('/tmp')
+    list_directory('/bogus')
+
+    make_directory('/tmp/__ut__', 0755)
+    make_directory('/tmp/__ut__', 0755)
+    remove_directory('/tmp/__ut__')
+    remove_directory('/tmp/__ut__')
+
+    make_directory('/tmp/__ut_chmod__', 0700)
+    change_mode('/tmp/__ut_chmod__', 0755)
+    remove_directory('/tmp/__ut_chmod__')