2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-01 06:25:34 +00:00

Merge branch 'trac1290'

This commit is contained in:
Jelte Jansen
2011-11-08 14:42:53 +01:00
24 changed files with 1536 additions and 183 deletions

View File

@@ -991,6 +991,7 @@ AC_OUTPUT([doc/version.ent
src/lib/util/python/mkpywrapper.py
src/lib/util/python/gen_wiredata.py
src/lib/server_common/tests/data_path.h
tests/lettuce/setup_intree_bind10.sh
tests/system/conf.sh
tests/system/run.sh
tests/system/glue/setup.sh

View File

@@ -675,6 +675,8 @@ class BoB:
args = ["b10-cmdctl"]
if self.cmdctl_port is not None:
args.append("--port=" + str(self.cmdctl_port))
if self.verbose:
args.append("-v")
self.start_process("b10-cmdctl", args, c_channel_env, self.cmdctl_port)
def start_all_processes(self):

View File

@@ -45,6 +45,5 @@ export B10_FROM_BUILD
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE
cd ${BIND10_PATH}
exec ${PYTHON_EXEC} -O bind10 "$@"
exec ${PYTHON_EXEC} -O ${BIND10_PATH}/bind10 "$@"

View File

@@ -133,19 +133,23 @@ class BindCmdInterpreter(Cmd):
'''Parse commands from user and send them to cmdctl. '''
try:
if not self.login_to_cmdctl():
return
return 1
self.cmdloop()
print('\nExit from bindctl')
return 0
except FailToLogin as err:
# error already printed when this was raised, ignoring
pass
return 1
except KeyboardInterrupt:
print('\nExit from bindctl')
return 0
except socket.error as err:
print('Failed to send request, the connection is closed')
return 1
except http.client.CannotSendRequest:
print('Can not send request, the connection is busy')
return 1
def _get_saved_user_info(self, dir, file_name):
''' Read all the available username and password pairs saved in
@@ -523,6 +527,7 @@ class BindCmdInterpreter(Cmd):
else:
return None
def _get_module_startswith(self, text):
return [module
for module in self.modules

View File

@@ -146,4 +146,5 @@ if __name__ == '__main__':
tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain,
csv_file_dir=options.csv_file_dir)
prepare_config_commands(tool)
tool.run()
result = tool.run()
sys.exit(result)

View File

@@ -342,7 +342,7 @@ class TestConfigCommands(unittest.TestCase):
# validate log message for socket.err
socket_err_output = io.StringIO()
sys.stdout = socket_err_output
self.assertRaises(None, self.tool.run())
self.assertEqual(1, self.tool.run())
self.assertEqual("Failed to send request, the connection is closed\n",
socket_err_output.getvalue())
socket_err_output.close()
@@ -350,7 +350,7 @@ class TestConfigCommands(unittest.TestCase):
# validate log message for http.client.CannotSendRequest
cannot_send_output = io.StringIO()
sys.stdout = cannot_send_output
self.assertRaises(None, self.tool.run())
self.assertEqual(1, self.tool.run())
self.assertEqual("Can not send request, the connection is busy\n",
cannot_send_output.getvalue())
cannot_send_output.close()

View File

@@ -460,6 +460,8 @@ class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
socketserver_mixin.NoPollMixIn.__init__(self)
try:
http.server.HTTPServer.__init__(self, server_address, RequestHandlerClass)
logger.debug(DBG_CMDCTL_MESSAGING, CMDCTL_STARTED,
server_address[0], server_address[1])
except socket.error as err:
raise CmdctlException("Error creating server, because: %s \n" % str(err))
@@ -566,10 +568,9 @@ def set_signal_handler():
def run(addr = 'localhost', port = 8080, idle_timeout = 1200, verbose = False):
''' Start cmdctl as one https server. '''
if verbose:
sys.stdout.write("[b10-cmdctl] starting on %s port:%d\n" %(addr, port))
httpd = SecureHTTPServer((addr, port), SecureHTTPRequestHandler,
CommandControl, idle_timeout, verbose)
httpd.serve_forever()
def check_port(option, opt_str, value, parser):
@@ -607,6 +608,8 @@ if __name__ == '__main__':
(options, args) = parser.parse_args()
result = 1 # in case of failure
try:
if options.verbose:
logger.set_severity("DEBUG", 99)
run(options.addr, options.port, options.idle_timeout, options.verbose)
result = 0
except isc.cc.SessionError as err:

View File

@@ -64,6 +64,9 @@ be set up. The specific error is given in the log message. Possible
causes may be that the ssl request itself was bad, or the local key or
certificate file could not be read.
% CMDCTL_STARTED cmdctl is listening for connections on %1:%2
The cmdctl daemon has started and is now listening for connections.
% CMDCTL_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
There was a keyboard interrupt signal to stop the cmdctl daemon. The
daemon will now shut down.

View File

@@ -18,6 +18,7 @@ import struct
import os
import copy
import subprocess
import copy
from isc.log_messages.bind10_messages import *
from libutil_io_python import recv_fd

View File

@@ -123,6 +123,7 @@ class ConfigManagerData:
output_file_name is not specified, the file used in
read_from_file is used."""
filename = None
try:
file = tempfile.NamedTemporaryFile(mode='w',
prefix="b10-config.db.",

View File

@@ -37,7 +37,7 @@ class TestConfigManagerData(unittest.TestCase):
It shouldn't append the data path to it.
"""
abs_path = self.data_path + os.sep + "b10-config-imaginary.db"
data = ConfigManagerData(os.getcwd(), abs_path)
data = ConfigManagerData(self.data_path, abs_path)
self.assertEqual(abs_path, data.db_filename)
self.assertEqual(self.data_path, data.data_path)

127
tests/lettuce/README Normal file
View File

@@ -0,0 +1,127 @@
BIND10 system testing with Lettuce
or: to BDD or not to BDD
In this directory, we define a set of behavioral tests for BIND 10. Currently,
these tests are specific for BIND10, but we are keeping in mind that RFC-related
tests could be separated, so that we can test other systems as well.
Prerequisites:
- Installed version of BIND 10 (but see below how to run it from source tree)
- dig
- lettuce (http://lettuce.it)
To install lettuce, if you have the python pip installation tool, simply do
pip install lettuce
See http://lettuce.it/intro/install.html
Most systems have the pip tool in a separate package; on Debian-based systems
it is called python-pip. On FreeBSD the port is devel/py-pip.
Running the tests
-----------------
At this moment, we have a fixed port for local tests in our setups, port 47806.
This port must be free. (TODO: can we make this run-time discovered?).
Port 47805 is used for cmdctl, and must also be available.
(note, we will need to extend this to a range, or if possible, we will need to
do some on-the-fly available port finding)
The bind10 main program, bindctl, and dig must all be in the default search
path of your environment, and BIND 10 must not be running if you use the
installed version when you run the tests.
If you want to test an installed version of bind 10, just run 'lettuce' in
this directory.
We have provided a script that sets up the shell environment to run the tests
with the build tree version of bind. If your shell uses export to set
environment variables, you can source the script setup_intree_bind10.sh, then
run lettuce.
Due to the default way lettuce prints its output, it is advisable to run it
in a terminal that is wide than the default. If you see a lot of lines twice
in different colors, the terminal is not wide enough.
If you just want to run one specific feature test, use
lettuce features/<feature file>
To run a specific scenario from a feature, use
lettuce features/<feature file> -s <scenario number>
We have set up the tests to assume that lettuce is run from this directory,
so even if you specify a specific feature file, you should do it from this
directory.
What to do when a test fails
----------------------------
First of all, look at the error it printed and see what step it occurred in.
If written well, the output should explain most of what went wrong.
The stacktrace that is printed is *not* of bind10, but of the testing
framework; this helps in finding more information about what exactly the test
tried to achieve when it failed (as well as help debug the tests themselves).
Furthermore, if any scenario fails, the output from long-running processes
will be stored in the directory output/. The name of the files will be
<Feature name>-<Scenario name>-<Process name>.stdout and
<Feature name>-<Scenario name>-<Process name>.stderr
Where spaces and other non-standard characters are replaced by an underscore.
The process name is either the standard name for said process (e.g. 'bind10'),
or the name given to it by the test ('when i run bind10 as <name>').
These files *will* be overwritten or deleted if the same scenarios are run
again, so if you want to inspect them after a failed test, either do so
immediately or move the files.
Adding and extending tests
--------------------------
If you want to add tests, it is advisable to first go through the examples to
see what is possible, and read the documentation on http://www.lettuce.it
There is also a README.tutorial file here.
We have a couple of conventions to keep things manageable.
Configuration files go into the configurations/ directory.
Data files go into the data/ directory.
Step definition go into the features/terrain/ directory (the name terrain is
chosen for the same reason Lettuce chose terrain.py, this is the place the
tests 'live' in).
Feature definitions go directly into the features/ directory.
These directories are currently not divided further; we may want to consider
this as the set grows. Due to a (current?) limitation of Lettuce, for
feature files this is currently not possible; the python files containing
steps and terrain must be below or at the same level of the feature files.
Long-running processes should be started through the world.RunningProcesses
instance. If you want to add a process (e.g. bind9), create start, stop and
control steps in terrain/<base_name>_control.py, and let it use the
RunningProcesses API (defined in terrain.py). See bind10_control.py for an
example.
For sending queries and checking the results, steps have been defined in
terrain/querying.py. These use dig and store the results split up into text
strings. This is intentionally not parsed through our own library (as that way
we might run into a 'symmetric bug'). If you need something more advanced from
query results, define it here.
Some very general steps are defined in terrain/steps.py.
Initialization code, cleanup code, and helper classes are defined in
terrain/terrain.py.
To find the right steps, case insensitive matching is used. Parameters taken
from the steps are case-sensitive though. So a step defined as
'do foo with value (bar)' will be matched when using
'Do Foo with value xyz', but xyz will be taken as given.
If you need to add steps that are very particular to one test, create a new
file with a name relevant for that test in terrain. We may want to consider
creating a specific subdirectory for these, but at this moment it is unclear
whether we need to.
We should try to keep steps as general as possible, while not making them to
complex and error-prone.

View File

@@ -0,0 +1,157 @@
Quick tutorial and overview
---------------------------
Lettuce is a framework for doing Behaviour Driven Development (BDD).
The idea behind BDD is that you first write down your requirements in
the form of scenarios, then implement their behaviour.
We do not plan on doing full BDD, but such a system should also help
us make system tests. And, hopefully, being able to better identify
what exactly is going wrong when a test fails.
Lettuce is a python implementation of the Cucumber framework, which is
a ruby system. So far we chose lettuce because we already need python
anyway, so chances are higher that any system we want to run it on
supports it. It only supports a subset of cucumber, but more cucumber
features are planned. As I do not know much details of cucumber, I
can't really say what is there and what is not.
A slight letdown is that the current version does not support python 3.
However, as long as the tool-calling glue is python2, this should not
cause any problems, since these aren't unit tests; We do not plan to use
our libraries directly, but only through the runnable scripts and
executables.
-----
Features, Scenarios, Steps.
Lettuce makes a distinction between features, scenarios, and steps.
Features are general, well, features. Each 'feature' has its own file
ending in .feature. A feature file contains a description and a number
of scenarios. Each scenario tests one or more particular parts of the
feature. Each scenario consists of a number of steps.
So let's open up a simple one.
-- example.feature
Feature: showing off BIND 10
This is to show BIND 10 running and that it answer queries
Scenario: Starting bind10
# steps go here
--
I have predefined a number of steps we can use, as we build test we
will need to expand these, but we will look at them shortly.
This file defines a feature, just under the feature name we can
provide a description of the feature.
The one scenario we have no has no steps, so if we run it we should
see something like:
-- output
> lettuce
Feature: showing off BIND 10
This is to show BIND 10 running and that it answer queries
Scenario: Starting bind10
1 feature (1 passed)
1 scenario (1 passed)
0 step (0 passed)
--
Let's first add some steps that send queries.
--
A query for www.example.com should have rcode REFUSED
A query for www.example.org should have rcode NOERROR
--
Since we didn't start any bind10, dig will time out and the result
should be an error saying it got no answer. Errors are in the
form of stack traces (trigger by failed assertions), so we can find
out easily where in the tests they occurred. Especially when the total
set of steps gets bigger we might need that.
So let's add a step that starts bind10.
--
When I start bind10 with configuration example.org.config
--
This is not good enough; it will fire of the process, but setting up
b10-auth may take a few moments, so we need to add a step to wait for
it to be started before we continue.
--
Then wait for bind10 auth to start
--
And let's run the tests again.
--
> lettuce
Feature: showing off BIND 10
This is to show BIND 10 running and that it answer queries
Scenario: Starting bind10
When I start bind10 with configuration example.org.config
Then wait for bind10 auth to start
A query for www.example.com should have rcode REFUSED
A query for www.example.org should have rcode NOERROR
1 feature (1 passed)
1 scenario (1 passed)
4 steps (4 passed)
(finished within 2 seconds)
--
So take a look at one of those steps, let's pick the first one.
A step is defined through a python decorator, which in essence is a regular
expression; lettuce searches through all defined steps to find one that
matches. These are 'partial' matches (unless specified otherwise in the
regular expression itself), so if the step is defined with "do foo bar", the
scenario can add words for readability "When I do foo bar".
Each captured group will be passed as an argument to the function we define.
For bind10, i defined a configuration file, a cmdctl port, and a process
name. The first two should be self-evident, and the process name is an
optional name we give it, should we want to address it in the rest of the
tests. This is most useful if we want to start multiple instances. In the
next step (the wait for auth to start), I added a 'of <instance>'. So if we
define the bind10 'as b10_second_instance', we can specify that one here as
'of b10_second_instance'.
--
When I start bind10 with configuration second.config
with cmdctl port 12345 as b10_second_instance
--
(line wrapped for readability)
But notice how we needed two steps, which we probably always need (but
not entirely always)? We can also combine steps; for instance:
--
@step('have bind10 running(?: with configuration ([\w.]+))?')
def have_bind10_running(step, config_file):
step.given('start bind10 with configuration ' + config_file)
step.given('wait for bind10 auth to start')
--
Now we can replace the two steps with one:
--
Given I have bind10 running
--
That's it for the quick overview. For some more examples, with comments,
take a look at features/example.feature. You can read more about lettuce and
its features on http://www.lettuce.it, and if you plan on adding tests and
scenarios, please consult the last section of the main README first.

View File

@@ -0,0 +1,17 @@
{
"version": 2,
"Logging": {
"loggers": [ {
"debuglevel": 99,
"severity": "DEBUG",
"name": "auth"
} ]
},
"Auth": {
"database_file": "data/example.org.sqlite3",
"listen_on": [ {
"port": 47806,
"address": "127.0.0.1"
} ]
}
}

View File

@@ -0,0 +1,18 @@
{
"version": 2,
"Logging": {
"loggers": [ {
"severity": "DEBUG",
"name": "auth",
"debuglevel": 99
}
]
},
"Auth": {
"database_file": "data/example.org.sqlite3",
"listen_on": [ {
"port": 47807,
"address": "127.0.0.1"
} ]
}
}

View File

@@ -0,0 +1,10 @@
{
"version": 2,
"Auth": {
"database_file": "data/test_nonexistent_db.sqlite3",
"listen_on": [ {
"port": 47806,
"address": "127.0.0.1"
} ]
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,142 @@
Feature: Example feature
This is an example Feature set. Is is mainly intended to show
our use of the lettuce tool and our own framework for it
The first scenario is to show what a simple test would look like, and
is intentionally uncommented.
The later scenarios have comments to show what the test steps do and
support
Scenario: A simple example
Given I have bind10 running with configuration example.org.config
A query for www.example.org should have rcode NOERROR
A query for www.doesnotexist.org should have rcode REFUSED
The SOA serial for example.org should be 1234
Scenario: New database
# This test checks whether a database file is automatically created
# Underwater, we take advantage of our intialization routines so
# that we are sure this file does not exist, see
# features/terrain/terrain.py
# Standard check to test (non-)existence of a file
# This file is actually automatically
The file data/test_nonexistent_db.sqlite3 should not exist
# In the first scenario, we used 'given I have bind10 running', which
# is actually a compound step consisting of the following two
# one to start the server
When I start bind10 with configuration no_db_file.config
# And one to wait until it reports that b10-auth has started
Then wait for bind10 auth to start
# This is a general step to stop a named process. By convention,
# the default name for any process is the same as the one we
# use in the start step (for bind 10, that is 'I start bind10 with')
# See scenario 'Multiple instances' for more.
Then stop process bind10
# Now we use the first step again to see if the file has been created
The file data/test_nonexistent_db.sqlite3 should exist
Scenario: example.org queries
# This scenario performs a number of queries and inspects the results
# Simple queries have already been show, but after we have sent a query,
# we can also do more extensive checks on the result.
# See querying.py for more information on these steps.
# note: lettuce can group similar checks by using tables, but we
# intentionally do not make use of that here
# This is a compound statement that starts and waits for the
# started message
Given I have bind10 running with configuration example.org.config
# Some simple queries that is not examined further
A query for www.example.com should have rcode REFUSED
A query for www.example.org should have rcode NOERROR
# A query where we look at some of the result properties
A query for www.example.org should have rcode NOERROR
The last query response should have qdcount 1
The last query response should have ancount 1
The last query response should have nscount 3
The last query response should have adcount 0
# The answer section can be inspected in its entirety; in the future
# we may add more granular inspection steps
The answer section of the last query response should be
"""
www.example.org. 3600 IN A 192.0.2.1
"""
A query for example.org type NS should have rcode NOERROR
The answer section of the last query response should be
"""
example.org. 3600 IN NS ns1.example.org.
example.org. 3600 IN NS ns2.example.org.
example.org. 3600 IN NS ns3.example.org.
"""
# We have a specific step for checking SOA serial numbers
The SOA serial for example.org should be 1234
# Another query where we look at some of the result properties
A query for doesnotexist.example.org should have rcode NXDOMAIN
The last query response should have qdcount 1
The last query response should have ancount 0
The last query response should have nscount 1
The last query response should have adcount 0
# When checking flags, we must pass them exactly as they appear in
# the output of dig.
The last query response should have flags qr aa rd
A query for www.example.org type TXT should have rcode NOERROR
The last query response should have ancount 0
# Some queries where we specify more details about what to send and
# where
A query for www.example.org class CH should have rcode REFUSED
A query for www.example.org to 127.0.0.1 should have rcode NOERROR
A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
A query for www.example.org type A class IN to 127.0.0.1:47806 should have rcode NOERROR
Scenario: changing database
# This scenario contains a lot of 'wait for' steps
# If those are not present, the asynchronous nature of the application
# can cause some of the things we send to be handled out of order;
# for instance auth could still be serving the old zone when we send
# the new query, or already respond from the new database.
# Therefore we wait for specific log messages after each operation
#
# This scenario outlines every single step, and does not use
# 'steps of steps' (e.g. Given I have bind10 running)
# We can do that but as an example this is probably better to learn
# the system
When I start bind10 with configuration example.org.config
Then wait for bind10 auth to start
Wait for bind10 stderr message CMDCTL_STARTED
A query for www.example.org should have rcode NOERROR
Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
Then set bind10 configuration Auth/database_file to data/empty_db.sqlite3
And wait for new bind10 stderr message DATASRC_SQLITE_OPEN
A query for www.example.org should have rcode REFUSED
Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
Then set bind10 configuration Auth/database_file to data/example.org.sqlite3
And wait for new bind10 stderr message DATASRC_SQLITE_OPEN
A query for www.example.org should have rcode NOERROR
Scenario: two bind10 instances
# This is more a test of the test system, start 2 bind10's
When I start bind10 with configuration example.org.config as bind10_one
And I start bind10 with configuration example2.org.config with cmdctl port 47804 as bind10_two
Then wait for bind10 auth of bind10_one to start
Then wait for bind10 auth of bind10_two to start
A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
A query for www.example.org to 127.0.0.1:47807 should have rcode NOERROR
Then set bind10 configuration Auth/database_file to data/empty_db.sqlite3
And wait for bind10_one stderr message DATASRC_SQLITE_OPEN
A query for www.example.org to 127.0.0.1:47806 should have rcode REFUSED
A query for www.example.org to 127.0.0.1:47807 should have rcode NOERROR

View File

@@ -0,0 +1,108 @@
# Copyright (C) 2011 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.
from lettuce import *
import subprocess
import re
@step('start bind10(?: with configuration (\S+))?' +\
'(?: with cmdctl port (\d+))?(?: as (\S+))?')
def start_bind10(step, config_file, cmdctl_port, process_name):
"""
Start BIND 10 with the given optional config file, cmdctl port, and
store the running process in world with the given process name.
Parameters:
config_file ('with configuration <file>', optional): this configuration
will be used. The path is relative to the base lettuce
directory.
cmdctl_port ('with cmdctl port <portnr>', optional): The port on which
b10-cmdctl listens for bindctl commands. Defaults to 47805.
process_name ('as <name>', optional). This is the name that can be used
in the following steps of the scenario to refer to this
BIND 10 instance. Defaults to 'bind10'.
This call will block until BIND10_STARTUP_COMPLETE or BIND10_STARTUP_ERROR
is logged. In the case of the latter, or if it times out, the step (and
scenario) will fail.
It will also fail if there is a running process with the given process_name
already.
"""
args = [ 'bind10', '-v' ]
if config_file is not None:
args.append('-p')
args.append("configurations/")
args.append('-c')
args.append(config_file)
if cmdctl_port is None:
args.append('--cmdctl-port=47805')
else:
args.append('--cmdctl-port=' + cmdctl_port)
if process_name is None:
process_name = "bind10"
else:
args.append('-m')
args.append(process_name + '_msgq.socket')
world.processes.add_process(step, process_name, args)
# check output to know when startup has been completed
message = world.processes.wait_for_stderr_str(process_name,
["BIND10_STARTUP_COMPLETE",
"BIND10_STARTUP_ERROR"])
assert message == "BIND10_STARTUP_COMPLETE", "Got: " + str(message)
@step('wait for bind10 auth (?:of (\w+) )?to start')
def wait_for_auth(step, process_name):
"""Wait for b10-auth to run. This is done by blocking until the message
AUTH_SERVER_STARTED is logged.
Parameters:
process_name ('of <name', optional): The name of the BIND 10 instance
to wait for. Defaults to 'bind10'.
"""
if process_name is None:
process_name = "bind10"
world.processes.wait_for_stderr_str(process_name, ['AUTH_SERVER_STARTED'],
False)
@step('have bind10 running(?: with configuration ([\w.]+))?')
def have_bind10_running(step, config_file):
"""
Compound convenience step for running bind10, which consists of
start_bind10 and wait_for_auth.
Currently only supports the 'with configuration' option.
"""
step.given('start bind10 with configuration ' + config_file)
step.given('wait for bind10 auth to start')
@step('set bind10 configuration (\S+) to (.*)(?: with cmdctl port (\d+))?')
def set_config_command(step, name, value, cmdctl_port):
"""
Run bindctl, set the given configuration to the given value, and commit it.
Parameters:
name ('configuration <name>'): Identifier of the configuration to set
value ('to <value>'): value to set it to.
cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
the command to. Defaults to 47805.
Fails if cmdctl does not exit with status code 0.
"""
if cmdctl_port is None:
cmdctl_port = '47805'
args = ['bindctl', '-p', cmdctl_port]
bindctl = subprocess.Popen(args, 1, None, subprocess.PIPE,
subprocess.PIPE, None)
bindctl.stdin.write("config set " + name + " " + value + "\n")
bindctl.stdin.write("config commit\n")
bindctl.stdin.write("quit\n")
result = bindctl.wait()
assert result == 0, "bindctl exit code: " + str(result)

View File

@@ -0,0 +1,279 @@
# Copyright (C) 2011 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 script provides querying functionality
# The most important step is
#
# query for <name> [type X] [class X] [to <addr>[:port]] should have rcode <rc>
#
# By default, it will send queries to 127.0.0.1:47806 unless specified
# otherwise. The rcode is always checked. If the result is not NO_ANSWER,
# the result will be stored in last_query_result, which can then be inspected
# more closely, for instance with the step
#
# "the last query response should have <property> <value>"
#
# Also see example.feature for some examples
from lettuce import *
import subprocess
import re
#
# define a class to easily access different parts
# We may consider using our full library for this, but for now
# simply store several parts of the response as text values in
# this structure.
# (this actually has the advantage of not relying on our own libraries
# to test our own, well, libraries)
#
# The following attributes are 'parsed' from the response, all as strings,
# and end up as direct attributes of the QueryResult object:
# opcode, rcode, id, flags, qdcount, ancount, nscount, adcount
# (flags is one string with all flags, in the order they appear in the
# response packet.)
#
# this will set 'rcode' as the result code, we 'define' one additional
# rcode, "NO_ANSWER", if the dig process returned an error code itself
# In this case none of the other attributes will be set.
#
# The different sections will be lists of strings, one for each RR in the
# section. The question section will start with ';', as per dig output
#
# See server_from_sqlite3.feature for various examples to perform queries
class QueryResult(object):
status_re = re.compile("opcode: ([A-Z])+, status: ([A-Z]+), id: ([0-9]+)")
flags_re = re.compile("flags: ([a-z ]+); QUERY: ([0-9]+), ANSWER: " +
"([0-9]+), AUTHORITY: ([0-9]+), ADDITIONAL: ([0-9]+)")
def __init__(self, name, qtype, qclass, address, port):
"""
Constructor. This fires of a query using dig.
Parameters:
name: The domain name to query
qtype: The RR type to query. Defaults to A if it is None.
qclass: The RR class to query. Defaults to IN if it is None.
address: The IP adress to send the query to.
port: The port number to send the query to.
All parameters must be either strings or have the correct string
representation.
Only one query attempt will be made.
"""
args = [ 'dig', '+tries=1', '@' + str(address), '-p', str(port) ]
if qtype is not None:
args.append('-t')
args.append(str(qtype))
if qclass is not None:
args.append('-c')
args.append(str(qclass))
args.append(name)
dig_process = subprocess.Popen(args, 1, None, None, subprocess.PIPE,
None)
result = dig_process.wait()
if result != 0:
self.rcode = "NO_ANSWER"
else:
self.rcode = None
parsing = "HEADER"
self.question_section = []
self.answer_section = []
self.authority_section = []
self.additional_section = []
self.line_handler = self.parse_header
for out in dig_process.stdout:
self.line_handler(out)
def _check_next_header(self, line):
"""
Returns true if we found a next header, and sets the internal
line handler to the appropriate value.
"""
if line == ";; ANSWER SECTION:\n":
self.line_handler = self.parse_answer
elif line == ";; AUTHORITY SECTION:\n":
self.line_handler = self.parse_authority
elif line == ";; ADDITIONAL SECTION:\n":
self.line_handler = self.parse_additional
elif line.startswith(";; Query time"):
self.line_handler = self.parse_footer
else:
return False
return True
def parse_header(self, line):
"""
Parse the header lines of the query response.
Parameters:
line: The current line of the response.
"""
if not self._check_next_header(line):
status_match = self.status_re.search(line)
flags_match = self.flags_re.search(line)
if status_match is not None:
self.opcode = status_match.group(1)
self.rcode = status_match.group(2)
elif flags_match is not None:
self.flags = flags_match.group(1)
self.qdcount = flags_match.group(2)
self.ancount = flags_match.group(3)
self.nscount = flags_match.group(4)
self.adcount = flags_match.group(5)
def parse_question(self, line):
"""
Parse the question section lines of the query response.
Parameters:
line: The current line of the response.
"""
if not self._check_next_header(line):
if line != "\n":
self.question_section.append(line.strip())
def parse_answer(self, line):
"""
Parse the answer section lines of the query response.
Parameters:
line: The current line of the response.
"""
if not self._check_next_header(line):
if line != "\n":
self.answer_section.append(line.strip())
def parse_authority(self, line):
"""
Parse the authority section lines of the query response.
Parameters:
line: The current line of the response.
"""
if not self._check_next_header(line):
if line != "\n":
self.authority_section.append(line.strip())
def parse_additional(self, line):
"""
Parse the additional section lines of the query response.
Parameters:
line: The current line of the response.
"""
if not self._check_next_header(line):
if line != "\n":
self.additional_section.append(line.strip())
def parse_footer(self, line):
"""
Parse the footer lines of the query response.
Parameters:
line: The current line of the response.
"""
pass
@step('A query for ([\w.]+) (?:type ([A-Z]+) )?(?:class ([A-Z]+) )?' +
'(?:to ([^:]+)(?::([0-9]+))? )?should have rcode ([\w.]+)')
def query(step, query_name, qtype, qclass, addr, port, rcode):
"""
Run a query, check the rcode of the response, and store the query
result in world.last_query_result.
Parameters:
query_name ('query for <name>'): The domain name to query.
qtype ('type <type>', optional): The RR type to query. Defaults to A.
qclass ('class <class>', optional): The RR class to query. Defaults to IN.
addr ('to <address>', optional): The IP address of the nameserver to query.
Defaults to 127.0.0.1.
port (':<port>', optional): The port number of the nameserver to query.
Defaults to 47806.
rcode ('should have rcode <rcode>'): The expected rcode of the answer.
"""
if qtype is None:
qtype = "A"
if qclass is None:
qclass = "IN"
if addr is None:
addr = "127.0.0.1"
if port is None:
port = 47806
query_result = QueryResult(query_name, qtype, qclass, addr, port)
assert query_result.rcode == rcode,\
"Expected: " + rcode + ", got " + query_result.rcode
world.last_query_result = query_result
@step('The SOA serial for ([\w.]+) should be ([0-9]+)')
def query_soa(step, query_name, serial):
"""
Convenience function to check the SOA SERIAL value of the given zone at
the nameserver at the default address (127.0.0.1:47806).
Parameters:
query_name ('for <name>'): The zone to find the SOA record for.
serial ('should be <number>'): The expected value of the SOA SERIAL.
If the rcode is not NOERROR, or the answer section does not contain the
SOA record, this step fails.
"""
query_result = QueryResult(query_name, "SOA", "IN", "127.0.0.1", "47806")
assert "NOERROR" == query_result.rcode,\
"Got " + query_result.rcode + ", expected NOERROR"
assert len(query_result.answer_section) == 1,\
"Too few or too many answers in SOA response"
soa_parts = query_result.answer_section[0].split()
assert serial == soa_parts[6],\
"Got SOA serial " + soa_parts[6] + ", expected " + serial
@step('last query response should have (\S+) (.+)')
def check_last_query(step, item, value):
"""
Check a specific value in the reponse from the last successful query sent.
Parameters:
item: The item to check the value of
value: The expected value.
This performs a very simple direct string comparison of the QueryResult
member with the given item name and the given value.
Fails if the item is unknown, or if its value does not match the expected
value.
"""
assert world.last_query_result is not None
assert item in world.last_query_result.__dict__
lq_val = world.last_query_result.__dict__[item]
assert str(value) == str(lq_val),\
"Got: " + str(lq_val) + ", expected: " + str(value)
@step('([a-zA-Z]+) section of the last query response should be')
def check_last_query_section(step, section):
"""
Check the entire contents of the given section of the response of the last
query.
Parameters:
section ('<section> section'): The name of the section (QUESTION, ANSWER,
AUTHORITY or ADDITIONAL).
The expected response is taken from the multiline part of the step in the
scenario. Differing whitespace is ignored, but currently the order is
significant.
Fails if they do not match.
"""
response_string = None
if section.lower() == 'question':
response_string = "\n".join(world.last_query_result.question_section)
elif section.lower() == 'answer':
response_string = "\n".join(world.last_query_result.answer_section)
elif section.lower() == 'authority':
response_string = "\n".join(world.last_query_result.answer_section)
elif section.lower() == 'additional':
response_string = "\n".join(world.last_query_result.answer_section)
else:
assert False, "Unknown section " + section
# replace whitespace of any length by one space
response_string = re.sub("[ \t]+", " ", response_string)
expect = re.sub("[ \t]+", " ", step.multiline)
assert response_string.strip() == expect.strip(),\
"Got:\n'" + response_string + "'\nExpected:\n'" + step.multiline +"'"

View File

@@ -0,0 +1,73 @@
# Copyright (C) 2011 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 contains a number of common steps that are general and may be used
# By a lot of feature files.
#
from lettuce import *
import os
@step('stop process (\w+)')
def stop_a_named_process(step, process_name):
"""
Stop the process with the given name.
Parameters:
process_name ('process <name>'): Name of the process to stop.
"""
world.processes.stop_process(process_name)
@step('wait for (new )?(\w+) stderr message (\w+)')
def wait_for_message(step, new, process_name, message):
"""
Block until the given message is printed to the given process's stderr
output.
Parameter:
new: (' new', optional): Only check the output printed since last time
this step was used for this process.
process_name ('<name> stderr'): Name of the process to check the output of.
message ('message <message>'): Output (part) to wait for.
Fails if the message is not found after 10 seconds.
"""
world.processes.wait_for_stderr_str(process_name, [message], new)
@step('wait for (new )?(\w+) stdout message (\w+)')
def wait_for_message(step, process_name, message):
"""
Block until the given message is printed to the given process's stdout
output.
Parameter:
new: (' new', optional): Only check the output printed since last time
this step was used for this process.
process_name ('<name> stderr'): Name of the process to check the output of.
message ('message <message>'): Output (part) to wait for.
Fails if the message is not found after 10 seconds.
"""
world.processes.wait_for_stdout_str(process_name, [message], new)
@step('the file (\S+) should (not )?exist')
def check_existence(step, file_name, should_not_exist):
"""
Check the existence of the given file.
Parameters:
file_name ('file <name>'): File to check existence of.
should_not_exist ('not', optional): Whether it should or should not exist.
Fails if the file should exist and does not, or vice versa.
"""
if should_not_exist is None:
assert os.path.exists(file_name), file_name + " does not exist"
else:
assert not os.path.exists(file_name), file_name + " exists"

View File

@@ -0,0 +1,360 @@
# Copyright (C) 2011 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 is the 'terrain' in which the lettuce lives. By convention, this is
# where global setup and teardown is defined.
#
# We declare some attributes of the global 'world' variables here, so the
# tests can safely assume they are present.
#
# We also use it to provide scenario invariants, such as resetting data.
#
from lettuce import *
import subprocess
import os.path
import shutil
import re
import time
# In order to make sure we start all tests with a 'clean' environment,
# We perform a number of initialization steps, like restoring configuration
# files, and removing generated data files.
# This approach may not scale; if so we should probably provide specific
# initialization steps for scenarios. But until that is shown to be a problem,
# It will keep the scenarios cleaner.
# This is a list of files that are freshly copied before each scenario
# The first element is the original, the second is the target that will be
# used by the tests that need them
copylist = [
["configurations/example.org.config.orig", "configurations/example.org.config"]
]
# This is a list of files that, if present, will be removed before a scenario
removelist = [
"data/test_nonexistent_db.sqlite3"
]
# When waiting for output data of a running process, use OUTPUT_WAIT_INTERVAL
# as the interval in which to check again if it has not been found yet.
# If we have waited OUTPUT_WAIT_MAX_INTERVALS times, we will abort with an
# error (so as not to hang indefinitely)
OUTPUT_WAIT_INTERVAL = 0.5
OUTPUT_WAIT_MAX_INTERVALS = 20
# class that keeps track of one running process and the files
# we created for it.
class RunningProcess:
def __init__(self, step, process_name, args):
# set it to none first so destructor won't error if initializer did
"""
Initialize the long-running process structure, and start the process.
Parameters:
step: The scenario step it was called from. This is used for
determining the output files for redirection of stdout
and stderr.
process_name: The name to refer to this running process later.
args: Array of arguments to pass to Popen().
"""
self.process = None
self.step = step
self.process_name = process_name
self.remove_files_on_exit = True
self._check_output_dir()
self._create_filenames()
self._start_process(args)
def _start_process(self, args):
"""
Start the process.
Parameters:
args:
Array of arguments to pass to Popen().
"""
stderr_write = open(self.stderr_filename, "w")
stdout_write = open(self.stdout_filename, "w")
self.process = subprocess.Popen(args, 1, None, subprocess.PIPE,
stdout_write, stderr_write)
# open them again, this time for reading
self.stderr = open(self.stderr_filename, "r")
self.stdout = open(self.stdout_filename, "r")
def mangle_filename(self, filebase, extension):
"""
Remove whitespace and non-default characters from a base string,
and return the substituted value. Whitespace is replaced by an
underscore. Any other character that is not an ASCII letter, a
number, a dot, or a hyphen or underscore is removed.
Parameter:
filebase: The string to perform the substitution and removal on
extension: An extension to append to the result value
Returns the modified filebase with the given extension
"""
filebase = re.sub("\s+", "_", filebase)
filebase = re.sub("[^a-zA-Z0-9.\-_]", "", filebase)
return filebase + "." + extension
def _check_output_dir(self):
# We may want to make this overridable by the user, perhaps
# through an environment variable. Since we currently expect
# lettuce to be run from our lettuce dir, we shall just use
# the relative path 'output/'
"""
Make sure the output directory for stdout/stderr redirection
exists.
Fails if it exists but is not a directory, or if it does not
and we are unable to create it.
"""
self._output_dir = os.getcwd() + os.sep + "output"
if not os.path.exists(self._output_dir):
os.mkdir(self._output_dir)
assert os.path.isdir(self._output_dir),\
self._output_dir + " is not a directory."
def _create_filenames(self):
"""
Derive the filenames for stdout/stderr redirection from the
feature, scenario, and process name. The base will be
"<Feature>-<Scenario>-<process name>.[stdout|stderr]"
"""
filebase = self.step.scenario.feature.name + "-" +\
self.step.scenario.name + "-" + self.process_name
self.stderr_filename = self._output_dir + os.sep +\
self.mangle_filename(filebase, "stderr")
self.stdout_filename = self._output_dir + os.sep +\
self.mangle_filename(filebase, "stdout")
def stop_process(self):
"""
Stop this process by calling terminate(). Blocks until process has
exited. If remove_files_on_exit is True, redirected output files
are removed.
"""
if self.process is not None:
self.process.terminate()
self.process.wait()
self.process = None
if self.remove_files_on_exit:
self._remove_files()
def _remove_files(self):
"""
Remove the files created for redirection of stdout/stderr output.
"""
os.remove(self.stderr_filename)
os.remove(self.stdout_filename)
def _wait_for_output_str(self, filename, running_file, strings, only_new):
"""
Wait for a line of output in this process. This will (if only_new is
False) first check all previous output from the process, and if not
found, check all output since the last time this method was called.
For each line in the output, the given strings array is checked. If
any output lines checked contains one of the strings in the strings
array, that string (not the line!) is returned.
Parameters:
filename: The filename to read previous output from, if applicable.
running_file: The open file to read new output from.
strings: Array of strings to look for.
only_new: If true, only check output since last time this method was
called. If false, first check earlier output.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
"""
if not only_new:
full_file = open(filename, "r")
for line in full_file:
for string in strings:
if line.find(string) != -1:
full_file.close()
return string
wait_count = 0
while wait_count < OUTPUT_WAIT_MAX_INTERVALS:
where = running_file.tell()
line = running_file.readline()
if line:
for string in strings:
if line.find(string) != -1:
return string
else:
wait_count += 1
time.sleep(OUTPUT_WAIT_INTERVAL)
running_file.seek(where)
assert False, "Timeout waiting for process output: " + str(strings)
def wait_for_stderr_str(self, strings, only_new = True):
"""
Wait for one of the given strings in this process's stderr output.
Parameters:
strings: Array of strings to look for.
only_new: If true, only check output since last time this method was
called. If false, first check earlier output.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
"""
return self._wait_for_output_str(self.stderr_filename, self.stderr,
strings, only_new)
def wait_for_stdout_str(self, strings, only_new = True):
"""
Wait for one of the given strings in this process's stdout output.
Parameters:
strings: Array of strings to look for.
only_new: If true, only check output since last time this method was
called. If false, first check earlier output.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
"""
return self._wait_for_output_str(self.stdout_filename, self.stdout,
strings, only_new)
# Container class for a number of running processes
# i.e. servers like bind10, etc
# one-shot programs like dig or bindctl are started and closed separately
class RunningProcesses:
def __init__(self):
"""
Initialize with no running processes.
"""
self.processes = {}
def add_process(self, step, process_name, args):
"""
Start a process with the given arguments, and store it under the given
name.
Parameters:
step: The scenario step it was called from. This is used for
determining the output files for redirection of stdout
and stderr.
process_name: The name to refer to this running process later.
args: Array of arguments to pass to Popen().
Fails if a process with the given name is already running.
"""
assert process_name not in self.processes,\
"Process " + name + " already running"
self.processes[process_name] = RunningProcess(step, process_name, args)
def get_process(self, process_name):
"""
Return the Process with the given process name.
Parameters:
process_name: The name of the process to return.
Fails if the process is not running.
"""
assert process_name in self.processes,\
"Process " + name + " unknown"
return self.processes[process_name]
def stop_process(self, process_name):
"""
Stop the Process with the given process name.
Parameters:
process_name: The name of the process to return.
Fails if the process is not running.
"""
assert process_name in self.processes,\
"Process " + name + " unknown"
self.processes[process_name].stop_process()
del self.processes[process_name]
def stop_all_processes(self):
"""
Stop all running processes.
"""
for process in self.processes.values():
process.stop_process()
def keep_files(self):
"""
Keep the redirection files for stdout/stderr output of all processes
instead of removing them when they are stopped later.
"""
for process in self.processes.values():
process.remove_files_on_exit = False
def wait_for_stderr_str(self, process_name, strings, only_new = True):
"""
Wait for one of the given strings in the given process's stderr output.
Parameters:
process_name: The name of the process to check the stderr output of.
strings: Array of strings to look for.
only_new: If true, only check output since last time this method was
called. If false, first check earlier output.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
Fails if the process is unknown.
"""
assert process_name in self.processes,\
"Process " + process_name + " unknown"
return self.processes[process_name].wait_for_stderr_str(strings,
only_new)
def wait_for_stdout_str(self, process_name, strings, only_new = True):
"""
Wait for one of the given strings in the given process's stdout output.
Parameters:
process_name: The name of the process to check the stdout output of.
strings: Array of strings to look for.
only_new: If true, only check output since last time this method was
called. If false, first check earlier output.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
Fails if the process is unknown.
"""
assert process_name in self.processes,\
"Process " + process_name + " unknown"
return self.processes[process_name].wait_for_stdout_str(strings,
only_new)
@before.each_scenario
def initialize(scenario):
"""
Global initialization for each scenario.
"""
# Keep track of running processes
world.processes = RunningProcesses()
# Convenience variable to access the last query result from querying.py
world.last_query_result = None
# Some tests can modify the settings. If the tests fail half-way, or
# don't clean up, this can leave configurations or data in a bad state,
# so we copy them from originals before each scenario
for item in copylist:
shutil.copy(item[0], item[1])
for item in removelist:
if os.path.exists(item):
os.remove(item)
@after.each_scenario
def cleanup(scenario):
"""
Global cleanup for each scenario.
"""
# Keep output files if the scenario failed
if not scenario.passed:
world.processes.keep_files()
# Stop any running processes we may have had around
world.processes.stop_all_processes()

View File

@@ -0,0 +1,46 @@
#! /bin/sh
# Copyright (C) 2010 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.
PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
export PYTHON_EXEC
BIND10_PATH=@abs_top_builddir@/src/bin/bind10
PATH=@abs_top_builddir@/src/bin/bind10:@abs_top_builddir@/src/bin/bindctl:@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
export PATH
PYTHONPATH=@abs_top_builddir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
@ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
B10_FROM_SOURCE=@abs_top_srcdir@
export B10_FROM_SOURCE
# TODO: We need to do this feature based (ie. no general from_source)
# But right now we need a second one because some spec files are
# generated and hence end up under builddir
B10_FROM_BUILD=@abs_top_builddir@
export B10_FROM_BUILD
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE