Browse Source

[master] Merge branch 'trac1843'

Jelte Jansen 13 years ago
parent
commit
551657702a

+ 1 - 1
src/bin/bindctl/Makefile.am

@@ -8,7 +8,7 @@ EXTRA_DIST = $(man_MANS) bindctl.xml
 noinst_SCRIPTS = run_bindctl.sh
 
 python_PYTHON = __init__.py bindcmd.py cmdparse.py exception.py moduleinfo.py \
-		mycollections.py
+		mycollections.py command_sets.py
 pythondir = $(pyexecdir)/bindctl
 
 bindctldir = $(pkgdatadir)

+ 94 - 12
src/bin/bindctl/bindcmd.py

@@ -23,6 +23,7 @@ from cmd import Cmd
 from bindctl.exception import *
 from bindctl.moduleinfo import *
 from bindctl.cmdparse import BindCmdParse
+from bindctl import command_sets
 from xml.dom import minidom
 import isc
 import isc.cc.data
@@ -37,6 +38,7 @@ from hashlib import sha1
 import csv
 import pwd
 import getpass
+import copy
 
 try:
     from collections import OrderedDict
@@ -393,8 +395,9 @@ class BindCmdInterpreter(Cmd):
                 param_nr += 1
 
         # Convert parameter value according parameter spec file.
-        # Ignore check for commands belongs to module 'config'
-        if cmd.module != CONFIG_MODULE_NAME:
+        # Ignore check for commands belongs to module 'config' or 'execute
+        if cmd.module != CONFIG_MODULE_NAME and\
+           cmd.module != command_sets.EXECUTE_MODULE_NAME:
             for param_name in cmd.params:
                 param_spec = command_info.get_param_with_name(param_name).param_spec
                 try:
@@ -408,16 +411,9 @@ class BindCmdInterpreter(Cmd):
         if cmd.command == "help" or ("help" in cmd.params.keys()):
             self._handle_help(cmd)
         elif cmd.module == CONFIG_MODULE_NAME:
-            try:
-                self.apply_config_cmd(cmd)
-            except isc.cc.data.DataTypeError as dte:
-                print("Error: " + str(dte))
-            except isc.cc.data.DataNotFoundError as dnfe:
-                print("Error: " + str(dnfe))
-            except isc.cc.data.DataAlreadyPresentError as dape:
-                print("Error: " + str(dape))
-            except KeyError as ke:
-                print("Error: missing " + str(ke))
+            self.apply_config_cmd(cmd)
+        elif cmd.module == command_sets.EXECUTE_MODULE_NAME:
+            self.apply_execute_cmd(cmd)
         else:
             self.apply_cmd(cmd)
 
@@ -576,6 +572,14 @@ class BindCmdInterpreter(Cmd):
             self._print_correct_usage(err)
         except isc.cc.data.DataTypeError as err:
             print("Error! ", err)
+        except isc.cc.data.DataTypeError as dte:
+            print("Error: " + str(dte))
+        except isc.cc.data.DataNotFoundError as dnfe:
+            print("Error: " + str(dnfe))
+        except isc.cc.data.DataAlreadyPresentError as dape:
+            print("Error: " + str(dape))
+        except KeyError as ke:
+            print("Error: missing " + str(ke))
 
     def _print_correct_usage(self, ept):
         if isinstance(ept, CmdUnknownModuleSyntaxError):
@@ -728,6 +732,84 @@ class BindCmdInterpreter(Cmd):
 
         self.location = new_location
 
+    def apply_execute_cmd(self, command):
+        '''Handles the 'execute' command, which executes a number of
+           (preset) statements. The command set to execute is either
+           read from a file (e.g. 'execute file <file>'.) or one
+           of the sets as defined in command_sets.py'''
+        if command.command == 'file':
+            try:
+                with open(command.params['filename']) as command_file:
+                    commands = command_file.readlines()
+            except IOError as ioe:
+                print("Error: " + str(ioe))
+                return
+        elif command_sets.has_command_set(command.command):
+            commands = command_sets.get_commands(command.command)
+        else:
+            # Should not be reachable; parser should've caught this
+            raise Exception("Unknown execute command type " + command.command)
+
+        # We have our set of commands now, depending on whether 'show' was
+        # specified, show or execute them
+        if 'show' in command.params and command.params['show'] == 'show':
+            self.__show_execute_commands(commands)
+        else:
+            self.__apply_execute_commands(commands)
+
+    def __show_execute_commands(self, commands):
+        '''Prints the command list without executing them'''
+        for line in commands:
+            print(line.strip())
+
+    def __apply_execute_commands(self, commands):
+        '''Applies the configuration commands from the given iterator.
+           This is the method that catches, comments, echo statements, and
+           other directives. All commands not filtered by this method are
+           interpreted as if they are directly entered in an active session.
+           Lines starting with any of the following characters are not
+           passed directly:
+           # - These are comments
+           ! - These are directives
+               !echo: print the rest of the line
+               !verbose on/off: print the commands themselves too
+               Unknown directives are ignored (with a warning)
+           The execution is stopped if there are any errors.
+        '''
+        verbose = False
+        try:
+            for line in commands:
+                line = line.strip()
+                if verbose:
+                    print(line)
+                if line.startswith('#') or len(line) == 0:
+                    continue
+                elif line.startswith('!'):
+                    if re.match('^!echo ', line, re.I) and len(line) > 6:
+                        print(line[6:])
+                    elif re.match('^!verbose\s+on\s*$', line, re.I):
+                        verbose = True
+                    elif re.match('^!verbose\s+off$', line, re.I):
+                        verbose = False
+                    else:
+                        print("Warning: ignoring unknown directive: " + line)
+                else:
+                    cmd = BindCmdParse(line)
+                    self._validate_cmd(cmd)
+                    self._handle_cmd(cmd)
+        except (isc.config.ModuleCCSessionError,
+                IOError, http.client.HTTPException,
+                BindCtlException, isc.cc.data.DataTypeError,
+                isc.cc.data.DataNotFoundError,
+                isc.cc.data.DataAlreadyPresentError,
+                KeyError) as err:
+            print('Error: ', err)
+            print()
+            print('Depending on the contents of the script, and which')
+            print('commands it has called, there can be committed and')
+            print('local changes. It is advised to check your settings,')
+            print('and revert local changes with "config revert".')
+
     def apply_cmd(self, cmd):
         '''Handles a general module command'''
         url = '/' + cmd.module + '/' + cmd.command

+ 2 - 0
src/bin/bindctl/bindctl_main.py.in

@@ -22,6 +22,7 @@ import sys; sys.path.append ('@@PYTHONPATH@@')
 
 from bindctl.moduleinfo import *
 from bindctl.bindcmd import *
+from bindctl import command_sets
 import pprint
 from optparse import OptionParser, OptionValueError
 import isc.util.process
@@ -146,5 +147,6 @@ if __name__ == '__main__':
     tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain,
                               csv_file_dir=options.csv_file_dir)
     prepare_config_commands(tool)
+    command_sets.prepare_execute_commands(tool)
     result = tool.run()
     sys.exit(result)

+ 95 - 0
src/bin/bindctl/command_sets.py

