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

[trac983] added bindings for RequestLoader as discussed in review.

the only exposed interface is load() right now.
also made overall document cleanup/enhancements.
parent d23f8473
......@@ -17,6 +17,7 @@ acl_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
dns_la_SOURCES = dns.h dns.cc dns_requestacl_python.h dns_requestacl_python.cc
dns_la_SOURCES += dns_requestcontext_python.h dns_requestcontext_python.cc
dns_la_SOURCES += dns_requestloader_python.h dns_requestloader_python.cc
dns_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
dns_la_LDFLAGS = $(PYTHON_LDFLAGS)
# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
......@@ -34,6 +35,7 @@ dns_la_LIBADD = $(top_builddir)/src/lib/acl/libdnsacl.la
dns_la_LIBADD += $(PYTHON_LIB)
EXTRA_DIST = acl.py dns.py
EXTRA_DIST += acl_inc.cc
EXTRA_DIST += dnsacl_inc.cc dns_requestacl_inc.cc dns_requestcontext_inc.cc
CLEANDIRS = __pycache__
......
......@@ -20,6 +20,8 @@
using namespace isc::util::python;
#include "acl_inc.cc"
namespace {
// Commonly used Python exception objects. Right now the acl module consists
// of only one .cc file, so we hide them in an unnamed namespace. If and when
......@@ -34,8 +36,7 @@ namespace {
PyModuleDef acl = {
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
"isc.acl.acl",
"This module provides Python bindings for the C++ classes in the "
"isc::acl namespace",
acl_doc,
-1,
NULL,
NULL,
......
namespace {
const char* const acl_doc = "\
Implementation module for ACL operations\n\n\
This module provides Python bindings for the C++ classes in the\n\
isc::acl namespace.\n\
\n\
Integer constants:\n\
\n\
ACCEPT, REJECT, DROP -- Default actions an ACL could perform.\n\
These are the commonly used actions in specific ACLs.\n\
It is possible to specify any other values, as the ACL class does\n\
nothing about them, but these look reasonable, so they are provided\n\
for convenience. It is not specified what exactly these mean and it's\n\
up to whoever uses them.\n\
";
} // unnamed namespace
......@@ -27,6 +27,7 @@
#include "dns.h"
#include "dns_requestcontext_python.h"
#include "dns_requestacl_python.h"
#include "dns_requestloader_python.h"
using namespace std;
using boost::shared_ptr;
......@@ -38,45 +39,21 @@ using namespace isc::acl::dns::python;
#include "dnsacl_inc.cc"
namespace {
PyObject*
loadRequestACL(PyObject*, PyObject* args) {
const char* acl_config;
if (PyArg_ParseTuple(args, "s", &acl_config)) {
try {
shared_ptr<RequestACL> acl(
getRequestLoader().load(Element::fromJSON(acl_config)));
s_RequestACL* py_acl = static_cast<s_RequestACL*>(
requestacl_type.tp_alloc(&requestacl_type, 0));
if (py_acl != NULL) {
py_acl->cppobj = acl;
}
return (py_acl);
} catch (const exception& ex) {
PyErr_SetString(getACLException("LoaderError"), ex.what());
return (NULL);
} catch (...) {
PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
return (NULL);
}
}
return (NULL);
}
// This is a Python binding object corresponding to the singleton loader used
// in the C++ version of the library.
// We can define it as a pure object rather than through an accessor function,
// because in Python we can ensure it has been created and initialized
// in the module initializer by the time it's actually used.
s_RequestLoader* po_REQUEST_LOADER;
PyMethodDef methods[] = {
{ "load_request_acl", loadRequestACL, METH_VARARGS, load_request_acl_doc },
{ NULL, NULL, 0, NULL }
};
PyModuleDef dnsacl = {
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
"isc.acl.dns",
"This module provides Python bindings for the C++ classes in the "
"isc::acl::dns namespace. Specifically, it defines Python interfaces of "
"handling access control lists (ACLs) with DNS related contexts.\n\n"
"These bindings are close match to the C++ API, but they are not complete "
"(some parts are not needed) and some are done in more python-like ways.",
dnsacl_doc,
-1,
methods,
NULL,
......@@ -127,6 +104,32 @@ PyInit_dns(void) {
Py_DECREF(mod);
return (NULL);
}
if (!initModulePart_RequestLoader(mod)) {
Py_DECREF(mod);
return (NULL);
}
// Module constants
try {
if (po_REQUEST_LOADER == NULL) {
po_REQUEST_LOADER = static_cast<s_RequestLoader*>(
requestloader_type.tp_alloc(&requestloader_type, 0));
}
if (po_REQUEST_LOADER != NULL) {
// We gain and keep our own reference to the singleton object
// for the same reason as that for exception objects (see comments
// in pycppwrapper_util for more details). Note also that we don't
// bother to release the reference even if exception is thrown
// below (in fact, we cannot delete the singleton loader).
po_REQUEST_LOADER->cppobj = &getRequestLoader();
Py_INCREF(po_REQUEST_LOADER);
}
PyObjectContainer(po_REQUEST_LOADER).installToModule(mod,
"REQUEST_LOADER");
} catch (...) {
Py_DECREF(mod);
return (NULL);
}
return (mod);
}
......@@ -4,7 +4,7 @@ The DNS Request ACL.\n\
\n\
It holds bunch of ordered entries, each one consisting of a check for\n\
a given DNS Request context and an action, which is one of ACCEPT,\n\
REJECT, or DROP, as defined in the isc.acl module.\n\
REJECT, or DROP, as defined in the isc.acl.acl module.\n\
The checks are tested in the order and first match counts.\n\
\n\
A RequestACL object cannot be constructed directly; an application\n\
......@@ -16,7 +16,7 @@ const char* const RequestACL_execute_doc = "\
execute(context) -> action \n\
\n\
The returned action is one of ACCEPT, REJECT or DROP as defined in\n\
the isc.acl module.\n\
the isc.acl.acl module.\n\
\n\
This is the function that takes the ACL entries one by one, checks the\n\
context against conditions and if it matches, returns the action that\n\
......
namespace {
const char* const RequestLoader_doc = "\
Loader of DNS Request ACLs.\n\
\n\
The goal of this class is to convert JSON description of an ACL to\n\
object of the ACL class (including the checks inside it).\n\
\n\
The class can be used to load the checks only. This is supposed to be\n\
used by compound checks to create the subexpressions.\n\
\n\
To allow any kind of checks to exist in the application, creators are\n\
registered for the names of the checks (this feature is not yet\n\
available for the python API).\n\
\n\
An ACL definition looks like this: [\n\
{\n\
\"action\": \"ACCEPT\",\n\
\"match-type\": <parameter>\n\
},\n\
{\n\
\"action\": \"REJECT\",\n\
\"match-type\": <parameter>,\n\
\"another-match-type\": [<parameter1>, <parameter2>]\n\
},\n\
{\n\
\"action\": \"DROP\"\n\
}\n\
]\n\
\n\
\n\
This is a list of elements. Each element must have an \"action\"\n\
entry/keyword. That one specifies which action is returned if this\n\
element matches (the value of the key is passed to the action loader\n\
(see the constructor), which is one of ACCEPT,\n\
REJECT, or DROP, as defined in the isc.acl.acl module.\n\
\n\
The rest of the element are matches. The left side is the name of the\n\
match type (for example \"from\" to match for source IP address).\n\
The <parameter> is whatever is needed to describe the\n\
match and depends on the match type, the loader passes it verbatim to\n\
creator of that match type.\n\
\n\
There may be multiple match types in single element. In such case, all\n\
of the matches must match for the element to take action (so, in the\n\
second element, both \"match-type\" and \"another-match-type\" must be\n\
satisfied). If there's no match in the element, the action is\n\
taken/returned without conditions, every time (makes sense as the last\n\
entry, as the ACL will never get past it).\n\
\n\
The second entry shows another thing - if there's a list as the value\n\
for some match and the match itself is not expecting a list, it is\n\
taken as an \"or\" - a match for at last one of the choices in the\n\
list must match. So, for the second entry, both \"match-type\" and\n\
\"another-match-type\" must be satisfied, but the another one is\n\
satisfied by either parameter1 or parameter2.\n\
\n\
Currently, a RequestLoader object cannot be constructed directly;\n\
an application must use the singleton loader defined in the\n\
isc.acl.dns module, i.e., isc.acl.dns.REQUEST_LOADER.\n\
A future version of this implementation may be extended to give\n\
applications full flexibility of creating arbitrary loader, when\n\
this restriction may be removed.\n\
";
const char* const RequestLoader_load_doc = "\
load(description) -> RequestACL\n\
\n\
Load a DNS ACL.\n\
\n\
This parses an ACL list, creates internal data for each rule\n\
and returns a RequestACl object that contains all given rules.\n\
\n\
Exceptions:\n\
LoaderError Load failed. The most likely cause of this is a syntax\n\
error in the description. Other internal errors such as\n\
memory allocation failure is also converted to this\n\
exception.\n\
\n\
Parameters:\n\
description String representation of the JSON list of ACL.\n\
\n\
Return Value(s): The newly created RequestACL object\n\
";
} // unnamed namespace
// 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.
// Enable this if you use s# variants with PyArg_ParseTuple(), see
// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
//#define PY_SSIZE_T_CLEAN
// Python.h needs to be placed at the head of the program file, see:
// http://docs.python.org/py3k/extending/extending.html#a-simple-example
#include <Python.h>
#include <string>
#include <stdexcept>
#include <boost/shared_ptr.hpp>
#include <util/python/pycppwrapper_util.h>
#include <cc/data.h>
#include <acl/dns.h>
#include "dns.h"
#include "dns_requestacl_python.h"
#include "dns_requestloader_python.h"
using namespace std;
using boost::shared_ptr;
using namespace isc::util::python;
using namespace isc::data;
using namespace isc::acl::dns;
using namespace isc::acl::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
//
// RequestLoader
//
// Trivial constructor.
s_RequestLoader::s_RequestLoader() : cppobj(NULL) {
}
// Import pydoc text
#include "dns_requestloader_inc.cc"
namespace {
//
// We declare the functions here, the definitions are below
// the type definition of the object, since both can use the other
//
int
RequestLoader_init(PyObject*, PyObject*, PyObject*) {
PyErr_SetString(getACLException("Error"),
"RequestLoader cannot be directly constructed");
return (-1);
}
void
RequestLoader_destroy(PyObject* po_self) {
s_RequestLoader* const self = static_cast<s_RequestLoader*>(po_self);
delete self->cppobj;
self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
PyObject*
RequestLoader_load(PyObject* po_self, PyObject* args) {
s_RequestLoader* const self = static_cast<s_RequestLoader*>(po_self);
const char* acl_config;
if (PyArg_ParseTuple(args, "s", &acl_config)) {
try {
shared_ptr<RequestACL> acl(
self->cppobj->load(Element::fromJSON(acl_config)));
s_RequestACL* py_acl = static_cast<s_RequestACL*>(
requestacl_type.tp_alloc(&requestacl_type, 0));
if (py_acl != NULL) {
py_acl->cppobj = acl;
}
return (py_acl);
} catch (const exception& ex) {
PyErr_SetString(getACLException("LoaderError"), ex.what());
return (NULL);
} catch (...) {
PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
return (NULL);
}
}
return (NULL);
}
// 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 RequestLoader_methods[] = {
{ "load", RequestLoader_load, METH_VARARGS, RequestLoader_load_doc },
{ NULL, NULL, 0, NULL }
};
} // end of unnamed namespace
namespace isc {
namespace acl {
namespace dns {
namespace python {
// This defines the complete type for reflection in python and
// parsing of PyObject* to s_RequestLoader
// Most of the functions are not actually implemented and NULL here.
PyTypeObject requestloader_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"isc.acl.dns.RequestLoader",
sizeof(s_RequestLoader), // tp_basicsize
0, // tp_itemsize
RequestLoader_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
RequestLoader_doc,
NULL, // tp_traverse
NULL, // tp_clear
NULL, // tp_richcompare
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
RequestLoader_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
RequestLoader_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
};
bool
initModulePart_RequestLoader(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(&requestloader_type) < 0) {
return (false);
}
void* p = &requestloader_type;
if (PyModule_AddObject(mod, "RequestLoader",
static_cast<PyObject*>(p)) < 0) {
return (false);
}
Py_INCREF(&requestloader_type);
return (true);
}
} // namespace python
} // namespace dns
} // namespace acl
} // namespace isc
// 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_REQUESTLOADER_H
#define __PYTHON_REQUESTLOADER_H 1
#include <Python.h>
#include <acl/dns.h>
namespace isc {
namespace acl {
namespace dns {
namespace python {
// The s_* Class simply covers one instantiation of the object
class s_RequestLoader : public PyObject {
public:
s_RequestLoader();
RequestLoader* cppobj;
};
extern PyTypeObject requestloader_type;
bool initModulePart_RequestLoader(PyObject* mod);
} // namespace python
} // namespace dns
} // namespace acl
} // namespace isc
#endif // __PYTHON_REQUESTLOADER_H
// Local Variables:
// mode: c++
// End:
namespace {
const char* const load_request_acl_doc = "\
load_request_acl(description) -> RequestACL\n\
const char* const dnsacl_doc = "\
Implementation module for DNS ACL operations\n\n\
This module provides Python bindings for the C++ classes in the\n\
isc::acl::dns namespace. Specifically, it defines Python interfaces of\n\
handling access control lists (ACLs) with DNS related contexts.\n\
These bindings are close match to the C++ API, but they are not complete\n\
(some parts are not needed) and some are done in more python-like ways.\n\
\n\
Load a DNS ACL.\n\
Special objects:\n\
\n\
This parses an ACL list, creates internal data for each rule\n\
and returns a RequestACl object that contains all given rules.\n\
\n\
Exceptions:\n\
LoaderError Load failed. The most likely cause of this is a syntax\n\
error in the description. Other internal errors such as\n\
memory allocation failure is also converted to this\n\
exception.\n\
\n\
Parameters:\n\
description String representation of the JSON list of ACL.\n\
\n\
Return Value(s): The newly created RequestACL object\n\
REQUEST_LOADER -- A singleton loader of ACLs. It is expected applications\n\
will use this function instead of creating their own loaders, because\n\
one is enough, this one will have registered default checks and it is\n\
known one, so any plugins can registrer additional checks as well.\n\
";
} // unnamed namespace
......@@ -29,7 +29,8 @@ def get_acl(prefix):
that accepts addresses for the given IP prefix (and reject any others
by default)
'''
return load_request_acl('[{"action": "ACCEPT", "from": "' + prefix + '"}]')
return REQUEST_LOADER.load('[{"action": "ACCEPT", "from": "' + \
prefix + '"}]')
def get_context(address):
'''This is a simple shortcut wrapper for creating a RequestContext
......@@ -98,64 +99,64 @@ class RequestACLTest(unittest.TestCase):
def test_request_loader(self):
# these shouldn't raise an exception
load_request_acl('[{"action": "DROP"}]')
load_request_acl('[{"action": "DROP", "from": "192.0.2.1"}]')
REQUEST_LOADER.load('[{"action": "DROP"}]')
REQUEST_LOADER.load('[{"action": "DROP", "from": "192.0.2.1"}]')
# Invalid types
self.assertRaises(TypeError, load_request_acl, 1)
self.assertRaises(TypeError, load_request_acl, [])
self.assertRaises(TypeError, REQUEST_LOADER.load, 1)
self.assertRaises(TypeError, REQUEST_LOADER.load, [])
# Incorrect number of arguments
self.assertRaises(TypeError, load_request_acl,
self.assertRaises(TypeError, REQUEST_LOADER.load,
'[{"action": "DROP"}]', 0)
def test_bad_acl_syntax(self):
# the following are derived from loader_test.cc
self.assertRaises(LoaderError, load_request_acl, '{}');
self.assertRaises(LoaderError, load_request_acl, '42');
self.assertRaises(LoaderError, load_request_acl, 'true');
self.assertRaises(LoaderError, load_request_acl, 'null');
self.assertRaises(LoaderError, load_request_acl, '"hello"');
self.assertRaises(LoaderError, load_request_acl, '[42]');
self.assertRaises(LoaderError, load_request_acl, '["hello"]');
self.assertRaises(LoaderError, load_request_acl, '[[]]');
self.assertRaises(LoaderError, load_request_acl, '[true]');
self.assertRaises(LoaderError, load_request_acl, '[null]');
self.assertRaises(LoaderError, load_request_acl, '[{}]');
self.assertRaises(LoaderError, REQUEST_LOADER.load, '{}');
self.assertRaises(LoaderError, REQUEST_LOADER.load, '42');
self.assertRaises(LoaderError, REQUEST_LOADER.load, 'true');
self.assertRaises(LoaderError, REQUEST_LOADER.load, 'null');
self.assertRaises(LoaderError, REQUEST_LOADER.load, '"hello"');
self.assertRaises(LoaderError, REQUEST_LOADER.load, '[42]');
self.assertRaises(LoaderError, REQUEST_LOADER.load, '["hello"]');
self.assertRaises(LoaderError, REQUEST_LOADER.load, '[[]]');
self.assertRaises(LoaderError, REQUEST_LOADER.load, '[true]');
self.assertRaises(LoaderError, REQUEST_LOADER.load, '[null]');
self.assertRaises(LoaderError, REQUEST_LOADER.load, '[{}]');
# the following are derived from dns_test.cc
self.assertRaises(LoaderError, load_request_acl,
self.assertRaises(LoaderError, REQUEST_LOADER.load,
'[{"action": "ACCEPT", "bad": "192.0.2.1"}]')
self.assertRaises(LoaderError, load_request_acl,
self.assertRaises(LoaderError, REQUEST_LOADER.load,
'[{"action": "ACCEPT", "from": 4}]')
self.assertRaises(LoaderError, load_request_acl,
self.assertRaises(LoaderError, REQUEST_LOADER.load,
'[{"action": "ACCEPT", "from": []}]')
self.assertRaises(LoaderError, load_request_acl,
self.assertRaises(LoaderError, REQUEST_LOADER.load,
'[{"action": "ACCEPT", "from": "bad"}]')
self.assertRaises(LoaderError, load_request_acl,
self.assertRaises(LoaderError, REQUEST_LOADER.load,
'[{"action": "ACCEPT", "from": null}]')
def test_bad_acl_ipsyntax(self):
# this test is derived from ip_check_unittest.cc