Repository: qpid-dispatch Updated Branches: refs/heads/master 9dcf20ca0 -> a62be32f1
DISPATCH-1013 - Enable vhost policies to be defined on router config Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/a62be32f Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/a62be32f Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/a62be32f Branch: refs/heads/master Commit: a62be32f143054ad2b10573abc2004b9752a3fe0 Parents: 9dcf20c Author: Fernando Giorgetti <[email protected]> Authored: Fri May 25 17:02:52 2018 -0300 Committer: Fernando Giorgetti <[email protected]> Committed: Tue Jun 5 15:21:49 2018 -0300 ---------------------------------------------------------------------- .../qpid_dispatch_internal/management/config.py | 18 ++++- .../policy/policy_local.py | 4 +- tests/system_test.py | 18 ++++- tests/system_tests_policy.py | 76 +++++++++++++++++++- 4 files changed, 107 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/a62be32f/python/qpid_dispatch_internal/management/config.py ---------------------------------------------------------------------- diff --git a/python/qpid_dispatch_internal/management/config.py b/python/qpid_dispatch_internal/management/config.py index ee76ce6..8cf1940 100644 --- a/python/qpid_dispatch_internal/management/config.py +++ b/python/qpid_dispatch_internal/management/config.py @@ -41,6 +41,9 @@ from qpid_dispatch_internal.compat import PY_TEXT_TYPE class Config(object): """Load config entities from qdrouterd.conf and validated against L{QdSchema}.""" + # static property to control depth level while reading the entities + child_level = 0 + def __init__(self, filename=None, schema=QdSchema(), raw_json=False): self.schema = schema self.config_types = [et for et in dict_itervalues(schema.entity_types) @@ -68,9 +71,10 @@ class Config(object): @staticmethod def _parse(lines): """Parse config file format into a section list""" - begin = re.compile(r'([\w-]+)[ \t]*{') # WORD { - end = re.compile(r'}') # } - attr = re.compile(r'([\w-]+)[ \t]*:[ \t]*(.+)') # WORD1: VALUE + begin = re.compile(r'([\w-]+)[ \t]*{[ \t]*($|#)') # WORD { + end = re.compile(r'^}') # } + attr = re.compile(r'([\w-]+)[ \t]*:[ \t]*(.+)') # WORD1: VALUE + child = re.compile(r'([\$]*[\w-]+)[ \t]*:[ \t]*{[ \t]*($|#)') # WORD: { # The 'pattern:' and 'bindingKey:' attributes in the schema are special # snowflakes. They allow '#' characters in their value, so they cannot @@ -85,6 +89,14 @@ class Config(object): return "" if line.split(':')[0].strip() in special_snowflakes: line = re.sub(hash_ok, r'"\1": "\2",', line) + elif child.search(line): + line = line.split('#')[0].strip() + line = re.sub(child, r'"\1": {', line) + Config.child_level += 1 + elif end.search(line) and Config.child_level > 0: + line = line.split('#')[0].strip() + line = re.sub(end, r'},', line) + Config.child_level -= 1 else: line = line.split('#')[0].strip() line = re.sub(begin, r'["\1", {', line) http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/a62be32f/python/qpid_dispatch_internal/policy/policy_local.py ---------------------------------------------------------------------- diff --git a/python/qpid_dispatch_internal/policy/policy_local.py b/python/qpid_dispatch_internal/policy/policy_local.py index d3d35f4..10bd6c4 100644 --- a/python/qpid_dispatch_internal/policy/policy_local.py +++ b/python/qpid_dispatch_internal/policy/policy_local.py @@ -268,7 +268,7 @@ class PolicyCompiler(object): errors.append("Policy vhost '%s' user group '%s' option '%s' has error '%s'." % (vhostname, usergroup, key, cerror[0])) return False - policy_out[key] = val + policy_out[key] = int(val) elif key == PolicyKeys.KW_REMOTE_HOSTS: # Conection groups are lists of IP addresses that need to be # converted into binary structures for comparisons. @@ -280,6 +280,8 @@ class PolicyCompiler(object): PolicyKeys.KW_ALLOW_DYNAMIC_SRC, PolicyKeys.KW_ALLOW_USERID_PROXY ]: + if type(val) in [unicode, str] and val.lower() in ['true', 'false']: + val = True if val == 'true' else False if not type(val) is bool: errors.append("Policy vhost '%s' user group '%s' option '%s' has illegal boolean value '%s'." % (vhostname, usergroup, key, val)) http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/a62be32f/tests/system_test.py ---------------------------------------------------------------------- diff --git a/tests/system_test.py b/tests/system_test.py index 37056b6..e4e1109 100755 --- a/tests/system_test.py +++ b/tests/system_test.py @@ -308,10 +308,22 @@ class Qdrouterd(Process): def __str__(self): """Generate config file content. Calls default() first.""" - def props(p): - return "".join([" %s: %s\n"%(k, v) for k, v in dict_iteritems(p)]) + def tabs(level): + return " " * level + + def sub_elem(l, level): + return "".join(["%s%s: {\n%s%s}\n" % (tabs(level), n, props(p, level + 1), tabs(level)) for n, p in l]) + + def child(v, level): + return "{\n%s%s}" % (sub_elem(v, level), tabs(level - 1)) + + def props(p, level): + return "".join( + ["%s%s: %s\n" % (tabs(level), k, v if not isinstance(v, list) else child(v, level + 1)) for k, v in + dict_iteritems(p)]) + self.defaults() - return "".join(["%s {\n%s}\n"%(n, props(p)) for n, p in self]) + return "".join(["%s {\n%s}\n"%(n, props(p, 1)) for n, p in self]) def __init__(self, name=None, config=Config(), pyinclude=None, wait=True, perform_teardown=True): """ http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/a62be32f/tests/system_tests_policy.py ---------------------------------------------------------------------- diff --git a/tests/system_tests_policy.py b/tests/system_tests_policy.py index 07ed91e..9fdd43b 100644 --- a/tests/system_tests_policy.py +++ b/tests/system_tests_policy.py @@ -26,8 +26,8 @@ import unittest as unittest import os, json from system_test import TestCase, Qdrouterd, main_module, Process, TIMEOUT, DIR from subprocess import PIPE, STDOUT -from proton import ConnectionException -from proton.utils import BlockingConnection, LinkDetached +from proton import ConnectionException, Timeout +from proton.utils import BlockingConnection, LinkDetached, SyncRequestResponse from qpid_dispatch_internal.policy.policy_util import is_ipv6_enabled from qpid_dispatch_internal.compat import dict_iteritems @@ -905,5 +905,77 @@ class PolicyHostamePatternTest(TestCase): self.assertFalse("222222" in qdm_out) +class VhostPolicyFromRouterConfig(TestCase): + """ + Verify that connections beyond the vhost limit are denied. + Differently than global maxConnections, opening a connection + does not raise a ConnectionException, but when an attempt to + create a sync request and response client is made after limit + is reached, the connection times out. + """ + @classmethod + def setUpClass(cls): + """Start the router""" + super(VhostPolicyFromRouterConfig, cls).setUpClass() + config = Qdrouterd.Config([ + ('router', {'mode': 'standalone', 'id': 'QDR.Policy'}), + ('listener', {'port': cls.tester.get_port()}), + ('policy', {'maxConnections': 100, 'enableVhostPolicy': 'true'}), + ('vhost', { + 'hostname': '0.0.0.0', 'maxConnections': 2, + 'allowUnknownUser': 'true', + 'groups': [( + '$default', { + 'users': '*', 'remoteHosts': '*', + 'sources': '*', 'targets': '*', + 'allowDynamicSource': 'true' + } + ), ( + 'anonymous', { + 'users': 'anonymous', 'remoteHosts': '*', + 'sources': '*', 'targets': '*', + 'allowDynamicSource': 'true', + 'allowAnonymousSender': 'true' + } + )] + }) + ]) + + cls.router = cls.tester.qdrouterd('vhost-conn-limit-router', config, wait=True) + + def address(self): + return self.router.addresses[0] + + def test_verify_vhost_maximum_connections(self): + addr = "%s/$management" % self.address() + timeout = 5 + + # two connections should be ok + denied = False + try: + bc1 = SyncRequestResponse(BlockingConnection(addr, timeout=timeout)) + bc2 = SyncRequestResponse(BlockingConnection(addr, timeout=timeout)) + except ConnectionException: + denied = True + except Timeout: + denied = True + + self.assertFalse(denied) # assert connections were opened + + # third connection should be denied + denied = False + try: + bc3 = SyncRequestResponse(BlockingConnection(addr, timeout=timeout)) + except ConnectionException: + denied = True + except Timeout: + denied = True + + self.assertTrue(denied) # assert if connection that should not open did open + + bc1.connection.close() + bc2.connection.close() + + if __name__ == '__main__': unittest.main(main_module()) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
