Commit c4df99ea authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[master] Merge branch 'trac2438'

resolved Conflicts:
	src/lib/dns/python/Makefile.am
	src/lib/dns/python/rrset_collection_python.cc
	src/lib/dns/python/rrset_collection_python.h
	src/lib/dns/python/rrset_collection_python_inc.cc
parents 99a6e847 39a3150a
......@@ -282,7 +282,10 @@ AC_SUBST(PYTHON_LOGMSGPKG_DIR)
# This is python package paths commonly used in python tests. See
# README of log_messages for why it's included.
COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python"
# lib/dns/python/.libs is necessary because __init__.py of isc package
# automatically imports isc.datasrc, which then requires the DNS loadable
# module. #2145 should eliminate the need for it.
COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python:\$(abs_top_builddir)/src/lib/dns/python/.libs"
AC_SUBST(COMMON_PYTHON_PATH)
# Check for python development environments
......
......@@ -20,7 +20,10 @@ export PYTHON_EXEC
BINDCTL_PATH=@abs_top_builddir@/src/bin/bindctl
PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
# automatically imports isc.datasrc, which then requires the DNS loadable
# module. #2145 should eliminate the need for it.
PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
......
......@@ -20,7 +20,10 @@ export PYTHON_EXEC
DBUTIL_PATH=@abs_top_builddir@/src/bin/dbutil
PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
# automatically imports isc.datasrc, which then requires the DNS loadable
# module. #2145 should eliminate the need for it.
PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
......
......@@ -20,10 +20,11 @@ export PYTHON_EXEC
SYSINFO_PATH=@abs_top_builddir@/src/bin/sysinfo
# Note: we shouldn't need log_messages except for the seemingly necessary
# dependency due to the automatic import in the isc package (its __init__.py
# imports some other modules)
PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages
# Note: we shouldn't need log_messages and lib/dns except for the seemingly
# necessary dependency due to the automatic import in the isc package (its
# __init__.py imports some other modules)
# #2145 should eliminate the need for them.
PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# Likewise, we need only because isc.log requires some loadable modules.
......
......@@ -55,10 +55,23 @@ setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
}
}
// RRsetCollectionBase: the base RRsetCollection class in Python.
//
// RRsetCollectionBase
//
// Any derived RRsetCollection class is supposed to be inherited from this
// class:
// - If the derived class is implemented via a C++ wrapper (associated with
// a C++ implementation of RRsetCollection), its PyTypeObject should
// specify rrset_collection_base_type for tp_base. Its C/C++-representation
// of objects should be compatible with s_RRsetCollection, and the wrapper
// should set its cppobj member to point to the corresponding C++
// RRsetCollection object. Normally it doesn't have to provide Python
// wrapper of find(); the Python interpreter will then call find() on
// the base class, which ensures that the corresponding C++ version of
// find() will be used.
// - If the derived class is implemented purely in Python, it must implement
// find() in Python within the class. As explained in the first bullet,
// the base class method is generally expected to be used only for C++
// wrapper of RRsetCollection derived class.
namespace {
int
RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
......@@ -70,8 +83,13 @@ RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
void
RRsetCollectionBase_destroy(PyObject* po_self) {
s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
delete self->cppobj;
self->cppobj = NULL;
// Any C++-wrapper of derived RRsetCollection class should have its own
// destroy function (as it may manage cppobj in its own way);
// Python-only derived classes shouldn't set cppobj (which is
// 0-initialized). So this assertion must hold.
assert(self->cppobj == NULL);
Py_TYPE(self)->tp_free(self);
}
......@@ -79,6 +97,9 @@ PyObject*
RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
// If this function is called with cppobj being NULL, this means
// a pure-Python derived class skips implementing its own find().
// This is an error (see general description above).
if (self->cppobj == NULL) {
PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
"derived RRsetCollection class");
......@@ -108,6 +129,9 @@ RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
}
Py_RETURN_NONE;
}
} catch (const RRsetCollectionError& ex) {
PyErr_SetString(po_RRsetCollectionError, ex.what());
return (NULL);
} catch (const std::exception& ex) {
const string ex_what = "Unexpected failure in "
"RRsetCollectionBase.find: " + string(ex.what());
......@@ -137,6 +161,9 @@ PyMethodDef RRsetCollectionBase_methods[] = {
namespace isc {
namespace dns {
namespace python {
// Definition of class specific exception(s)
PyObject* po_RRsetCollectionError;
// This defines the complete type for reflection in python and
// parsing of PyObject* to s_RRsetCollection
// Most of the functions are not actually implemented and NULL here.
......@@ -193,18 +220,27 @@ PyTypeObject rrset_collection_base_type = {
// Module Initialization, all statics are initialized here
bool
initModulePart_RRsetCollectionBase(PyObject* mod) {
// We initialize the static description object with PyType_Ready(),
// then add it to the module. This is not just a check! (leaving
// this out results in segmentation faults)
if (PyType_Ready(&rrset_collection_base_type) < 0) {
if (!initClass(rrset_collection_base_type, "RRsetCollectionBase", mod)) {
return (false);
}
void* p = &rrset_collection_base_type;
if (PyModule_AddObject(mod, "RRsetCollectionBase",
static_cast<PyObject*>(p)) < 0) {
try {
po_RRsetCollectionError =
PyErr_NewException("dns.RRsetCollectionError",
po_IscException, NULL);
PyObjectContainer(po_RRsetCollectionError).installToModule(
mod, "RRsetCollectionError");
} catch (const std::exception& ex) {
const std::string ex_what =
"Unexpected failure in RRsetCollectionBase initialization: " +
std::string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
return (false);
} catch (...) {
PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
"RRsetCollectionBase initialization");
return (false);
}
Py_INCREF(&rrset_collection_base_type);
return (true);
}
......@@ -405,18 +441,9 @@ PyTypeObject rrset_collection_type = {
// Module Initialization, all statics are initialized here
bool
initModulePart_RRsetCollection(PyObject* mod) {
// We initialize the static description object with PyType_Ready(),
// then add it to the module. This is not just a check! (leaving
// this out results in segmentation faults)
if (PyType_Ready(&rrset_collection_type) < 0) {
return (false);
}
void* p = &rrset_collection_type;
if (PyModule_AddObject(mod, "RRsetCollection",
static_cast<PyObject*>(p)) < 0) {
if (!initClass(rrset_collection_type, "RRsetCollection", mod)) {
return (false);
}
Py_INCREF(&rrset_collection_type);
return (true);
}
......
......@@ -23,10 +23,13 @@ class RRsetCollectionBase;
namespace python {
// The s_* Class simply covers one instantiation of the object
// The s_* Class simply covers one instantiation of the object.
// This structure will be commonly used for all derived classes of
// RRsetCollectionBase. cppobj will point to an instance of the specific
// derived class.
// derived class of (C++) RRsetCollectionBase.
//
// A C++ wrapper for Python version of RRsetCollection should set this
// variable, and skip the implementation of C++ wrapper of find() method.
class s_RRsetCollection : public PyObject {
public:
s_RRsetCollection() : cppobj(NULL) {}
......@@ -39,6 +42,9 @@ extern PyTypeObject rrset_collection_base_type;
// Python type information for dns.RRsetCollection
extern PyTypeObject rrset_collection_type;
// Class specific exceptions
extern PyObject* po_RRsetCollectionError;
bool initModulePart_RRsetCollectionBase(PyObject* mod);
bool initModulePart_RRsetCollection(PyObject* mod);
......
namespace {
// Modifications
// - libdns++ => isc.dns, libdatasrc => isc.datasrc
// - note about the constructor.
// - note about the direct construction.
// - add note about iteration
const char* const RRsetCollectionBase_doc = "\
Generic class to represent a set of RRsets.\n\
......@@ -18,8 +18,8 @@ maybe class) and a way to iterate over all RRsets.\n\
See RRsetCollection for a simple isc.dns implementation. Other modules\n\
such as isc.datasrc will have another implementation.\n\
\n\
This base class cannot be directly instantiated, so no constructor is\n\
defined.\n\
This base class cannot be directly instantiated. Such an attempt will\n\
result in a TypeError exception.\n\
\n\
";
......@@ -79,18 +79,18 @@ RRsetCollection(filename, origin, rrclass)\n\
origin (isc.dns.Name) The zone origin.\n\
rrclass (isc.dns.RRClass) The zone class.\n\
\n\
RRsetCollection(input_stream, origin, rrclass)\n\
RRsetCollection(input, origin, rrclass)\n\
\n\
Constructor.\n\
\n\
This constructor is similar to the previous one, but instead of\n\
taking a filename to load a zone from, it takes a byte object,\n\
taking a filename to load a zone from, it takes a bytes object,\n\
representing the zone contents in text.\n\
The constructor throws IscException if there is an error\n\
during loading.\n\
\n\
Parameters:\n\
input (byte) Textual representation of the zone.\n\
input (bytes) Textual representation of the zone.\n\
origin (isc.dns.Name) The zone origin.\n\
rrclass (isc.dns.RRClass) The zone class.\n\
\n\
......
......@@ -185,6 +185,8 @@ public:
}
};
typedef boost::shared_ptr<RRsetCollectionBase> RRsetCollectionPtr;
} // end of namespace dns
} // end of namespace isc
......
......@@ -217,23 +217,6 @@ initModulePart_ZoneLoader(PyObject* mod) {
return (true);
}
bool
initModulePart_ZoneUpdater(PyObject* mod) {
// We initialize the static description object with PyType_Ready(),
// then add it to the module. This is not just a check! (leaving
// this out results in segmentation faults)
if (PyType_Ready(&zoneupdater_type) < 0) {
return (false);
}
void* zip = &zoneupdater_type;
if (PyModule_AddObject(mod, "ZoneUpdater", static_cast<PyObject*>(zip)) < 0) {
return (false);
}
Py_INCREF(&zoneupdater_type);
return (true);
}
bool
initModulePart_ZoneJournalReader(PyObject* mod) {
if (PyType_Ready(&journal_reader_type) < 0) {
......
......@@ -528,6 +528,41 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
def test_updater_rrset_collection(self):
dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
updater = dsc.get_updater(isc.dns.Name("example.com"), False)
updater_refs = sys.getrefcount(updater)
# Get (internally create) updater's RRset collection
rrsets = updater.get_rrset_collection()
# From this point we cannot make further updates
rrset = RRset(isc.dns.Name('www.example.com'), isc.dns.RRClass.IN(),
isc.dns.RRType.AAAA(), isc.dns.RRTTL(10))
rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.AAAA(),
isc.dns.RRClass.IN(), '2001:db8::1'))
self.assertRaises(isc.datasrc.Error, updater.add_rrset, rrset)
# Checks basic API
found = rrsets.find(isc.dns.Name("www.example.com"),
isc.dns.RRClass.IN(), isc.dns.RRType.A())
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
found.to_text())
self.assertEqual(None, rrsets.find(isc.dns.Name("www.example.com"),
isc.dns.RRClass.IN(),
isc.dns.RRType.AAAA()))
# Once committed collection cannot be used any more.
updater.commit()
self.assertRaises(isc.dns.RRsetCollectionError,
rrsets.find, isc.dns.Name("www.example.com"),
isc.dns.RRClass.IN(), isc.dns.RRType.A())
# When we destroy the RRsetCollection it should release the refcount
# to the updater.
rrsets = None
self.assertEqual(updater_refs, sys.getrefcount(updater))
def test_two_modules(self):
# load two modules, and check if they don't interfere
mem_cfg = { "type": "memory", "class": "IN", "zones": [] };
......
......@@ -76,6 +76,10 @@ This method must not be called once commit() is performed. If it calls\n\
after commit() the implementation must throw a isc.datasrc.Error\n\
exception.\n\
\n\
Implementations of ZoneUpdater may not allow adding or deleting RRsets\n\
after get_rrset_collection() is called. In this case, implementations\n\
throw an InvalidOperation exception.\n\
\n\
Todo As noted above we may have to revisit the design details as we\n\
gain experiences:\n\
\n\
......@@ -133,6 +137,10 @@ This method must not be called once commit() is performed. If it calls\n\
after commit() the implementation must throw a isc.datasrc.Error\n\
exception.\n\
\n\
Implementations of ZoneUpdater may not allow adding or deleting RRsets\n\
after get_rrset_collection() is called. In this case, implementations\n\
throw an InvalidOperation exception.\n\
\n\
Todo: As noted above we may have to revisit the design details as we\n\
gain experiences:\n\
\n\
......@@ -178,4 +186,32 @@ Exceptions:\n\
error, or wrapper error\n\\n\
\n\
";
// Modifications
// - isc.datasrc.RRsetCollectionBase => isc.dns.RRsetCollectionBase
// (in the Python wrapper, the former is completely invisible)
// - remove other reference to isc.datasrc.RRsetCollectionBase
const char* const ZoneUpdater_getRRsetCollection_doc = "\
get_rrset_collection() -> isc.dns.RRsetCollectionBase \n\
\n\
Return an RRsetCollection for the updater.\n\
\n\
This method returns an RRsetCollection for the updater, implementing\n\
the isc.dns.RRsetCollectionBase interface. Typically, the returned\n\
RRsetCollection is a singleton for its ZoneUpdater. The returned\n\
RRsetCollection object must not be used after its corresponding\n\
ZoneUpdater has been destroyed. The returned RRsetCollection object\n\
may be used to search RRsets from the ZoneUpdater. The actual\n\
RRsetCollection returned has a behavior dependent on the ZoneUpdater\n\
implementation.\n\
\n\
The behavior of the RRsetCollection is similar to the behavior of the\n\
Zonefinder returned by get_finder(). Implementations of ZoneUpdater\n\
may not allow adding or deleting RRsets after get_rrset_collection()\n\
is called. Implementations of ZoneUpdater may disable a previously\n\
returned RRsetCollection after commit() is called. If an\n\
RRsetCollection is disabled, using methods such as find() and using\n\
its iterator would cause an exception to be thrown.\n\
\n\
";
} // unnamed namespace
......@@ -32,6 +32,7 @@
#include <dns/python/rrset_python.h>
#include <dns/python/rrclass_python.h>
#include <dns/python/rrtype_python.h>
#include <dns/python/rrset_collection_python.h>
#include "datasrc.h"
#include "updater_python.h"
......@@ -195,6 +196,107 @@ ZoneUpdater_find_all(PyObject* po_self, PyObject* args) {
&self->cppobj->getFinder(), args));
}
namespace {
// Below we define Python RRsetCollection class generated by the updater.
// It's never expected to be instantiated directly from Python code, so
// everything is hidden here, and tp_init always fails.
class s_UpdaterRRsetCollection : public s_RRsetCollection {
public:
s_UpdaterRRsetCollection() : s_RRsetCollection() {}
PyObject* base_obj_;
};
int
RRsetCollection_init(PyObject*, PyObject*, PyObject*) {
// can't be called directly; actually, the constructor shouldn't even be
// called, but we catch the case just in case.
PyErr_SetString(PyExc_TypeError,
"datasrc.RRsetCollection cannot be constructed directly");
return (-1);
}
void
RRsetCollection_destroy(PyObject* po_self) {
s_UpdaterRRsetCollection* const self =
static_cast<s_UpdaterRRsetCollection*>(po_self);
// We don't own the C++ collection object; we shouldn't delete it here.
// Note: we need to check if this is NULL; it remains NULL in case of
// direct instantiation (which fails).
if (self->base_obj_ != NULL) {
Py_DECREF(self->base_obj_);
}
Py_TYPE(self)->tp_free(self);
}
PyTypeObject updater_rrset_collection_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"datasrc.UpdaterRRsetCollection",
sizeof(s_UpdaterRRsetCollection), // tp_basicsize
0, // tp_itemsize
RRsetCollection_destroy, // tp_dealloc
NULL, // tp_print
NULL, // tp_getattr
NULL, // tp_setattr
NULL, // tp_reserved
NULL, // tp_repr
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
NULL, // tp_hash
NULL, // tp_call
NULL, // tp_str
NULL, // tp_getattro
NULL, // tp_setattro
NULL, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
NULL,
NULL, // tp_traverse
NULL, // tp_clear
NULL, // tp_richcompare
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
NULL, // tp_methods
NULL, // tp_members
NULL, // tp_getset
NULL, // tp_base (rrset_collection_base_type, set at run time)
NULL, // tp_dict
NULL, // tp_descr_get
NULL, // tp_descr_set
0, // tp_dictoffset
RRsetCollection_init, // tp_init
NULL, // tp_alloc
PyType_GenericNew, // tp_new
NULL, // tp_free
NULL, // tp_is_gc
NULL, // tp_bases
NULL, // tp_mro
NULL, // tp_cache
NULL, // tp_subclasses
NULL, // tp_weaklist
NULL, // tp_del
0 // tp_version_tag
};
} // unnamed namespace
PyObject*
ZoneUpdater_getRRsetCollection(PyObject* po_self, PyObject*) {
s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
s_UpdaterRRsetCollection* collection =
static_cast<s_UpdaterRRsetCollection*>(
PyObject_New(s_RRsetCollection, &updater_rrset_collection_type));
collection->cppobj = &self->cppobj->getRRsetCollection();
collection->base_obj_ = po_self;;
Py_INCREF(collection->base_obj_);
return (collection);
}
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
......@@ -207,6 +309,8 @@ PyMethodDef ZoneUpdater_methods[] = {
{ "delete_rrset", ZoneUpdater_deleteRRset,
METH_VARARGS, ZoneUpdater_deleteRRset_doc },
{ "commit", ZoneUpdater_commit, METH_NOARGS, ZoneUpdater_commit_doc },
{ "get_rrset_collection", ZoneUpdater_getRRsetCollection,
METH_NOARGS, ZoneUpdater_getRRsetCollection_doc },
// Instead of a getFinder, we implement the finder functionality directly
// This is because ZoneFinder is non-copyable, and we should not create
// a ZoneFinder object from a reference only (which is what is returned
......@@ -292,6 +396,56 @@ createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
return (py_zu);
}
bool
initModulePart_ZoneUpdater(PyObject* mod) {
// We initialize the static description object with PyType_Ready(),
// then add it to the module. This is not just a check! (leaving
// this out results in segmentation faults)
if (PyType_Ready(&zoneupdater_type) < 0) {
return (false);
}
void* zip = &zoneupdater_type;
if (PyModule_AddObject(mod, "ZoneUpdater",
static_cast<PyObject*>(zip)) < 0)
{
return (false);
}
Py_INCREF(&zoneupdater_type);
// get_rrset_collection() needs the base class type information. Since
// it's defined in a separate loadable module, we retrieve its C object
// via the Python interpreter. Directly referring to
// isc::dns::python::rrset_collection_base_type might work depending on
// the runtime environment (and in fact it does for some), but that would
// be less portable.
try {
if (updater_rrset_collection_type.tp_base == NULL) {
PyObjectContainer dns_module(PyImport_ImportModule("isc.dns"));
PyObjectContainer dns_dict(PyModule_GetDict(dns_module.get()));
// GetDict doesn't acquire a reference, so we need to get it to
// meet the container's requirement.
Py_INCREF(dns_dict.get());
PyObjectContainer base(
PyDict_GetItemString(dns_dict.get(), "RRsetCollectionBase"));
PyTypeObject* pt_rrset_collection_base =
static_cast<PyTypeObject*>(static_cast<void*>(base.get()));
updater_rrset_collection_type.tp_base = pt_rrset_collection_base;
if (PyType_Ready(&updater_rrset_collection_type) < 0) {
isc_throw(Unexpected, "failed to import isc.dns module");
}
// Make sure the base type won't suddenly disappear. Note that we
// are effectively leaking it; it's intentional.
Py_INCREF(base.get());
}
} catch (...) {
PyErr_SetString(PyExc_SystemError,
"Unexpected failure in ZoneUpdater initialization");
return (false);
}
return (true);
}
} // namespace python
} // namespace datasrc
} // namespace isc
......
......@@ -36,7 +36,7 @@ extern PyTypeObject zoneupdater_type;
PyObject* createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
PyObject* base_obj = NULL);
bool initModulePart_ZoneUpdater(PyObject* mod);
} // namespace python
} // namespace datasrc
} // namespace isc
......
......@@ -63,7 +63,7 @@ esac
case "$1" in
start|up)
for ns in 1 2 3 4 5 6 7
for ns in 1 2 3 4 5 6 7 8
do
if test -n "$base"
then
......@@ -145,7 +145,7 @@ case "$1" in
;;
stop|down)
for ns in 7 6 5 4 3 2 1
for ns in 8 7 6 5 4 3 2 1
do
if test -n "$base"
then
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment