2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 05:55:28 +00:00

Merge branch 'master' into trac848

This commit is contained in:
Shane Kerr
2011-05-16 18:39:29 +02:00
23 changed files with 1553 additions and 107 deletions

View File

@@ -842,6 +842,7 @@ AC_OUTPUT([doc/version.ent
src/lib/cc/session_config.h.pre
src/lib/cc/tests/session_unittests_config.h
src/lib/log/tests/run_time_init_test.sh
src/lib/util/python/mkpywrapper.py
tests/system/conf.sh
tests/system/glue/setup.sh
tests/system/glue/nsx1/b10-config.db
@@ -867,6 +868,7 @@ AC_OUTPUT([doc/version.ent
chmod +x src/lib/dns/gen-rdatacode.py
chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
chmod +x src/lib/log/tests/run_time_init_test.sh
chmod +x src/lib/util/python/mkpywrapper.py
chmod +x tests/system/conf.sh
])
AC_OUTPUT

View File

@@ -1165,7 +1165,7 @@ XML_DTD =
# and cross-referencing information) to the XML output. Note that
# enabling this will significantly increase the size of the XML output.
XML_PROGRAMLISTING = YES
XML_PROGRAMLISTING = NO
#---------------------------------------------------------------------------
# configuration options for the AutoGen Definitions output

View File

@@ -3,6 +3,13 @@ PYTESTS = tsig_keys_test.py
EXTRA_DIST = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs
endif
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
if ENABLE_PYTHON_COVERAGE
@@ -14,6 +21,7 @@ endif
echo Running test: $$pytest ; \
env B10_TEST_PLUGIN_DIR=$(abs_srcdir)/..:$(abs_builddir)/.. \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/dns/python/.libs \
$(LIBRARY_PATH_PLACEHOLDER) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done

View File

@@ -6,6 +6,9 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
pyexec_LTLIBRARIES = pydnspp.la
pydnspp_la_SOURCES = pydnspp.cc pydnspp_common.cc
pydnspp_la_SOURCES += rcode_python.cc rcode_python.h
pydnspp_la_SOURCES += tsigerror_python.cc tsigerror_python.h
pydnspp_la_SOURCES += tsigerror_python_inc.cc
pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)

View File

@@ -336,15 +336,15 @@ Message_getRcode(s_Message* self) {
rcode = static_cast<s_Rcode*>(rcode_type.tp_alloc(&rcode_type, 0));
if (rcode != NULL) {
rcode->rcode = NULL;
rcode->cppobj = NULL;
try {
rcode->rcode = new Rcode(self->message->getRcode());
rcode->cppobj = new Rcode(self->message->getRcode());
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
} catch (...) {
PyErr_SetString(po_IscException, "Unexpected exception");
}
if (rcode->rcode == NULL) {
if (rcode->cppobj == NULL) {
Py_DECREF(rcode);
return (NULL);
}
@@ -360,7 +360,7 @@ Message_setRcode(s_Message* self, PyObject* args) {
return (NULL);
}
try {
self->message->setRcode(*rcode->rcode);
self->message->setRcode(*rcode->cppobj);
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());

View File

@@ -32,20 +32,32 @@
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/exceptions.h>
#include <dns/name.h>
#include <dns/messagerenderer.h>
#include <dns/python/pydnspp_common.h>
#include "pydnspp_common.h"
namespace isc {
namespace dns {
namespace python {
// For our 'general' isc::Exceptions
static PyObject* po_IscException;
static PyObject* po_InvalidParameter;
PyObject* po_IscException;
PyObject* po_InvalidParameter;
// For our own isc::dns::Exception
static PyObject* po_DNSMessageBADVERS;
PyObject* po_DNSMessageBADVERS;
}
}
}
#include "rcode_python.h"
#include "tsigerror_python.h"
// order is important here!
using namespace isc::dns::python;
#include <dns/python/messagerenderer_python.cc>
#include <dns/python/name_python.cc> // needs Messagerenderer
#include <dns/python/rrclass_python.cc> // needs Messagerenderer
@@ -58,14 +70,14 @@ static PyObject* po_DNSMessageBADVERS;
#include <dns/python/tsigkey_python.cc> // needs Name
#include <dns/python/tsig_python.cc> // needs tsigkey
#include <dns/python/opcode_python.cc>
#include <dns/python/rcode_python.cc>
#include <dns/python/edns_python.cc> // needs Messagerenderer, Rcode
#include <dns/python/message_python.cc> // needs RRset, Question
//
// Definition of the module
//
static PyModuleDef pydnspp = {
namespace {
PyModuleDef pydnspp = {
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
"pydnspp",
"Python bindings for the classes in the isc::dns namespace.\n\n"
@@ -80,10 +92,11 @@ static PyModuleDef pydnspp = {
NULL,
NULL
};
}
PyMODINIT_FUNC
PyInit_pydnspp(void) {
PyObject *mod = PyModule_Create(&pydnspp);
PyObject* mod = PyModule_Create(&pydnspp);
if (mod == NULL) {
return (NULL);
}
@@ -154,6 +167,10 @@ PyInit_pydnspp(void) {
return (NULL);
}
if (!initModulePart_TSIGError(mod)) {
return (NULL);
}
if (!initModulePart_TSIGContext(mod)) {
return (NULL);
}

View File

@@ -15,6 +15,9 @@
#include <Python.h>
#include <pydnspp_common.h>
namespace isc {
namespace dns {
namespace python {
int
readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
PyObject* el = NULL;
@@ -44,8 +47,15 @@ readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
}
void addClassVariable(PyTypeObject& c, const char* name,
PyObject* obj)
{
PyDict_SetItemString(c.tp_dict, name, obj);
int
addClassVariable(PyTypeObject& c, const char* name, PyObject* obj) {
if (obj == NULL) {
PyErr_SetString(PyExc_ValueError,
"NULL object is specified for a class variable");
return (-1);
}
return (PyDict_SetItemString(c.tp_dict, name, obj));
}
}
}
}

View File

@@ -15,9 +15,22 @@
#ifndef __LIBDNS_PYTHON_COMMON_H
#define __LIBDNS_PYTHON_COMMON_H 1
//
// Shared functions for python/c API
//
#include <Python.h>
#include <stdexcept>
#include <string>
#include <util/python/pycppwrapper_util.h>
namespace isc {
namespace dns {
namespace python {
// For our 'general' isc::Exceptions
extern PyObject* po_IscException;
extern PyObject* po_InvalidParameter;
// For our own isc::dns::Exception
extern PyObject* po_DNSMessageBADVERS;
// This function reads 'bytes' from a sequence
// This sequence can be anything that implements the Sequence interface,
@@ -31,6 +44,12 @@
// case nothing is removed
int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
void addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
} // namespace python
} // namespace dns
} // namespace isc
#endif // __LIBDNS_PYTHON_COMMON_H
// Local Variables:
// mode: c++
// End:

View File

@@ -12,9 +12,17 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <Python.h>
#include <exceptions/exceptions.h>
#include <dns/rcode.h>
#include "pydnspp_common.h"
#include "rcode_python.h"
using namespace isc::dns;
using namespace isc::dns::python;
//
// Declaration of the custom exceptions (None for this class)
@@ -27,25 +35,14 @@ using namespace isc::dns;
// and static wrappers around the methods we export), a list of methods,
// and a type description
namespace {
//
// Rcode
//
// We added a helper variable static_code here
// Since we can create Rcodes dynamically with Rcode(int), but also
// use the static globals (Rcode::NOERROR() etc), we use this
// variable to see if the code came from one of the latter, in which
// case Rcode_destroy should not free it (the other option is to
// allocate new Rcodes for every use of the static ones, but this
// seems more efficient).
class s_Rcode : public PyObject {
public:
s_Rcode() : rcode(NULL), static_code(false) {}
const Rcode* rcode;
bool static_code;
};
// Trivial constructor.
s_Rcode::s_Rcode() : cppobj(NULL), static_code(false) {}
namespace {
int Rcode_init(s_Rcode* const self, PyObject* args);
void Rcode_destroy(s_Rcode* const self);
@@ -118,57 +115,6 @@ PyMethodDef Rcode_methods[] = {
{ NULL, NULL, 0, NULL }
};
PyTypeObject rcode_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"pydnspp.Rcode",
sizeof(s_Rcode), // tp_basicsize
0, // tp_itemsize
(destructor)Rcode_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
Rcode_str, // tp_str
NULL, // tp_getattro
NULL, // tp_setattro
NULL, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
"The Rcode class objects represent standard RCODEs"
"of the header section of DNS messages.",
NULL, // tp_traverse
NULL, // tp_clear
(richcmpfunc)Rcode_richcmp, // tp_richcompare
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
Rcode_methods, // tp_methods
NULL, // tp_members
NULL, // tp_getset
NULL, // tp_base
NULL, // tp_dict
NULL, // tp_descr_get
NULL, // tp_descr_set
0, // tp_dictoffset
(initproc)Rcode_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
};
int
Rcode_init(s_Rcode* const self, PyObject* args) {
long code = 0;
@@ -193,9 +139,9 @@ Rcode_init(s_Rcode* const self, PyObject* args) {
}
try {
if (ext_code == -1) {
self->rcode = new Rcode(code);
self->cppobj = new Rcode(code);
} else {
self->rcode = new Rcode(code, ext_code);
self->cppobj = new Rcode(code, ext_code);
}
self->static_code = false;
} catch (const isc::OutOfRange& ex) {
@@ -211,27 +157,27 @@ Rcode_init(s_Rcode* const self, PyObject* args) {
void
Rcode_destroy(s_Rcode* const self) {
// Depending on whether we created the rcode or are referring
// to a global one, we do or do not delete self->rcode here
// to a global one, we do or do not delete self->cppobj here
if (!self->static_code) {
delete self->rcode;
delete self->cppobj;
}
self->rcode = NULL;
self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
PyObject*
Rcode_getCode(const s_Rcode* const self) {
return (Py_BuildValue("I", self->rcode->getCode()));
return (Py_BuildValue("I", self->cppobj->getCode()));
}
PyObject*
Rcode_getExtendedCode(const s_Rcode* const self) {
return (Py_BuildValue("I", self->rcode->getExtendedCode()));
return (Py_BuildValue("I", self->cppobj->getExtendedCode()));
}
PyObject*
Rcode_toText(const s_Rcode* const self) {
return (Py_BuildValue("s", self->rcode->toText().c_str()));
return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
PyObject*
@@ -245,7 +191,7 @@ PyObject*
Rcode_createStatic(const Rcode& rcode) {
s_Rcode* ret = PyObject_New(s_Rcode, &rcode_type);
if (ret != NULL) {
ret->rcode = &rcode;
ret->cppobj = &rcode;
ret->static_code = true;
}
return (ret);
@@ -357,10 +303,10 @@ Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
return (NULL);
case Py_EQ:
c = (*self->rcode == *other->rcode);
c = (*self->cppobj == *other->cppobj);
break;
case Py_NE:
c = (*self->rcode != *other->rcode);
c = (*self->cppobj != *other->cppobj);
break;
case Py_GT:
PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
@@ -374,6 +320,61 @@ Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
else
Py_RETURN_FALSE;
}
} // end of unnamed namespace
namespace isc {
namespace dns {
namespace python {
PyTypeObject rcode_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"pydnspp.Rcode",
sizeof(s_Rcode), // tp_basicsize
0, // tp_itemsize
(destructor)Rcode_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
Rcode_str, // tp_str
NULL, // tp_getattro
NULL, // tp_setattro
NULL, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
"The Rcode class objects represent standard RCODEs"
"of the header section of DNS messages.",
NULL, // tp_traverse
NULL, // tp_clear
reinterpret_cast<richcmpfunc>(Rcode_richcmp), // tp_richcompare
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
Rcode_methods, // tp_methods
NULL, // tp_members
NULL, // tp_getset
NULL, // tp_base
NULL, // tp_dict
NULL, // tp_descr_get
NULL, // tp_descr_set
0, // tp_dictoffset
(initproc)Rcode_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
};
// Module Initialization, all statics are initialized here
bool
@@ -428,4 +429,6 @@ initModulePart_Rcode(PyObject* mod) {
return (true);
}
} // end of unnamed namespace
} // namespace python
} // namespace dns
} // namespace isc

View File

@@ -0,0 +1,57 @@
// Copyright (C) 2011 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.
#ifndef __PYTHON_RCODE_H
#define __PYTHON_RCODE_H 1
#include <Python.h>
namespace isc {
namespace dns {
class Rcode;
namespace python {
// The s_* Class simply covers one instantiation of the object.
//
// We added a helper variable static_code here
// Since we can create Rcodes dynamically with Rcode(int), but also
// use the static globals (Rcode::NOERROR() etc), we use this
// variable to see if the code came from one of the latter, in which
// case Rcode_destroy should not free it (the other option is to
// allocate new Rcodes for every use of the static ones, but this
// seems more efficient).
//
// Follow-up note: we don't have to use the proxy function in the python lib;
// we can just define class specific constants directly (see TSIGError).
// We should make this cleanup later.
class s_Rcode : public PyObject {
public:
s_Rcode();
const Rcode* cppobj;
bool static_code;
};
extern PyTypeObject rcode_type;
bool initModulePart_Rcode(PyObject* mod);
} // namespace python
} // namespace dns
} // namespace isc
#endif // __PYTHON_RCODE_H
// Local Variables:
// mode: c++
// End:

View File

@@ -12,6 +12,7 @@ PYTESTS += rrset_python_test.py
PYTESTS += rrttl_python_test.py
PYTESTS += rrtype_python_test.py
PYTESTS += tsig_python_test.py
PYTESTS += tsigerror_python_test.py
PYTESTS += tsigkey_python_test.py
EXTRA_DIST = $(PYTESTS)

View File

@@ -0,0 +1,97 @@
# 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.
import unittest
import sys
from pydnspp import *
class TSIGErrorTest(unittest.TestCase):
def test_from_code(self):
self.assertEqual(0, TSIGError(0).get_code())
self.assertEqual(18, TSIGError(18).get_code())
self.assertEqual(65535, TSIGError(65535).get_code())
self.assertRaises(ValueError, TSIGError, 65536)
self.assertRaises(ValueError, TSIGError, -1)
self.assertRaises(TypeError, TSIGError, "not yet supported")
def test_from_rcode(self):
# We use RCODE for code values from 0-15.
self.assertEqual(0, TSIGError(Rcode.NOERROR()).get_code())
self.assertEqual(15, TSIGError(Rcode(15)).get_code())
# From error code 16 TSIG errors define a separate space, so passing
# corresponding RCODE for such code values should be prohibited.
self.assertRaises(ValueError, TSIGError, Rcode(16))
def test_constants(self):
# We'll only test arbitrarily chosen subsets of the codes.
# This class is quite simple, so it should be suffice.
self.assertEqual(TSIGError.BAD_SIG_CODE, TSIGError(16).get_code())
self.assertEqual(TSIGError.BAD_KEY_CODE, TSIGError(17).get_code())
self.assertEqual(TSIGError.BAD_TIME_CODE, TSIGError(18).get_code())
self.assertEqual(0, TSIGError.NOERROR.get_code())
self.assertEqual(9, TSIGError.NOTAUTH.get_code())
self.assertEqual(14, TSIGError.RESERVED14.get_code())
self.assertEqual(TSIGError.BAD_SIG_CODE, TSIGError.BAD_SIG.get_code())
self.assertEqual(TSIGError.BAD_KEY_CODE, TSIGError.BAD_KEY.get_code())
self.assertEqual(TSIGError.BAD_TIME_CODE, TSIGError.BAD_TIME.get_code())
def test_equal(self):
self.assertTrue(TSIGError.NOERROR == TSIGError(Rcode.NOERROR()))
self.assertTrue(TSIGError(Rcode.NOERROR()) == TSIGError.NOERROR)
self.assertTrue(TSIGError.BAD_SIG == TSIGError(16))
self.assertTrue(TSIGError(16) == TSIGError.BAD_SIG)
def test_nequal(self):
self.assertTrue(TSIGError.BAD_KEY != TSIGError(Rcode.NOERROR()))
self.assertTrue(TSIGError(Rcode.NOERROR()) != TSIGError.BAD_KEY)
def test_to_text(self):
# TSIGError derived from the standard Rcode
self.assertEqual("NOERROR", TSIGError(Rcode.NOERROR()).to_text())
# Well known TSIG errors
self.assertEqual("BADSIG", TSIGError.BAD_SIG.to_text())
self.assertEqual("BADKEY", TSIGError.BAD_KEY.to_text())
self.assertEqual("BADTIME", TSIGError.BAD_TIME.to_text())
# Unknown (or not yet supported) codes. Simply converted as numeric.
self.assertEqual("19", TSIGError(19).to_text());
self.assertEqual("65535", TSIGError(65535).to_text());
# also check str() works same way
self.assertEqual("NOERROR", str(TSIGError(Rcode.NOERROR())))
self.assertEqual("BADSIG", str(TSIGError.BAD_SIG))
def test_to_rcode(self):
# TSIGError derived from the standard Rcode
self.assertEqual(Rcode.NOERROR(), TSIGError(Rcode.NOERROR()).to_rcode())
# Well known TSIG errors
self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_SIG.to_rcode())
self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_KEY.to_rcode())
self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_TIME.to_rcode())
# Unknown (or not yet supported) codes are treated as SERVFAIL.
self.assertEqual(Rcode.SERVFAIL(), TSIGError(19).to_rcode())
self.assertEqual(Rcode.SERVFAIL(), TSIGError(65535).to_rcode())
# Check there's no redundant refcount (which would cause leak)
self.assertEqual(1, sys.getrefcount(TSIGError.BAD_SIG.to_rcode()))
if __name__ == '__main__':
unittest.main()

View File

@@ -26,7 +26,6 @@ using namespace isc::dns;
namespace {
// The s_* Class simply covers one instantiation of the object
class s_TSIGContext : public PyObject {
public:
TSIGContext* tsig_ctx;

View File

@@ -0,0 +1,362 @@
// Copyright (C) 2011 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.
#include <Python.h>
#include <string>
#include <stdexcept>
#include <util/python/pycppwrapper_util.h>
#include <dns/tsigerror.h>
#include "pydnspp_common.h"
#include "rcode_python.h"
#include "tsigerror_python.h"
using namespace std;
using namespace isc::util::python;
using namespace isc::dns;
using namespace isc::dns::python;
//
// Definition of the classes
//
// For each class, we need a struct, a helper functions (init, destroy,
// and static wrappers around the methods we export), a list of methods,
// and a type description
//
// TSIGError
//
// Trivial constructor.
s_TSIGError::s_TSIGError() : cppobj(NULL) {
}
// Import pydoc text
#include "tsigerror_python_inc.cc"
namespace {
// Shortcut type which would be convenient for adding class variables safely.
typedef CPPPyObjectContainer<s_TSIGError, TSIGError> TSIGErrorContainer;
//
// We declare the functions here, the definitions are below
// the type definition of the object, since both can use the other
//
// General creation and destruction
int TSIGError_init(s_TSIGError* self, PyObject* args);
void TSIGError_destroy(s_TSIGError* self);
// These are the functions we export
PyObject* TSIGError_getCode(const s_TSIGError* const self);
PyObject* TSIGError_toText(const s_TSIGError* const self);
PyObject* TSIGError_toRcode(const s_TSIGError* const self);
PyObject* TSIGError_str(PyObject* self);
PyObject* TSIGError_richcmp(const s_TSIGError* const self,
const s_TSIGError* const other, int op);
// These are the functions we export
// For a minimal support, we don't need them.
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
// 2. Our static function here
// 3. Argument type
// 4. Documentation
PyMethodDef TSIGError_methods[] = {
{ "get_code", reinterpret_cast<PyCFunction>(TSIGError_getCode),
METH_NOARGS,
TSIGError_getCode_doc },
{ "to_text", reinterpret_cast<PyCFunction>(TSIGError_toText), METH_NOARGS,
TSIGError_toText_doc },
{ "to_rcode", reinterpret_cast<PyCFunction>(TSIGError_toRcode),
METH_NOARGS,
TSIGError_toRcode_doc },
{ NULL, NULL, 0, NULL }
};
int
TSIGError_init(s_TSIGError* self, PyObject* args) {
try {
// Constructor from the code value
long code = 0;
if (PyArg_ParseTuple(args, "l", &code)) {
if (code < 0 || code > 0xffff) {
PyErr_SetString(PyExc_ValueError, "TSIG error out of range");
return (-1);
}
self->cppobj = new TSIGError(code);
return (0);
}
// Constructor from Rcode
s_Rcode* py_rcode;
if (PyArg_ParseTuple(args, "O!", &rcode_type, &py_rcode)) {
self->cppobj = new TSIGError(*py_rcode->cppobj);
return (0);
}
} catch (const isc::OutOfRange& ex) {
const string ex_what = "Failed to construct TSIGError object: " +
string(ex.what());
PyErr_SetString(PyExc_ValueError, ex_what.c_str());
return (-1);
} catch (const exception& ex) {
const string ex_what = "Failed to construct TSIGError object: " +
string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
return (-1);
} catch (...) {
PyErr_SetString(po_IscException,
"Unexpected exception in constructing TSIGError");
return (-1);
}
PyErr_SetString(PyExc_TypeError,
"Invalid arguments to TSIGError constructor");
return (-1);
}
void
TSIGError_destroy(s_TSIGError* const self) {
delete self->cppobj;
self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
PyObject*
TSIGError_getCode(const s_TSIGError* const self) {
return (Py_BuildValue("I", self->cppobj->getCode()));
}
PyObject*
TSIGError_toText(const s_TSIGError* const self) {
try {
// toText() could throw, so we need to catch any exceptions below.
return (Py_BuildValue("s", self->cppobj->toText().c_str()));
} catch (const exception& ex) {
const string ex_what =
"Failed to convert TSIGError object to text: " +
string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
"converting TSIGError object to text");
}
return (NULL);
}
PyObject*
TSIGError_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
const_cast<char*>("")));
}
PyObject*
TSIGError_toRcode(const s_TSIGError* const self) {
typedef CPPPyObjectContainer<s_Rcode, Rcode> RcodePyObjectContainer;
try {
RcodePyObjectContainer rcode_container(PyObject_New(s_Rcode,
&rcode_type));
rcode_container.set(new Rcode(self->cppobj->toRcode()));
return (rcode_container.release());
} catch (const exception& ex) {
const string ex_what =
"Failed to convert TSIGError to Rcode: " + string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
"converting TSIGError to Rcode");
}
return (NULL);
}
PyObject*
TSIGError_richcmp(const s_TSIGError* const self,
const s_TSIGError* const other,
const int op)
{
bool c = false;
// Check for null and if the types match. If different type,
// simply return False
if (other == NULL || (self->ob_type != other->ob_type)) {
Py_RETURN_FALSE;
}
// Only equals and not equals here, unorderable type
switch (op) {
case Py_LT:
PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
return (NULL);
case Py_LE:
PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
return (NULL);
case Py_EQ:
c = (*self->cppobj == *other->cppobj);
break;
case Py_NE:
c = (*self->cppobj != *other->cppobj);
break;
case Py_GT:
PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
return (NULL);
case Py_GE:
PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
return (NULL);
}
if (c) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
} // end of unnamed namespace
namespace isc {
namespace dns {
namespace python {
// This defines the complete type for reflection in python and
// parsing of PyObject* to s_TSIGError
// Most of the functions are not actually implemented and NULL here.
PyTypeObject tsigerror_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"libdns_python.TSIGError",
sizeof(s_TSIGError), // tp_basicsize
0, // tp_itemsize
reinterpret_cast<destructor>(TSIGError_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
// THIS MAY HAVE TO BE CHANGED TO NULL:
TSIGError_str, // tp_str
NULL, // tp_getattro
NULL, // tp_setattro
NULL, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
TSIGError_doc,
NULL, // tp_traverse
NULL, // tp_clear
// THIS MAY HAVE TO BE CHANGED TO NULL:
reinterpret_cast<richcmpfunc>(TSIGError_richcmp), // tp_richcompare
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
TSIGError_methods, // tp_methods
NULL, // tp_members
NULL, // tp_getset
NULL, // tp_base
NULL, // tp_dict
NULL, // tp_descr_get
NULL, // tp_descr_set
0, // tp_dictoffset
reinterpret_cast<initproc>(TSIGError_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
};
namespace {
// Trivial shortcut to create and install TSIGError constants.
inline void
installTSIGErrorConstant(const char* name, const TSIGError& val) {
TSIGErrorContainer container(PyObject_New(s_TSIGError, &tsigerror_type));
container.installAsClassVariable(tsigerror_type, name, new TSIGError(val));
}
}
// Module Initialization, all statics are initialized here
bool
initModulePart_TSIGError(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(&tsigerror_type) < 0) {
return (false);
}
void* p = &tsigerror_type;
if (PyModule_AddObject(mod, "TSIGError", static_cast<PyObject*>(p)) < 0) {
return (false);
}
Py_INCREF(&tsigerror_type);
try {
// Constant class variables
// Error codes (bare values)
installClassVariable(tsigerror_type, "BAD_SIG_CODE",
Py_BuildValue("H", TSIGError::BAD_SIG_CODE));
installClassVariable(tsigerror_type, "BAD_KEY_CODE",
Py_BuildValue("H", TSIGError::BAD_KEY_CODE));
installClassVariable(tsigerror_type, "BAD_TIME_CODE",
Py_BuildValue("H", TSIGError::BAD_TIME_CODE));
// Error codes (constant objects)
installTSIGErrorConstant("NOERROR", TSIGError::NOERROR());
installTSIGErrorConstant("FORMERR", TSIGError::FORMERR());
installTSIGErrorConstant("SERVFAIL", TSIGError::SERVFAIL());
installTSIGErrorConstant("NXDOMAIN", TSIGError::NXDOMAIN());
installTSIGErrorConstant("NOTIMP", TSIGError::NOTIMP());
installTSIGErrorConstant("REFUSED", TSIGError::REFUSED());
installTSIGErrorConstant("YXDOMAIN", TSIGError::YXDOMAIN());
installTSIGErrorConstant("YXRRSET", TSIGError::YXRRSET());
installTSIGErrorConstant("NXRRSET", TSIGError::NXRRSET());
installTSIGErrorConstant("NOTAUTH", TSIGError::NOTAUTH());
installTSIGErrorConstant("NOTZONE", TSIGError::NOTZONE());
installTSIGErrorConstant("RESERVED11", TSIGError::RESERVED11());
installTSIGErrorConstant("RESERVED12", TSIGError::RESERVED12());
installTSIGErrorConstant("RESERVED13", TSIGError::RESERVED13());
installTSIGErrorConstant("RESERVED14", TSIGError::RESERVED14());
installTSIGErrorConstant("RESERVED15", TSIGError::RESERVED15());
installTSIGErrorConstant("BAD_SIG", TSIGError::BAD_SIG());
installTSIGErrorConstant("BAD_KEY", TSIGError::BAD_KEY());
installTSIGErrorConstant("BAD_TIME", TSIGError::BAD_TIME());
} catch (const exception& ex) {
const string ex_what =
"Unexpected failure in TSIGError initialization: " +
string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
return (false);
} catch (...) {
PyErr_SetString(PyExc_SystemError,
"Unexpected failure in TSIGError initialization");
return (false);
}
return (true);
}
} // namespace python
} // namespace dns
} // namespace isc

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2011 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.
#ifndef __PYTHON_TSIGERROR_H
#define __PYTHON_TSIGERROR_H 1
#include <Python.h>
namespace isc {
namespace dns {
class TSIGError;
namespace python {
// The s_* Class simply covers one instantiation of the object
class s_TSIGError : public PyObject {
public:
s_TSIGError();
const TSIGError* cppobj;
};
extern PyTypeObject tsigerror_type;
bool initModulePart_TSIGError(PyObject* mod);
} // namespace python
} // namespace dns
} // namespace isc
#endif // __PYTHON_TSIGERROR_H
// Local Variables:
// mode: c++
// End:

View File

@@ -0,0 +1,83 @@
namespace {
const char* const TSIGError_doc = "\n\
TSIG errors.\n\
\n\
\n\
The TSIGError class objects represent standard errors related to TSIG\n\
protocol operations as defined in related specifications, mainly in\n\
RFC2845.\n\
\n\
TSIGError(error_code)\n\
\n\
Constructor from the code value.\n\
\n\
Exceptions:\n\
None: \n\
\n\
Parameters:\n\
error_code: The underlying 16-bit error code value of the TSIGError.\n\
\n\
TSIGError(rcode)\n\
\n\
Constructor from Rcode.\n\
\n\
As defined in RFC2845, error code values from 0 to 15 (inclusive) are\n\
derived from the DNS RCODEs, which are represented via the Rcode class\n\
in this library. This constructor works as a converter from these\n\
RCODEs to corresponding TSIGError objects.\n\
\n\
Exceptions:\n\
ValueError: Given rcode is not convertible to TSIGErrors.\n\
\n\
Parameters:\n\
rcode: the Rcode from which the TSIGError should be derived.\n\
\n\
";
const char* const TSIGError_getCode_doc = "get_code() -> integer\n\
\n\
Returns the TSIGCode error code value.\n\
\n\
Exceptions:\n\
None: \n\
\n\
Return Value(s):\n\
The underlying code value corresponding to the TSIGError.\n\
";
const char* const TSIGError_toText_doc = "to_text() -> string\n\
\n\
Convert the TSIGError to a string.\n\
\n\
For codes derived from RCODEs up to 15, this method returns the same\n\
string as Rcode.to_text() for the corresponding code. For other pre-\n\
defined code values (see TSIGError.CodeValue), this method returns a\n\
string representation of the \"mnemonic' used for the enum and\n\
constant objects as defined in RFC2845. For example, the string for\n\
code value 16 is \"BADSIG\", etc. For other code values it returns a\n\
string representation of the decimal number of the value, e.g. \"32\",\n\
\"100\", etc.\n\
\n\
Exceptions:\n\
None\n\
\n\
Return Value(s):\n\
A string representation of the TSIGError.\n\
";
const char* const TSIGError_toRcode_doc = "to_rcode() -> Rcode\n\
\n\
Convert the TSIGError to a Rcode.\n\
\n\
This method returns an Rcode object that is corresponding to the TSIG\n\
error. The returned Rcode is expected to be used by a verifying server\n\
to specify the RCODE of a response when TSIG verification fails.\n\
\n\
Specifically, this method returns Rcode.NOTAUTH() for the TSIG\n\
specific errors, BADSIG, BADKEY, BADTIME, as described in RFC2845. For\n\
errors derived from the standard Rcode (code 0-15), it returns the\n\
corresponding Rcode. For others, this method returns Rcode.SERVFAIL()\n\
as a last resort.\n\
\n\
Exceptions:\n\
None: \n\
\n\
";
}

View File

@@ -22,17 +22,11 @@
namespace isc {
namespace dns {
class RRClass;
/// TSIG errors
///
/// The \c TSIGError class objects represent standard errors related to
/// TSIG protocol operations as defined in related specifications, mainly
/// in RFC2845.
///
/// (RCODEs) of the header section of DNS messages, and extended response
/// codes as defined in the EDNS specification.
class TSIGError {
public:
/// Constants for pre-defined TSIG error values.
@@ -58,7 +52,7 @@ public:
///
/// \exception None
///
/// \param code The underlying 16-bit error code value of the \c TSIGError.
/// \param error_code The underlying 16-bit error code value of the \c TSIGError.
explicit TSIGError(uint16_t error_code) : code_(error_code) {}
/// Constructor from \c Rcode.

View File

@@ -27,6 +27,7 @@ def reload():
"@PACKAGE_NAME@",
"msgq_socket").replace("${prefix}",
"@prefix@")
PREFIX = "@prefix@"
# If B10_FROM_SOURCE is set in the environment, we use data files
# from a directory relative to the value of that variable, or, if defined,
@@ -43,7 +44,6 @@ def reload():
DATA_PATH = os.environ["B10_FROM_SOURCE"]
PLUGIN_PATHS = [DATA_PATH + '/src/bin/cfgmgr/plugins']
else:
PREFIX = "@prefix@"
DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
# For testing the plugins so they can find their own spec files

View File

@@ -24,5 +24,7 @@ libutil_la_SOURCES += encode/binary_from_base16.h
libutil_la_SOURCES += random/qid_gen.h random/qid_gen.cc
libutil_la_SOURCES += random/random_number_generator.h
EXTRA_DIST = python/pycppwrapper_util.h
libutil_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
CLEANFILES = *.gcno *.gcda

View File

@@ -0,0 +1,100 @@
#!@PYTHON@
# 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 utility program generates a C++ header and implementation files
that can be used as a template of C++ python binding for a C++ class.
Usage: ./mkpywrapper.py ClassName
(the script should be run on this directory)
It will generate two files: classname_python.h and classname_python.cc,
many of whose definitions are in the namespace isc::MODULE_NAME::python.
By default MODULE_NAME will be 'dns' (because this tool is originally
intended to be used for the C++ python binding of the DNS library), but
can be changed via the -m command line option.
The generated files contain code fragments that are commonly used in
C++ python binding implementations. It will define a class named
s_ClassName which is a derived class of PyModule and can meet the
requirement of the CPPPyObjectContainer template class (see
pycppwrapper_util.h). It also defines (and declares in the header file)
"classname_type", which is of PyTypeObject and is intended to be used
to define details of the python bindings for the ClassName class.
In many cases the header file can be used as a startpoint of the
binding development without modification. But you may want to make
ClassName::cppobj a constant variable (and you should if you can).
Many definitions of classname_python.cc should also be able to be used
just as defined, but some will need to be changed or removed. In
particular, you should at least adjust ClassName_init(). You'll
probably also need to add more definitions to that file to provide
complete features of the C++ class.
"""
import datetime, string, sys
from optparse import OptionParser
# Remember the current year to produce the copyright boilerplate
YEAR = datetime.date.today().timetuple()[0]
def dump_file(out_file, temp_file, class_name, module):
for line in temp_file.readlines():
line = line.replace("@YEAR@", str(YEAR))
line = line.replace("@CPPCLASS@_H", class_name.upper() + "_H")
line = line.replace("@CPPCLASS@", class_name)
line = line.replace("@cppclass@", class_name.lower())
line = line.replace("@MODULE@", module)
out_file.write(line)
def dump_wrappers(class_name, output, module):
try:
if output == "-":
header_file = sys.stdout
else:
header_file = open(output + "_python.h", "w")
header_template_file = open("wrapper_template.h", "r")
if output == "-":
impl_file = sys.stdout
else:
impl_file = open(output + "_python.cc", "w")
impl_template_file = open("wrapper_template.cc", "r")
except:
sys.stderr.write('Failed to open C++ file(s)\n')
sys.exit(1)
dump_file(header_file, header_template_file, class_name, module)
dump_file(impl_file, impl_template_file, class_name, module)
usage = '''usage: %prog [options] class_name'''
if __name__ == "__main__":
parser = OptionParser(usage=usage)
parser.add_option('-o', '--output', action='store', dest='output',
default=None, metavar='FILE',
help='prefix of output file names [default: derived from the class name]')
parser.add_option('-m', '--module', action='store', dest='module',
default='dns',
help='C++ module name of the wrapper (for namespaces) [default: dns]')
(options, args) = parser.parse_args()
if len(args) == 0:
parser.error('input file is missing')
class_name = args[0]
if not options.output:
options.output = class_name.lower()
dump_wrappers(class_name, options.output, options.module)

View File

@@ -0,0 +1,308 @@
// Copyright (C) 2011 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.
#ifndef __PYCPPWRAPPER_UTIL_H
#define __PYCPPWRAPPER_UTIL_H 1
#include <Python.h>
#include <exceptions/exceptions.h>
/**
* @file pycppwrapper_util.h
* @short Shared definitions for python/C(++) API
*
* This utility defines a set of convenient wrappers for the python C API
* to use it safely from our C++ bindings. The python C API has many pitfalls
* such as not-so-consistent reference count policies. Also, many existing
* examples are careless about error handling. It's easy to find on the net
* example (even of "production use") python extensions like this:
*
* \code
* new_exception = PyErr_NewException("mymodule.Exception", NULL, NULL);
* // new_exception can be NULL, in which case the call to
* // PyModule_AddObject will cause a surprising disruption.
* PyModule_AddObject(mymodule, "Exception", new_exception); \endcode
*
* When using the python C API with C++, we should also be careful about
* exception safety. The underlying C++ code (including standard C++ libraries
* and memory allocation) can throw exceptions, in which case we need to
* make sure any intermediate python objects are cleaned up (we also need to
* catch the C++ exceptions inside the binding and convert them to python
* errors, but that's a different subject). This is not a trivial task
* because the python objects are represented as bare C pointers (so there's
* no destructor) and we need to address the exception safety along with python
* reference counters (so we cannot naively apply standard smart pointers).
*
* This utility tries to help address these issues.
*
* Also, it's intentional that this is a header-only utility. This way the
* C++ loadable module won't depend on another C++ library (which is not
* necessarily wrong, but would increase management cost such as link-time
* troubles only for a small utility feature).
*/
namespace isc {
namespace util {
namespace python {
/// This is thrown inside this utility when it finds a NULL pointer is passed
/// when it should not be NULL.
class PyCPPWrapperException : public isc::Exception {
public:
PyCPPWrapperException(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
/// This helper class is similar to the standard autoptr and manages PyObject
/// using some kind of RAII techniques. It is, however, customized for the
/// python C API.
///
/// A PyObjectContainer object is constructed with a pointer to PyObject,
/// which is often just created dynamically. The caller will eventually
/// attach the object to a different python object (often a module or class)
/// via specific methods or directly return it to the python interpreter.
///
/// There are two cases in destructing the object: with or without decreasing
/// a reference to the PyObject. If the object is intended to be an argument
/// to another python C library that increases the reference to the object for
/// itself, we should normally release our own reference; otherwise the
/// reference will leak and the object won't be garbage collected. Also, when
/// an unexpected error happens in the form of C++ exception, we should
/// release the reference to prevent resource leak.
///
/// In some other cases, we should simply give our reference to the caller.
/// That is the case when the created object itself is a return value of
/// an extended python method written in the C++ binding. Likewise, some
/// python C library functions "steal" the reference. In these cases we
/// should not decrease the reference; otherwise it would cause duplicate free.
///
/// By default, the destructor of this class releases the reference to the
/// PyObject. If this behavior is desirable, you can extract the original
/// bare pointer to the PyObject by the \c get() method. If you don't want
/// the reference to be decreased, the original bare pointer should be
/// extracted using the \c release() method.
///
/// There are two convenience methods for commonly used operations:
/// \c installAsClassVariable() to add the PyObject as a class variable
/// and \c installToModule to add the PyObject to a specified python module.
/// These methods (at least to some extent) take care of the reference to
/// the object (either release or keep) depending on the usage context so
/// that the user don't have to worry about it.
///
/// On construction, this class expects the pointer can be NULL.
/// If it happens it immediately throws a \c PyCPPWrapperException exception.
/// This behavior is to convert failures in the python C API (such as
/// PyObject_New() returning NULL) to C++ exception so that we can unify
/// error handling in the style of C++ exceptions.
///
/// Examples 1: To create a tuple of two python objects, do this:
///
/// \code
/// try {
/// PyObjectContainer container0(Py_BuildValue("I", 0));
/// PyObjectContainer container1(Py_BuildValue("s", cppobj.toText().c_str()));
/// return (Py_BuildValue("OO", container0.get(), container1.get()));
/// } catch { ... set python exception, etc ... } \endcode
///
/// Commonly deployed buggy implementation to achieve this would be like this:
/// \code
/// return (Py_BuildValue("OO", Py_BuildValue("I", 0),
/// Py_BuildValue("s", cppobj.toText().c_str())));
/// \endcode
/// One clear bug of this code is that references to the element objects of
/// the tuple will leak.
/// (Assuming \c cppobj.toText() can throw) this code is also not exception
/// safe; if \c cppobj.toText() throws the reference to the first object
/// will leak, even if the code tried to do the necessary cleanup in the
/// successful case.
/// Further, this code naively passes the result of the first two calls to
/// \c Py_BuildValue() to the third one even if they can be NULL.
/// In this specific case, it happens to be okay because \c Py_BuildValue()
/// accepts NULL and treats it as an indication of error. But not all
/// python C library works that way (remember, the API is so inconsistent)
/// and we need to refer to the API manual every time we have to worry about
/// passing a NULL object to a library function. We'd certainly like to
/// avoid such development overhead. The code using \c PyObjectContainer
/// addresses all these problems.
///
/// Examples 2: Install a (constant) variable to a class.
///
/// \code
/// try {
/// // installClassVariable is a wrapper of
/// // PyObjectContainer::installAsClassVariable. See below.
/// installClassVariable(myclass_type, "SOME_CONSTANT",
/// Py_BuildValue("I", 0));
/// } catch { ... }
/// \endcode
///
/// Examples 3: Install a custom exception to a module.
///
/// \code
/// PyObject* new_exception; // publicly visible
/// ...
/// try {
/// new_exception = PyErr_NewException("mymodule.NewException",
/// NULL, NULL);
/// PyObjectContainer(new_exception).installToModule(mymodule,
/// "NewException");
/// } catch { ... }
/// \endcode
///
/// Note that \c installToModule() keeps the reference to \c new_exception
/// by default. This is a common practice when we introduce a custom
/// exception in a python biding written in C/C++. See the code comment
/// of the method for more details.
struct PyObjectContainer {
PyObjectContainer(PyObject* obj) : obj_(obj) {
if (obj_ == NULL) {
isc_throw(PyCPPWrapperException, "Unexpected NULL PyObject, "
"probably due to short memory");
}
}
virtual ~PyObjectContainer() {
if (obj_ != NULL) {
Py_DECREF(obj_);
}
}
PyObject* get() {
return (obj_);
}
PyObject* release() {
PyObject* ret = obj_;
obj_ = NULL;
return (ret);
}
// Install the enclosed PyObject to the specified python class 'pyclass'
// as a variable named 'name'.
void installAsClassVariable(PyTypeObject& pyclass, const char* name) {
if (PyDict_SetItemString(pyclass.tp_dict, name, obj_) < 0) {
isc_throw(PyCPPWrapperException, "Failed to set a class variable, "
"probably due to short memory");
}
// Ownership successfully transferred to the class object. We'll let
// it be released in the destructor.
}
// Install the enclosed PyObject to the specified module 'mod' as an
// object named 'name'.
// By default, this method explicitly keeps the reference to the object
// even after the module "steals" it. To cancel this behavior and give
// the reference to the module completely, the third parameter 'keep_ref'
// should be set to false.
void installToModule(PyObject* mod, const char* name,
bool keep_ref = true)
{
if (PyModule_AddObject(mod, name, obj_) < 0) {
isc_throw(PyCPPWrapperException, "Failed to add an object to "
"module, probably due to short memory");
}
// PyModule_AddObject has "stolen" the reference, so unless we
// have to retain it ourselves we don't (shouldn't) decrease it.
// However, we actually often need to keep our own reference because
// objects added to a module are often referenced via non local
// C/C++ variables in various places of the C/C++ code. In order
// for the code to run safely even if some buggy/evil python program
// performs 'del mod.obj', we need the extra reference. See, e.g.:
// http://docs.python.org/py3k/c-api/init.html#Py_Initialize
// http://mail.python.org/pipermail/python-dev/2005-June/054238.html
if (keep_ref) {
Py_INCREF(obj_);
}
obj_ = NULL;
}
protected:
PyObject* obj_;
};
/// This templated class is a derived class of \c PyObjectContainer and
/// manages C++-class based python objects.
///
/// The template parameter \c PYSTRUCT must be a derived class (structure) of
/// \c PyObject that has a member variable named \c cppobj, which must be a
/// a pointer to \c CPPCLASS (the second template parameter).
///
/// For example, to define a custom python class based on a C++ class, MyClass,
/// we'd define a class (struct) named \c s_MyClass like this:
/// \code
/// class s_MyClass : public PyObject {
/// public:
/// s_MyClass() : cppobj(NULL) {}
/// MyClass* cppobj;
/// };
/// \endcode
///
/// And, to build and return a python version of MyClass object, write the
/// following C++ code:
/// \code
/// typedef CPPPyObjectContainer<s_MyClass, MyClass> MyContainer;
/// try {
/// // below, myclass_type is of \c PyTypeObject that defines
/// // a python class (type) for MyClass
/// MyContainer container(PyObject_New(s_MyClass, myclass_type));
/// container.set(new MyClass());
/// return (container.release()); // give the reference to the caller
/// } catch { ... }
/// \endcode
///
/// This code prevents bugs like NULL pointer dereference when \c PyObject_New
/// fails or resource leaks when new'ing \c MyClass results in an exception.
/// Note that we use \c release() (derived from the base class) instead of
/// \c get(); in this case we should simply pass the reference generated in
/// \c PyObject_New() to the caller.
template <typename PYSTRUCT, typename CPPCLASS>
struct CPPPyObjectContainer : public PyObjectContainer {
CPPPyObjectContainer(PYSTRUCT* obj) : PyObjectContainer(obj) {}
// This method associates a C++ object with the corresponding python
// object enclosed in this class.
void set(CPPCLASS* value) {
if (value == NULL) {
isc_throw(PyCPPWrapperException, "Unexpected NULL C++ object, "
"probably due to short memory");
}
static_cast<PYSTRUCT*>(obj_)->cppobj = value;
}
// This is a convenience short cut to associate a C++ object with the
// python object and install it to the specified python class \c pyclass
// as a variable named \c name.
void installAsClassVariable(PyTypeObject& pyclass, const char* name,
CPPCLASS* value)
{
set(value);
PyObjectContainer::installAsClassVariable(pyclass, name);
}
};
/// A shortcut function to install a python class variable.
///
/// It installs a python object \c obj to a specified class \c pyclass
/// as a variable named \c name.
inline void
installClassVariable(PyTypeObject& pyclass, const char* name, PyObject* obj) {
PyObjectContainer(obj).installAsClassVariable(pyclass, name);
}
} // namespace python
} // namespace util
} // namespace isc
#endif // __PYCPPWRAPPER_UTIL_H
// Local Variables:
// mode: c++
// End:

View File

@@ -0,0 +1,293 @@
// Copyright (C) @YEAR@ 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.
#include <string>
#include <stdexcept>
#include <util/python/pycppwrapper_util.h>
#include "@cppclass@_python.h"
using namespace std;
using namespace isc::util::python;
using namespace isc::@MODULE@;
using namespace isc::@MODULE@::python;
//
// Definition of the classes
//
// For each class, we need a struct, a helper functions (init, destroy,
// and static wrappers around the methods we export), a list of methods,
// and a type description
//
// @CPPCLASS@
//
// Trivial constructor.
s_@CPPCLASS@::s_@CPPCLASS@() : cppobj(NULL) {
}
namespace {
// Shortcut type which would be convenient for adding class variables safely.
typedef CPPPyObjectContainer<s_@CPPCLASS@, @CPPCLASS@> @CPPCLASS@Container;
//
// We declare the functions here, the definitions are below
// the type definition of the object, since both can use the other
//
// General creation and destruction
int @CPPCLASS@_init(s_@CPPCLASS@* self, PyObject* args);
void @CPPCLASS@_destroy(s_@CPPCLASS@* self);
// These are the functions we export
// ADD/REMOVE/MODIFY THE FOLLOWING AS APPROPRIATE FOR THE ACTUAL CLASS.
//
PyObject* @CPPCLASS@_toText(const s_@CPPCLASS@* const self);
PyObject* @CPPCLASS@_str(PyObject* self);
PyObject* @CPPCLASS@_richcmp(const s_@CPPCLASS@* const self,
const s_@CPPCLASS@* const other, int op);
// This is quite specific pydnspp. For other wrappers this should probably
// be removed.
PyObject* @CPPCLASS@_toWire(const s_@CPPCLASS@* self, PyObject* args);
// These are the functions we export
// For a minimal support, we don't need them.
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
// 2. Our static function here
// 3. Argument type
// 4. Documentation
PyMethodDef @CPPCLASS@_methods[] = {
{ "to_text", reinterpret_cast<PyCFunction>(@CPPCLASS@_toText), METH_NOARGS,
"Returns the text representation" },
// This is quite specific pydnspp. For other wrappers this should probably
// be removed:
{ "to_wire", reinterpret_cast<PyCFunction>(@CPPCLASS@_toWire), METH_VARARGS,
"Converts the @CPPCLASS@ object to wire format.\n"
"The argument can be either a MessageRenderer or an object that "
"implements the sequence interface. If the object is mutable "
"(for instance a bytearray()), the wire data is added in-place.\n"
"If it is not (for instance a bytes() object), a new object is "
"returned" },
{ NULL, NULL, 0, NULL }
};
// This is a template of typical code logic of python class initialization
// with C++ backend. You'll need to adjust it according to details of the
// actual C++ class.
int
@CPPCLASS@_init(s_@CPPCLASS@* self, PyObject* args) {
try {
if (PyArg_ParseTuple(args, "REPLACE ME")) {
// YOU'LL NEED SOME VALIDATION, PREPARATION, ETC, HERE.
self->cppobj = new @CPPCLASS@(/*NECESSARY PARAMS*/);
return (0);
}
} catch (const exception& ex) {
const string ex_what = "Failed to construct @CPPCLASS@ object: " +
string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
return (-1);
} catch (...) {
PyErr_SetString(po_IscException,
"Unexpected exception in constructing @CPPCLASS@");
return (-1);
}
PyErr_SetString(PyExc_TypeError,
"Invalid arguments to @CPPCLASS@ constructor");
return (-1);
}
// This is a template of typical code logic of python object destructor.
// In many cases you can use it without modification, but check that carefully.
void
@CPPCLASS@_destroy(s_@CPPCLASS@* const self) {
delete self->cppobj;
self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
// This should be able to be used without modification as long as the
// underlying C++ class has toText().
PyObject*
@CPPCLASS@_toText(const s_@CPPCLASS@* const self) {
try {
// toText() could throw, so we need to catch any exceptions below.
return (Py_BuildValue("s", self->cppobj->toText().c_str()));
} catch (const exception& ex) {
const string ex_what =
"Failed to convert @CPPCLASS@ object to text: " +
string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
"converting @CPPCLASS@ object to text");
}
return (NULL);
}
PyObject*
@CPPCLASS@_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
const_cast<char*>("")));
}
PyObject*
@CPPCLASS@_richcmp(const s_@CPPCLASS@* const self,
const s_@CPPCLASS@* const other,
const int op)
{
bool c = false;
// Check for null and if the types match. If different type,
// simply return False
if (other == NULL || (self->ob_type != other->ob_type)) {
Py_RETURN_FALSE;
}
// Only equals and not equals here, unorderable type
switch (op) {
case Py_LT:
PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
return (NULL);
case Py_LE:
PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
return (NULL);
case Py_EQ:
c = (*self->cppobj == *other->cppobj);
break;
case Py_NE:
c = (*self->cppobj != *other->cppobj);
break;
case Py_GT:
PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
return (NULL);
case Py_GE:
PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
return (NULL);
}
if (c) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
} // end of unnamed namespace
namespace isc {
namespace @MODULE@ {
namespace python {
// This defines the complete type for reflection in python and
// parsing of PyObject* to s_@CPPCLASS@
// Most of the functions are not actually implemented and NULL here.
PyTypeObject @cppclass@_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"libdns_python.@CPPCLASS@",
sizeof(s_@CPPCLASS@), // tp_basicsize
0, // tp_itemsize
reinterpret_cast<destructor>(@CPPCLASS@_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
// THIS MAY HAVE TO BE CHANGED TO NULL:
@CPPCLASS@_str, // tp_str
NULL, // tp_getattro
NULL, // tp_setattro
NULL, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
"The @CPPCLASS@ class objects is...(COMPLETE THIS)",
NULL, // tp_traverse
NULL, // tp_clear
// THIS MAY HAVE TO BE CHANGED TO NULL:
reinterpret_cast<richcmpfunc>(@CPPCLASS@_richcmp), // tp_richcompare
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
@CPPCLASS@_methods, // tp_methods
NULL, // tp_members
NULL, // tp_getset
NULL, // tp_base
NULL, // tp_dict
NULL, // tp_descr_get
NULL, // tp_descr_set
0, // tp_dictoffset
reinterpret_cast<initproc>(@CPPCLASS@_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
};
// Module Initialization, all statics are initialized here
bool
initModulePart_@CPPCLASS@(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(&@cppclass@_type) < 0) {
return (false);
}
void* p = &@cppclass@_type;
if (PyModule_AddObject(mod, "@CPPCLASS@", static_cast<PyObject*>(p)) < 0) {
return (false);
}
Py_INCREF(&@cppclass@_type);
// The following template is the typical procedure for installing class
// variables. If the class doesn't have a class variable, remove the
// entire try-catch clauses.
try {
// Constant class variables
installClassVariable(@cppclass@_type, "REPLACE_ME",
Py_BuildValue("REPLACE ME"));
} catch (const exception& ex) {
const string ex_what =
"Unexpected failure in @CPPCLASS@ initialization: " +
string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
return (false);
} catch (...) {
PyErr_SetString(PyExc_SystemError,
"Unexpected failure in @CPPCLASS@ initialization");
return (false);
}
return (true);
}
} // namespace python
} // namespace @MODULE@
} // namespace isc

View File

@@ -0,0 +1,44 @@
// Copyright (C) @YEAR@ 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.
#ifndef __PYTHON_@CPPCLASS@_H
#define __PYTHON_@CPPCLASS@_H 1
#include <Python.h>
namespace isc {
namespace @MODULE@ {
class @CPPCLASS@;
namespace python {
// The s_* Class simply covers one instantiation of the object
class s_@CPPCLASS@ : public PyObject {
public:
s_@CPPCLASS@();
@CPPCLASS@* cppobj;
};
extern PyTypeObject @cppclass@_type;
bool initModulePart_@CPPCLASS@(PyObject* mod);
} // namespace python
} // namespace @MODULE@
} // namespace isc
#endif // __PYTHON_@CPPCLASS@_H
// Local Variables:
// mode: c++
// End: