From f7785bb0e4b3324ad8d0b4752b51ea31a4caecd2 Mon Sep 17 00:00:00 2001
From: William Brown <firstyear@redhat.com>
Date: Wed, 31 Aug 2016 15:30:09 +1000
Subject: [PATCH] Ticket 48951 - dsadm dsconfig status and plugin

Fix Description:  Add support for dsadm and dsconf to be able to check the
server status. Additionally, we add plugin support, and move fixtures to a
shared fixture module.

For example:

> dsadm instance status
Directory Server instance name : localhost
INFO:dsadm:Instance is running

> dsconf -D 'cn=Directory Manager' -H ldap://localhost plugin list
Enter password for cn=Directory Manager on ldap://localhost :

INFO:dsconf.plugin_list:7-bit check
INFO:dsconf.plugin_list:Account Policy Plugin
INFO:dsconf.plugin_list:Account Usability Plugin
...
INFO:dsconf.plugin_list:SSHA384
INFO:dsconf.plugin_list:SSHA512

https://fedorahosted.org/389/ticket/48951

Author: wibrown

Review by: ???
---
 cli/dsadm                        | 17 +++++++-
 cli/dsconf                       |  2 +
 lib389/__init__.py               | 13 ++++++
 lib389/cli_adm/instance.py       | 37 ++++++++---------
 lib389/cli_conf/plugin.py        | 88 ++++++++++++++++++++++++++++++++++++++++
 lib389/tests/cli/__init__.py     | 67 ++++++++++++++++++++++++++++++
 lib389/tests/cli/conf_backend.py | 68 ++-----------------------------
 lib389/tests/cli/conf_plugin.py  | 53 ++++++++++++++++++++++++
 lib389/tools.py                  |  8 ++--
 9 files changed, 264 insertions(+), 89 deletions(-)
 create mode 100644 lib389/cli_conf/plugin.py
 create mode 100644 lib389/tests/cli/conf_plugin.py

diff --git a/cli/dsadm b/cli/dsadm
index 00ea54d..77262f0 100755
--- a/cli/dsadm
+++ b/cli/dsadm
@@ -12,6 +12,7 @@ import argparse
 import logging
 import sys
 
+from lib389.cli_base import _get_arg
 from lib389 import DirSrv
 from lib389.cli_adm import instance as cli_instance
 from lib389.cli_base import disconnect_instance
@@ -66,12 +67,24 @@ if __name__ == '__main__':
     inst = DirSrv(verbose=args.verbose)
 
     result = True
+
+    # we allocate an instance in all cases unless we are CREATING the server.
+    if not (hasattr(args, 'noinst') and args.noinst is True):
+        # Get the instance name if needed.
+        inst_id = _get_arg( args.instance, msg="Directory Server instance name")
+
+        # Allocate the instance based on name
+        insts = inst.list(serverid=inst_id)
+        if len(insts) != 1:
+            raise ValueError("No such instance %s" % inst_id)
+
+        inst.allocate(insts[0])
+        log.debug('Instance allocated')
+
     if args.verbose:
-        # inst = connect_instance(args.ldapurl, args.binddn, args.verbose, args.starttls)
         result = args.func(inst, log, args)
     else:
         try:
-            # inst = connect_instance(args.ldapurl, args.binddn, args.verbose, args.starttls)
             result = args.func(inst, log, args)
         except Exception as e:
             log.debug(e, exc_info=True)
diff --git a/cli/dsconf b/cli/dsconf
index 84507ba..2646224 100755
--- a/cli/dsconf
+++ b/cli/dsconf
@@ -15,6 +15,7 @@ import sys
 
 from lib389 import DirSrv
 from lib389.cli_conf import backend as cli_backend
+from lib389.cli_conf import plugin as cli_plugin
 from lib389.cli_base import disconnect_instance, connect_instance
 
 logging.basicConfig()
@@ -51,6 +52,7 @@ if __name__ == '__main__':
     subparsers = parser.add_subparsers(help="resources to act upon")
 
     cli_backend.create_parser(subparsers)
+    cli_plugin.create_parser(subparsers)
 
     args = parser.parse_args()
 
diff --git a/lib389/__init__.py b/lib389/__init__.py
index 59a564e..e16db6d 100644
--- a/lib389/__init__.py
+++ b/lib389/__init__.py
@@ -1119,6 +1119,19 @@ class DirSrv(SimpleLDAPObject):
         # whatever the initial state, the instance is now Offline
         self.state = DIRSRV_STATE_OFFLINE
 
+    def status(self):
+        """
+        Determine if an instance is running or not.
+
+        Will update the self.state parameter.
+        """
+        status_prog = os.path.join(self.prefix, 'sbin', 'status-dirsrv')
+        try:
+            subprocess.check_call([status_prog, self.serverid])
+            return True
+        except subprocess.CalledProcessError:
+            return False
+
     def restart(self, timeout=120):
         '''
             It restarts an instance and rebind it. Its final state after rebind
diff --git a/lib389/cli_adm/instance.py b/lib389/cli_adm/instance.py
index 057418e..7c1ac75 100644
--- a/lib389/cli_adm/instance.py
+++ b/lib389/cli_adm/instance.py
@@ -7,7 +7,6 @@
 # --- END COPYRIGHT BLOCK ---
 
 from lib389._constants import *
-from lib389.cli_base import _get_arg
 
 from lib389.tools import DirSrvTools
 from lib389.instance.setup import SetupDs
@@ -31,25 +30,19 @@ def instance_list(inst, log, args):
         log.info(e)
         log.info("Perhaps you need to be a different user?")
 
-def instance_action(inst, log, args, act):
-    # Get the instance name if needed.
-    inst_id = _get_arg( args.instance, msg="Directory Server instance name to %s" % act)
-
-    # Allocate the instance based on name
-    insts = inst.list(serverid=inst_id)
-    if len(insts) != 1:
-        raise ValueError("No such instance %s" % inst_id)
-
-    inst.allocate(insts[0])
-
-    # Start it!
-    DirSrvTools.serverCmd(inst, act, True)
-
 def instance_start(inst, log, args):
-    instance_action(inst, log, args, "start")
+    if inst.status() is False:
+        inst.start()
 
 def instance_stop(inst, log, args):
-    instance_action(inst, log, args, "stop")
+    if inst.status() is True:
+        inst.stop()
+
+def instance_status(inst, log, args):
+    if inst.status() is True:
+        log.info("Instance is running")
+    else:
+        log.info("Instance is not running")
 
 def instance_create(inst, log, args):
     if not args.ack:
@@ -124,13 +117,17 @@ def create_parser(subparsers):
     list_parser.set_defaults(func=instance_list)
 
     start_parser = subcommands.add_parser('start', help="Start an instance of Directory Server, if it is not currently running")
-    start_parser.add_argument('instance', nargs=1, help="The name of the instance to start.")
+    # start_parser.add_argument('instance', nargs=1, help="The name of the instance to start.")
     start_parser.set_defaults(func=instance_start)
 
     stop_parser = subcommands.add_parser('stop', help="Stop an instance of Directory Server, if it is currently running")
-    stop_parser.add_argument('instance', nargs=1, help="The name of the instance to stop.")
+    # stop_parser.add_argument('instance', nargs=1, help="The name of the instance to stop.")
     stop_parser.set_defaults(func=instance_stop)
 
+    status_parser = subcommands.add_parser('status', help="Check running status of an instance of Directory Server")
+    # status_parser.add_argument('instance', nargs=1, help="The name of the instance to check.")
+    status_parser.set_defaults(func=instance_status)
+
     create_parser = subcommands.add_parser('create', help="Create an instance of Directory Server. Can be interactive or silent with an inf answer file")
     create_parser.add_argument('-n', '--dryrun', help="Validate system and configurations only. Do not alter the system.", action='store_true', default=False)
     create_parser.add_argument('-f', '--file', help="Inf file to use with prepared answers")
@@ -140,7 +137,9 @@ By setting this value you acknowledge and take responsibility for the fact this
 """,
                         action='store_true', default=False)
     create_parser.set_defaults(func=instance_create)
+    create_parser.set_defaults(noinst=True)
 
     example_parser = subcommands.add_parser('example', help="Display an example ini answer file, with comments")
     example_parser.set_defaults(func=instance_example)
+    create_parser.set_defaults(noinst=True)
 
diff --git a/lib389/cli_conf/plugin.py b/lib389/cli_conf/plugin.py
new file mode 100644
index 0000000..8736c3e
--- /dev/null
+++ b/lib389/cli_conf/plugin.py
@@ -0,0 +1,88 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+
+from lib389.plugins import Plugin, Plugins
+import argparse
+
+from lib389.cli_base import (
+    _generic_list,
+    _generic_get,
+    _generic_get_dn,
+    _generic_create,
+    _generic_delete,
+    _get_arg,
+    _get_args,
+    _get_attributes,
+    _warn,
+    )
+
+SINGULAR = Plugin
+MANY = Plugins
+RDN = 'cn'
+
+
+def plugin_list(inst, basedn, log, args):
+    _generic_list(inst, basedn, log.getChild('plugin_list'), MANY)
+
+def plugin_get(inst, basedn, log, args):
+    rdn = _get_arg( args.selector, msg="Enter %s to retrieve" % RDN)
+    _generic_get(inst, basedn, log.getChild('plugin_get'), MANY, rdn)
+
+def plugin_get_dn(inst, basedn, log, args):
+    dn = _get_arg( args.dn, msg="Enter dn to retrieve")
+    _generic_get_dn(inst, basedn, log.getChild('plugin_get_dn'), MANY, dn)
+
+# Plugin enable
+def plugin_enable(inst, basedn, log, args):
+    dn = _get_arg( args.dn, msg="Enter plugin dn to enable")
+    mc = MANY(inst, basedn)
+    o = mc.get(dn=dn)
+    o.enable()
+    o_str = o.display()
+    log.info('Enabled %s', o_str)
+
+# Plugin disable
+def plugin_disable(inst, basedn, log, args, warn=True):
+    dn = _get_arg( args.dn, msg="Enter plugin dn to disable")
+    if warn:
+        _warn(dn, msg="Disabling %s %s" % (SINGULAR.__name__, dn))
+    mc = MANY(inst, basedn)
+    o = mc.get(dn=dn)
+    o.disable()
+    o_str = o.display()
+    log.info('Disabled %s', o_str)
+
+# Plugin configure?
+def plugin_configure(inst, basedn, log, args):
+    pass
+
+def create_parser(subparsers):
+    plugin_parser = subparsers.add_parser('plugin', help="Manage plugins available on the server")
+
+    subcommands = plugin_parser.add_subparsers(help="action")
+
+    list_parser = subcommands.add_parser('list', help="List current configured (enabled and disabled) plugins")
+    list_parser.set_defaults(func=plugin_list)
+
+    get_parser = subcommands.add_parser('get', help='get')
+    get_parser.set_defaults(func=plugin_get)
+    get_parser.add_argument('selector', nargs='?', help='The plugin to search for')
+
+    get_dn_parser = subcommands.add_parser('get_dn', help='get_dn')
+    get_dn_parser.set_defaults(func=plugin_get_dn)
+    get_dn_parser.add_argument('dn', nargs='?', help='The plugin dn to get')
+
+    enable_parser = subcommands.add_parser('enable', help='enable a plugin in the server')
+    enable_parser.set_defaults(func=plugin_enable)
+    enable_parser.add_argument('dn', nargs='?', help='The dn to enable')
+
+    disable_parser = subcommands.add_parser('disable', help='disable the plugin configuration')
+    disable_parser.set_defaults(func=plugin_disable)
+    disable_parser.add_argument('dn', nargs='?', help='The dn to disable')
+
+
diff --git a/lib389/tests/cli/__init__.py b/lib389/tests/cli/__init__.py
index d57ac33..9a57230 100644
--- a/lib389/tests/cli/__init__.py
+++ b/lib389/tests/cli/__init__.py
@@ -6,3 +6,70 @@
 # See LICENSE for details.
 # --- END COPYRIGHT BLOCK ---
 
+import pytest
+
+from lib389 import DirSrv
+from lib389.cli_base import LogCapture, FakeArgs
+
+from lib389.instance.setup import SetupDs
+from lib389.instance.options import General2Base, Slapd2Base
+from lib389._constants import *
+
+INSTANCE_PORT = 54321
+INSTANCE_SERVERID = 'standalone'
+
+DEBUGGING = True
+
+class TopologyInstance(object):
+    def __init__(self, standalone, logcap):
+        # For these tests, we don't want to open the instance.
+        # instance.open()
+        self.standalone = standalone
+        self.logcap = logcap
+
+# Need a teardown to destroy the instance.
+@pytest.fixture
+def topology(request):
+
+    lc = LogCapture()
+    instance = DirSrv(verbose=DEBUGGING)
+    instance.log.debug("Instance allocated")
+    args = {SER_PORT: INSTANCE_PORT,
+            SER_SERVERID_PROP: INSTANCE_SERVERID}
+    instance.allocate(args)
+    if instance.exists():
+        instance.delete()
+
+    # This will need to change to instance.create in the future
+    # when it's linked up!
+    sds = SetupDs(verbose=DEBUGGING, dryrun=False, log=lc.log)
+
+    # Get the dicts from Type2Base, as though they were from _validate_ds_2_config
+    # IE get the defaults back just from Slapd2Base.collect
+    # Override instance name, root password, port and secure port.
+
+    general_options = General2Base(lc.log)
+    general_options.verify()
+    general = general_options.collect()
+
+    # Need an args -> options2 ...
+    slapd_options = Slapd2Base(lc.log)
+    slapd_options.set('instance_name', INSTANCE_SERVERID)
+    slapd_options.set('port', INSTANCE_PORT)
+    slapd_options.set('root_password', PW_DM)
+    slapd_options.verify()
+    slapd = slapd_options.collect()
+
+    sds.create_from_args(general, slapd, {}, None)
+    insts = instance.list(serverid=INSTANCE_SERVERID)
+    # Assert we did change the system.
+    assert(len(insts) == 1)
+    # Make sure we can connect
+    instance.open(connOnly=True)
+
+    def fin():
+        if instance.exists() and not DEBUGGING:
+            instance.delete()
+    request.addfinalizer(fin)
+
+    return TopologyInstance(instance, lc)
diff --git a/lib389/tests/cli/conf_backend.py b/lib389/tests/cli/conf_backend.py
index 33f9b28..da8ec73 100644
--- a/lib389/tests/cli/conf_backend.py
+++ b/lib389/tests/cli/conf_backend.py
@@ -9,73 +9,11 @@
 import pytest
 
 from lib389.cli_conf.backend import backend_list, backend_get, backend_get_dn, backend_create, backend_delete
-from lib389 import DirSrv
-from lib389.cli_base import LogCapture, FakeArgs
-
-from lib389.instance.setup import SetupDs
-from lib389.instance.options import General2Base, Slapd2Base
-from lib389._constants import *
-
-INSTANCE_PORT = 54321
-INSTANCE_SERVERID = 'standalone'
-
-DEBUGGING = True
-
-class TopologyInstance(object):
-    def __init__(self, standalone, logcap):
-        # For these tests, we don't want to open the instance.
-        # instance.open()
-        self.standalone = standalone
-        self.logcap = logcap
-
-# Need a teardown to destroy the instance.
-@pytest.fixture
-def topology(request):
-
-    lc = LogCapture()
-    instance = DirSrv(verbose=DEBUGGING)
-    instance.log.debug("Instance allocated")
-    args = {SER_PORT: INSTANCE_PORT,
-            SER_SERVERID_PROP: INSTANCE_SERVERID}
-    instance.allocate(args)
-    if instance.exists():
-        instance.delete()
-
-    # This will need to change to instance.create in the future
-    # when it's linked up!
-    sds = SetupDs(verbose=DEBUGGING, dryrun=False, log=lc.log)
-
-    # Get the dicts from Type2Base, as though they were from _validate_ds_2_config
-    # IE get the defaults back just from Slapd2Base.collect
-    # Override instance name, root password, port and secure port.
-
-    general_options = General2Base(lc.log)
-    general_options.verify()
-    general = general_options.collect()
-
-    # Need an args -> options2 ...
-    slapd_options = Slapd2Base(lc.log)
-    slapd_options.set('instance_name', INSTANCE_SERVERID)
-    slapd_options.set('port', INSTANCE_PORT)
-    slapd_options.set('root_password', PW_DM)
-    slapd_options.verify()
-    slapd = slapd_options.collect()
-
-    sds.create_from_args(general, slapd, {}, None)
-    insts = instance.list(serverid=INSTANCE_SERVERID)
-    # Assert we did change the system.
-    assert(len(insts) == 1)
-    # Make sure we can connect
-    instance.open(connOnly=True)
-
-    def fin():
-        if instance.exists() and not DEBUGGING:
-            instance.delete()
-    request.addfinalizer(fin)
-
-    return TopologyInstance(instance, lc)
 
+from lib389.cli_base import LogCapture, FakeArgs
+from lib389.tests.cli import topology
 
+# Topology is pulled from __init__.py
 def test_backend_cli(topology):
     # 
     args = FakeArgs()
diff --git a/lib389/tests/cli/conf_plugin.py b/lib389/tests/cli/conf_plugin.py
new file mode 100644
index 0000000..3f1955f
--- /dev/null
+++ b/lib389/tests/cli/conf_plugin.py
@@ -0,0 +1,53 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+
+import pytest
+
+from lib389.cli_conf.plugin import plugin_list, plugin_get, plugin_get_dn, plugin_enable, plugin_disable
+
+from lib389.cli_base import LogCapture, FakeArgs
+
+from lib389.tests.cli import topology
+
+plugins = [
+    'Class of Service',
+    'ldbm database',
+    'Roles Plugin',
+    'USN',
+    'SSHA512',
+]
+
+# Topology is pulled from __init__.py
+def test_plugin_cli(topology):
+    args = FakeArgs()
+
+    plugin_list(topology.standalone, None, topology.logcap.log, None)
+    for p in plugins:
+        assert(topology.logcap.contains(p))
+    topology.logcap.flush()
+
+    # print(topology.logcap.outputs)
+    # Need to delete something, then re-add it.
+    args.selector = 'USN'
+    plugin_get(topology.standalone, None, topology.logcap.log, args)
+    assert(topology.logcap.contains('USN'))
+    topology.logcap.flush()
+
+    args.dn = 'cn=USN,cn=plugins,cn=config'
+    plugin_get_dn(topology.standalone, None, topology.logcap.log, args)
+    assert(topology.logcap.contains('USN'))
+    topology.logcap.flush()
+
+    plugin_disable(topology.standalone, None, topology.logcap.log, args, warn=False)
+    assert(topology.logcap.contains('Disabled'))
+    topology.logcap.flush()
+
+    plugin_enable(topology.standalone, None, topology.logcap.log, args)
+    assert(topology.logcap.contains('Enabled'))
+    topology.logcap.flush()
+
diff --git a/lib389/tools.py b/lib389/tools.py
index 58ab4ca..4223774 100644
--- a/lib389/tools.py
+++ b/lib389/tools.py
@@ -250,9 +250,11 @@ class DirSrvTools(object):
         if cmd == 'start':
             fullCmd = os.path.join(sbinDir, 'start-dirsrv %s' % self.serverid)
             cmdPat = 'slapd started.'
-        else:
+        elif cmd == 'stop':
             fullCmd = os.path.join(sbinDir, 'stop-dirsrv %s' % self.serverid)
             cmdPat = 'slapd stopped.'
+        else:
+            raise Exception('Invalid cmd passed!')
 
         if "USE_GDB" in os.environ or "USE_VALGRIND" in os.environ:
             timeout = timeout * 3
@@ -284,7 +286,7 @@ class DirSrvTools(object):
             else:
                 done = True
 
-        log.warn("Running command: %r - timeout(%d)" % (fullCmd, timeout))
+        log.info("Running command: %r - timeout(%d)" % (fullCmd, timeout))
         rc = runCmd("%s" % fullCmd, timeout)
         while rc == 0 and not done and int(time.time()) < full_timeout:
             line = logfp.readline()
@@ -292,7 +294,7 @@ class DirSrvTools(object):
                 lastLine = line
                 if verbose:
                     log.debug("current line: %r" % line.strip())
-                if line.find(cmdPat) >= 0:
+                if cmdPat is not None and line.find(cmdPat) >= 0:
                     started += 1
                     if started == 2:
                         done = True
-- 
1.8.3.1