@@ -0,0 +1,95 @@
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# This file provides a built-in set of 'execute' commands, for common
+# functions, such as adding an initial auth server.
+# By calling the function prepare_execute_commands, the
+# commands in the command_sets map are added to the virtual
+# component called 'execute'. This is done in bindctl_main.
+
+from bindctl.moduleinfo import *
+# The name of the 'virtual' command set execution module in bindctl
+EXECUTE_MODULE_NAME = 'execute'
+
+# This is a map of command names to lists
+# Each element in the set should itself be a dict containing:
+# 'description': A string with a description of the command set
+# 'commands': A list of bindctl commands
+command_sets = {
+    'init_authoritative_server': {
+        'description':
+            'Configure and run a basic Authoritative server, with default '+
+            'SQLite3 backend, and xfrin and xfrout functionality',
+        'commands':
+            [
+            '!echo adding Authoritative server component',
+            'config add /Boss/components b10-auth',
+            'config set /Boss/components/b10-auth/kind needed',
+            'config set /Boss/components/b10-auth/special auth',
+            '!echo adding Xfrin component',
+            'config add /Boss/components b10-xfrin',
+            'config set /Boss/components/b10-xfrin/address Xfrin',
+            'config set /Boss/components/b10-xfrin/kind dispensable',
+            '!echo adding Xfrout component',
+            'config add /Boss/components b10-xfrout',
+            'config set /Boss/components/b10-xfrout/address Xfrout',
+            'config set /Boss/components/b10-xfrout/kind dispensable',
+            '!echo adding Zone Manager component',
+            'config add /Boss/components b10-zonemgr',
+            'config set /Boss/components/b10-zonemgr/address Zonemgr',
+            'config set /Boss/components/b10-zonemgr/kind dispensable',
+            '!echo Components added. Please enter "config commit" to',
+            '!echo finalize initial setup and run the components.'
+            ]
+    }
+}
+
+def has_command_set(name):
+    return name in command_sets
+
+def get_commands(name):
+    return command_sets[name]['commands']
+
+def get_description(name):
+    return command_sets[name]['description']
+
+# For each
+def prepare_execute_commands(tool):
+    """This function is called by bindctl_main, and sets up the commands
+       defined here for use in bindctl."""
+    # common parameter
+    param_show = ParamInfo(name="show", type="string", optional=True,
+        desc="Show the list of commands without executing them")
+
+    # The command module
+    module = ModuleInfo(name=EXECUTE_MODULE_NAME,
+                        desc="Execute a given set of commands")
+
+    # Command to execute a file
+    cmd = CommandInfo(name="file", desc="Read commands from file")
+    param = ParamInfo(name="filename", type="string", optional=False,
+                      desc="File to read the set of commands from.")
+    cmd.add_param(param)
+    cmd.add_param(param_show)
+    module.add_command(cmd)
+
+    # and loop through all command sets defined above
+    for name in command_sets:
+        cmd = CommandInfo(name=name, desc=get_description(name))
+        cmd.add_param(param_show)
+        module.add_command(cmd)
+
+    tool.add_module_info(module)
+

+ 11 - 1
src/lib/python/isc/config/config_data.py

@@ -23,6 +23,7 @@ two through the classes in ccsession)
 import isc.cc.data
 import isc.config.module_spec
 import ast
+import copy
 
 class ConfigDataError(Exception): pass
 
@@ -210,7 +211,8 @@ def find_spec_part(element, identifier, strict_identifier = True):
         cur_el = _get_map_or_list(cur_el)
 
     cur_el = _find_spec_part_single(cur_el, id_parts[-1])
-    return cur_el
+    # Due to the raw datatypes we use, it is safer to return a deep copy here
+    return copy.deepcopy(cur_el)
 
 def spec_name_list(spec, prefix="", recurse=False):
     """Returns a full list of all possible item identifiers in the
@@ -418,6 +420,14 @@ class MultiConfigData:
            manager or the modules."""
         return self._local_changes
 
+    def set_local_changes(self, new_local_changes):
+        """Sets the entire set of local changes, used when reverting
+           changes done automatically in case there was a problem (e.g.
+           when executing commands from a script that fails halfway
+           through).
+        """
+        self._local_changes = new_local_changes
+
     def clear_local_changes(self):
         """Reverts all local changes"""
         self._local_changes = {}

+ 12 - 1
src/lib/python/isc/config/tests/config_data_test.py

@@ -186,6 +186,10 @@ class TestConfigData(unittest.TestCase):
         spec_part = find_spec_part(config_spec, "item6/value1")
         self.assertEqual({'item_name': 'value1', 'item_type': 'string', 'item_optional': True, 'item_default': 'default'}, spec_part)
 
