Commit 2bfafa08 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[trac983] implemented the framework for python version of RequestContext.

added constructor and __str__() with tests for the constructor.
parent 7819ba75
......@@ -12,6 +12,7 @@ acl_la_LDFLAGS = $(PYTHON_LDFLAGS)
acl_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
dns_la_SOURCES = dns.cc dns_requestacl_python.h dns_requestacl_python.cc
dns_la_SOURCES += dns_requestcontext_python.h dns_requestcontext_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
......
......@@ -24,6 +24,7 @@ using namespace isc::acl::python;
namespace isc {
namespace acl {
namespace python {
PyObject* po_ACLError;
PyObject* po_LoaderError;
}
}
......@@ -52,6 +53,9 @@ PyInit_acl(void) {
}
try {
po_ACLError = PyErr_NewException("isc.acl.Error", NULL, NULL);
PyObjectContainer(po_ACLError).installToModule(mod, "Error");
po_LoaderError = PyErr_NewException("isc.acl.LoaderError", NULL, NULL);
PyObjectContainer(po_LoaderError).installToModule(mod, "LoaderError");
} catch (...) {
......
......@@ -21,6 +21,7 @@ namespace isc {
namespace acl {
namespace python {
extern PyObject* po_ACLError;
extern PyObject* po_LoaderError;
} // namespace python
......
......@@ -25,6 +25,7 @@
#include <acl/dns.h>
#include "acl.h"
#include "dns_requestcontext_python.h"
#include "dns_requestacl_python.h"
using namespace std;
......@@ -90,6 +91,10 @@ PyInit_dns(void) {
return (NULL);
}
if (!initModulePart_RequestContext(mod)) {
Py_DECREF(mod);
return (NULL);
}
if (!initModulePart_RequestACL(mod)) {
Py_DECREF(mod);
return (NULL);
......
......@@ -129,7 +129,7 @@ namespace python {
// Most of the functions are not actually implemented and NULL here.
PyTypeObject requestacl_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"pydnspp.RequestACL",
"isc.acl.dns.RequestACL",
sizeof(s_RequestACL), // tp_basicsize
0, // tp_itemsize
reinterpret_cast<destructor>(RequestACL_destroy), // tp_dealloc
......
// 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 <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <cassert>
#include <memory>
#include <string>
#include <sstream>
#include <stdexcept>
#include <boost/scoped_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <exceptions/exceptions.h>
#include <util/python/pycppwrapper_util.h>
#include <acl/dns.h>
#include <acl/ip_check.h>
#include "acl.h"
#include "dns_requestcontext_python.h"
using namespace std;
using boost::scoped_ptr;
using boost::lexical_cast;
using namespace isc;
using namespace isc::util::python;
using namespace isc::acl::dns;
using namespace isc::acl::python;
using namespace isc::acl::dns::python;
namespace isc {
namespace acl {
namespace dns {
namespace python {
struct s_RequestContext::Data {
Data(const char* const remote_addr, const unsigned short remote_port) {
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
const int error(getaddrinfo(remote_addr,
lexical_cast<string>(remote_port).c_str(),
&hints, &res));
if (error != 0) {
isc_throw(InvalidParameter, "Failed to convert [" << remote_addr
<< "]:" << remote_port << ", " << gai_strerror(error));
}
assert(sizeof(remote_ss) > res->ai_addrlen);
memcpy(&remote_ss, res->ai_addr, res->ai_addrlen);
remote_salen = res->ai_addrlen;
freeaddrinfo(res);
remote_ipaddr.reset(new IPAddress(getRemoteSockaddr()));
}
const struct sockaddr& getRemoteSockaddr() const {
const void* p = &remote_ss;
return (*static_cast<const struct sockaddr*>(p));
}
scoped_ptr<IPAddress> remote_ipaddr;
socklen_t remote_salen;
private:
struct sockaddr_storage remote_ss;
};
} // namespace python
} // namespace dns
} // namespace acl
} // namespace isc
//
// 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
//
// RequestContext
//
// Trivial constructor.
s_RequestContext::s_RequestContext() : cppobj(NULL), data_(NULL) {
}
namespace {
// Shortcut type which would be convenient for adding class variables safely.
typedef CPPPyObjectContainer<s_RequestContext, RequestContext> RequestContextContainer;
//
// We declare the functions here, the definitions are below
// the type definition of the object, since both can use the other
//
// 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 RequestContext_methods[] = {
{ NULL, NULL, 0, NULL }
};
int
RequestContext_init(PyObject* po_self, PyObject* args, PyObject*) {
s_RequestContext* const self = static_cast<s_RequestContext*>(po_self);
try {
// In this initial implementation, the constructor is simply: It
// takes a single parameter, which should be a Python socket address
// object. For IPv4, it's ('address test', numeric_port); for IPv6,
// it's ('address text', num_port, num_flowid, num_zoneid).
// Below, we parse the argument in the most straightforward way.
// As the constructor becomes more complicated, we should probably
// make it more structural (for example, we should first retrieve
// the socket address as a PyObject, and parse it recursively)
const char* remote_addr;
unsigned short remote_port;
unsigned int remote_flowinfo; // IPv6 only, unused here
unsigned int remote_zoneid; // IPv6 only, unused here
if (PyArg_ParseTuple(args, "(sH)", &remote_addr, &remote_port) ||
PyArg_ParseTuple(args, "(sHII)", &remote_addr, &remote_port,
&remote_flowinfo, &remote_zoneid))
{
// We need to clear the error in case the first call to PareTuple
// fails.
PyErr_Clear();
auto_ptr<s_RequestContext::Data> dataptr(
new s_RequestContext::Data(remote_addr, remote_port));
self->cppobj = new RequestContext(*dataptr->remote_ipaddr);
self->data_ = dataptr.release();
return (0);
}
} catch (const exception& ex) {
const string ex_what = "Failed to construct RequestContext object: " +
string(ex.what());
PyErr_SetString(po_ACLError, ex_what.c_str());
return (-1);
} catch (...) {
PyErr_SetString(PyExc_RuntimeError,
"Unexpected exception in constructing RequestContext");
return (-1);
}
PyErr_SetString(PyExc_TypeError,
"Invalid arguments to RequestContext constructor");
return (-1);
}
void
RequestContext_destroy(PyObject* po_self) {
s_RequestContext* const self = static_cast<s_RequestContext*>(po_self);
delete self->cppobj;
delete self->data_;
Py_TYPE(self)->tp_free(self);
}
// A helper function for __str()__
string
sockaddrToText(const struct sockaddr& sa, socklen_t sa_len) {
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
if (getnameinfo(&sa, sa_len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
NI_NUMERICHOST | NI_NUMERICSERV)) {
// In this context this should never fail.
isc_throw(Unexpected, "Unexpected failure in getnameinfo");
}
return ("[" + string(hbuf) + "]:" + string(sbuf));
}
// for the __str__() method. This method is provided mainly for internal
// testing.
PyObject*
RequestContext_str(PyObject* po_self) {
const s_RequestContext* const self =
static_cast<s_RequestContext*>(po_self);
try {
stringstream objss;
objss << "<" << requestcontext_type.tp_name << " object, "
<< "remote_addr="
<< sockaddrToText(self->data_->getRemoteSockaddr(),
self->data_->remote_salen) << ">";
return (Py_BuildValue("s", objss.str().c_str()));
} catch (const exception& ex) {
const string ex_what =
"Failed to convert RequestContext object to text: " +
string(ex.what());
PyErr_SetString(PyExc_RuntimeError, ex_what.c_str());
} catch (...) {
PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
"converting RequestContext object to text");
}
return (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_RequestContext
// Most of the functions are not actually implemented and NULL here.
PyTypeObject requestcontext_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"isc.acl.dns.RequestContext",
sizeof(s_RequestContext), // tp_basicsize
0, // tp_itemsize
RequestContext_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
RequestContext_str, // tp_str
NULL, // tp_getattro
NULL, // tp_setattro
NULL, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
"The RequestContext class objects is...(COMPLETE THIS)",
NULL, // tp_traverse
NULL, // tp_clear
NULL, // tp_richcompare
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
RequestContext_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
RequestContext_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_RequestContext(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(&requestcontext_type) < 0) {
return (false);
}
void* p = &requestcontext_type;
if (PyModule_AddObject(mod, "RequestContext",
static_cast<PyObject*>(p)) < 0) {
return (false);
}
Py_INCREF(&requestcontext_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_REQUESTCONTEXT_H
#define __PYTHON_REQUESTCONTEXT_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_RequestContext : public PyObject {
public:
s_RequestContext();
RequestContext* cppobj;
// This object needs to maintain some source data to construct the
// underlying RequestContext object throughout its lifetime.
// These are "public" so that it can be accessed in the python wrapper
// implementation, but essentially they should be private, and the
// implementation details are hidden.
struct Data;
Data* data_;
};
extern PyTypeObject requestcontext_type;
bool initModulePart_RequestContext(PyObject* mod);
} // namespace python
} // namespace dns
} // namespace acl
} // namespace isc
#endif // __PYTHON_REQUESTCONTEXT_H
// Local Variables:
// mode: c++
// End:
......@@ -14,9 +14,64 @@
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
from isc.acl.acl import LoaderError
import socket
from isc.acl.acl import LoaderError, Error
from isc.acl.dns import *
def get_sockaddr(address, port):
'''This is a simple shortcut wrapper for getaddrinfo'''
ai = socket.getaddrinfo(address, port, 0, 0, 0, socket.AI_NUMERICHOST)[0]
return ai[4]
class RequestContextTest(unittest.TestCase):
def test_construct(self):
# Construct the context from IPv4/IPv6 addresses, check the object
# by printing it.
self.assertEqual('<isc.acl.dns.RequestContext object, ' + \
'remote_addr=[192.0.2.1]:53001>',
RequestContext(('192.0.2.1', 53001)).__str__())
self.assertEqual('<isc.acl.dns.RequestContext object, ' + \
'remote_addr=[2001:db8::1234]:53006>',
RequestContext(('2001:db8::1234', 53006,
0, 0)).__str__())
# Unusual case: port number overflows (this constructor allows that,
# although it should be rare anyway; the socket address should
# normally come from the Python socket module.
self.assertEqual('<isc.acl.dns.RequestContext object, ' + \
'remote_addr=[192.0.2.1]:0>',
RequestContext(('192.0.2.1', 65536)).__str__())
# same test using socket.getaddrinfo() to ensure it accepts the sock
# address representation used in the Python socket module.
self.assertEqual('<isc.acl.dns.RequestContext object, ' + \
'remote_addr=[192.0.2.1]:53001>',
RequestContext(get_sockaddr('192.0.2.1',
53001)).__str__())
self.assertEqual('<isc.acl.dns.RequestContext object, ' + \
'remote_addr=[2001:db8::1234]:53006>',
RequestContext(get_sockaddr('2001:db8::1234',
53006)).__str__())
#
# Invalid parameters (in our expected usage this should not happen
# because the sockaddr would come from the Python socket module, but
# validation should still be performed correctly)
#
# not a tuple
self.assertRaises(TypeError, RequestContext, 1)
# invalid number of parameters
self.assertRaises(TypeError, RequestContext, ('192.0.2.1', 53), 0)
# tuple is not in the form of sockaddr
self.assertRaises(TypeError, RequestContext, (0, 53))
self.assertRaises(TypeError, RequestContext, ('192.0.2.1', 'http'))
self.assertRaises(TypeError, RequestContext, ('::', 0, 'flow', 0))
# invalid address
self.assertRaises(Error, RequestContext, ('example.com', 5300))
self.assertRaises(Error, RequestContext, ('192.0.2.1.1', 5300))
self.assertRaises(Error, RequestContext, ('2001:db8:::1', 5300))
class RequestACLTest(unittest.TestCase):
def test_request_loader(self):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment