Commit 86daa843 authored by JINMEI Tatuya's avatar JINMEI Tatuya

[master] Merge branch 'trac1512'

parents 77a9d4c2 be4044ab
......@@ -1067,6 +1067,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/testutils/Makefile
src/lib/python/isc/bind10/Makefile
src/lib/python/isc/bind10/tests/Makefile
src/lib/python/isc/ddns/Makefile
src/lib/python/isc/ddns/tests/Makefile
src/lib/python/isc/xfrin/Makefile
src/lib/python/isc/xfrin/tests/Makefile
src/lib/python/isc/server_common/Makefile
......
......@@ -561,6 +561,10 @@ Message::removeRRset(const Section section, RRsetIterator& iterator) {
void
Message::clearSection(const Section section) {
if (impl_->mode_ != Message::RENDER) {
isc_throw(InvalidMessageOperation,
"clearSection performed in non-render mode");
}
if (section >= MessageImpl::NUM_SECTIONS) {
isc_throw(OutOfRange, "Invalid message section: " << section);
}
......
......@@ -513,6 +513,12 @@ public:
/// \brief Remove all RRSets from the given Section
///
/// This method is only allowed in the \c RENDER mode, and the given
/// section must be valid.
///
/// \throw InvalidMessageOperation Message is not in the \c RENDER mode
/// \throw OutOfRange The specified section is not valid
///
/// \param section Section to remove all rrsets from
void clearSection(const Section section);
......
......@@ -76,6 +76,7 @@ PyObject* Message_getSection(PyObject* self, PyObject* args);
PyObject* Message_addQuestion(s_Message* self, PyObject* args);
PyObject* Message_addRRset(s_Message* self, PyObject* args);
PyObject* Message_clear(s_Message* self, PyObject* args);
PyObject* Message_clearSection(PyObject* pyself, PyObject* args);
PyObject* Message_makeResponse(s_Message* self);
PyObject* Message_toText(s_Message* self);
PyObject* Message_str(PyObject* self);
......@@ -149,6 +150,8 @@ PyMethodDef Message_methods[] = {
"Clears the message content (if any) and reinitialize the "
"message in the given mode\n"
"The argument must be either Message.PARSE or Message.RENDER"},
{ "clear_section", Message_clearSection, METH_VARARGS,
Message_clearSection_doc },
{ "make_response", reinterpret_cast<PyCFunction>(Message_makeResponse), METH_NOARGS,
"Prepare for making a response from a request.\n"
"This will clear the DNS header except those fields that should be kept "
......@@ -563,6 +566,30 @@ Message_clear(s_Message* self, PyObject* args) {
}
}
PyObject*
Message_clearSection(PyObject* pyself, PyObject* args) {
s_Message* const self = static_cast<s_Message*>(pyself);
int section;
if (!PyArg_ParseTuple(args, "i", &section)) {
return (NULL);
}
try {
self->cppobj->clearSection(static_cast<Message::Section>(section));
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
return (NULL);
} catch (const isc::OutOfRange& ex) {
PyErr_SetString(PyExc_OverflowError, ex.what());
return (NULL);
} catch (...) {
PyErr_SetString(po_IscException,
"Unexpected exception in adding RRset");
return (NULL);
}
}
PyObject*
Message_makeResponse(s_Message* self) {
self->cppobj->makeResponse();
......
......@@ -38,4 +38,21 @@ Parameters:\n\
options Parse options\n\
\n\
";
const char* const Message_clearSection_doc = "\
clear_section(section) -> void\n\
\n\
Remove all RRSets from the given Section.\n\
\n\
This method is only allowed in the RENDER mode, and the given section\n\
must be valid.\n\
\n\
Exceptions:\n\
InvalidMessageOperation Message is not in the RENDER mode\n\
OverflowError The specified section is not valid\n\
\n\
Parameters:\n\
section Section to remove all rrsets from\n\
\n\
";
} // unnamed namespace
......@@ -20,6 +20,7 @@
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
#include <dns/labelsequence.h>
#include "pydnspp_common.h"
#include "messagerenderer_python.h"
......@@ -114,6 +115,7 @@ PyObject* Name_reverse(s_Name* self);
PyObject* Name_concatenate(s_Name* self, PyObject* args);
PyObject* Name_downcase(s_Name* self);
PyObject* Name_isWildCard(s_Name* self);
long Name_hash(PyObject* py_self);
PyMethodDef Name_methods[] = {
{ "at", reinterpret_cast<PyCFunction>(Name_at), METH_VARARGS,
......@@ -518,6 +520,12 @@ Name_isWildCard(s_Name* self) {
}
}
long
Name_hash(PyObject* pyself) {
s_Name* const self = static_cast<s_Name*>(pyself);
return (LabelSequence(*self->cppobj).getHash(false));
}
} // end of unnamed namespace
namespace isc {
......@@ -615,7 +623,7 @@ PyTypeObject name_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
NULL, // tp_hash
Name_hash, // tp_hash
NULL, // tp_call
Name_str, // tp_str
NULL, // tp_getattro
......
......@@ -52,6 +52,7 @@ PyObject* RRClass_str(PyObject* self);
PyObject* RRClass_toWire(s_RRClass* self, PyObject* args);
PyObject* RRClass_getCode(s_RRClass* self);
PyObject* RRClass_richcmp(s_RRClass* self, s_RRClass* other, int op);
long RRClass_hash(PyObject* pyself);
// Static function for direct class creation
PyObject* RRClass_IN(s_RRClass *self);
......@@ -264,6 +265,12 @@ PyObject* RRClass_ANY(s_RRClass*) {
return (RRClass_createStatic(RRClass::ANY()));
}
long
RRClass_hash(PyObject* pyself) {
s_RRClass* const self = static_cast<s_RRClass*>(pyself);
return (self->cppobj->getCode());
}
} // end anonymous namespace
namespace isc {
......@@ -296,7 +303,7 @@ PyTypeObject rrclass_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
NULL, // tp_hash
RRClass_hash, // tp_hash
NULL, // tp_call
RRClass_str, // tp_str
NULL, // tp_getattro
......
......@@ -289,6 +289,26 @@ class MessageTest(unittest.TestCase):
self.assertRaises(TypeError, self.r.clear, "wrong")
self.assertRaises(TypeError, self.r.clear, 3)
def test_clear_question_section(self):
self.r.add_question(Question(Name("www.example.com"), RRClass.IN(),
RRType.A()))
self.assertEqual(1, self.r.get_rr_count(Message.SECTION_QUESTION))
self.r.clear_section(Message.SECTION_QUESTION)
self.assertEqual(0, self.r.get_rr_count(Message.SECTION_QUESTION))
def test_clear_section(self):
for section in [Message.SECTION_ANSWER, Message.SECTION_AUTHORITY,
Message.SECTION_ADDITIONAL]:
self.r.add_rrset(section, self.rrset_a)
self.assertEqual(2, self.r.get_rr_count(section))
self.r.clear_section(section)
self.assertEqual(0, self.r.get_rr_count(section))
self.assertRaises(InvalidMessageOperation, self.p.clear_section,
Message.SECTION_ANSWER)
self.assertRaises(OverflowError, self.r.clear_section,
self.bogus_section)
def test_to_wire(self):
self.assertRaises(TypeError, self.r.to_wire, 1)
self.assertRaises(InvalidMessageOperation,
......
......@@ -218,5 +218,27 @@ class NameTest(unittest.TestCase):
self.assertTrue(self.name4 <= self.name1)
self.assertFalse(self.name2 >= self.name1)
def test_hash(self):
# The same name should have the same hash value.
self.assertEqual(hash(Name('example.com')), hash(Name('example.com')))
# Hash is case insensitive.
self.assertEqual(hash(Name('example.com')), hash(Name('EXAMPLE.COM')))
# These pairs happen to be known to have different hashes.
# It may be naive to assume the hash value is always the same (we use
# an external library and it depends on its internal details). If
# it turns out that this assumption isn't always held, we should
# disable this test.
self.assertNotEqual(hash(Name('example.com')),
hash(Name('example.org')))
# Check insensitiveness for the case of inequality.
# Based on the assumption above, this 'if' should be true and
# we'll always test the case inside it. We'll still keep the if in
# case we end up disabling the above test.
if hash(Name('example.com')) != hash(Name('example.org')):
self.assertNotEqual(hash(Name('example.com')),
hash(Name('EXAMPLE.ORG')))
if __name__ == '__main__':
unittest.main()
......@@ -78,6 +78,14 @@ class RRClassTest(unittest.TestCase):
self.assertTrue(self.c1 <= self.c2)
self.assertFalse(self.c1 != other_rrclass)
def test_hash(self):
# Exploiting the knowledge that the hash value is the numeric class
# value, we can predict the comparison result.
self.assertEqual(hash(RRClass.IN()), hash(RRClass("IN")))
self.assertEqual(hash(RRClass("in")), hash(RRClass("IN")))
self.assertNotEqual(hash(RRClass.IN()), hash(RRClass.CH()))
self.assertNotEqual(hash(RRClass.IN()), hash(RRClass("CLASS65535")))
def test_statics(self):
self.assertEqual(RRClass.IN(), RRClass("IN"))
self.assertEqual(RRClass.CH(), RRClass("CH"))
......
......@@ -466,6 +466,13 @@ TEST_F(MessageTest, clearAdditionalSection) {
EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
}
TEST_F(MessageTest, badClearSection) {
// attempt of clearing a message in the parse mode.
EXPECT_THROW(message_parse.clearSection(Message::SECTION_QUESTION),
InvalidMessageOperation);
// attempt of clearing out-of-range section
EXPECT_THROW(message_render.clearSection(bogus_section), OutOfRange);
}
TEST_F(MessageTest, badBeginSection) {
// valid cases are tested via other tests
......
SUBDIRS = datasrc cc config dns log net notify util testutils acl bind10
SUBDIRS += xfrin log_messages server_common
SUBDIRS += xfrin log_messages server_common ddns
python_PYTHON = __init__.py
......
SUBDIRS = . tests
python_PYTHON = __init__.py session.py logger.py zone_config.py
BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py
nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py
pylogmessagedir = $(pyexecdir)/isc/log_messages/
EXTRA_DIST = libddns_messages.mes
CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.pyc
# Define rule to build logging source files from message file
$(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py: libddns_messages.mes
$(top_builddir)/src/lib/log/compiler/message \
-d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/libddns_messages.mes
pythondir = $(pyexecdir)/isc/ddns
CLEANDIRS = __pycache__
clean-local:
rm -rf $(CLEANDIRS)
# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL ISC 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.
# No namespace declaration - these constants go in the global namespace
# of the libddns_messages python module.
% LIBDDNS_UPDATE_ERROR update client %1 for zone %2: %3
Debug message. An error is found in processing a dynamic update
request. This log message is used for general errors that are not
normally expected to happen. So, in general, it would mean some
problem in the client implementation or an interoperability issue
with this implementation. The client's address, the zone name and
class, and description of the error are logged.
% LIBDDNS_UPDATE_FORWARD_FAIL update client %1 for zone %2: update forwarding not supported
Debug message. An update request is sent to a secondary server. This
is not necessarily invalid, but this implementation does not yet
support update forwarding as specified in Section 6 of RFC2136 and it
will simply return a response with an RCODE of NOTIMP to the client.
The client's address and the zone name/class are logged.
% LIBDDNS_UPDATE_NOTAUTH update client %1 for zone %2: not authoritative for update zone
Debug message. An update request for a zone for which the receiving
server doesn't have authority. In theory this is an unexpected event,
but there are client implementations that could send update requests
carelessly, so it may not necessarily be so uncommon in practice. If
possible, you may want to check the implementation or configuration of
those clients to suppress the requests. As specified in Section 3.1
of RFC2136, the receiving server will return a response with an RCODE
of NOTAUTH.
# 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 is a logging utility module for other modules of the ddns library
package.
"""
import isc.log
# The logger for this package
logger = isc.log.Logger('libddns')
class ClientFormatter:
"""A utility class to convert a client address to string.
This class is constructed with a Python standard socket address tuple.
If it's 2-element tuple, it's assumed to be an IPv4 socket address
and will be converted to the form of '<addr>:<port>'.
If it's 4-element tuple, it's assumed to be an IPv6 socket address.
and will be converted to the form of '[<addr>]:<por>'.
This class is designed to delay the conversion until it's explicitly
requested, so the conversion doesn't happen if the corresponding log
message is suppressed because of its log level (which is often the case
for debug messages).
Note: this optimization comes with the cost of instantiating the
formatter object itself. It's not really clear which overhead is
heavier, and we may conclude it's actually better to just generate
the strings unconditionally. Alternatively, we can make the stored
address of this object replaceable so that this object can be reused.
Right now this is an open issue.
"""
def __init__(self, addr):
self.__addr = addr
def __str__(self):
if len(self.__addr) == 2:
return self.__addr[0] + ':' + str(self.__addr[1])
elif len(self.__addr) == 4:
return '[' + self.__addr[0] + ']:' + str(self.__addr[1])
return None
class ZoneFormatter:
"""A utility class to convert zone name and class to string.
This class is constructed with a name of a zone (isc.dns.Name object)
and its RR class (isc.dns.RRClass object). Its text conversion method
(__str__) converts them into a string in the form of
'<zone name>/<zone class>' where the trailing dot of the zone name
is omitted.
If the given zone name on construction is None, it's assumed to be
the zone isn't identified but needs to be somehow logged. The conversion
method returns a special string to indicate this case.
This class is designed to delay the conversion until it's explicitly
requested, so the conversion doesn't happen if the corresponding log
message is suppressed because of its log level (which is often the case
for debug messages).
See the note for the ClientFormatter class about overhead tradeoff.
This class shares the same discussion.
"""
def __init__(self, zname, zclass):
self.__zname = zname
self.__zclass = zclass
def __str__(self):
if self.__zname is None:
return '(zone unknown/not determined)'
return self.__zname.to_text(True) + '/' + self.__zclass.to_text()
# 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.
from isc.dns import *
import isc.ddns.zone_config
from isc.log import *
from isc.ddns.logger import logger, ClientFormatter, ZoneFormatter
from isc.log_messages.libddns_messages import *
# Result codes for UpdateSession.handle()
UPDATE_SUCCESS = 0
UPDATE_ERROR = 1
UPDATE_DROP = 2
# Convenient aliases of update-specific section names
SECTION_ZONE = Message.SECTION_QUESTION
SECTION_PREREQUISITE = Message.SECTION_ANSWER
SECTION_UPDATE = Message.SECTION_AUTHORITY
# Shortcut
DBGLVL_TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
class UpdateError(Exception):
'''Exception for general error in update request handling.
This exception is intended to be used internally within this module.
When UpdateSession.handle() encounters an error in handling an update
request it can raise this exception to terminate the handling.
This class is constructed with some information that may be useful for
subsequent possible logging:
- msg (string) A string explaining the error.
- zname (isc.dns.Name) The zone name. Can be None when not identified.
- zclass (isc.dns.RRClass) The zone class. Like zname, can be None.
- rcode (isc.dns.RCode) The RCODE to be set in the response message.
- nolog (bool) If True, it indicates there's no more need for logging.
'''
def __init__(self, msg, zname, zclass, rcode, nolog=False):
Exception.__init__(self, msg)
self.zname = zname
self.zclass = zclass
self.rcode = rcode
self.nolog = nolog
class UpdateSession:
'''Protocol handling for a single dynamic update request.
This class is instantiated with a request message and some other
information that will be used for handling the request. Its main
method, handle(), will process the request, and normally build
a response message according to the result. The application of this
class can use the message to send a response to the client.
'''
def __init__(self, req_message, req_data, client_addr, zone_config):
'''Constructor.
Note: req_data is not really used as of #1512 but is listed since
it's quite likely we need it in a subsequent task soon. We'll
also need to get other parameters such as ACLs, for which, it's less
clear in which form we want to get the information, so it's left
open for now.
Parameters:
- req_message (isc.dns.Message) The request message. This must be
in the PARSE mode.
- req_data (binary) Wire format data of the request message.
It will be used for TSIG verification if necessary.
- client_addr (socket address) The address/port of the update client
in the form of Python socket address object. This is mainly for
logging and access control.
- zone_config (ZoneConfig) A tentative container that encapsulates
the server's zone configuration. See zone_config.py.
(It'll soon need to be passed ACL in some way, too)
'''
self.__message = req_message
self.__client_addr = client_addr
self.__zone_config = zone_config
def get_message(self):
'''Return the update message.
After handle() is called, it's generally transformed to the response
to be returned to the client; otherwise it would be identical to
the request message passed on construction.
'''
return self.__message
def handle(self):
'''Handle the update request according to RFC2136.
This method returns a tuple of the following three elements that
indicate the result of the request.
- Result code of the request processing, which are:
UPDATE_SUCCESS Update request granted and succeeded.
UPDATE_ERROR Some error happened to be reported in the response.
UPDATE_DROP Error happened and no response should be sent.
Except the case of UPDATE_DROP, the UpdateSession object will have
created a response that is to be returned to the request client,
which can be retrieved by get_message().
- The name of the updated zone (isc.dns.Name object) in case of
UPDATE_SUCCESS; otherwise None.
- The RR class of the updated zone (isc.dns.RRClass object) in case
of UPDATE_SUCCESS; otherwise None.
'''
try:
datasrc_client, zname, zclass = self.__get_update_zone()
# conceptual code that would follow
# self.__check_prerequisites()
# self.__check_update_acl()
# self.__do_update()
# self.__make_response(Rcode.NOERROR())
return UPDATE_SUCCESS, zname, zclass
except UpdateError as e:
if not e.nolog:
logger.debug(logger.DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_ERROR,
ClientFormatter(self.__client_addr),
ZoneFormatter(e.zname, e.zclass), e)
self.__make_response(e.rcode)
return UPDATE_ERROR, None, None
def __get_update_zone(self):
'''Parse the zone section and find the zone to be updated.
If the zone section is valid and the specified zone is found in
the configuration, it returns a tuple of:
- A matching data source that contains the specified zone
- The zone name as a Name object
- The zone class as an RRClass object
'''
# Validation: the zone section must contain exactly one question,
# and it must be of type SOA.
n_zones = self.__message.get_rr_count(SECTION_ZONE)
if n_zones != 1:
raise UpdateError('Invalid number of records in zone section: ' +
str(n_zones), None, None, Rcode.FORMERR())
zrecord = self.__message.get_question()[0]
if zrecord.get_type() != RRType.SOA():
raise UpdateError('update zone section contains non-SOA',
None, None, Rcode.FORMERR())
# See if we're serving a primary zone specified in the zone section.
zname = zrecord.get_name()
zclass = zrecord.get_class()
zone_type, datasrc_client = self.__zone_config.find_zone(zname, zclass)
if zone_type == isc.ddns.zone_config.ZONE_PRIMARY:
return datasrc_client, zname, zclass
elif zone_type == isc.ddns.zone_config.ZONE_SECONDARY:
# We are a secondary server; since we don't yet support update
# forwarding, we return 'not implemented'.
logger.debug(DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_FORWARD_FAIL,
ClientFormatter(self.__client_addr),
ZoneFormatter(zname, zclass))
raise UpdateError('forward', zname, zclass, Rcode.NOTIMP(), True)
# zone wasn't found
logger.debug(DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_NOTAUTH,
ClientFormatter(self.__client_addr),
ZoneFormatter(zname, zclass))
raise UpdateError('notauth', zname, zclass, Rcode.NOTAUTH(), True)
def __make_response(self, rcode):
'''Transform the internal message to the update response.
According RFC2136 Section 3.8, the zone section will be cleared
as well as other sections. The response Rcode will be set to the
given value.
'''
self.__message.make_response()
self.__message.clear_section(SECTION_ZONE)
self.__message.set_rcode(rcode)
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = session_tests.py zone_config_tests.py
EXTRA_DIST = $(PYTESTS)
CLEANFILES = $(builddir)/rwtest.sqlite3.copied
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
if SET_ENV_LIBRARY_PATH
LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.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/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
# B10_FROM_BUILD is necessary to load data source backend from the build tree.
check-local:
if ENABLE_PYTHON_COVERAGE
touch $(abs_top_srcdir)/.coverage
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
TESTDATA_WRITE_PATH=$(builddir) \
B10_FROM_BUILD=$(abs_top_builddir) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
# 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.
import os
import shutil
import isc.log
import unittest
from isc.dns import *
from isc.datasrc import DataSourceClient
from isc.ddns.session import *
from isc.ddns.zone_config import *
# Some common test parameters
TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
READ_ZONE_DB_FILE = TESTDATA_PATH + "rwtest.sqlite3" # original, to be copied
TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
WRITE_ZONE_DB_CONFIG = "{ \"database_file\": \"" + WRITE_ZONE_DB_FILE + "\"}"
TEST_ZONE_NAME = Name('example.org')