mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 05:55:28 +00:00
[trac800] Interface and tests for the sockcreator parser
This commit is contained in:
@@ -902,7 +902,7 @@ AC_OUTPUT([doc/version.ent
|
||||
src/bin/zonemgr/run_b10-zonemgr.sh
|
||||
src/bin/stats/stats.py
|
||||
src/bin/stats/stats_httpd.py
|
||||
src/bin/bind10/bind10.py
|
||||
src/bin/bind10/bind10_src.py
|
||||
src/bin/bind10/run_bind10.sh
|
||||
src/bin/bind10/tests/bind10_test.py
|
||||
src/bin/bindctl/run_bindctl.sh
|
||||
|
@@ -1,7 +1,11 @@
|
||||
SUBDIRS = . tests
|
||||
|
||||
sbin_SCRIPTS = bind10
|
||||
CLEANFILES = bind10 bind10.pyc bind10_messages.py bind10_messages.pyc
|
||||
CLEANFILES = bind10 bind10_src.pyc bind10_messages.py bind10_messages.pyc \
|
||||
sockcreator.pyc
|
||||
|
||||
python_PYTHON = __init__.py sockcreator.py
|
||||
pythondir = $(pyexecdir)/bind10
|
||||
|
||||
pkglibexecdir = $(libexecdir)/@PACKAGE@
|
||||
pyexec_DATA = bind10_messages.py
|
||||
@@ -24,9 +28,9 @@ bind10_messages.py: bind10_messages.mes
|
||||
$(top_builddir)/src/lib/log/compiler/message -p $(top_srcdir)/src/bin/bind10/bind10_messages.mes
|
||||
|
||||
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
|
||||
bind10: bind10.py
|
||||
bind10: bind10_src.py
|
||||
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
|
||||
-e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bind10.py >$@
|
||||
-e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bind10_src.py >$@
|
||||
chmod a+x $@
|
||||
|
||||
pytest:
|
||||
|
0
src/bin/bind10/__init__.py
Normal file
0
src/bin/bind10/__init__.py
Normal file
78
src/bin/bind10/sockcreator.py
Normal file
78
src/bin/bind10/sockcreator.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Module that comunicates with the priviledget socket creator (b10-sockcreator).
|
||||
"""
|
||||
|
||||
class CreatorError(Exception):
|
||||
"""
|
||||
Exception for socket creator related errors.
|
||||
|
||||
It has two members: fatal and errno and they are just holding the values
|
||||
passed to the __init__ function.
|
||||
"""
|
||||
|
||||
def __init__(self, message, fatal, errno=None):
|
||||
"""
|
||||
Creates the exception. The message argument is the usual string.
|
||||
The fatal one tells if the error is fatal (eg. the creator crashed)
|
||||
and errno is the errno value returned from socket creator, if
|
||||
applicable.
|
||||
"""
|
||||
Exception.__init__(self, message)
|
||||
self.fatal = fatal
|
||||
self.errno = errno
|
||||
|
||||
class Parser:
|
||||
"""
|
||||
This class knows the sockcreator language. It creates commands, sends them
|
||||
and receives the answers and parses them.
|
||||
|
||||
It does not start it, the communication channel must be provided.
|
||||
|
||||
In theory, anything here can throw a fatal CreatorError exception, but it
|
||||
happens only in case something like the creator process crashes. Any other
|
||||
occations are mentioned explicitly.
|
||||
"""
|
||||
|
||||
def __init__(self, creator_socket):
|
||||
"""
|
||||
Creates the parser. The creator_socket is socket to the socket creator
|
||||
process that will be used for communication. However, the object must
|
||||
have a read_fd() method to read the file descriptor. This slightly
|
||||
unusual modification of socket object is used to easy up testing.
|
||||
"""
|
||||
pass # TODO Implement
|
||||
|
||||
def terminate(self):
|
||||
"""
|
||||
Asks the creator process to terminate and waits for it to close the
|
||||
socket. Does not return anything.
|
||||
"""
|
||||
pass # TODO Implement
|
||||
|
||||
def get_socket(self, address, port, socktype):
|
||||
"""
|
||||
Asks the socket creator process to create a socket. Pass an address
|
||||
(the isc.net.IPaddr object), port number and socket type (either
|
||||
string "UDP", "TCP" or constant socket.SOCK_DGRAM or
|
||||
socket.SOCK_STREAM.
|
||||
|
||||
Blocks until it is provided by the socket creator process (which
|
||||
should be fast, as it is on localhost) and returns the file descriptor
|
||||
number. It raises a CreatorError exception if the creation fails.
|
||||
"""
|
||||
pass # TODO Implement
|
@@ -1,7 +1,7 @@
|
||||
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
|
||||
#PYTESTS = args_test.py bind10_test.py
|
||||
# NOTE: this has a generated test found in the builddir
|
||||
PYTESTS = bind10_test.py
|
||||
PYTESTS = bind10_test.py sockcreator_test.py
|
||||
EXTRA_DIST = $(PYTESTS)
|
||||
|
||||
# If necessary (rare cases), explicitly specify paths to dynamic libraries
|
||||
@@ -21,7 +21,7 @@ endif
|
||||
for pytest in $(PYTESTS) ; do \
|
||||
echo Running test: $$pytest ; \
|
||||
$(LIBRARY_PATH_PLACEHOLDER) \
|
||||
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/bind10 \
|
||||
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10 \
|
||||
BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
|
||||
$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
|
||||
done
|
||||
|
@@ -13,7 +13,7 @@
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from bind10 import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file, _BASETIME
|
||||
from bind10_src import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file, _BASETIME
|
||||
|
||||
# XXX: environment tests are currently disabled, due to the preprocessor
|
||||
# setup that we have now complicating the environment
|
||||
|
193
src/bin/bind10/tests/sockcreator_test.py
Normal file
193
src/bin/bind10/tests/sockcreator_test.py
Normal file
@@ -0,0 +1,193 @@
|
||||
# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Tests for the bind10.sockcreator module.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import struct
|
||||
import socket
|
||||
from bind10.sockcreator import Parser, CreatorError
|
||||
from isc.net.addr import IPAddr
|
||||
|
||||
class FakeCreator:
|
||||
"""
|
||||
Class emulating the socket to the socket creator. It can be given expected
|
||||
data to receive (and check) and responses to give to the Parser class
|
||||
during testing.
|
||||
"""
|
||||
|
||||
class InvalidPlan(Exception):
|
||||
"""
|
||||
Raised when someone wants to recv when sending is planned or vice
|
||||
versa.
|
||||
"""
|
||||
pass
|
||||
|
||||
class InvalidData(Exception):
|
||||
"""
|
||||
Raises when the data passed to sendall are not the same as expected.
|
||||
"""
|
||||
pass
|
||||
|
||||
def __init__(self, plan):
|
||||
"""
|
||||
Create the object. The plan variable contains list of expected actions,
|
||||
in form:
|
||||
|
||||
[('r', 'Data to return from recv'), ('s', 'Data expected on sendall'),
|
||||
, ('d', 'File descriptor number to return from read_sock'), ('e',
|
||||
None), ...]
|
||||
|
||||
It modifies the array as it goes.
|
||||
"""
|
||||
self.__plan = plan
|
||||
|
||||
def __get_plan(self, expected):
|
||||
if len(self.__plan) == 0:
|
||||
raise InvalidPlan('Nothing more planned')
|
||||
(kind, data) = self.__plan[0]
|
||||
if kind == 'e':
|
||||
raise socket.error('False socket error')
|
||||
if kind != expected:
|
||||
raise InvalidPlan('Planned ' + kind + ', but ' + expected +
|
||||
'requested')
|
||||
return data
|
||||
|
||||
def recv(self, maxsize):
|
||||
"""
|
||||
Emulate recv. Returs maxsize bytes from the current recv plan. If
|
||||
there are data left from previous recv call, it is used first.
|
||||
|
||||
If no recv is planned, raises InvalidPlan.
|
||||
"""
|
||||
data = self.__get_plan('r')
|
||||
result, rest = data[:maxsize], data[maxsize:]
|
||||
if len(rest) > 0:
|
||||
self.__plan[0] = ('r', rest)
|
||||
else:
|
||||
self.__plan.pop(0)
|
||||
return result
|
||||
|
||||
def read_fd(self):
|
||||
"""
|
||||
Emulate the reading of file descriptor. Returns one from a plan.
|
||||
|
||||
It raises InvalidPlan if no socket is planned now.
|
||||
"""
|
||||
fd = self.__get_plan('f')
|
||||
self.__plan.pop(0)
|
||||
return fd
|
||||
|
||||
def sendall(self, data):
|
||||
"""
|
||||
Checks that the data passed are correct according to plan. It raises
|
||||
InvalidData if the data differs or InvalidPlan when sendall is not
|
||||
expected.
|
||||
"""
|
||||
planned = self.__get_plan('s')
|
||||
dlen = len(data)
|
||||
prefix, rest = planned[:dlen], planned[dlen:]
|
||||
if prefix != data:
|
||||
raise InvalidData('Expected "' + str(prefix)+ '", got "' +
|
||||
str(data) + '"')
|
||||
if len(rest) > 0:
|
||||
self.__plan[0] = ('s', rest)
|
||||
else:
|
||||
self.__plan.pop(0)
|
||||
def all_used(self):
|
||||
"""
|
||||
Returns if the whole plan was consumed.
|
||||
"""
|
||||
return len(self.__plan) == 0
|
||||
|
||||
class ParserTests(unittest.TestCase):
|
||||
"""
|
||||
Testcases for the Parser class.
|
||||
"""
|
||||
def test_terminate(self):
|
||||
"""
|
||||
Test if the command to terminate is correct and it waits for reading the
|
||||
EOF.
|
||||
"""
|
||||
creator = FakeCreator([('s', b'T'), ('r', b'')])
|
||||
parser = Parser(creator)
|
||||
self.assertEqual(None, parser.terminate())
|
||||
self.assertTrue(creator.all_used())
|
||||
|
||||
def test_crash(self):
|
||||
"""
|
||||
Tests that the parser correctly raises exception when it crashes
|
||||
unexpectedly.
|
||||
"""
|
||||
creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'')])
|
||||
parser = Parser(creator)
|
||||
with self.assertRaises(CreatorError) as cm:
|
||||
parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
|
||||
self.assertTrue(creator.all_used())
|
||||
# Is the exception correct?
|
||||
self.assertTrue(cm.exception.fatal)
|
||||
self.assertEqual(None, cm.exception.errno)
|
||||
|
||||
def test_error(self):
|
||||
"""
|
||||
Tests that the parser correctly raises non-fatal exception when
|
||||
the socket can not be created.
|
||||
"""
|
||||
# We split the int to see if it can cope with data coming in
|
||||
# different packets
|
||||
intpart = struct.pack('@i', 42)
|
||||
creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'ES' +
|
||||
intpart[:1]), ('r', intpart[1:])])
|
||||
parser = Parser(creator)
|
||||
with self.assertRaises(CreatorError) as cm:
|
||||
parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
|
||||
self.assertTrue(creator.all_used())
|
||||
# Is the exception correct?
|
||||
self.assertFalse(cm.exception.fatal)
|
||||
self.assertEqual(42, cm.exception.errno)
|
||||
|
||||
def __error(self, plan):
|
||||
creator = FakeCreator(plan)
|
||||
parser = Parser(creator)
|
||||
with self.assertRaises(CreatorError) as cm:
|
||||
parser.get_socket(IPAddr('0.0.0.0'), 0, socket.SOCK_DGRAM)
|
||||
self.assertTrue(creator.all_used())
|
||||
self.assertTrue(cm.exception.fatal)
|
||||
|
||||
def test_error_send(self):
|
||||
self.__error([('e', None)])
|
||||
|
||||
def test_error_recv(self):
|
||||
self.__error([('s', b'SU4\0\0\0\0\0\0'), ('e', None)])
|
||||
|
||||
def test_error_read_fd(self):
|
||||
self.__error([('s', b'SU4\0\0\0\0\0\0'), ('r', b'S'), ('e', None)])
|
||||
|
||||
def __create(self, addr, socktype, encoded):
|
||||
creator = FakeCreator([('s', b'S' + encoded), ('r', b'S'), ('f', 42)])
|
||||
parser = Parser(creator)
|
||||
self.assertEqual(42, parser.get_socket(IPAddr(addr), 42, socktype))
|
||||
|
||||
def test_create1(self):
|
||||
self.__create('192.0.2.0', 'UDP', b'U4\0\x2A\xC0\0\x02\0')
|
||||
|
||||
def test_create2(self):
|
||||
self.__create('2001:db8::', socket.SOCK_STREAM,
|
||||
b'T6\0\x2A\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\0\0')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Reference in New Issue
Block a user