+        # make sure the returned data is a copy
+        spec_part['item_default'] = 'foo'
+        self.assertNotEqual(spec_part, find_spec_part(config_spec, "item6/value1"))
+
     def test_find_spec_part_lists(self):
         # A few specific tests for list data
         module_spec = isc.config.module_spec_from_file(self.data_path +
@@ -420,7 +424,14 @@ class TestMultiConfigData(unittest.TestCase):
         self.mcd.set_value("Spec2/item1", 2)
         local_changes = self.mcd.get_local_changes()
         self.assertEqual({"Spec2": { "item1": 2}}, local_changes)
-        
+
+    def test_set_local_changes(self):
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.mcd.set_specification(module_spec)
+        self.assertEqual({}, self.mcd.get_local_changes())
+        new_local_changes = {"Spec2": { "item1": 2}}
+        self.mcd.set_local_changes(new_local_changes)
+        self.assertEqual(new_local_changes, self.mcd.get_local_changes())
 
     def test_clear_local_changes(self):
         module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")

+ 22 - 0
tests/lettuce/configurations/bindctl/bindctl.config.orig

@@ -0,0 +1,22 @@
+{
+    "version": 2,
+    "Logging": {
+        "loggers": [ {
+            "debuglevel": 99,
+            "severity": "DEBUG",
+            "name": "*"
+        } ]
+    },
+    "Auth": {
+        "database_file": "data/example.org.sqlite3",
+        "listen_on": [ {
+            "port": 47806,
+            "address": "127.0.0.1"
+        } ]
+    },
+    "Boss": {
+        "components": {
+            "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        }
+    }
+}

+ 9 - 0
tests/lettuce/data/commands/bad_command

@@ -0,0 +1,9 @@
+!echo shouldshow
+# just add something so the test can verify it's reverted
+config add /Boss/components b10-auth
+config set /Boss/components/b10-auth/kind needed
+config set /Boss/components/b10-auth/special auth
+bad command
+# this should not be reached
+!echo shouldnotshow
+config commit

+ 19 - 0
tests/lettuce/data/commands/directives

@@ -0,0 +1,19 @@
+# this is a comment: commentexample1
+!echo this is an echo: echoexample2
+!verbose on
+# this is a comment with verbose on: verbosecommentexample3
+!verbose off
+# this is a comment with verbose off again: commentexample4
+# empty lines and lines with only whitespace should be ignored
+
+
+
+	
+	    	
+# directives are case insensitive, and should handle whitespace
+!ECHO echoexample5
+!eChO echoexample6
+!Verbose     ON
+# verbosecommentexample7
+!verBOSE		off	
+# commentexample8

+ 0 - 0
tests/lettuce/data/commands/empty


+ 2 - 0
tests/lettuce/data/commands/nested

@@ -0,0 +1,2 @@
+# include a different file
+execute file data/commands/nested1

+ 2 - 0
tests/lettuce/data/commands/nested1

@@ -0,0 +1,2 @@
+# this is included by nested
+!echo shouldshow

+ 152 - 61
tests/lettuce/features/bindctl_commands.feature

@@ -1,65 +1,156 @@
 Feature: control with bindctl
     Assorted tests using bindctl for the administration of BIND 10.
 
+
     Scenario: Removing modules
-    # This test runs the original example configuration, which has
-    # a number of modules. It then removes all non-essential modules,
-    # and checks whether they do disappear from the list of running
-    # modules (note that it 'misuses' the help command for this,
-    # there is a Boss command 'show_processes' but it's output is
-    # currently less standardized than 'help')
-    Given I have bind10 running with configuration bindctl_commands.config
-    And wait for bind10 stderr message BIND10_STARTED_CC
-    And wait for bind10 stderr message CMDCTL_STARTED
-    And wait for bind10 stderr message ZONEMGR_STARTED
-    And wait for bind10 stderr message AUTH_SERVER_STARTED
-    And wait for bind10 stderr message XFRIN_STARTED
-    And wait for bind10 stderr message XFROUT_STARTED
-    And wait for bind10 stderr message STATS_STARTING
-    And wait for bind10 stderr message STATHTTPD_STARTED
-
-    Then remove bind10 configuration Boss/components/NOSUCHMODULE
-    last bindctl output should contain Error
-
-    bind10 module Xfrout should be running
-    bind10 module Stats should be running
-    bind10 module Zonemgr should be running
-    bind10 module Xfrin should be running
-    bind10 module Auth should be running
-    bind10 module StatsHttpd should be running
-    bind10 module Resolver should not be running
-
-    Then remove bind10 configuration Boss/components value b10-xfrout
-    And wait for new bind10 stderr message BIND10_PROCESS_ENDED
-    last bindctl output should not contain Error
-
-    # assuming it won't error for further modules (if it does, the final
-    # 'should not be running' tests would fail anyway)
-    Then remove bind10 configuration Boss/components value b10-stats-httpd
-    And wait for new bind10 stderr message BIND10_PROCESS_ENDED
-    last bindctl output should not contain Error
-
-    Then remove bind10 configuration Boss/components value b10-stats
-    And wait for new bind10 stderr message BIND10_PROCESS_ENDED
-    last bindctl output should not contain Error
-
-    Then remove bind10 configuration Boss/components value b10-zonemgr
-    And wait for new bind10 stderr message BIND10_PROCESS_ENDED
-    last bindctl output should not contain Error
-
-    Then remove bind10 configuration Boss/components value b10-xfrin
-    And wait for new bind10 stderr message BIND10_PROCESS_ENDED
-    last bindctl output should not contain Error
-
-    Then remove bind10 configuration Boss/components value b10-auth
-    And wait for new bind10 stderr message BIND10_PROCESS_ENDED
-    last bindctl output should not contain Error
-
-    # After these ^^^ have been stopped...
-    bind10 module Xfrout should not be running
-    bind10 module Zonemgr should not be running
-    bind10 module Xfrin should not be running
-    bind10 module Auth should not be running
-    bind10 module StatsHttpd should not be running
-    bind10 module Stats should not be running
-    bind10 module Resolver should not be running
+        # This test runs the original example configuration, which has
+        # a number of modules. It then removes all non-essential modules,
+        # and checks whether they do disappear from the list of running
+        # modules (note that it 'misuses' the help command for this,
+        # there is a Boss command 'show_processes' but it's output is
+        # currently less standardized than 'help')
+        Given I have bind10 running with configuration bindctl_commands.config
+        And wait for bind10 stderr message BIND10_STARTED_CC
+        And wait for bind10 stderr message CMDCTL_STARTED
+        And wait for bind10 stderr message ZONEMGR_STARTED
+        And wait for bind10 stderr message AUTH_SERVER_STARTED
+        And wait for bind10 stderr message XFRIN_STARTED
+        And wait for bind10 stderr message XFROUT_STARTED
+        And wait for bind10 stderr message STATS_STARTING
+        And wait for bind10 stderr message STATHTTPD_STARTED
+
+        Then remove bind10 configuration Boss/components/NOSUCHMODULE
+        last bindctl output should contain Error
+
+        bind10 module Xfrout should be running
+        bind10 module Stats should be running
+        bind10 module Zonemgr should be running
+        bind10 module Xfrin should be running
+        bind10 module Auth should be running
+        bind10 module StatsHttpd should be running
+        bind10 module Resolver should not be running
+
+        Then remove bind10 configuration Boss/components value b10-xfrout
+        And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+        last bindctl output should not contain Error
+
+        # assuming it won't error for further modules (if it does, the final
+        # 'should not be running' tests would fail anyway)
+        Then remove bind10 configuration Boss/components value b10-stats-httpd
+        And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+        last bindctl output should not contain Error
+
+        Then remove bind10 configuration Boss/components value b10-stats
+        And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+        last bindctl output should not contain Error
+
+        Then remove bind10 configuration Boss/components value b10-zonemgr
+        And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+        last bindctl output should not contain Error
+
+        Then remove bind10 configuration Boss/components value b10-xfrin
+        And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+        last bindctl output should not contain Error
+
+        Then remove bind10 configuration Boss/components value b10-auth
+        And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+        last bindctl output should not contain Error
+
+        # After these ^^^ have been stopped...
+        bind10 module Xfrout should not be running
+        bind10 module Zonemgr should not be running
+        bind10 module Xfrin should not be running
+        bind10 module Auth should not be running
+        bind10 module StatsHttpd should not be running
+        bind10 module Stats should not be running
+        bind10 module Resolver should not be running
+
+    Scenario: Executing scripts from files
+        # This test tests the 'execute' command, which reads and executes
+        # bindctl commands from a file
+        Given I have bind10 running with configuration bindctl/bindctl.config
+        And wait for bind10 stderr message BIND10_STARTED_CC
+        And wait for bind10 stderr message CMDCTL_STARTED
+
+        # first a few bad commands
+        When I send bind10 the command execute
+        last bindctl output should contain Error
+        When I send bind10 the command execute file
+        last bindctl output should contain Error
+        When I send bind10 the command execute file data/commands/nosuchfile
+        last bindctl output should contain Error
+
+        # empty list should be no-op
+        When I send bind10 the command execute file data/commands/empty
+        last bindctl output should not contain Error
+
+        # some tests of directives like !echo and !verbose
+        When I send bind10 the command execute file data/commands/directives
+        last bindctl output should not contain Error
+        last bindctl output should not contain commentexample1
+        last bindctl output should contain echoexample2
+        last bindctl output should contain verbosecommentexample3
+        last bindctl output should not contain commentexample4
+        last bindctl output should contain echoexample5
+        last bindctl output should contain echoexample6
+        last bindctl output should contain verbosecommentexample7
+        last bindctl output should not contain commentexample8
+
+        # bad_command contains a bad command, at which point execution should stop
+        When I send bind10 the command execute file data/commands/bad_command
+        last bindctl output should contain shouldshow
+        last bindctl output should contain Error
+        last bindctl output should not contain shouldnotshow
+        # This would fail if the entire list was passed, or the configuration
+        # was committed
+        send bind10 the command config show Boss/components
+        last bindctl output should not contain b10-auth
+
+        # nested_command contains another execute script
+        When I send bind10 the command execute file data/commands/nested
+        last bindctl output should contain shouldshow
+        last bindctl output should not contain Error    
+
+        # show commands from a file
+        When I send bind10 the command execute file data/commands/bad_command show
+        last bindctl output should not contain Error
+        last bindctl output should contain shouldshow
+        last bindctl output should contain shouldnotshow
+
+    Scenario: Executing builting script init_authoritative_server
+        Given I have bind10 running with configuration bindctl/bindctl.config
+        And wait for bind10 stderr message BIND10_STARTED_CC
+        And wait for bind10 stderr message CMDCTL_STARTED
+
+        When I send bind10 the command execute init_authoritative_server show
+        # just test some parts of the output
+        last bindctl output should contain /Boss/components/b10-auth/special
+        last bindctl output should contain /Boss/components/b10-zonemgr/kind
+        last bindctl output should contain Please
+
+        # nothing should have been changed
+        When I send bind10 the command config diff
+        last bindctl output should contain {}
+
+        # ok now make sure modules aren't running, execute it, and make
+        # sure modules are running
+        bind10 module Auth should not be running
+        bind10 module Xfrout should not be running
+        bind10 module Xfrin should not be running
+        bind10 module Zonemgr should not be running
+
+        When I send bind10 the following commands:
+        """
+        execute init_authoritative_server
+        config commit
+        """
+        And wait for bind10 stderr message AUTH_SERVER_STARTED
+        And wait for bind10 stderr message ZONEMGR_STARTED
+        And wait for bind10 stderr message XFRIN_STARTED
+        And wait for bind10 stderr message XFROUT_STARTED
+
+        last bindctl output should not contain Error
+        bind10 module Auth should be running
+        bind10 module Xfrout should be running
+        bind10 module Xfrin should be running
+        bind10 module Zonemgr should be running

+ 2 - 0
tests/lettuce/features/terrain/terrain.py

@@ -47,6 +47,8 @@ copylist = [
      "configurations/bindctl_commands.config"],
     ["configurations/example.org.config.orig",
      "configurations/example.org.config"],
+    ["configurations/bindctl/bindctl.config.orig",
+     "configurations/bindctl/bindctl.config"],
     ["configurations/resolver/resolver_basic.config.orig",
      "configurations/resolver/resolver_basic.config"],
     ["configurations/multi_instance/multi_auth.config.orig",