Browse Source

[trac983] added bindings for RequestLoader as discussed in review.
the only exposed interface is load() right now.
also made overall document cleanup/enhancements.

JINMEI Tatuya 14 years ago
parent
commit
d0df4daafe

+ 2 - 0
src/lib/python/isc/acl/Makefile.am

@@ -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.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_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_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 dns_la_LDFLAGS = $(PYTHON_LDFLAGS)
 dns_la_LDFLAGS = $(PYTHON_LDFLAGS)
 # Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
 # 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)
 dns_la_LIBADD += $(PYTHON_LIB)
 
 
 EXTRA_DIST = acl.py dns.py
 EXTRA_DIST = acl.py dns.py
+EXTRA_DIST += acl_inc.cc
 EXTRA_DIST += dnsacl_inc.cc dns_requestacl_inc.cc dns_requestcontext_inc.cc
 EXTRA_DIST += dnsacl_inc.cc dns_requestacl_inc.cc dns_requestcontext_inc.cc
 
 
 CLEANDIRS = __pycache__
 CLEANDIRS = __pycache__

+ 3 - 2
src/lib/python/isc/acl/acl.cc

@@ -20,6 +20,8 @@
 
 
 using namespace isc::util::python;
 using namespace isc::util::python;
 
 
+#include "acl_inc.cc"
+
 namespace {
 namespace {
 // Commonly used Python exception objects.  Right now the acl module consists
 // 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
 // of only one .cc file, so we hide them in an unnamed namespace.  If and when
@@ -34,8 +36,7 @@ namespace {
 PyModuleDef acl = {
 PyModuleDef acl = {
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
     "isc.acl.acl",
     "isc.acl.acl",
-    "This module provides Python bindings for the C++ classes in the "
-    "isc::acl namespace",
+    acl_doc,
     -1,
     -1,
     NULL,
     NULL,
     NULL,
     NULL,

+ 16 - 0
src/lib/python/isc/acl/acl_inc.cc

@@ -0,0 +1,16 @@
+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

+ 34 - 31
src/lib/python/isc/acl/dns.cc

@@ -27,6 +27,7 @@
 #include "dns.h"
 #include "dns.h"
 #include "dns_requestcontext_python.h"
 #include "dns_requestcontext_python.h"
 #include "dns_requestacl_python.h"
 #include "dns_requestacl_python.h"
+#include "dns_requestloader_python.h"
 
 
 using namespace std;
 using namespace std;
 using boost::shared_ptr;
 using boost::shared_ptr;
@@ -38,45 +39,21 @@ using namespace isc::acl::dns::python;
 #include "dnsacl_inc.cc"
 #include "dnsacl_inc.cc"
 
 
 namespace {
 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[] = {
 PyMethodDef methods[] = {
-    { "load_request_acl", loadRequestACL, METH_VARARGS, load_request_acl_doc },
     { NULL, NULL, 0, NULL }
     { NULL, NULL, 0, NULL }
 };
 };
 
 
 PyModuleDef dnsacl = {
 PyModuleDef dnsacl = {
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
     "isc.acl.dns",
     "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,
     -1,
     methods,
     methods,
     NULL,
     NULL,
@@ -127,6 +104,32 @@ PyInit_dns(void) {
         Py_DECREF(mod);
         Py_DECREF(mod);
         return (NULL);
         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);
     return (mod);
 }
 }

+ 2 - 2
src/lib/python/isc/acl/dns_requestacl_inc.cc

@@ -4,7 +4,7 @@ The DNS Request ACL.\n\
 \n\
 \n\
 It holds bunch of ordered entries, each one consisting of a check for\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\
 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\
 The checks are tested in the order and first match counts.\n\
 \n\
 \n\
 A RequestACL object cannot be constructed directly; an application\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\
 execute(context) -> action \n\
 \n\
 \n\
 The returned action is one of ACCEPT, REJECT or DROP as defined in\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\
 \n\
 This is the function that takes the ACL entries one by one, checks the\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\
 context against conditions and if it matches, returns the action that\n\

+ 84 - 0
src/lib/python/isc/acl/dns_requestloader_inc.cc

@@ -0,0 +1,84 @@
+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

+ 201 - 0
src/lib/python/isc/acl/dns_requestloader_python.cc

@@ -0,0 +1,201 @@
+// 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

+ 46 - 0
src/lib/python/isc/acl/dns_requestloader_python.h

@@ -0,0 +1,46 @@
+// 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:

+ 12 - 16
src/lib/python/isc/acl/dnsacl_inc.cc

@@ -1,21 +1,17 @@
 namespace {
 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\
 \n\
-Load a DNS ACL.\n\
+Special objects:\n\
 \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
 } // unnamed namespace

+ 47 - 39
src/lib/python/isc/acl/tests/dns_test.py

@@ -29,7 +29,8 @@ def get_acl(prefix):
     that accepts addresses for the given IP prefix (and reject any others
     that accepts addresses for the given IP prefix (and reject any others
     by default)
     by default)
     '''
     '''
-    return load_request_acl('[{"action": "ACCEPT", "from": "' + prefix + '"}]')
+    return REQUEST_LOADER.load('[{"action": "ACCEPT", "from": "' + \
+                                   prefix + '"}]')
 
 
 def get_context(address):
 def get_context(address):
     '''This is a simple shortcut wrapper for creating a RequestContext
     '''This is a simple shortcut wrapper for creating a RequestContext
@@ -98,64 +99,64 @@ class RequestACLTest(unittest.TestCase):
 
 
     def test_request_loader(self):
     def test_request_loader(self):
         # these shouldn't raise an exception
         # 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
         # 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
         # Incorrect number of arguments
-        self.assertRaises(TypeError, load_request_acl,
+        self.assertRaises(TypeError, REQUEST_LOADER.load,
                           '[{"action": "DROP"}]', 0)
                           '[{"action": "DROP"}]', 0)
 
 
     def test_bad_acl_syntax(self):
     def test_bad_acl_syntax(self):
         # the following are derived from loader_test.cc
         # 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
         # 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"}]')
                           '[{"action": "ACCEPT", "bad": "192.0.2.1"}]')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "ACCEPT", "from": 4}]')
                           '[{"action": "ACCEPT", "from": 4}]')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "ACCEPT", "from": []}]')
                           '[{"action": "ACCEPT", "from": []}]')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "ACCEPT", "from": "bad"}]')
                           '[{"action": "ACCEPT", "from": "bad"}]')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "ACCEPT", "from": null}]')
                           '[{"action": "ACCEPT", "from": null}]')
 
 
     def test_bad_acl_ipsyntax(self):
     def test_bad_acl_ipsyntax(self):
         # this test is derived from ip_check_unittest.cc
         # this test is derived from ip_check_unittest.cc
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "DROP", "from": "192.0.2.43/-1"}]')
                           '[{"action": "DROP", "from": "192.0.2.43/-1"}]')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "DROP", "from": "192.0.2.43//1"')
                           '[{"action": "DROP", "from": "192.0.2.43//1"')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "DROP", "from": "192.0.2.43/1/"')
                           '[{"action": "DROP", "from": "192.0.2.43/1/"')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "DROP", "from": "/192.0.2.43/1"')
                           '[{"action": "DROP", "from": "/192.0.2.43/1"')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "DROP", "from": "2001:db8::/xxxx"')
                           '[{"action": "DROP", "from": "2001:db8::/xxxx"')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "DROP", "from": "2001:db8::/32/s"')
                           '[{"action": "DROP", "from": "2001:db8::/32/s"')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "DROP", "from": "1/"')
                           '[{"action": "DROP", "from": "1/"')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "DROP", "from": "/1"')
                           '[{"action": "DROP", "from": "/1"')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "DROP", "from": "192.0.2.0/33"')
                           '[{"action": "DROP", "from": "192.0.2.0/33"')
-        self.assertRaises(LoaderError, load_request_acl,
+        self.assertRaises(LoaderError, REQUEST_LOADER.load,
                           '[{"action": "DROP", "from": "::1/129"')
                           '[{"action": "DROP", "from": "::1/129"')
 
 
     def test_execute(self):
     def test_execute(self):
@@ -174,13 +175,13 @@ class RequestACLTest(unittest.TestCase):
         self.assertEqual(REJECT, get_acl('32.1.13.184').execute(CONTEXT6))
         self.assertEqual(REJECT, get_acl('32.1.13.184').execute(CONTEXT6))
 
 
         # A bit more complicated example, derived from resolver_config_unittest
         # A bit more complicated example, derived from resolver_config_unittest
-        acl = load_request_acl('[ {"action": "ACCEPT", ' +
-                               '     "from": "192.0.2.1"},' +
-                               '    {"action": "REJECT",' +
-                               '     "from": "192.0.2.0/24"},' +
-                               '    {"action": "DROP",' +
-                               '     "from": "2001:db8::1"},' +
-                               '] }')
+        acl = REQUEST_LOADER.load('[ {"action": "ACCEPT", ' +
+                                  '     "from": "192.0.2.1"},' +
+                                  '    {"action": "REJECT",' +
+                                  '     "from": "192.0.2.0/24"},' +
+                                  '    {"action": "DROP",' +
+                                  '     "from": "2001:db8::1"},' +
+                                  '] }')
         self.assertEqual(ACCEPT, acl.execute(CONTEXT4))
         self.assertEqual(ACCEPT, acl.execute(CONTEXT4))
         self.assertEqual(REJECT, acl.execute(get_context('192.0.2.2')))
         self.assertEqual(REJECT, acl.execute(get_context('192.0.2.2')))
         self.assertEqual(DROP, acl.execute(get_context('2001:db8::1')))
         self.assertEqual(DROP, acl.execute(get_context('2001:db8::1')))
@@ -195,5 +196,12 @@ class RequestACLTest(unittest.TestCase):
         # type mismatch
         # type mismatch
         self.assertRaises(TypeError, acl.execute, 'bad parameter')
         self.assertRaises(TypeError, acl.execute, 'bad parameter')
 
 
+class RequestLoaderTest(unittest.TestCase):
+    # Note: loading ACLs is tested in other test cases.
+
+    def test_construct(self):
+        # at least for now, we don't allow direct construction.
+        self.assertRaises(Error, RequestLoader)
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()