You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ch...@apache.org on 2016/01/27 23:28:29 UTC

[1/5] qpid-dispatch git commit: settings sources and targets are strings. cut/paste error in test policy

Repository: qpid-dispatch
Updated Branches:
  refs/heads/crolke-DISPATCH-188-1 6bfd1272d -> dfb8cfda4


settings sources and targets are strings. cut/paste error in test policy


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/11e9f078
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/11e9f078
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/11e9f078

Branch: refs/heads/crolke-DISPATCH-188-1
Commit: 11e9f0784c975b44e00155bb21734886237d8003
Parents: 6bfd127
Author: Chuck Rolke <cr...@redhat.com>
Authored: Mon Jan 25 15:31:54 2016 -0500
Committer: Chuck Rolke <cr...@redhat.com>
Committed: Mon Jan 25 15:31:54 2016 -0500

----------------------------------------------------------------------
 python/qpid_dispatch/management/qdrouter.json | 4 ++--
 tests/policy-1/test-router-with-policy.json   | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/11e9f078/python/qpid_dispatch/management/qdrouter.json
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch/management/qdrouter.json b/python/qpid_dispatch/management/qdrouter.json
index 923cf80..9565ba7 100644
--- a/python/qpid_dispatch/management/qdrouter.json
+++ b/python/qpid_dispatch/management/qdrouter.json
@@ -1170,13 +1170,13 @@
                     "create": true
                 },
                 "sources": {
-                    "type": "list",
+                    "type": "string",
                     "description": "List of Source addresses allowed when creating receiving links.",
                     "required": false,
                     "create": true
                 },
                 "targets": {
-                    "type": "list",
+                    "type": "string",
                     "description": "List of Target addresses allowed when creating sending links.",
                     "required": false,
                     "create": true

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/11e9f078/tests/policy-1/test-router-with-policy.json
----------------------------------------------------------------------
diff --git a/tests/policy-1/test-router-with-policy.json b/tests/policy-1/test-router-with-policy.json
index 38a6f08..4988093 100644
--- a/tests/policy-1/test-router-with-policy.json
+++ b/tests/policy-1/test-router-with-policy.json
@@ -176,7 +176,7 @@
     }
   ],  ["policyAppSettings", {
       "applicationName": "photoserver",
-      "userGroupName":   "users",
+      "userGroupName":   "default",
       "maxFrameSize":     222222,
       "maxMessageSize":   222222,
       "maxSessionWindow": 222222,


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[5/5] qpid-dispatch git commit: Move policy code to qpid_dispatch_internal/policy/*. Remove policy test-as-main code.

Posted by ch...@apache.org.
Move policy code to qpid_dispatch_internal/policy/*.
Remove policy test-as-main code.


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/dfb8cfda
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/dfb8cfda
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/dfb8cfda

Branch: refs/heads/crolke-DISPATCH-188-1
Commit: dfb8cfda47daebb95f2487dd85aa9f1b2685281a
Parents: 2fb2ef4
Author: Chuck Rolke <cr...@redhat.com>
Authored: Wed Jan 27 17:27:05 2016 -0500
Committer: Chuck Rolke <cr...@redhat.com>
Committed: Wed Jan 27 17:27:05 2016 -0500

----------------------------------------------------------------------
 .../qpid_dispatch_internal/management/agent.py  |   2 +-
 .../management/policy_local.py                  | 713 -------------------
 .../management/policy_util.py                   | 335 ---------
 .../qpid_dispatch_internal/policy/__init__.py   |  20 +
 .../policy/policy_local.py                      | 593 +++++++++++++++
 .../policy/policy_util.py                       | 335 +++++++++
 tests/system_tests_policy.py                    |   4 +-
 7 files changed, 951 insertions(+), 1051 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/management/agent.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/agent.py b/python/qpid_dispatch_internal/management/agent.py
index 0d53bc1..013eddb 100644
--- a/python/qpid_dispatch_internal/management/agent.py
+++ b/python/qpid_dispatch_internal/management/agent.py
@@ -81,7 +81,7 @@ from .schema import ValidationError, SchemaEntity, EntityType
 from .qdrouter import QdSchema
 from ..router.message import Message
 from ..router.address import Address
-from policy_local import PolicyLocal
+from ..policy.policy_local import PolicyLocal
 
 
 def dictstr(d):

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/management/policy_local.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/policy_local.py b/python/qpid_dispatch_internal/management/policy_local.py
deleted file mode 100644
index 6c455a7..0000000
--- a/python/qpid_dispatch_internal/management/policy_local.py
+++ /dev/null
@@ -1,713 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License
-#
-
-"""
-
-"""
-
-import sys, os
-import json
-import optparse
-from policy_util import PolicyError, HostStruct, HostAddr, PolicyAppConnectionMgr
-import pdb #; pdb.set_trace()
-
-
-
-"""
-Entity implementing the business logic of user connection/access policy.
-"""
-
-#
-#
-class PolicyKeys(object):
-    # Common key words
-    KW_IGNORED_NAME             = "name"
-    KW_IGNORED_IDENTITY         = "identity"
-    KW_IGNORED_TYPE             = "type"
-    KW_APPLICATION_NAME         = "applicationName"
-
-    # Policy ruleset key words
-    KW_MAXCONN                  = "maxConnections"
-    KW_MAXCONNPERHOST           = "maxConnPerHost"
-    KW_MAXCONNPERUSER           = "maxConnPerUser"
-    KW_USER_GROUPS              = "userGroups"
-    KW_CONNECTION_GROUPS        = "connectionGroups"
-    KW_CONNECTION_POLICY        = "connectionIngressPolicies"
-    KW_CONNECTION_ALLOW_DEFAULT = "connectionAllowDefault"
-
-    # Policy settings key words
-    KW_USER_GROUP_NAME          = "userGroupName"
-    KW_MAX_FRAME_SIZE           = "maxFrameSize"
-    KW_MAX_MESSAGE_SIZE         = "maxMessageSize"
-    KW_MAX_SESSION_WINDOW       = "maxSessionWindow"
-    KW_MAX_SESSIONS             = "maxSessions"
-    KW_MAX_SENDERS              = "maxSenders"
-    KW_MAX_RECEIVERS            = "maxReceivers"
-    KW_ALLOW_DYNAMIC_SRC        = "allowDynamicSrc"
-    KW_ALLOW_ANONYMOUS_SENDER   = "allowAnonymousSender"
-    KW_SOURCES                  = "sources"
-    KW_TARGETS                  = "targets"
-
-    # What settings does a user get when allowed to connect but
-    # not restricted by a user group?
-    KW_DEFAULT_SETTINGS         = "default"
-
-    # Config file separator character for two IP addresses in a range
-    KC_CONFIG_IP_SEP            = "-"
-
-    # Config file separator character for names in a list
-    KC_CONFIG_LIST_SEP          = ","
-#
-#
-class PolicyCompiler(object):
-    """
-    Validate incoming configuration for legal schema.
-    - Warn about section options that go unused.
-    - Disallow negative max connection numbers.
-    - Check that connectionOrigins resolve to IP hosts
-    """
-
-    allowed_ruleset_options = [
-        PolicyKeys.KW_IGNORED_NAME,
-        PolicyKeys.KW_IGNORED_IDENTITY,
-        PolicyKeys.KW_IGNORED_TYPE,
-        PolicyKeys.KW_APPLICATION_NAME,
-        PolicyKeys.KW_MAXCONN,
-        PolicyKeys.KW_MAXCONNPERHOST,
-        PolicyKeys.KW_MAXCONNPERUSER,
-        PolicyKeys.KW_USER_GROUPS,
-        PolicyKeys.KW_CONNECTION_GROUPS,
-        PolicyKeys.KW_CONNECTION_POLICY,
-        PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT
-        ]
-
-    allowed_settings_options = [
-        PolicyKeys.KW_IGNORED_NAME,
-        PolicyKeys.KW_IGNORED_IDENTITY,
-        PolicyKeys.KW_IGNORED_TYPE,
-        PolicyKeys.KW_APPLICATION_NAME,
-        PolicyKeys.KW_USER_GROUP_NAME,
-        PolicyKeys.KW_MAX_FRAME_SIZE,
-        PolicyKeys.KW_MAX_MESSAGE_SIZE,
-        PolicyKeys.KW_MAX_SESSION_WINDOW,
-        PolicyKeys.KW_MAX_SESSIONS,
-        PolicyKeys.KW_MAX_SENDERS,
-        PolicyKeys.KW_MAX_RECEIVERS,
-        PolicyKeys.KW_ALLOW_DYNAMIC_SRC,
-        PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
-        PolicyKeys.KW_SOURCES,
-        PolicyKeys.KW_TARGETS
-        ]
-
-    def __init__(self):
-        """
-        Create a validator
-        """
-        pass
-
-
-    def validateNumber(self, val, v_min, v_max, errors):
-        """
-        Range check a numeric int policy value
-        @param[in] val policy value to check
-        @param[in] v_min minumum value
-        @param[in] v_max maximum value. zero disables check
-        @param[out] errors failure message
-        @return v_min <= val <= v_max
-        """
-        error = ""
-        try:
-            v_int = int(val)
-        except Exception, e:
-            errors.append("Value '%s' does not resolve to an integer." % val)
-            return False
-        if v_int < v_min:
-            errors.append("Value '%s' is below minimum '%s'." % (val, v_min))
-            return False
-        if v_max > 0 and v_int > v_max:
-            errors.append("Value '%s' is above maximum '%s'." % (val, v_max))
-            return False
-        return True
-
-
-    def compile_connection_groups(self, name, submap, warnings, errors):
-        """
-        Handle an connectionGroups submap.
-        Each origin value is verified. On a successful run the submap
-        is replaced parsed lists of HostAddr objects.
-        @param[in] name application name
-        @param[in,out] submap user input origin list as text strings
-                       modified in place to be list of HostAddr objects
-        @param[out] warnings nonfatal irregularities observed
-        @param[out] errors descriptions of failure
-        @return - origins is usable. If True then warnings[] may contain useful
-                  information about fields that are ignored. If False then
-                  warnings[] may contain info and errors[0] will hold the
-                  description of why the origin was rejected.
-        """
-        key = PolicyKeys.KW_CONNECTION_GROUPS
-        newmap = {}
-        for coname in submap:
-            try:
-                ostr = str(submap[coname])
-                olist = [x.strip(' ') for x in ostr.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
-                newmap[coname] = []
-                for co in olist:
-                    coha = HostAddr(co, PolicyKeys.KC_CONFIG_IP_SEP)
-                    newmap[coname].append(coha)
-            except Exception, e:
-                errors.append("Application '%s' option '%s' connectionOption '%s' failed to translate: '%s'." %
-                                (name, key, coname, e))
-                return False
-        submap.update(newmap)
-        return True
-
-
-    def compile_app_settings(self, appname, usergroup, policy_in, policy_out, warnings, errors):
-        """
-        Compile a schema from processed json format to local internal format.
-        @param[in] name application name
-        @param[in] policy_in user config settings
-        @param[out] policy_out validated Internal format
-        @param[out] warnings nonfatal irregularities observed
-        @param[out] errors descriptions of failure
-        @return - settings are usable. If True then warnings[] may contain useful
-                  information about fields that are ignored. If False then
-                  warnings[] may contain info and errors[0] will hold the
-                  description of why the policy was rejected.
-        """
-        cerror = []
-        for key, val in policy_in.iteritems():
-            if key not in self.allowed_settings_options:
-                warnings.append("Application '%s' user group '%s' option '%s' is ignored." %
-                                (appname, usergroup, key))
-            if key in [PolicyKeys.KW_MAX_FRAME_SIZE,
-                       PolicyKeys.KW_MAX_MESSAGE_SIZE,
-                       PolicyKeys.KW_MAX_RECEIVERS,
-                       PolicyKeys.KW_MAX_SENDERS,
-                       PolicyKeys.KW_MAX_SESSION_WINDOW,
-                       PolicyKeys.KW_MAX_SESSIONS
-                       ]:
-                if not self.validateNumber(val, 0, 0, cerror):
-                    errors.append("Application '%s' user group '%s' option '%s' has error '%s'." %
-                                  (appname, usergroup, key, cerror[0]))
-                    return False
-                policy_out[key] = val
-            elif key in [PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
-                         PolicyKeys.KW_ALLOW_DYNAMIC_SRC
-                         ]:
-                if not type(val) is bool:
-                    errors.append("Application '%s' user group '%s' option '%s' has illegal boolean value '%s'." %
-                                  (appname, usergroup, key, val))
-                    return False
-                policy_out[key] = val
-            elif key in [PolicyKeys.KW_SOURCES,
-                         PolicyKeys.KW_TARGETS
-                         ]:
-                val = [x.strip(' ') for x in val.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
-                # deduplicate address lists
-                val = list(set(val))
-                policy_out[key] = val
-        return True
-
-
-    def compile_access_ruleset(self, name, policy_in, policy_out, warnings, errors):
-        """
-        Compile a schema from processed json format to local internal format.
-        @param[in] name application name
-        @param[in] policy_in raw policy to be validated
-        @param[out] policy_out validated Internal format
-        @param[out] warnings nonfatal irregularities observed
-        @param[out] errors descriptions of failure
-        @return - policy is usable. If True then warnings[] may contain useful
-                  information about fields that are ignored. If False then
-                  warnings[] may contain info and errors[0] will hold the
-                  description of why the policy was rejected.
-        """
-        cerror = []
-        # validate the options
-        for key, val in policy_in.iteritems():
-            if key not in self.allowed_ruleset_options:
-                warnings.append("Application '%s' option '%s' is ignored." %
-                                (name, key))
-            if key in [PolicyKeys.KW_MAXCONN,
-                       PolicyKeys.KW_MAXCONNPERHOST,
-                       PolicyKeys.KW_MAXCONNPERUSER
-                       ]:
-                if not self.validateNumber(val, 0, 65535, cerror):
-                    msg = ("Application '%s' option '%s' has error '%s'." % 
-                           (name, key, cerror[0]))
-                    errors.append(msg)
-                    return False
-                policy_out[key] = val
-            elif key in [PolicyKeys.KW_USER_GROUPS,
-                         PolicyKeys.KW_CONNECTION_GROUPS,
-                         PolicyKeys.KW_CONNECTION_POLICY
-                         ]:
-                try:
-                    if not type(val) is dict:
-                        errors.append("Application '%s' option '%s' must be of type 'dict' but is '%s'" %
-                                      (name, key, type(val)))
-                        return False
-                    if key == PolicyKeys.KW_CONNECTION_GROUPS:
-                        if not self.compile_connection_groups(name, val, warnings, errors):
-                            return False
-                    else:
-                        # deduplicate connectionIngressPolicy and userGroups lists
-                        for k,v in val.iteritems():
-                            v = [x.strip(' ') for x in v.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
-                            v = list(set(v))
-                            val[k] = v
-                    policy_out[key] = val
-                except Exception, e:
-                    errors.append("Application '%s' option '%s' error processing map: %s" %
-                                  (name, key, e))
-                    return False
-        return True
-
-class PolicyLocal(object):
-    """
-    The policy database.
-    """
-
-    def __init__(self):
-        """
-        Create instance
-        @params folder: relative path from __file__ to conf file folder
-        """
-        self.policydb = {}
-        self.settingsdb = {}
-        self.lookup_cache = {}
-        self.stats = {}
-        self.policy_compiler = PolicyCompiler()
-        self.name_lookup_cache = {}
-        self.blob_lookup_cache = {}
-
-    #
-    # Service interfaces
-    #
-    def create_ruleset(self, attributes):
-        """
-        Create named policy ruleset
-        @param[in] attributes: from config
-        """
-        warnings = []
-        diag = []
-        candidate = {}
-        name = attributes[PolicyKeys.KW_APPLICATION_NAME]
-        result = self.policy_compiler.compile_access_ruleset(name, attributes, candidate, warnings, diag)
-        if not result:
-            raise PolicyError( "Policy '%s' is invalid: %s" % (name, diag[0]) )
-        if len(warnings) > 0:
-            print ("LogMe: Application '%s' has warnings: %s" %
-                   (name, warnings))
-        self.policydb[name] = candidate
-        # TODO: Create stats
-
-    def create_settings(self, attributes):
-        """
-        Create named policy ruleset
-        @param[in] attributes: from config
-        """
-        warnings = []
-        diag = []
-        candidate = {}
-        app_name = attributes[PolicyKeys.KW_APPLICATION_NAME]
-        usergroup = attributes[PolicyKeys.KW_USER_GROUP_NAME]
-        result = self.policy_compiler.compile_app_settings(app_name, usergroup, attributes, candidate, warnings, diag)
-        if not result:
-            raise PolicyError( "Policy '%s' is invalid: %s" % (app_name, diag[0]) )
-        if len(warnings) > 0:
-            print ("LogMe: Application '%s' has warnings: %s" %
-                   (app_name, warnings))
-        if not app_name in self.settingsdb:
-            self.settingsdb[app_name] = {}
-        self.settingsdb[app_name][usergroup] = candidate  # create named settings
-
-    def policy_read(self, name):
-        """
-        Read policy for named application
-        @param[in] name application name
-        @return policy data in raw user format
-        """
-        return self.policydb[name]
-
-    def policy_update(self, name, policy):
-        """
-        Update named policy
-        @param[in] name application name
-        @param[in] policy data in raw user input
-        """
-        if not name in self.policydb:
-            raise PolicyError("Policy '%s' does not exist" % name)
-        self.policy_create(name, policy)
-
-    def policy_delete(self, name):
-        """
-        Delete named policy
-        @param[in] name application name
-        """
-        if not name in self.policydb:
-            raise PolicyError("Policy '%s' does not exist" % name)
-        del self.policydb[name]
-
-    #
-    # db enumerator
-    #
-    def policy_db_get_names(self):
-        """
-        Return a list of application names in this policy
-        """
-        return self.policydb.keys()
-
-
-    #
-    # Runtime query interface
-    #
-    def policy_aggregate_limits(self, upolicy, policy, settingname):
-        """
-        Force a max count value into user policy
-        param[in,out] upolicy user policy receiving aggregations
-        param[in] policy Internal policy holding settings to be aggregated
-        param[in] settingname setting of interest
-        """
-        if settingname in policy:
-            upolicy[settingname] = policy[settingname]
-
-    def policy_aggregate_policy_int(self, upolicy, appsettings, groups, settingname):
-        """
-        Pull int out of policy.policies[group] and install into upolicy.
-        Integers are set to max(new, existing)
-        param[in,out] upolicy user policy receiving aggregations
-        param[in] policy Internal policy holding settings to be aggregated
-        param[in] settingname setting of interest
-        """
-        for group in groups:
-            if group in appsettings:
-                rpol = appsettings[group]
-                if settingname in rpol:
-                    sp = rpol[settingname]
-                    if settingname in upolicy:
-                        up = upolicy[settingname]
-                        if sp > up:
-                            # policy bumps up user setting
-                            upolicy[settingname] = sp
-                        else:
-                            # user policy is already better
-                            pass
-                    else:
-                        # user policy doesn't have setting so force it
-                        upolicy[settingname] = sp
-                else:
-                    # no setting of this name in the group's policy
-                    pass
-            else:
-                # no policy for this group
-                pass
-
-    def policy_aggregate_policy_bool(self, upolicy, appsettings, groups, settingname):
-        """
-        Pull bool out of policy and install into upolicy if true
-        param[in,out] upolicy user policy receiving aggregations
-        param[in] policy Internal policy holding settings to be aggregated
-        param[in] settingname setting of interest
-        """
-        for group in groups:
-            if group in appsettings:
-                rpol = appsettings[group]
-                if settingname in rpol:
-                    if rpol[settingname]:
-                        upolicy[settingname] = True
-                else:
-                    # no setting of this name in the group's policy
-                    pass
-            else:
-                # no policy for this group
-                pass
-
-    def policy_aggregate_policy_list(self, upolicy, appsettings, groups, settingname):
-        """
-        Pull list out of policy and append into upolicy
-        param[in,out] upolicy user policy receiving aggregations
-        param[in] policy Internal policy holding settings to be aggregated
-        param[in] settingname setting of interest
-        """
-        for group in groups:
-            if group in appsettings:
-                rpol = appsettings[group]
-                if settingname in rpol:
-                    sp = rpol[settingname]
-                    if settingname in upolicy:
-                        upolicy[settingname].extend( sp )
-                        upolicy[settingname] = list(set(upolicy[settingname]))
-                    else:
-                        # user policy doesn't have setting so force it
-                        upolicy[settingname] = sp
-                else:
-                    # no setting of this name in the group's policy
-                    pass
-            else:
-                # no policy for this group
-                pass
-
-    #
-    #
-    def lookup_user(self, user, host, app, conn_name, policyname):
-        """
-        Lookup function called from C.
-        Determine if a user on host accessing app through AMQP Open is allowed
-        according to the policy access rules.
-        If allowed then return the policy settings name
-        @param[in] user connection authId
-        @param[in] host connection remote host numeric IP address as string
-        @param[in] app application user is accessing
-        @param[out] policyname name of the policy settings blob for this user
-        @return if allowed by policy
-        # Note: the upolicy[0] output is list of group names joined with '|'.
-        TODO: handle the AccessStats
-        """
-        try:
-            lookup_id = user + "|" + host + "|" + app
-            if lookup_id in self.name_lookup_cache:
-                policyname.append( self.name_lookup_cache[lookup_id] )
-                return True
-
-            if not app in self.policydb:
-                # TODO: ("LogMe: no policy defined for application %s" % app)
-                policyname.append("")
-                return False
-
-            settings = self.policydb[app]
-            # User allowed to connect from host?
-            allowed = False
-            restricted = False
-            uhs = HostStruct(host)
-            ugroups = []
-            if PolicyKeys.KW_USER_GROUPS in settings:
-                for r in settings[PolicyKeys.KW_USER_GROUPS]:
-                    if user in settings[PolicyKeys.KW_USER_GROUPS][r]:
-                        restricted = True
-                        ugroups.append(r)
-            uorigins = []
-            if PolicyKeys.KW_CONNECTION_POLICY in settings:
-                for ur in ugroups:
-                    if ur in settings[PolicyKeys.KW_CONNECTION_POLICY]:
-                        uorigins.extend(settings[PolicyKeys.KW_CONNECTION_POLICY][ur])
-            if PolicyKeys.KW_CONNECTION_GROUPS in settings:
-                for co in settings[PolicyKeys.KW_CONNECTION_GROUPS]:
-                    if co in uorigins:
-                        for cohost in settings[PolicyKeys.KW_CONNECTION_GROUPS][co]:
-                            if cohost.match_bin(uhs):
-                                allowed = True
-                                break
-                    if allowed:
-                        break
-            if not allowed and not restricted:
-                if PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT in settings:
-                    allowed = settings[PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT]
-            if not allowed:
-                return False
-            if not restricted:
-                ugroups.append(PolicyKeys.KW_DEFAULT_SETTINGS)
-            #
-            ugroups.sort()
-            result = "|".join(ugroups)
-            self.name_lookup_cache[lookup_id] = result
-            policyname.append(result)
-            return True
-
-        except Exception, e:
-            #print str(e)
-            #pdb.set_trace()
-            return False
-
-    def lookup_settings(self, appname, name, upolicy):
-        """
-        Given a settings name, return the aggregated policy blob.
-        @param[in] appname: application user is accessing
-        @param[in] name: user group name or concatenation of names of the policy settings blob
-        @param[out] upolicy: dict holding policy values - the settings blob
-        @return if allowed by policy
-        # Note: the upolicy output is a non-nested dict with settings of interest
-        # TODO: figure out decent defaults for upolicy settings that are undefined
-        """
-        try:
-            cachekey = appname + "|" + name
-            if cachekey in self.blob_lookup_cache:
-                upolicy.update( self.blob_lookup_cache[cachekey] )
-                return True
-            settings = self.settingsdb[appname]
-            ugroups = name.split("|")
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_FRAME_SIZE)
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_MESSAGE_SIZE)
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_SESSION_WINDOW)
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_SESSIONS)
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_SENDERS)
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_RECEIVERS)
-            self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.KW_ALLOW_DYNAMIC_SRC)
-            self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER)
-            self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.KW_SOURCES)
-            self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.KW_TARGETS)
-            c_upolicy = {}
-            c_upolicy.update(upolicy)
-            self.blob_lookup_cache[cachekey] = c_upolicy
-            return True
-        except Exception, e:
-            #print str(e)
-            #pdb.set_trace()
-            return False
-
-    def test_load_config(self):
-        ruleset_str = '["policyAccessRuleset", {"applicationName": "photoserver","maxConnections": 50,"maxConnPerUser": 5,"maxConnPerHost": 20,"userGroups": {"anonymous":       "anonymous","users":           "u1, u2","paidsubscribers": "p1, p2","test":            "zeke, ynot","admin":           "alice, bob, ellen","superuser":       "ellen"},"connectionGroups": {"Ten18":     "10.18.0.0-10.18.255.255","EllensWS":  "72.135.2.9","TheLabs":   "10.48.0.0-10.48.255.255, 192.168.100.0-192.168.100.255","localhost": "127.0.0.1, ::1","TheWorld":  "*"},"connectionIngressPolicies": {"anonymous":       "TheWorld","users":           "TheWorld","paidsubscribers": "TheWorld","test":            "TheLabs","admin":           "Ten18, TheLabs, localhost","superuser":       "EllensWS, localhost"},"connectionAllowDefault": true}]'
-        ruleset = json.loads(ruleset_str)
-
-        self.create_ruleset(ruleset[1])
-
-        settings_strs = []
-        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"anonymous",      "maxFrameSize": 111111,"maxMessageSize":   111111,"maxSessionWindow": 111111,"maxSessions":           1,"maxSenders":           11,"maxReceivers":         11,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": "public",                           "targets": ""}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"users",          "maxFrameSize": 222222,"maxMessageSize":   222222,"maxSessionWindow": 222222,"maxSessions":           2,"maxSenders":           22,"maxReceivers":         22,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": "public, private",                  "targets": "public"}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"paidsubscribers","maxFrameSize": 333333,"maxMessageSize":   333333,"maxSessionWindow": 333333,"maxSessions":           3,"maxSenders":           33,"maxReceivers":         33,"allowDynamicSrc":      true, "allowAnonymousSender": false,"sources": "public, private",                  "targets": "public, private"}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"test",           "maxFrameSize": 444444,"maxMessageSize":   444444,"maxSessionWindow": 444444,"maxSessions":           4,"maxSenders":           44,"maxReceivers":         44,"allowDynamicSrc":      true, "allowAnonymousSender": true, "sources": "private",                          "targets": "private"}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"admin",          "maxFrameSize": 555555,"maxMessageSize":   555555,"maxSessionWindow": 555555,"maxSessions":           5,"maxSenders":           55,"maxReceivers":         55,"allowDynamicSrc":      true, "allowAnonymousSender": true, "sources": "public, private, management",      "targets": "public, private, management"}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"superuser",      "maxFrameSize": 666666,"maxMessageSize":   666666,"maxSessionWindow": 666666,"maxSessions":           6,"maxSenders":           66,"maxReceivers":         66,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": "public, private, management, root","targets": "public, private, management, root"}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"default",        "maxFrameSize": 222222,"maxMessageSize":   222222,"maxSessionWindow": 222222,"maxSessions":           2,"maxSenders":           22,"maxReceivers":         22,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": "public, private",                  "targets": "public"}]')
-
-        for sstr in settings_strs:
-            settings = json.loads(sstr)
-            self.create_settings(settings[1])
-
-
-#
-# HACK ALERT: Temporary
-# Functions related to main
-#
-class ExitStatus(Exception):
-    """Raised if a command wants a non-0 exit status from the script"""
-    def __init__(self, status): self.status = status
-
-def main_except(argv):
-
-    def read_files(policy, path):
-        """
-        Read all .json conf files in path and create the policies they contain.
-        @param policy: The policy_local to receive the configuration.
-        @param path: The path relative to policy_local.py
-        """
-        apath = os.path.abspath(os.path.dirname(__file__))
-        apath = os.path.join(apath, path)
-        for i in os.listdir(apath):
-            if i.endswith(".json"):
-                read_file(policy, os.path.join(apath, i))
-
-    def read_file(policy, fn):
-        """
-        Read a qdrouterd config file and extract the policy sections.
-        @param policy: The policy_local to receive the configuration.
-        @param fn: absolute path to file
-        """
-        try:
-            with open(fn) as json_file:
-                cp = json.load(json_file)
-            for i in range(0, len(cp)):
-                if cp[i][0] == "policyAccessRuleset":
-                    policy.create_ruleset(cp[i][1])
-                elif cp[i][0] == "policyAppSettings":
-                    policy.create_settings(cp[i][1])
-                else:
-                    # some config option we don't care about
-                    pass
-        except Exception, e:
-            # complain but otherwise ignore errors
-            print("Error processing policy configuration file '%s' : %s" % (fn, e))
-
-    usage = "usage: %prog [options]\nExercise policy_local functions."
-    parser = optparse.OptionParser(usage=usage)
-    parser.set_defaults(folder="")
-    parser.add_option("-f", "--folder", action="store", type="string", dest="folder",
-                      help="Built-in configuration settings are loaded by default or by using an empty folder string."
-                      " Use '-f /some/path' to load a config from all .json files in that folder."
-                      " Paths may be absolute or relative to  policy_local.py.")
-    parser.add_option("-e", "--exercise", action="store_true", dest="exercise",
-                      help="Run canned tests. Expect canned tests to work with configs in ../../../tests/policy-1.")
-
-    (options, args) = parser.parse_args()
-
-    policy = PolicyLocal()
-
-    if options.folder == "":
-        # Empty folder name uses built-in configuration
-        policy.test_load_config()
-    else:
-        # Load all .json files in given folder
-        read_files(policy, options.folder)
-
-    print("Policy rulesets available: %s" % policy.policy_db_get_names())
-
-    if not options.exercise:
-        return
-
-    # Exercise a few functions
-    # Empty policy
-    policy2 = PolicyLocal()
-
-    print("Print some Policy details:")
-    for pname in policy.policy_db_get_names():
-        print("policy : %s" % pname)
-        p = ("%s" % policy.policy_read(pname))
-        print(p.replace('\\n', '\n'))
-
-    # Lookups
-    policynames = []
-    # pdb.set_trace()
-    res1 = policy.lookup_user('zeke', '192.168.100.5', 'photoserver', '192.168.100.5:33334', policynames)
-    print "\nLookup zeke from 192.168.100.5. Expecting True, result is %s" % res1
-    print "\nResulting policy expecting 'test', is: %s" % policynames[0]
-    # Hit the cache
-    policynames = []
-    res2  = policy.lookup_user('zeke', '192.168.100.5', 'photoserver', '192.168.100.5:33335', policynames)
-
-    policynames3 = []
-    res3 = policy.lookup_user('ellen', '72.135.2.9', 'photoserver', '72.135.2.9:33333', policynames3)
-    print "\nLookup ellen from 72.135.2.9. Expect true. Result is %s" % res3
-    print "Resulting policy is: %s" % policynames[0]
-
-    policynames = []
-    res4 = policy2.lookup_user('ellen', '72.135.2.9', 'photoserver', '72.135.2.9:33334', policynames)
-    print "\nLookup policy2 ellen from 72.135.2.9. Expect false. Result is %s" % res4
-
-    upolicy6 = {}
-    res6 = policy.lookup_settings('photoserver', policynames3[0], upolicy6)
-    res6a = upolicy6['maxFrameSize'] == 666666
-    print "\nNamed settings lookup result = %s, and value check = %s" % (res6, res6a)
-
-    print ("Tests success: %s" % (res1 and res2 and res3 and not res4 and res6 and res6a))
-
-
-def main(argv):
-    try:
-        main_except(argv)
-        return 0
-    except ExitStatus, e:
-        return e.status
-    except Exception, e:
-        print "%s: %s"%(type(e).__name__, e)
-        return 1
-
-if __name__ == "__main__":
-    sys.exit(main(sys.argv))

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/management/policy_util.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/policy_util.py b/python/qpid_dispatch_internal/management/policy_util.py
deleted file mode 100644
index edcd01f..0000000
--- a/python/qpid_dispatch_internal/management/policy_util.py
+++ /dev/null
@@ -1,335 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License
-#
-
-import sys, os
-import socket
-import binascii
-
-
-#
-#
-class PolicyError(Exception):
-    def __init__(self, value):
-        self.value = value
-    def __str__(self):
-        return repr(self.value)
-
-#
-#
-class HostStruct():
-    """
-    HostStruct represents a single, binary socket address from getaddrinfo
-        - name     : name given to constructor; numeric IP or host name
-        - saddr    : net name resolved by getaddrinfo; numeric IP
-        - family   : saddr.family; int
-        - binary   : saddr packed binary address; binary string
-    """
-    families = [socket.AF_INET]
-    famnames = ["IPv4"]
-    if socket.has_ipv6:
-        families.append(socket.AF_INET6)
-        famnames.append("IPv6")
-
-    def __init__(self, hostname):
-        """
-        Given a host name text string, return the socket info for it.
-        @param[in] hostname host IP address to parse
-        """
-        try:
-            res = socket.getaddrinfo(hostname, 0)
-            if len(res) == 0:
-                raise PolicyError("HostStruct: '%s' did not resolve to an IP address" % hostname)
-            foundFirst = False
-            saddr = ""
-            sfamily = socket.AF_UNSPEC
-            for i0 in range(0, len(res)):
-                family, dum0, dum1, dum2, sockaddr = res[i0]
-                if not foundFirst:
-                    if family in self.families:
-                        saddr = sockaddr[0]
-                        sfamily = family
-                        foundFirst = True
-                else:
-                    if family in self.families:
-                        if not saddr == sockaddr[0] or not sfamily == family:
-                            raise PolicyError("HostStruct: '%s' resolves to multiple IP addresses" %
-                                              hostname)
-
-            if not foundFirst:
-                raise PolicyError("HostStruct: '%s' did not resolve to one of the supported address family" %
-                        hostname)
-            self.name = hostname
-            self.saddr = saddr
-            self.family = sfamily
-            self.binary = socket.inet_pton(family, saddr)
-            return
-        except Exception, e:
-            raise PolicyError("HostStruct: '%s' failed to resolve: '%s'" %
-                              (hostname, e))
-
-    def __str__(self):
-        return self.name
-
-    def __repr__(self):
-        return self.__str__()
-
-    def dump(self):
-        return ("(%s, %s, %s, %s)" %
-                (self.name,
-                 self.saddr,
-                 "AF_INET" if self.family == socket.AF_INET else "AF_INET6",
-                 binascii.hexlify(self.binary)))
-
-#
-#
-class HostAddr():
-    """
-    Provide HostIP address ranges and comparison functions.
-    A HostIP may be:
-    - single address:      10.10.1.1
-    - a pair of addresses: 10.10.0.0,10.10.255.255
-    - a wildcard:          *
-    Only IPv4 and IPv6 are supported.
-    - No unix sockets.
-    HostIP names must resolve to a single IP address.
-    Address pairs define a range.
-    - The second address must be numerically larger than the first address.
-    - The addresses must be of the same address 'family', IPv4 or IPv6.
-    The wildcard '*' matches all address IPv4 or IPv6.
-    IPv6 support is conditional based on underlying OS network options.
-    Raises a PolicyError on validation error in constructor.
-    """
-
-    def has_ipv6(self):
-        return socket.has_ipv6
-
-    def __init__(self, hostspec, separator=","):
-        """
-        Parse host spec into binary structures to use for comparisons.
-        Validate the hostspec to enforce usage rules.
-        """
-        self.hoststructs = []
-
-        if hostspec == "*":
-            self.wildcard = True
-        else:
-            self.wildcard = False
-
-            hosts = [x.strip() for x in hostspec.split(separator)]
-
-            # hosts must contain one or two host specs
-            if len(hosts) not in [1, 2]:
-                raise PolicyError("hostspec must contain 1 or 2 host names")
-            self.hoststructs.append(HostStruct(hosts[0]))
-            if len(hosts) > 1:
-                self.hoststructs.append(HostStruct(hosts[1]))
-                if not self.hoststructs[0].family == self.hoststructs[1].family:
-                    raise PolicyError("mixed IPv4 and IPv6 host specs in range not allowed")
-                c0 = self.memcmp(self.hoststructs[0].binary, self.hoststructs[1].binary)
-                if c0 > 0:
-                    raise PolicyError("host specs in range must have lower numeric address first")
-
-    def __str__(self):
-        if self.wildcard:
-            return "*"
-        res = self.hoststructs[0].name
-        if len(self.hoststructs) > 1:
-            res += "," + self.hoststructs[1].name
-        return res
-
-    def __repr__(self):
-        return self.__str__()
-
-    def dump(self):
-        if self.wildcard:
-            return "(*)"
-        res = "(" + self.hoststructs[0].dump()
-        if len(self.hoststructs) > 1:
-            res += "," + self.hoststructs[1].dump()
-        res += ")"
-        return res
-
-    def memcmp(self, a, b):
-        res = 0
-        for i in range(0,len(a)):
-            if a[i] > b[i]:
-                res = 1
-                break;
-            elif a[i] < b[i]:
-                res = -1
-                break
-        return res
-
-    def match_bin(self, candidate):
-        """
-        Does the candidate hoststruct match the IP or range of IP addresses represented by this?
-        @param[in] candidate the IP address to be tested
-        @return candidate matches this or not
-        """
-        if self.wildcard:
-            return True
-        try:
-            if not candidate.family == self.hoststructs[0].family:
-                # sorry, wrong AF_INET family
-                return False
-            c0 = self.memcmp(candidate.binary, self.hoststructs[0].binary)
-            if len(self.hoststructs) == 1:
-                return c0 == 0
-            c1 = self.memcmp(candidate.binary, self.hoststructs[1].binary)
-            return c0 >= 0 and c1 <= 0
-        except PolicyError:
-            return False
-        except Exception, e:
-            assert isinstance(candidate, HostStruct), \
-                ("Wrong type. Expected HostStruct but received %s" % candidate.__class__.__name__)
-            return False
-
-    def match_str(self, candidate):
-        """
-        Does the candidate string match the IP or range represented by this?
-        @param[in] candidate the IP address to be tested
-        @return candidate matches this or not
-        """
-        try:
-            hoststruct = HostStruct(candidate)
-        except PolicyError:
-            return False
-        return self.match_bin(hoststruct)
-
-#
-#
-class PolicyAppConnectionMgr():
-    """
-    Track policy user/host connection limits and statistics for one app.
-    # limits - set at creation and by update()
-    max_total            : 20
-    max_per_user         : 5
-    max_per_host         : 10
-    # statistics - maintained for the lifetime of corresponding application
-    connections_approved : N
-    connections_denied   : N
-    # live state - maintained for the lifetime of corresponding application
-    connections_active   : 5
-    per_host_state : { 'host1' : [conn1, conn2, conn3],
-                       'host2' : [conn4, conn5] }
-    per_user_state : { 'user1' : [conn1, conn2, conn3],
-                       'user2' : [conn4, conn5] }
-    """
-    def __init__(self, maxconn, maxconnperuser, maxconnperhost):
-        """
-        The object is constructed with the policy limits and zeroed counts.
-        @param[in] maxconn maximum total concurrent connections
-        @param[in] maxconnperuser maximum total conncurrent connections for each user
-        @param[in] maxconnperuser maximum total conncurrent connections for each host
-        """
-        if maxconn < 0 or maxconnperuser < 0 or maxconnperhost < 0:
-            raise PolicyError("PolicyAppConnectionMgr settings must be >= 0")
-        self.max_total    = maxconn
-        self.max_per_user = maxconnperuser
-        self.max_per_host = maxconnperhost
-        self.connections_approved = 0
-        self.connections_denied   = 0
-        self.connections_active   = 0
-        self.per_host_state = {}
-        self.per_user_state = {}
-
-    def __str__(self):
-        res = ("Connection Limits: total: %s, per user: %s, per host: %s\n" %
-            (self.max_total, self.max_per_user, self.max_per_host))
-        res += ("Connections Statistics: total approved: %s, total denied: %s" %
-                (self.connections_approved, self.connections_denied))
-        res += ("Connection State: total current: %s" % self.connections_active)
-        res += ("User state: %s\n" % self.per_user_state)
-        res += ("Host state: %s"   % self.per_host_state)
-        return res
-
-    def __repr__(self):
-        return self.__str__()
-
-    def update(self, maxconn, maxconnperuser, maxconnperhost):
-        """
-        Reset connection limits
-        @param[in] maxconn maximum total concurrent connections
-        @param[in] maxconnperuser maximum total conncurrent connections for each user
-        @param[in] maxconnperuser maximum total conncurrent connections for each host
-        """
-        if maxconn < 0 or maxconnperuser < 0 or maxconnperhost < 0:
-            raise PolicyError("PolicyAppConnectionMgr settings must be >= 0")
-        self.max_total    = maxconn
-        self.max_per_user = maxconnperuser
-        self.max_per_host = maxconnperhost
-
-    def can_connect(self, conn_id, user, host, diags):
-        """
-        Register a connection attempt.
-        If all the connection limit rules pass then add the
-        user/host to the connection tables.
-        @param[in] conn_id unique ID for connection, usually IP:port
-        @param[in] user authenticated user ID
-        @param[in] host IP address of host
-        @param[out] diags on failure holds 1, 2, or 3 error strings
-        @return connection is allowed and tracked in state tables
-        """
-        n_user = 0
-        if user in self.per_user_state:
-            n_user = len(self.per_user_state[user])
-        n_host = 0
-        if host in self.per_host_state:
-            n_host = len(self.per_host_state[host])
-
-        allowbytotal = self.max_total == 0 or self.connections_active < self.max_total
-        allowbyuser  = self.max_per_user == 0 or n_user < self.max_per_user
-        allowbyhost  = self.max_per_host == 0 or n_host < self.max_per_host
-
-        if allowbytotal and allowbyuser and allowbyhost:
-            if not user in self.per_user_state:
-                self.per_user_state[user] = []
-            self.per_user_state[user].append(conn_id)
-            if not host in self.per_host_state:
-                self.per_host_state[host] = []
-            self.per_host_state[host].append(conn_id)
-            self.connections_active += 1
-            self.connections_approved += 1
-            return True
-        else:
-            if not allowbytotal:
-                diags.append("LogMe: INFO user '%s' from host '%s' denied connection by total connection limit" %
-                             (user, host))
-            if not allowbyuser:
-                diags.append("LogMe: INFO user '%s' from host '%s' denied connection by per user limit" %
-                             (user, host))
-            if not allowbyhost:
-                diags.append("LogMe: INFO user '%s' from host '%s' denied connection by per host limit" %
-                             (user, host))
-            self.connections_denied += 1
-            return False
-
-    def disconnect(self, conn_id, user, host):
-        """
-        Unregister a connection
-        """
-        assert(self.connections_active > 0)
-        assert(user in self.per_user_state)
-        assert(conn_id in self.per_user_state[user])
-        assert(host in self.max_per_host)
-        assert(conn_id in self.max_per_host[host])
-        self.connections_active -= 1
-        self.per_user_state[user].remove(conn_id)
-        self.per_host_state[host].remove(conn_id)
-

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/policy/__init__.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/policy/__init__.py b/python/qpid_dispatch_internal/policy/__init__.py
new file mode 100644
index 0000000..6417447
--- /dev/null
+++ b/python/qpid_dispatch_internal/policy/__init__.py
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+"""Qpid Dispatch internal policy package."""
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/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
new file mode 100644
index 0000000..7cc17de
--- /dev/null
+++ b/python/qpid_dispatch_internal/policy/policy_local.py
@@ -0,0 +1,593 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License
+#
+
+"""
+
+"""
+
+import sys, os
+import json
+import optparse
+from policy_util import PolicyError, HostStruct, HostAddr, PolicyAppConnectionMgr
+import pdb #; pdb.set_trace()
+
+
+
+"""
+Entity implementing the business logic of user connection/access policy.
+"""
+
+#
+#
+class PolicyKeys(object):
+    # Common key words
+    KW_IGNORED_NAME             = "name"
+    KW_IGNORED_IDENTITY         = "identity"
+    KW_IGNORED_TYPE             = "type"
+    KW_APPLICATION_NAME         = "applicationName"
+
+    # Policy ruleset key words
+    KW_MAXCONN                  = "maxConnections"
+    KW_MAXCONNPERHOST           = "maxConnPerHost"
+    KW_MAXCONNPERUSER           = "maxConnPerUser"
+    KW_USER_GROUPS              = "userGroups"
+    KW_CONNECTION_GROUPS        = "connectionGroups"
+    KW_CONNECTION_POLICY        = "connectionIngressPolicies"
+    KW_CONNECTION_ALLOW_DEFAULT = "connectionAllowDefault"
+
+    # Policy settings key words
+    KW_USER_GROUP_NAME          = "userGroupName"
+    KW_MAX_FRAME_SIZE           = "maxFrameSize"
+    KW_MAX_MESSAGE_SIZE         = "maxMessageSize"
+    KW_MAX_SESSION_WINDOW       = "maxSessionWindow"
+    KW_MAX_SESSIONS             = "maxSessions"
+    KW_MAX_SENDERS              = "maxSenders"
+    KW_MAX_RECEIVERS            = "maxReceivers"
+    KW_ALLOW_DYNAMIC_SRC        = "allowDynamicSrc"
+    KW_ALLOW_ANONYMOUS_SENDER   = "allowAnonymousSender"
+    KW_SOURCES                  = "sources"
+    KW_TARGETS                  = "targets"
+
+    # What settings does a user get when allowed to connect but
+    # not restricted by a user group?
+    KW_DEFAULT_SETTINGS         = "default"
+
+    # Config file separator character for two IP addresses in a range
+    KC_CONFIG_IP_SEP            = "-"
+
+    # Config file separator character for names in a list
+    KC_CONFIG_LIST_SEP          = ","
+#
+#
+class PolicyCompiler(object):
+    """
+    Validate incoming configuration for legal schema.
+    - Warn about section options that go unused.
+    - Disallow negative max connection numbers.
+    - Check that connectionOrigins resolve to IP hosts
+    """
+
+    allowed_ruleset_options = [
+        PolicyKeys.KW_IGNORED_NAME,
+        PolicyKeys.KW_IGNORED_IDENTITY,
+        PolicyKeys.KW_IGNORED_TYPE,
+        PolicyKeys.KW_APPLICATION_NAME,
+        PolicyKeys.KW_MAXCONN,
+        PolicyKeys.KW_MAXCONNPERHOST,
+        PolicyKeys.KW_MAXCONNPERUSER,
+        PolicyKeys.KW_USER_GROUPS,
+        PolicyKeys.KW_CONNECTION_GROUPS,
+        PolicyKeys.KW_CONNECTION_POLICY,
+        PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT
+        ]
+
+    allowed_settings_options = [
+        PolicyKeys.KW_IGNORED_NAME,
+        PolicyKeys.KW_IGNORED_IDENTITY,
+        PolicyKeys.KW_IGNORED_TYPE,
+        PolicyKeys.KW_APPLICATION_NAME,
+        PolicyKeys.KW_USER_GROUP_NAME,
+        PolicyKeys.KW_MAX_FRAME_SIZE,
+        PolicyKeys.KW_MAX_MESSAGE_SIZE,
+        PolicyKeys.KW_MAX_SESSION_WINDOW,
+        PolicyKeys.KW_MAX_SESSIONS,
+        PolicyKeys.KW_MAX_SENDERS,
+        PolicyKeys.KW_MAX_RECEIVERS,
+        PolicyKeys.KW_ALLOW_DYNAMIC_SRC,
+        PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
+        PolicyKeys.KW_SOURCES,
+        PolicyKeys.KW_TARGETS
+        ]
+
+    def __init__(self):
+        """
+        Create a validator
+        """
+        pass
+
+
+    def validateNumber(self, val, v_min, v_max, errors):
+        """
+        Range check a numeric int policy value
+        @param[in] val policy value to check
+        @param[in] v_min minumum value
+        @param[in] v_max maximum value. zero disables check
+        @param[out] errors failure message
+        @return v_min <= val <= v_max
+        """
+        error = ""
+        try:
+            v_int = int(val)
+        except Exception, e:
+            errors.append("Value '%s' does not resolve to an integer." % val)
+            return False
+        if v_int < v_min:
+            errors.append("Value '%s' is below minimum '%s'." % (val, v_min))
+            return False
+        if v_max > 0 and v_int > v_max:
+            errors.append("Value '%s' is above maximum '%s'." % (val, v_max))
+            return False
+        return True
+
+
+    def compile_connection_groups(self, name, submap, warnings, errors):
+        """
+        Handle an connectionGroups submap.
+        Each origin value is verified. On a successful run the submap
+        is replaced parsed lists of HostAddr objects.
+        @param[in] name application name
+        @param[in,out] submap user input origin list as text strings
+                       modified in place to be list of HostAddr objects
+        @param[out] warnings nonfatal irregularities observed
+        @param[out] errors descriptions of failure
+        @return - origins is usable. If True then warnings[] may contain useful
+                  information about fields that are ignored. If False then
+                  warnings[] may contain info and errors[0] will hold the
+                  description of why the origin was rejected.
+        """
+        key = PolicyKeys.KW_CONNECTION_GROUPS
+        newmap = {}
+        for coname in submap:
+            try:
+                ostr = str(submap[coname])
+                olist = [x.strip(' ') for x in ostr.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
+                newmap[coname] = []
+                for co in olist:
+                    coha = HostAddr(co, PolicyKeys.KC_CONFIG_IP_SEP)
+                    newmap[coname].append(coha)
+            except Exception, e:
+                errors.append("Application '%s' option '%s' connectionOption '%s' failed to translate: '%s'." %
+                                (name, key, coname, e))
+                return False
+        submap.update(newmap)
+        return True
+
+
+    def compile_app_settings(self, appname, usergroup, policy_in, policy_out, warnings, errors):
+        """
+        Compile a schema from processed json format to local internal format.
+        @param[in] name application name
+        @param[in] policy_in user config settings
+        @param[out] policy_out validated Internal format
+        @param[out] warnings nonfatal irregularities observed
+        @param[out] errors descriptions of failure
+        @return - settings are usable. If True then warnings[] may contain useful
+                  information about fields that are ignored. If False then
+                  warnings[] may contain info and errors[0] will hold the
+                  description of why the policy was rejected.
+        """
+        cerror = []
+        for key, val in policy_in.iteritems():
+            if key not in self.allowed_settings_options:
+                warnings.append("Application '%s' user group '%s' option '%s' is ignored." %
+                                (appname, usergroup, key))
+            if key in [PolicyKeys.KW_MAX_FRAME_SIZE,
+                       PolicyKeys.KW_MAX_MESSAGE_SIZE,
+                       PolicyKeys.KW_MAX_RECEIVERS,
+                       PolicyKeys.KW_MAX_SENDERS,
+                       PolicyKeys.KW_MAX_SESSION_WINDOW,
+                       PolicyKeys.KW_MAX_SESSIONS
+                       ]:
+                if not self.validateNumber(val, 0, 0, cerror):
+                    errors.append("Application '%s' user group '%s' option '%s' has error '%s'." %
+                                  (appname, usergroup, key, cerror[0]))
+                    return False
+                policy_out[key] = val
+            elif key in [PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
+                         PolicyKeys.KW_ALLOW_DYNAMIC_SRC
+                         ]:
+                if not type(val) is bool:
+                    errors.append("Application '%s' user group '%s' option '%s' has illegal boolean value '%s'." %
+                                  (appname, usergroup, key, val))
+                    return False
+                policy_out[key] = val
+            elif key in [PolicyKeys.KW_SOURCES,
+                         PolicyKeys.KW_TARGETS
+                         ]:
+                val = [x.strip(' ') for x in val.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
+                # deduplicate address lists
+                val = list(set(val))
+                policy_out[key] = val
+        return True
+
+
+    def compile_access_ruleset(self, name, policy_in, policy_out, warnings, errors):
+        """
+        Compile a schema from processed json format to local internal format.
+        @param[in] name application name
+        @param[in] policy_in raw policy to be validated
+        @param[out] policy_out validated Internal format
+        @param[out] warnings nonfatal irregularities observed
+        @param[out] errors descriptions of failure
+        @return - policy is usable. If True then warnings[] may contain useful
+                  information about fields that are ignored. If False then
+                  warnings[] may contain info and errors[0] will hold the
+                  description of why the policy was rejected.
+        """
+        cerror = []
+        # validate the options
+        for key, val in policy_in.iteritems():
+            if key not in self.allowed_ruleset_options:
+                warnings.append("Application '%s' option '%s' is ignored." %
+                                (name, key))
+            if key in [PolicyKeys.KW_MAXCONN,
+                       PolicyKeys.KW_MAXCONNPERHOST,
+                       PolicyKeys.KW_MAXCONNPERUSER
+                       ]:
+                if not self.validateNumber(val, 0, 65535, cerror):
+                    msg = ("Application '%s' option '%s' has error '%s'." % 
+                           (name, key, cerror[0]))
+                    errors.append(msg)
+                    return False
+                policy_out[key] = val
+            elif key in [PolicyKeys.KW_USER_GROUPS,
+                         PolicyKeys.KW_CONNECTION_GROUPS,
+                         PolicyKeys.KW_CONNECTION_POLICY
+                         ]:
+                try:
+                    if not type(val) is dict:
+                        errors.append("Application '%s' option '%s' must be of type 'dict' but is '%s'" %
+                                      (name, key, type(val)))
+                        return False
+                    if key == PolicyKeys.KW_CONNECTION_GROUPS:
+                        if not self.compile_connection_groups(name, val, warnings, errors):
+                            return False
+                    else:
+                        # deduplicate connectionIngressPolicy and userGroups lists
+                        for k,v in val.iteritems():
+                            v = [x.strip(' ') for x in v.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
+                            v = list(set(v))
+                            val[k] = v
+                    policy_out[key] = val
+                except Exception, e:
+                    errors.append("Application '%s' option '%s' error processing map: %s" %
+                                  (name, key, e))
+                    return False
+        return True
+
+class PolicyLocal(object):
+    """
+    The policy database.
+    """
+
+    def __init__(self):
+        """
+        Create instance
+        @params folder: relative path from __file__ to conf file folder
+        """
+        self.policydb = {}
+        self.settingsdb = {}
+        self.lookup_cache = {}
+        self.stats = {}
+        self.policy_compiler = PolicyCompiler()
+        self.name_lookup_cache = {}
+        self.blob_lookup_cache = {}
+
+    #
+    # Service interfaces
+    #
+    def create_ruleset(self, attributes):
+        """
+        Create named policy ruleset
+        @param[in] attributes: from config
+        """
+        warnings = []
+        diag = []
+        candidate = {}
+        name = attributes[PolicyKeys.KW_APPLICATION_NAME]
+        result = self.policy_compiler.compile_access_ruleset(name, attributes, candidate, warnings, diag)
+        if not result:
+            raise PolicyError( "Policy '%s' is invalid: %s" % (name, diag[0]) )
+        if len(warnings) > 0:
+            print ("LogMe: Application '%s' has warnings: %s" %
+                   (name, warnings))
+        self.policydb[name] = candidate
+        # TODO: Create stats
+
+    def create_settings(self, attributes):
+        """
+        Create named policy ruleset
+        @param[in] attributes: from config
+        """
+        warnings = []
+        diag = []
+        candidate = {}
+        app_name = attributes[PolicyKeys.KW_APPLICATION_NAME]
+        usergroup = attributes[PolicyKeys.KW_USER_GROUP_NAME]
+        result = self.policy_compiler.compile_app_settings(app_name, usergroup, attributes, candidate, warnings, diag)
+        if not result:
+            raise PolicyError( "Policy '%s' is invalid: %s" % (app_name, diag[0]) )
+        if len(warnings) > 0:
+            print ("LogMe: Application '%s' has warnings: %s" %
+                   (app_name, warnings))
+        if not app_name in self.settingsdb:
+            self.settingsdb[app_name] = {}
+        self.settingsdb[app_name][usergroup] = candidate  # create named settings
+
+    def policy_read(self, name):
+        """
+        Read policy for named application
+        @param[in] name application name
+        @return policy data in raw user format
+        """
+        return self.policydb[name]
+
+    def policy_update(self, name, policy):
+        """
+        Update named policy
+        @param[in] name application name
+        @param[in] policy data in raw user input
+        """
+        if not name in self.policydb:
+            raise PolicyError("Policy '%s' does not exist" % name)
+        self.policy_create(name, policy)
+
+    def policy_delete(self, name):
+        """
+        Delete named policy
+        @param[in] name application name
+        """
+        if not name in self.policydb:
+            raise PolicyError("Policy '%s' does not exist" % name)
+        del self.policydb[name]
+
+    #
+    # db enumerator
+    #
+    def policy_db_get_names(self):
+        """
+        Return a list of application names in this policy
+        """
+        return self.policydb.keys()
+
+
+    #
+    # Runtime query interface
+    #
+    def policy_aggregate_limits(self, upolicy, policy, settingname):
+        """
+        Force a max count value into user policy
+        param[in,out] upolicy user policy receiving aggregations
+        param[in] policy Internal policy holding settings to be aggregated
+        param[in] settingname setting of interest
+        """
+        if settingname in policy:
+            upolicy[settingname] = policy[settingname]
+
+    def policy_aggregate_policy_int(self, upolicy, appsettings, groups, settingname):
+        """
+        Pull int out of policy.policies[group] and install into upolicy.
+        Integers are set to max(new, existing)
+        param[in,out] upolicy user policy receiving aggregations
+        param[in] policy Internal policy holding settings to be aggregated
+        param[in] settingname setting of interest
+        """
+        for group in groups:
+            if group in appsettings:
+                rpol = appsettings[group]
+                if settingname in rpol:
+                    sp = rpol[settingname]
+                    if settingname in upolicy:
+                        up = upolicy[settingname]
+                        if sp > up:
+                            # policy bumps up user setting
+                            upolicy[settingname] = sp
+                        else:
+                            # user policy is already better
+                            pass
+                    else:
+                        # user policy doesn't have setting so force it
+                        upolicy[settingname] = sp
+                else:
+                    # no setting of this name in the group's policy
+                    pass
+            else:
+                # no policy for this group
+                pass
+
+    def policy_aggregate_policy_bool(self, upolicy, appsettings, groups, settingname):
+        """
+        Pull bool out of policy and install into upolicy if true
+        param[in,out] upolicy user policy receiving aggregations
+        param[in] policy Internal policy holding settings to be aggregated
+        param[in] settingname setting of interest
+        """
+        for group in groups:
+            if group in appsettings:
+                rpol = appsettings[group]
+                if settingname in rpol:
+                    if rpol[settingname]:
+                        upolicy[settingname] = True
+                else:
+                    # no setting of this name in the group's policy
+                    pass
+            else:
+                # no policy for this group
+                pass
+
+    def policy_aggregate_policy_list(self, upolicy, appsettings, groups, settingname):
+        """
+        Pull list out of policy and append into upolicy
+        param[in,out] upolicy user policy receiving aggregations
+        param[in] policy Internal policy holding settings to be aggregated
+        param[in] settingname setting of interest
+        """
+        for group in groups:
+            if group in appsettings:
+                rpol = appsettings[group]
+                if settingname in rpol:
+                    sp = rpol[settingname]
+                    if settingname in upolicy:
+                        upolicy[settingname].extend( sp )
+                        upolicy[settingname] = list(set(upolicy[settingname]))
+                    else:
+                        # user policy doesn't have setting so force it
+                        upolicy[settingname] = sp
+                else:
+                    # no setting of this name in the group's policy
+                    pass
+            else:
+                # no policy for this group
+                pass
+
+    #
+    #
+    def lookup_user(self, user, host, app, conn_name, policyname):
+        """
+        Lookup function called from C.
+        Determine if a user on host accessing app through AMQP Open is allowed
+        according to the policy access rules.
+        If allowed then return the policy settings name
+        @param[in] user connection authId
+        @param[in] host connection remote host numeric IP address as string
+        @param[in] app application user is accessing
+        @param[out] policyname name of the policy settings blob for this user
+        @return if allowed by policy
+        # Note: the upolicy[0] output is list of group names joined with '|'.
+        TODO: handle the AccessStats
+        """
+        try:
+            lookup_id = user + "|" + host + "|" + app
+            if lookup_id in self.name_lookup_cache:
+                policyname.append( self.name_lookup_cache[lookup_id] )
+                return True
+
+            if not app in self.policydb:
+                # TODO: ("LogMe: no policy defined for application %s" % app)
+                policyname.append("")
+                return False
+
+            settings = self.policydb[app]
+            # User allowed to connect from host?
+            allowed = False
+            restricted = False
+            uhs = HostStruct(host)
+            ugroups = []
+            if PolicyKeys.KW_USER_GROUPS in settings:
+                for r in settings[PolicyKeys.KW_USER_GROUPS]:
+                    if user in settings[PolicyKeys.KW_USER_GROUPS][r]:
+                        restricted = True
+                        ugroups.append(r)
+            uorigins = []
+            if PolicyKeys.KW_CONNECTION_POLICY in settings:
+                for ur in ugroups:
+                    if ur in settings[PolicyKeys.KW_CONNECTION_POLICY]:
+                        uorigins.extend(settings[PolicyKeys.KW_CONNECTION_POLICY][ur])
+            if PolicyKeys.KW_CONNECTION_GROUPS in settings:
+                for co in settings[PolicyKeys.KW_CONNECTION_GROUPS]:
+                    if co in uorigins:
+                        for cohost in settings[PolicyKeys.KW_CONNECTION_GROUPS][co]:
+                            if cohost.match_bin(uhs):
+                                allowed = True
+                                break
+                    if allowed:
+                        break
+            if not allowed and not restricted:
+                if PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT in settings:
+                    allowed = settings[PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT]
+            if not allowed:
+                return False
+            if not restricted:
+                ugroups.append(PolicyKeys.KW_DEFAULT_SETTINGS)
+            #
+            ugroups.sort()
+            result = "|".join(ugroups)
+            self.name_lookup_cache[lookup_id] = result
+            policyname.append(result)
+            return True
+
+        except Exception, e:
+            #print str(e)
+            #pdb.set_trace()
+            return False
+
+    def lookup_settings(self, appname, name, upolicy):
+        """
+        Given a settings name, return the aggregated policy blob.
+        @param[in] appname: application user is accessing
+        @param[in] name: user group name or concatenation of names of the policy settings blob
+        @param[out] upolicy: dict holding policy values - the settings blob
+        @return if allowed by policy
+        # Note: the upolicy output is a non-nested dict with settings of interest
+        # TODO: figure out decent defaults for upolicy settings that are undefined
+        """
+        try:
+            cachekey = appname + "|" + name
+            if cachekey in self.blob_lookup_cache:
+                upolicy.update( self.blob_lookup_cache[cachekey] )
+                return True
+            settings = self.settingsdb[appname]
+            ugroups = name.split("|")
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_FRAME_SIZE)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_MESSAGE_SIZE)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_SESSION_WINDOW)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_SESSIONS)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_SENDERS)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, PolicyKeys.KW_MAX_RECEIVERS)
+            self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.KW_ALLOW_DYNAMIC_SRC)
+            self.policy_aggregate_policy_bool(upolicy, settings, ugroups, PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER)
+            self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.KW_SOURCES)
+            self.policy_aggregate_policy_list(upolicy, settings, ugroups, PolicyKeys.KW_TARGETS)
+            c_upolicy = {}
+            c_upolicy.update(upolicy)
+            self.blob_lookup_cache[cachekey] = c_upolicy
+            return True
+        except Exception, e:
+            #print str(e)
+            #pdb.set_trace()
+            return False
+
+    def test_load_config(self):
+        ruleset_str = '["policyAccessRuleset", {"applicationName": "photoserver","maxConnections": 50,"maxConnPerUser": 5,"maxConnPerHost": 20,"userGroups": {"anonymous":       "anonymous","users":           "u1, u2","paidsubscribers": "p1, p2","test":            "zeke, ynot","admin":           "alice, bob, ellen","superuser":       "ellen"},"connectionGroups": {"Ten18":     "10.18.0.0-10.18.255.255","EllensWS":  "72.135.2.9","TheLabs":   "10.48.0.0-10.48.255.255, 192.168.100.0-192.168.100.255","localhost": "127.0.0.1, ::1","TheWorld":  "*"},"connectionIngressPolicies": {"anonymous":       "TheWorld","users":           "TheWorld","paidsubscribers": "TheWorld","test":            "TheLabs","admin":           "Ten18, TheLabs, localhost","superuser":       "EllensWS, localhost"},"connectionAllowDefault": true}]'
+        ruleset = json.loads(ruleset_str)
+
+        self.create_ruleset(ruleset[1])
+
+        settings_strs = []
+        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"anonymous",      "maxFrameSize": 111111,"maxMessageSize":   111111,"maxSessionWindow": 111111,"maxSessions":           1,"maxSenders":           11,"maxReceivers":         11,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": "public",                           "targets": ""}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"users",          "maxFrameSize": 222222,"maxMessageSize":   222222,"maxSessionWindow": 222222,"maxSessions":           2,"maxSenders":           22,"maxReceivers":         22,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": "public, private",                  "targets": "public"}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"paidsubscribers","maxFrameSize": 333333,"maxMessageSize":   333333,"maxSessionWindow": 333333,"maxSessions":           3,"maxSenders":           33,"maxReceivers":         33,"allowDynamicSrc":      true, "allowAnonymousSender": false,"sources": "public, private",                  "targets": "public, private"}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"test",           "maxFrameSize": 444444,"maxMessageSize":   444444,"maxSessionWindow": 444444,"maxSessions":           4,"maxSenders":           44,"maxReceivers":         44,"allowDynamicSrc":      true, "allowAnonymousSender": true, "sources": "private",                          "targets": "private"}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"admin",          "maxFrameSize": 555555,"maxMessageSize":   555555,"maxSessionWindow": 555555,"maxSessions":           5,"maxSenders":           55,"maxReceivers":         55,"allowDynamicSrc":      true, "allowAnonymousSender": true, "sources": "public, private, management",      "targets": "public, private, management"}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"superuser",      "maxFrameSize": 666666,"maxMessageSize":   666666,"maxSessionWindow": 666666,"maxSessions":           6,"maxSenders":           66,"maxReceivers":         66,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": "public, private, management, root","targets": "public, private, management, root"}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": "photoserver","userGroupName":"default",        "maxFrameSize": 222222,"maxMessageSize":   222222,"maxSessionWindow": 222222,"maxSessions":           2,"maxSenders":           22,"maxReceivers":         22,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": "public, private",                  "targets": "public"}]')
+
+        for sstr in settings_strs:
+            settings = json.loads(sstr)
+            self.create_settings(settings[1])


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[2/5] qpid-dispatch git commit: Add example .json file replacement for common .conf file.

Posted by ch...@apache.org.
Add example .json file replacement for common .conf file.


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/bb094238
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/bb094238
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/bb094238

Branch: refs/heads/crolke-DISPATCH-188-1
Commit: bb09423817c21ec61284240412f7abc5ddff78a4
Parents: 11e9f07
Author: Chuck Rolke <cr...@redhat.com>
Authored: Wed Jan 27 10:19:26 2016 -0500
Committer: Chuck Rolke <cr...@redhat.com>
Committed: Wed Jan 27 10:19:26 2016 -0500

----------------------------------------------------------------------
 tests/config-1/A.json | 81 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/bb094238/tests/config-1/A.json
----------------------------------------------------------------------
diff --git a/tests/config-1/A.json b/tests/config-1/A.json
new file mode 100644
index 0000000..01d62e1
--- /dev/null
+++ b/tests/config-1/A.json
@@ -0,0 +1,81 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License.  You may obtain a copy of the License at
+##
+##   http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+## KIND, either express or implied.  See the License for the
+## specific language governing permissions and limitations
+## under the License
+##
+
+
+[
+##
+## Container section - Configures the general operation of the AMQP container.
+##
+    ["container", {
+##
+## workerThreads - The number of threads that will be created to
+## process message traffic and other application work (timers, nonAmqp
+## file descriptors, etc.)
+##
+## The number of threads should be related to the number of available
+## processor cores.  To fully utilize a quad-core system, set the
+## number of threads to 4.
+##
+	"workerThreads": 4,
+	
+##
+## containerName - The name of the AMQP container.  If not specified,
+## the container name will be set to a value of the container's
+## choosing.  The automatically assigned container name is not
+## guaranteed to be persistent across restarts of the container.
+##
+	"containerName": "Qpid.Dispatch.Router.A"
+    }],
+
+    ["router", {
+	"mode": "standalone",
+	"routerId": "QDR"
+    }],
+
+##
+## Listeners and Connectors
+##
+    ["listener", {
+	"addr": "0.0.0.0",
+	"port": 20000,
+	"saslMechanisms": "ANONYMOUS"
+    }],
+    ["fixedAddress", {
+	"prefix": "/closest/",
+	"fanout": "single",
+	"bias": "closest"
+    }],
+
+    ["fixedAddress", {
+	"prefix": "/spread/",
+	"fanout": "single",
+	"bias": "spread"
+    }],
+
+    ["fixedAddress", {
+	"prefix": "/multicast/",
+	"fanout": "multiple"
+    }],
+
+    ["fixedAddress", {
+	"prefix": "/",
+	"fanout": "multiple"
+    }]
+]
+


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[4/5] qpid-dispatch git commit: Move policy code to qpid_dispatch_internal/policy/*. Remove policy test-as-main code.

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/policy/policy_util.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/policy/policy_util.py b/python/qpid_dispatch_internal/policy/policy_util.py
new file mode 100644
index 0000000..edcd01f
--- /dev/null
+++ b/python/qpid_dispatch_internal/policy/policy_util.py
@@ -0,0 +1,335 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License
+#
+
+import sys, os
+import socket
+import binascii
+
+
+#
+#
+class PolicyError(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+#
+#
+class HostStruct():
+    """
+    HostStruct represents a single, binary socket address from getaddrinfo
+        - name     : name given to constructor; numeric IP or host name
+        - saddr    : net name resolved by getaddrinfo; numeric IP
+        - family   : saddr.family; int
+        - binary   : saddr packed binary address; binary string
+    """
+    families = [socket.AF_INET]
+    famnames = ["IPv4"]
+    if socket.has_ipv6:
+        families.append(socket.AF_INET6)
+        famnames.append("IPv6")
+
+    def __init__(self, hostname):
+        """
+        Given a host name text string, return the socket info for it.
+        @param[in] hostname host IP address to parse
+        """
+        try:
+            res = socket.getaddrinfo(hostname, 0)
+            if len(res) == 0:
+                raise PolicyError("HostStruct: '%s' did not resolve to an IP address" % hostname)
+            foundFirst = False
+            saddr = ""
+            sfamily = socket.AF_UNSPEC
+            for i0 in range(0, len(res)):
+                family, dum0, dum1, dum2, sockaddr = res[i0]
+                if not foundFirst:
+                    if family in self.families:
+                        saddr = sockaddr[0]
+                        sfamily = family
+                        foundFirst = True
+                else:
+                    if family in self.families:
+                        if not saddr == sockaddr[0] or not sfamily == family:
+                            raise PolicyError("HostStruct: '%s' resolves to multiple IP addresses" %
+                                              hostname)
+
+            if not foundFirst:
+                raise PolicyError("HostStruct: '%s' did not resolve to one of the supported address family" %
+                        hostname)
+            self.name = hostname
+            self.saddr = saddr
+            self.family = sfamily
+            self.binary = socket.inet_pton(family, saddr)
+            return
+        except Exception, e:
+            raise PolicyError("HostStruct: '%s' failed to resolve: '%s'" %
+                              (hostname, e))
+
+    def __str__(self):
+        return self.name
+
+    def __repr__(self):
+        return self.__str__()
+
+    def dump(self):
+        return ("(%s, %s, %s, %s)" %
+                (self.name,
+                 self.saddr,
+                 "AF_INET" if self.family == socket.AF_INET else "AF_INET6",
+                 binascii.hexlify(self.binary)))
+
+#
+#
+class HostAddr():
+    """
+    Provide HostIP address ranges and comparison functions.
+    A HostIP may be:
+    - single address:      10.10.1.1
+    - a pair of addresses: 10.10.0.0,10.10.255.255
+    - a wildcard:          *
+    Only IPv4 and IPv6 are supported.
+    - No unix sockets.
+    HostIP names must resolve to a single IP address.
+    Address pairs define a range.
+    - The second address must be numerically larger than the first address.
+    - The addresses must be of the same address 'family', IPv4 or IPv6.
+    The wildcard '*' matches all address IPv4 or IPv6.
+    IPv6 support is conditional based on underlying OS network options.
+    Raises a PolicyError on validation error in constructor.
+    """
+
+    def has_ipv6(self):
+        return socket.has_ipv6
+
+    def __init__(self, hostspec, separator=","):
+        """
+        Parse host spec into binary structures to use for comparisons.
+        Validate the hostspec to enforce usage rules.
+        """
+        self.hoststructs = []
+
+        if hostspec == "*":
+            self.wildcard = True
+        else:
+            self.wildcard = False
+
+            hosts = [x.strip() for x in hostspec.split(separator)]
+
+            # hosts must contain one or two host specs
+            if len(hosts) not in [1, 2]:
+                raise PolicyError("hostspec must contain 1 or 2 host names")
+            self.hoststructs.append(HostStruct(hosts[0]))
+            if len(hosts) > 1:
+                self.hoststructs.append(HostStruct(hosts[1]))
+                if not self.hoststructs[0].family == self.hoststructs[1].family:
+                    raise PolicyError("mixed IPv4 and IPv6 host specs in range not allowed")
+                c0 = self.memcmp(self.hoststructs[0].binary, self.hoststructs[1].binary)
+                if c0 > 0:
+                    raise PolicyError("host specs in range must have lower numeric address first")
+
+    def __str__(self):
+        if self.wildcard:
+            return "*"
+        res = self.hoststructs[0].name
+        if len(self.hoststructs) > 1:
+            res += "," + self.hoststructs[1].name
+        return res
+
+    def __repr__(self):
+        return self.__str__()
+
+    def dump(self):
+        if self.wildcard:
+            return "(*)"
+        res = "(" + self.hoststructs[0].dump()
+        if len(self.hoststructs) > 1:
+            res += "," + self.hoststructs[1].dump()
+        res += ")"
+        return res
+
+    def memcmp(self, a, b):
+        res = 0
+        for i in range(0,len(a)):
+            if a[i] > b[i]:
+                res = 1
+                break;
+            elif a[i] < b[i]:
+                res = -1
+                break
+        return res
+
+    def match_bin(self, candidate):
+        """
+        Does the candidate hoststruct match the IP or range of IP addresses represented by this?
+        @param[in] candidate the IP address to be tested
+        @return candidate matches this or not
+        """
+        if self.wildcard:
+            return True
+        try:
+            if not candidate.family == self.hoststructs[0].family:
+                # sorry, wrong AF_INET family
+                return False
+            c0 = self.memcmp(candidate.binary, self.hoststructs[0].binary)
+            if len(self.hoststructs) == 1:
+                return c0 == 0
+            c1 = self.memcmp(candidate.binary, self.hoststructs[1].binary)
+            return c0 >= 0 and c1 <= 0
+        except PolicyError:
+            return False
+        except Exception, e:
+            assert isinstance(candidate, HostStruct), \
+                ("Wrong type. Expected HostStruct but received %s" % candidate.__class__.__name__)
+            return False
+
+    def match_str(self, candidate):
+        """
+        Does the candidate string match the IP or range represented by this?
+        @param[in] candidate the IP address to be tested
+        @return candidate matches this or not
+        """
+        try:
+            hoststruct = HostStruct(candidate)
+        except PolicyError:
+            return False
+        return self.match_bin(hoststruct)
+
+#
+#
+class PolicyAppConnectionMgr():
+    """
+    Track policy user/host connection limits and statistics for one app.
+    # limits - set at creation and by update()
+    max_total            : 20
+    max_per_user         : 5
+    max_per_host         : 10
+    # statistics - maintained for the lifetime of corresponding application
+    connections_approved : N
+    connections_denied   : N
+    # live state - maintained for the lifetime of corresponding application
+    connections_active   : 5
+    per_host_state : { 'host1' : [conn1, conn2, conn3],
+                       'host2' : [conn4, conn5] }
+    per_user_state : { 'user1' : [conn1, conn2, conn3],
+                       'user2' : [conn4, conn5] }
+    """
+    def __init__(self, maxconn, maxconnperuser, maxconnperhost):
+        """
+        The object is constructed with the policy limits and zeroed counts.
+        @param[in] maxconn maximum total concurrent connections
+        @param[in] maxconnperuser maximum total conncurrent connections for each user
+        @param[in] maxconnperuser maximum total conncurrent connections for each host
+        """
+        if maxconn < 0 or maxconnperuser < 0 or maxconnperhost < 0:
+            raise PolicyError("PolicyAppConnectionMgr settings must be >= 0")
+        self.max_total    = maxconn
+        self.max_per_user = maxconnperuser
+        self.max_per_host = maxconnperhost
+        self.connections_approved = 0
+        self.connections_denied   = 0
+        self.connections_active   = 0
+        self.per_host_state = {}
+        self.per_user_state = {}
+
+    def __str__(self):
+        res = ("Connection Limits: total: %s, per user: %s, per host: %s\n" %
+            (self.max_total, self.max_per_user, self.max_per_host))
+        res += ("Connections Statistics: total approved: %s, total denied: %s" %
+                (self.connections_approved, self.connections_denied))
+        res += ("Connection State: total current: %s" % self.connections_active)
+        res += ("User state: %s\n" % self.per_user_state)
+        res += ("Host state: %s"   % self.per_host_state)
+        return res
+
+    def __repr__(self):
+        return self.__str__()
+
+    def update(self, maxconn, maxconnperuser, maxconnperhost):
+        """
+        Reset connection limits
+        @param[in] maxconn maximum total concurrent connections
+        @param[in] maxconnperuser maximum total conncurrent connections for each user
+        @param[in] maxconnperuser maximum total conncurrent connections for each host
+        """
+        if maxconn < 0 or maxconnperuser < 0 or maxconnperhost < 0:
+            raise PolicyError("PolicyAppConnectionMgr settings must be >= 0")
+        self.max_total    = maxconn
+        self.max_per_user = maxconnperuser
+        self.max_per_host = maxconnperhost
+
+    def can_connect(self, conn_id, user, host, diags):
+        """
+        Register a connection attempt.
+        If all the connection limit rules pass then add the
+        user/host to the connection tables.
+        @param[in] conn_id unique ID for connection, usually IP:port
+        @param[in] user authenticated user ID
+        @param[in] host IP address of host
+        @param[out] diags on failure holds 1, 2, or 3 error strings
+        @return connection is allowed and tracked in state tables
+        """
+        n_user = 0
+        if user in self.per_user_state:
+            n_user = len(self.per_user_state[user])
+        n_host = 0
+        if host in self.per_host_state:
+            n_host = len(self.per_host_state[host])
+
+        allowbytotal = self.max_total == 0 or self.connections_active < self.max_total
+        allowbyuser  = self.max_per_user == 0 or n_user < self.max_per_user
+        allowbyhost  = self.max_per_host == 0 or n_host < self.max_per_host
+
+        if allowbytotal and allowbyuser and allowbyhost:
+            if not user in self.per_user_state:
+                self.per_user_state[user] = []
+            self.per_user_state[user].append(conn_id)
+            if not host in self.per_host_state:
+                self.per_host_state[host] = []
+            self.per_host_state[host].append(conn_id)
+            self.connections_active += 1
+            self.connections_approved += 1
+            return True
+        else:
+            if not allowbytotal:
+                diags.append("LogMe: INFO user '%s' from host '%s' denied connection by total connection limit" %
+                             (user, host))
+            if not allowbyuser:
+                diags.append("LogMe: INFO user '%s' from host '%s' denied connection by per user limit" %
+                             (user, host))
+            if not allowbyhost:
+                diags.append("LogMe: INFO user '%s' from host '%s' denied connection by per host limit" %
+                             (user, host))
+            self.connections_denied += 1
+            return False
+
+    def disconnect(self, conn_id, user, host):
+        """
+        Unregister a connection
+        """
+        assert(self.connections_active > 0)
+        assert(user in self.per_user_state)
+        assert(conn_id in self.per_user_state[user])
+        assert(host in self.max_per_host)
+        assert(conn_id in self.max_per_host[host])
+        self.connections_active -= 1
+        self.per_user_state[user].remove(conn_id)
+        self.per_host_state[host].remove(conn_id)
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/tests/system_tests_policy.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_policy.py b/tests/system_tests_policy.py
index be7389f..14dfbf0 100644
--- a/tests/system_tests_policy.py
+++ b/tests/system_tests_policy.py
@@ -30,9 +30,9 @@ from proton.utils import BlockingConnection, LinkDetached
 from qpid_dispatch.management.client import Node
 from system_test import TIMEOUT
 
-from qpid_dispatch_internal.management.policy_util import \
+from qpid_dispatch_internal.policy.policy_util import \
     HostAddr, PolicyError, HostStruct, PolicyAppConnectionMgr
-from qpid_dispatch_internal.management.policy_local import PolicyLocal
+from qpid_dispatch_internal.policy.policy_local import PolicyLocal
 
 class AbsoluteConnectionCountLimit(TestCase):
     """


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[3/5] qpid-dispatch git commit: Revert "DISPATCH-187 - Add requirement that the depended-on PythonLibs package be version 2."

Posted by ch...@apache.org.
Revert "DISPATCH-187 - Add requirement that the depended-on PythonLibs package be version 2."

This reverts commit 3eee868e6ed4449c02de6a179cb595f321a37371.


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/2fb2ef41
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/2fb2ef41
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/2fb2ef41

Branch: refs/heads/crolke-DISPATCH-188-1
Commit: 2fb2ef41787427ffe1531f6e35e59eb65ec9fd70
Parents: bb09423
Author: Ted Ross <tr...@redhat.com>
Authored: Wed Nov 4 17:07:36 2015 -0500
Committer: Chuck Rolke <cr...@redhat.com>
Committed: Wed Jan 27 11:29:43 2016 -0500

----------------------------------------------------------------------
 CMakeLists.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/2fb2ef41/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f289a29..0d55205 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,8 +36,8 @@ include(CheckLibraryExists)
 include(CheckSymbolExists)
 include(CheckFunctionExists)
 include(CheckIncludeFiles)
-
-find_package(PythonLibs 2 REQUIRED)
+include(FindPythonInterp)
+include(FindPythonLibs)
 
 enable_testing()
 include (CTest)


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org