diff --git a/gbp/neutron/db/grouppolicy/group_policy_db.py b/gbp/neutron/db/grouppolicy/group_policy_db.py index f6f8e6bf9..25a5ddfd8 100644 --- a/gbp/neutron/db/grouppolicy/group_policy_db.py +++ b/gbp/neutron/db/grouppolicy/group_policy_db.py @@ -738,6 +738,7 @@ class GroupPolicyDbPlugin(gpolicy.GroupPolicyPluginBase, external_segment_id=es_db.id, destination=rt['destination'], nexthop=rt['nexthop'] or ADDRESS_NOT_SPECIFIED) + es_db.external_routes.append(target) def _set_ess_for_l3p(self, context, l3p_db, es_dict): @@ -1555,10 +1556,17 @@ class GroupPolicyDbPlugin(gpolicy.GroupPolicyPluginBase, return self._make_external_policy_dict(ep_db) @log.log - def get_external_policies(self, context, filters=None, fields=None): + def get_external_policies(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + marker_obj = self._get_marker_obj(context, 'external_policy', limit, + marker) return self._get_collection(context, ExternalPolicy, self._make_external_policy_dict, - filters=filters, fields=fields) + filters=filters, fields=fields, + sorts=sorts, limit=limit, + marker_obj=marker_obj, + page_reverse=page_reverse) @log.log def get_external_policies_count(self, context, filters=None): @@ -1608,10 +1616,17 @@ class GroupPolicyDbPlugin(gpolicy.GroupPolicyPluginBase, return self._make_external_segment_dict(es_db) @log.log - def get_external_segments(self, context, filters=None, fields=None): + def get_external_segments(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + marker_obj = self._get_marker_obj(context, 'external_segment', limit, + marker) return self._get_collection(context, ExternalSegment, self._make_external_segment_dict, - filters=filters, fields=fields) + filters=filters, fields=fields, + sorts=sorts, limit=limit, + marker_obj=marker_obj, + page_reverse=page_reverse) @log.log def get_external_segments_count(self, context, filters=None): @@ -1655,10 +1670,17 @@ class GroupPolicyDbPlugin(gpolicy.GroupPolicyPluginBase, return self._make_nat_pool_dict(np_db) @log.log - def get_nat_pools(self, context, filters=None, fields=None): + def get_nat_pools(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + marker_obj = self._get_marker_obj(context, 'nat_pool', limit, + marker) return self._get_collection(context, NATPool, self._make_nat_pool_dict, - filters=filters, fields=fields) + filters=filters, fields=fields, + sorts=sorts, limit=limit, + marker_obj=marker_obj, + page_reverse=page_reverse) @log.log def get_nat_pools_count(self, context, filters=None): diff --git a/gbp/neutron/services/grouppolicy/common/exceptions.py b/gbp/neutron/services/grouppolicy/common/exceptions.py index 0b7a6224f..db8d9a15b 100644 --- a/gbp/neutron/services/grouppolicy/common/exceptions.py +++ b/gbp/neutron/services/grouppolicy/common/exceptions.py @@ -108,3 +108,23 @@ class InvalidSharedAttributeUpdate(GroupPolicyBadRequest): message = _("Invalid shared attribute update. Shared resource %(id)s is" "referenced by %(rid)s, which is either shared or owned by a " "different tenant.") + + +class ExternalRouteOverlapsWithL3PIpPool(GroupPolicyBadRequest): + message = _("Destination %(destination)s for ES %(es_id)s overlaps with " + "L3P %(l3p_id)s.") + + +class ExternalSegmentSubnetOverlapsWithL3PIpPool(GroupPolicyBadRequest): + message = _("Subnet %(subnet)s for ES %(es_id)s overlaps with " + "L3P %(l3p_id)s.") + + +class ExternalRouteNextHopNotInExternalSegment(GroupPolicyBadRequest): + message = _("One or more external routes' nexthop are not part of " + "subnet %(cidr)s.") + + +class InvalidL3PExternalIPAddress(GroupPolicyBadRequest): + message = _("Address %(ip)s allocated for l3p %(l3p_id)s on segment " + "%(es_id)s doesn't belong to the segment subnet %(es_cidr)s") diff --git a/gbp/neutron/services/grouppolicy/extension_manager.py b/gbp/neutron/services/grouppolicy/extension_manager.py index 99fe3e391..c4a6a1182 100644 --- a/gbp/neutron/services/grouppolicy/extension_manager.py +++ b/gbp/neutron/services/grouppolicy/extension_manager.py @@ -1,17 +1,15 @@ -# Copyright (c) 2014 OpenStack Foundation -# All Rights Reserved. +# Licensed 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 # -# Licensed 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 # -# 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. +# 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. from neutron.openstack.common import log from oslo.config import cfg @@ -209,3 +207,48 @@ class ExtensionManager(stevedore.named.NamedExtensionManager): """Call all extension drivers to extend NSP dictionary.""" for driver in self.ordered_ext_drivers: driver.obj.extend_network_service_policy_dict(session, result) + + def process_create_external_segment(self, session, data, result): + """Call all extension drivers during EP creation.""" + self._call_on_ext_drivers("process_create_external_segment", + session, data, result) + + def process_update_external_segment(self, session, data, result): + """Call all extension drivers during EP update.""" + self._call_on_ext_drivers("process_update_external_segment", + session, data, result) + + def extend_external_segment_dict(self, session, result): + """Call all extension drivers to extend EP dictionary.""" + for driver in self.ordered_ext_drivers: + driver.obj.extend_external_segment_dict(session, result) + + def process_create_external_policy(self, session, data, result): + """Call all extension drivers during EP creation.""" + self._call_on_ext_drivers("process_create_external_policy", + session, data, result) + + def process_update_external_policy(self, session, data, result): + """Call all extension drivers during EP update.""" + self._call_on_ext_drivers("process_update_external_policy", + session, data, result) + + def extend_external_policy_dict(self, session, result): + """Call all extension drivers to extend EP dictionary.""" + for driver in self.ordered_ext_drivers: + driver.obj.extend_external_policy_dict(session, result) + + def process_create_nat_pool(self, session, data, result): + """Call all extension drivers during NP creation.""" + self._call_on_ext_drivers("process_create_nat_pool", + session, data, result) + + def process_update_nat_pool(self, session, data, result): + """Call all extension drivers during NP update.""" + self._call_on_ext_drivers("process_update_nat_pool", + session, data, result) + + def extend_nat_pool_dict(self, session, result): + """Call all extension drivers to extend NP dictionary.""" + for driver in self.ordered_ext_drivers: + driver.obj.extend_nat_pool_dict(session, result) \ No newline at end of file diff --git a/gbp/neutron/services/grouppolicy/group_policy_context.py b/gbp/neutron/services/grouppolicy/group_policy_context.py index 17e773300..eb8762e68 100644 --- a/gbp/neutron/services/grouppolicy/group_policy_context.py +++ b/gbp/neutron/services/grouppolicy/group_policy_context.py @@ -20,6 +20,21 @@ class GroupPolicyContext(object): self._plugin_context = plugin_context +class BaseResouceContext(GroupPolicyContext): + def __init__(self, plugin, plugin_context, resource, original=None): + super(BaseResouceContext, self).__init__(plugin, plugin_context) + self._resource = resource + self._original = original + + @property + def current(self): + return self._resource + + @property + def original(self): + return self._original + + class PolicyTargetContext(GroupPolicyContext, api.PolicyTargetContext): def __init__(self, plugin, plugin_context, policy_target, @@ -211,3 +226,15 @@ class PolicyRuleSetContext(GroupPolicyContext, api.PolicyRuleSetContext): @property def original(self): return self._original_policy_rule_set + + +class ExternalSegmentContext(BaseResouceContext, api.ExternalSegmentContext): + pass + + +class ExternalPolicyContext(BaseResouceContext, api.ExternalPolicyContext): + pass + + +class NatPoolContext(BaseResouceContext, api.NatPoolContext): + pass \ No newline at end of file diff --git a/gbp/neutron/services/grouppolicy/group_policy_driver_api.py b/gbp/neutron/services/grouppolicy/group_policy_driver_api.py index af145640c..f082076f4 100644 --- a/gbp/neutron/services/grouppolicy/group_policy_driver_api.py +++ b/gbp/neutron/services/grouppolicy/group_policy_driver_api.py @@ -357,6 +357,102 @@ class PolicyRuleSetContext(object): pass +@six.add_metaclass(abc.ABCMeta) +class ExternalSegmentContext(object): + + """Context passed to policy engine for external_segment resource. + + A ExternalSegmentContext instance wraps an external_segment + resource. + It provides helper methods for accessing other relevant information. + Results from expensive operations are cached for convenient access. + """ + + @abc.abstractproperty + def current(self): + """Return the current state of the external_segment. + + Return the current state of the external_segment, as defined by + GroupPolicyPlugin.create_external_segment. + """ + pass + + @abc.abstractproperty + def original(self): + """Return the original state of the external_segment. + + Return the original state of the external_segment, prior to a + call to update_external_segment. Method is only valid within + calls to update_external_segment_precommit and + update_external_segment_postcommit. + """ + pass + + +@six.add_metaclass(abc.ABCMeta) +class ExternalPolicyContext(object): + + """Context passed to policy engine for external_policy resource. + + A ExternalPolicyContext instance wraps an external_policy + resource. + It provides helper methods for accessing other relevant information. + Results from expensive operations are cached for convenient access. + """ + + @abc.abstractproperty + def current(self): + """Return the current state of the external_policy. + + Return the current state of the external_policy, as defined by + GroupPolicyPlugin.create_external_policy. + """ + pass + + @abc.abstractproperty + def original(self): + """Return the original state of the external_policy. + + Return the original state of the external_policy, prior to a + call to update_external_policy. Method is only valid within + calls to update_external_policy_precommit and + update_external_policy_postcommit. + """ + pass + + +@six.add_metaclass(abc.ABCMeta) +class NatPoolContext(object): + + """Context passed to policy engine for nat_pool resource. + + A NatPoolContext instance wraps an nat_pool + resource. + It provides helper methods for accessing other relevant information. + Results from expensive operations are cached for convenient access. + """ + + @abc.abstractproperty + def current(self): + """Return the current state of the nat_pool. + + Return the current state of the nat_pool, as defined by + GroupPolicyPlugin.create_nat_pool. + """ + pass + + @abc.abstractproperty + def original(self): + """Return the original state of the nat_pool. + + Return the original state of the nat_pool, prior to a + call to update_nat_pool. Method is only valid within + calls to update_nat_pool_precommit and + update_nat_pool_postcommit. + """ + pass + + @six.add_metaclass(abc.ABCMeta) class PolicyDriver(object): """Define stable abstract interface for Group Policy drivers. @@ -843,6 +939,162 @@ class PolicyDriver(object): """ pass + def create_external_segment_precommit(self, context): + """Allocate resources for a new network service policy. + + :param context: ExternalSegmentContext instance describing the + new network service policy. + """ + pass + + def create_external_segment_postcommit(self, context): + """Create a network service policy. + + :param context: ExternalSegmentContext instance describing the + new network service policy. + """ + pass + + def update_external_segment_precommit(self, context): + """Update resources of a network service policy. + + :param context: ExternalSegmentContext instance describing the + new state of the ExternalSegment, as well as the original state + prior to the update_external_segment call. + """ + pass + + def update_external_segment_postcommit(self, context): + """Update a network service policy. + + :param context: ExternalSegmentContext instance describing the + new state of the ExternalSegment, as well as the original state + prior to the update_external_segment call. + """ + pass + + def delete_external_segment_precommit(self, context): + """Delete resources for a network service policy. + + :param context: ExternalSegmentContext instance describing the + current state of the ExternalSegment, prior to the call to + delete it. + """ + pass + + def delete_external_segment_postcommit(self, context): + """Delete a network service policy. + + :param context: ExternalSegmentContext instance describing the + current state of the ExternalSegment, prior to the call to + delete it. + """ + pass + + def create_external_policy_precommit(self, context): + """Allocate resources for a new network service policy. + + :param context: ExternalPolicyContext instance describing the + new network service policy. + """ + pass + + def create_external_policy_postcommit(self, context): + """Create a network service policy. + + :param context: ExternalPolicyContext instance describing the + new network service policy. + """ + pass + + def update_external_policy_precommit(self, context): + """Update resources of a network service policy. + + :param context: ExternalPolicyContext instance describing the + new state of the ExternalPolicy, as well as the original state + prior to the update_external_policy call. + """ + pass + + def update_external_policy_postcommit(self, context): + """Update a network service policy. + + :param context: ExternalPolicyContext instance describing the + new state of the ExternalPolicy, as well as the original state + prior to the update_external_policy call. + """ + pass + + def delete_external_policy_precommit(self, context): + """Delete resources for a network service policy. + + :param context: ExternalPolicyContext instance describing the + current state of the ExternalPolicy, prior to the call to + delete it. + """ + pass + + def delete_external_policy_postcommit(self, context): + """Delete a network service policy. + + :param context: ExternalPolicyContext instance describing the + current state of the ExternalPolicy, prior to the call to + delete it. + """ + pass + + def create_nat_pool_precommit(self, context): + """Allocate resources for a new network service policy. + + :param context: NatPoolContext instance describing the + new network service policy. + """ + pass + + def create_nat_pool_postcommit(self, context): + """Create a network service policy. + + :param context: NatPoolContext instance describing the + new network service policy. + """ + pass + + def update_nat_pool_precommit(self, context): + """Update resources of a network service policy. + + :param context: NatPoolContext instance describing the + new state of the NatPool, as well as the original state + prior to the update_nat_pool call. + """ + pass + + def update_nat_pool_postcommit(self, context): + """Update a network service policy. + + :param context: NatPoolContext instance describing the + new state of the NatPool, as well as the original state + prior to the update_nat_pool call. + """ + pass + + def delete_nat_pool_precommit(self, context): + """Delete resources for a network service policy. + + :param context: NatPoolContext instance describing the + current state of the NatPool, prior to the call to + delete it. + """ + pass + + def delete_nat_pool_postcommit(self, context): + """Delete a network service policy. + + :param context: NatPoolContext instance describing the + current state of the NatPool, prior to the call to + delete it. + """ + pass + @six.add_metaclass(abc.ABCMeta) class ExtensionDriver(object): @@ -1248,3 +1500,129 @@ class ExtensionDriver(object): network_service_policy operation. """ pass + + def process_create_external_segment(self, session, data, result): + """Process extended attributes for external_segment creation. + + :param session: database session + :param data: dictionary of incoming external_segment data + :param result: external_segment dictionary to extend + + Called inside transaction context on session to validate and + persist any extended external_segment attributes defined + by this driver. Extended attribute values must also be added + to result. + """ + pass + + def process_update_external_segment(self, session, data, result): + """Process extended attributes for external_segment update. + + :param session: database session + :param data: dictionary of incoming external_segment data + :param result: external_segment dictionary to extend + + Called inside transaction context on session to validate and + update any extended external_segment attributes defined by this + driver. Extended attribute values, whether updated or not, + must also be added to result. + """ + pass + + def extend_external_segment_dict(self, session, result): + """Add extended attributes to external_segment dictionary. + + :param session: database session + :param result: external_segment dictionary to extend + + Called inside transaction context on session to add any + extended attributes defined by this driver to a + external_segment dictionary to be used for mechanism + driver calls and/or returned as the result of a + external_segment operation. + """ + pass + + def process_create_external_policy(self, session, data, result): + """Process extended attributes for external_policy creation. + + :param session: database session + :param data: dictionary of incoming external_policy data + :param result: external_policy dictionary to extend + + Called inside transaction context on session to validate and + persist any extended external_policy attributes defined + by this driver. Extended attribute values must also be added + to result. + """ + pass + + def process_update_external_policy(self, session, data, result): + """Process extended attributes for external_policy update. + + :param session: database session + :param data: dictionary of incoming external_policy data + :param result: external_policy dictionary to extend + + Called inside transaction context on session to validate and + update any extended external_policy attributes defined by this + driver. Extended attribute values, whether updated or not, + must also be added to result. + """ + pass + + def extend_external_policy_dict(self, session, result): + """Add extended attributes to external_policy dictionary. + + :param session: database session + :param result: external_policy dictionary to extend + + Called inside transaction context on session to add any + extended attributes defined by this driver to a + external_policy dictionary to be used for mechanism + driver calls and/or returned as the result of a + external_policy operation. + """ + pass + + def process_create_nat_pool(self, session, data, result): + """Process extended attributes for nat_pool creation. + + :param session: database session + :param data: dictionary of incoming nat_pool data + :param result: nat_pool dictionary to extend + + Called inside transaction context on session to validate and + persist any extended nat_pool attributes defined + by this driver. Extended attribute values must also be added + to result. + """ + pass + + def process_update_nat_pool(self, session, data, result): + """Process extended attributes for nat_pool update. + + :param session: database session + :param data: dictionary of incoming nat_pool data + :param result: nat_pool dictionary to extend + + Called inside transaction context on session to validate and + update any extended nat_pool attributes defined by this + driver. Extended attribute values, whether updated or not, + must also be added to result. + """ + pass + + def extend_nat_pool_dict(self, session, result): + """Add extended attributes to nat_pool dictionary. + + :param session: database session + :param result: nat_pool dictionary to extend + + Called inside transaction context on session to add any + extended attributes defined by this driver to a + nat_pool dictionary to be used for mechanism + driver calls and/or returned as the result of a + nat_pool operation. + """ + pass \ No newline at end of file diff --git a/gbp/neutron/services/grouppolicy/plugin.py b/gbp/neutron/services/grouppolicy/plugin.py index 378346e29..69ac22285 100644 --- a/gbp/neutron/services/grouppolicy/plugin.py +++ b/gbp/neutron/services/grouppolicy/plugin.py @@ -10,11 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. +import netaddr + from neutron.api.v2 import attributes as nattr from neutron.common import log from neutron.openstack.common import excutils from neutron.openstack.common import log as logging +from gbp.neutron.db.grouppolicy import group_policy_db as gdb from gbp.neutron.db.grouppolicy import group_policy_mapping_db from gbp.neutron.services.grouppolicy.common import exceptions as gp_exc from gbp.neutron.services.grouppolicy import extension_manager as ext_manager @@ -54,7 +57,8 @@ class GroupPolicyPlugin(group_policy_mapping_db.GroupPolicyMappingDbPlugin): # is the field on the dictionary that can be used # to retrieve the UUID/s of the specific object - usage_graph = {'l3_policy': {}, + usage_graph = {'l3_policy': {'external_segments': + 'external_segment'}, 'l2_policy': {'l3_policy_id': 'l3_policy'}, 'policy_target_group': { 'network_service_policy_id': 'network_service_policy', @@ -70,6 +74,13 @@ class GroupPolicyPlugin(group_policy_mapping_db.GroupPolicyMappingDbPlugin): 'policy_rule_set': { 'parent_id': 'policy_rule_set', 'policy_rules': 'policy_rule'}, + 'external_segment': {}, + 'external_policy': { + 'external_segments': 'external_segment', + 'provided_policy_rule_sets': 'policy_rule_set', + 'consumed_policy_rule_sets': 'policy_rule_set'}, + 'nat_pool': {'external_segment_id': + 'external_segment'} } _plurals = None @@ -101,7 +112,7 @@ class GroupPolicyPlugin(group_policy_mapping_db.GroupPolicyMappingDbPlugin): def _validate_shared_update(self, context, original, updated, identity): if updated.get('shared'): # Even though the shared attribute may not be changed, the objects - # it is referring to might. For this reason we run the reference + # it is referring to might. For this reson we run the reference # validation every time a shared resource is updated # TODO(ivar): run only when relevant updates happen self._validate_shared_create(context, updated, identity) @@ -139,6 +150,10 @@ class GroupPolicyPlugin(group_policy_mapping_db.GroupPolicyMappingDbPlugin): context, obj, self.get_policy_target_groups, 'id', obj['providing_policy_target_groups'] + obj['consuming_policy_target_groups']) + self._check_shared_or_different_tenant( + context, obj, self.get_external_policies, 'id', + obj['providing_external_policies'] + + obj['consuming_external_policies']) def _validate_policy_classifier_unshare(self, context, obj): self._check_shared_or_different_tenant( @@ -154,6 +169,85 @@ class GroupPolicyPlugin(group_policy_mapping_db.GroupPolicyMappingDbPlugin): self._check_shared_or_different_tenant( context, obj, self.get_policy_rules, 'id', r_ids) + def _validate_external_segment_unshare(self, context, obj): + self._check_shared_or_different_tenant( + context, obj, self.get_l3_policies, 'id', obj['l3_policies']) + self._check_shared_or_different_tenant( + context, obj, self.get_external_policies, 'id', + obj['external_policies']) + self._check_shared_or_different_tenant( + context, obj, self.get_nat_pools, 'external_segment_id') + + def _validate_external_policy_unshare(self, context, obj): + pass + + def _validate_nat_pool_unshare(self, context, obj): + pass + + def _validate_routes(self, context, current, original=None): + if original: + added = (set((x['destination'], x['nexthop']) for x in + current['external_routes']) - + set((x['destination'], x['nexthop']) for x in + original['external_routes'])) + else: + added = set((x['destination'], x['nexthop']) for x in + current['external_routes']) + if added: + # Verify new ones don't overlap with the existing L3P + added_dest = set(x[0] for x in added) + # Remove default routes + added_dest.discard('0.0.0.0/0') + added_dest.discard('::/0') + added_ipset = netaddr.IPSet(added_dest) + if current['l3_policies']: + l3ps = self.get_l3_policies( + context, filters={'id': current['l3_policies']}) + for l3p in l3ps: + if netaddr.IPSet([l3p['ip_pool']]) & added_ipset: + raise gp_exc.ExternalRouteOverlapsWithL3PIpPool( + destination=added_dest, l3p_id=l3p['id'], + es_id=current['id']) + # Verify NH in ES pool + added_nexthop = netaddr.IPSet(x[1] for x in added if x[1]) + es_subnet = netaddr.IPSet([current['cidr']]) + if added_nexthop & es_subnet != added_nexthop: + raise gp_exc.ExternalRouteNextHopNotInExternalSegment( + cidr=current['cidr']) + + def _validate_l3p_es(self, context, current, original=None): + if original: + added = (set(current['external_segments'].keys()) - + set(original['external_segments'].keys())) + else: + added = set(current['external_segments'].keys()) + if added: + es_list = self.get_external_segments(context, + filters={'id': added}) + l3p_ipset = netaddr.IPSet([current['ip_pool']]) + for es in es_list: + # Verify no route overlap + dest_set = set(x['destination'] for x in + es['external_routes']) + dest_set.discard('0.0.0.0/0') + dest_set.discard('::/0') + if l3p_ipset & netaddr.IPSet(dest_set): + raise gp_exc.ExternalRouteOverlapsWithL3PIpPool( + destination=dest_set, l3p_id=current['id'], + es_id=es['id']) + # Verify segment CIDR doesn't overlap with L3P's + if l3p_ipset & netaddr.IPSet([es['cidr']]): + raise gp_exc.ExternalSegmentSubnetOverlapsWithL3PIpPool( + subnet=es['cidr'], l3p_id=current['id'], + es_id=current['id']) + # Verify allocated address correctly in subnet + for addr in current['external_segments'][es['id']]: + if addr != gdb.ADDRESS_NOT_SPECIFIED: + if addr not in netaddr.IPNetwork(es['cidr']): + raise gp_exc.InvalidL3PExternalIPAddress( + ip=addr, es_id=es['id'], l3p_id=current['id'], + es_cidr=es['cidr']) + def __init__(self): self.extension_manager = ext_manager.ExtensionManager() self.policy_driver_manager = manager.PolicyDriverManager() @@ -547,6 +641,7 @@ class GroupPolicyPlugin(group_policy_mapping_db.GroupPolicyMappingDbPlugin): self.extension_manager.process_create_l3_policy( session, l3_policy, result) self._validate_shared_create(context, result, 'l3_policy') + self._validate_l3p_es(context, result) policy_context = p_context.L3PolicyContext(self, context, result) self.policy_driver_manager.create_l3_policy_precommit( @@ -575,6 +670,8 @@ class GroupPolicyPlugin(group_policy_mapping_db.GroupPolicyMappingDbPlugin): session, l3_policy, updated_l3_policy) self._validate_shared_update(context, original_l3_policy, updated_l3_policy, 'l3_policy') + self._validate_l3p_es(context, updated_l3_policy, + original_l3_policy) policy_context = p_context.L3PolicyContext( self, context, updated_l3_policy, original_l3_policy=original_l3_policy) @@ -995,3 +1092,291 @@ class GroupPolicyPlugin(group_policy_mapping_db.GroupPolicyMappingDbPlugin): self.extension_manager.extend_policy_rule_set_dict( session, result) return [self._fields(result, fields) for result in results] + + @log.log + def create_external_segment(self, context, external_segment): + session = context.session + with session.begin(subtransactions=True): + result = super(GroupPolicyPlugin, + self).create_external_segment(context, + external_segment) + self.extension_manager.process_create_external_segment( + session, external_segment, result) + self._validate_shared_create(context, result, + 'external_segment') + self._validate_routes(context, result) + policy_context = p_context.ExternalSegmentContext( + self, context, result) + (self.policy_driver_manager. + create_external_segment_precommit(policy_context)) + + try: + (self.policy_driver_manager. + create_external_segment_postcommit(policy_context)) + except gp_exc.GroupPolicyDriverError: + with excutils.save_and_reraise_exception(): + LOG.error(_("create_external_segment_postcommit " + "failed, deleting external_segment " + "'%s'"), result['id']) + self.delete_external_segment(context, result['id']) + + return result + + @log.log + def update_external_segment(self, context, external_segment_id, + external_segment): + session = context.session + with session.begin(subtransactions=True): + original_external_segment = super( + GroupPolicyPlugin, self).get_external_segment( + context, external_segment_id) + updated_external_segment = super( + GroupPolicyPlugin, self).update_external_segment( + context, external_segment_id, + external_segment) + self.extension_manager.process_update_external_segment( + session, external_segment, updated_external_segment) + self._validate_shared_update( + context, original_external_segment, + updated_external_segment, 'external_segment') + self._validate_routes(context, updated_external_segment, + original_external_segment) + # TODO(ivar): Validate Routes' GW in es subnet + policy_context = p_context.ExternalSegmentContext( + self, context, updated_external_segment, + original_external_segment) + (self.policy_driver_manager. + update_external_segment_precommit(policy_context)) + + self.policy_driver_manager.update_external_segment_postcommit( + policy_context) + return updated_external_segment + + @log.log + def delete_external_segment(self, context, external_segment_id): + session = context.session + with session.begin(subtransactions=True): + es = self.get_external_segment(context, external_segment_id) + if es['l3_policies'] or es['nat_pools'] or es['external_policies']: + return False + policy_context = p_context.ExternalSegmentContext( + self, context, es) + (self.policy_driver_manager. + delete_external_segment_precommit(policy_context)) + super(GroupPolicyPlugin, self).delete_external_segment( + context, external_segment_id) + + try: + (self.policy_driver_manager. + delete_external_segment_postcommit(policy_context)) + except gp_exc.GroupPolicyDriverError: + with excutils.save_and_reraise_exception(): + LOG.error(_("delete_external_segment_postcommit " + "failed, deleting external_segment '%s'"), + external_segment_id) + return True + + def get_external_segment(self, context, external_segment_id, fields=None): + session = context.session + with session.begin(subtransactions=True): + result = super(GroupPolicyPlugin, self).get_external_segment( + context, external_segment_id, None) + self.extension_manager.extend_external_segment_dict(session, + result) + return self._fields(result, fields) + + def get_external_segments(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + session = context.session + with session.begin(subtransactions=True): + results = super(GroupPolicyPlugin, self).get_external_segments( + context, filters, None, sorts, limit, marker, page_reverse) + for result in results: + self.extension_manager.extend_external_segment_dict( + session, result) + return [self._fields(result, fields) for result in results] + + @log.log + def create_external_policy(self, context, external_policy): + session = context.session + with session.begin(subtransactions=True): + result = super(GroupPolicyPlugin, + self).create_external_policy( + context, external_policy) + self.extension_manager.process_create_external_policy( + session, external_policy, result) + self._validate_shared_create(context, result, + 'external_policy') + policy_context = p_context.ExternalPolicyContext( + self, context, result) + (self.policy_driver_manager. + create_external_policy_precommit(policy_context)) + + try: + (self.policy_driver_manager. + create_external_policy_postcommit(policy_context)) + except gp_exc.GroupPolicyDriverError: + with excutils.save_and_reraise_exception(): + LOG.error(_("create_external_policy_postcommit " + "failed, deleting external_policy " + "'%s'"), result['id']) + self.delete_external_policy(context, result['id']) + + return result + + @log.log + def update_external_policy(self, context, external_policy_id, + external_policy): + session = context.session + with session.begin(subtransactions=True): + original_external_policy = super( + GroupPolicyPlugin, self).get_external_policy( + context, external_policy_id) + updated_external_policy = super( + GroupPolicyPlugin, self).update_external_policy( + context, external_policy_id, + external_policy) + self.extension_manager.process_update_external_policy( + session, external_policy, updated_external_policy) + self._validate_shared_update( + context, original_external_policy, + updated_external_policy, 'external_policy') + policy_context = p_context.ExternalPolicyContext( + self, context, updated_external_policy, + original_external_policy) + (self.policy_driver_manager. + update_external_policy_precommit(policy_context)) + + self.policy_driver_manager.update_external_policy_postcommit( + policy_context) + return updated_external_policy + + @log.log + def delete_external_policy(self, context, external_policy_id, + check_unused=False): + session = context.session + with session.begin(subtransactions=True): + es = self.get_external_policy(context, external_policy_id) + policy_context = p_context.ExternalPolicyContext( + self, context, es) + (self.policy_driver_manager. + delete_external_policy_precommit(policy_context)) + super(GroupPolicyPlugin, self).delete_external_policy( + context, external_policy_id) + + try: + (self.policy_driver_manager. + delete_external_policy_postcommit(policy_context)) + except gp_exc.GroupPolicyDriverError: + with excutils.save_and_reraise_exception(): + LOG.error(_("delete_external_policy_postcommit " + "failed, deleting external_policy '%s'"), + external_policy_id) + return True + + def get_external_policy(self, context, external_policy_id, fields=None): + session = context.session + with session.begin(subtransactions=True): + result = super(GroupPolicyPlugin, self).get_external_policy( + context, external_policy_id, None) + self.extension_manager.extend_external_policy_dict(session, + result) + return self._fields(result, fields) + + def get_external_policies(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + session = context.session + with session.begin(subtransactions=True): + results = super(GroupPolicyPlugin, self).get_external_policies( + context, filters, None, sorts, limit, marker, page_reverse) + for result in results: + self.extension_manager.extend_external_policy_dict( + session, result) + return [self._fields(result, fields) for result in results] + + @log.log + def create_nat_pool(self, context, nat_pool): + session = context.session + with session.begin(subtransactions=True): + result = super(GroupPolicyPlugin, self).create_nat_pool( + context, nat_pool) + self.extension_manager.process_create_nat_pool(session, nat_pool, + result) + self._validate_shared_create(context, result, 'nat_pool') + policy_context = p_context.NatPoolContext(self, context, result) + (self.policy_driver_manager. + create_nat_pool_precommit(policy_context)) + + try: + (self.policy_driver_manager. + create_nat_pool_postcommit(policy_context)) + except gp_exc.GroupPolicyDriverError: + with excutils.save_and_reraise_exception(): + LOG.error(_("create_nat_pool_postcommit failed, deleting " + "nat_pool '%s'"), result['id']) + self.delete_nat_pool(context, result['id']) + + return result + + @log.log + def update_nat_pool(self, context, nat_pool_id, nat_pool): + session = context.session + with session.begin(subtransactions=True): + original_nat_pool = super( + GroupPolicyPlugin, self).get_nat_pool(context, nat_pool_id) + updated_nat_pool = super( + GroupPolicyPlugin, self).update_nat_pool(context, nat_pool_id, + nat_pool) + self.extension_manager.process_update_nat_pool( + session, nat_pool, updated_nat_pool) + self._validate_shared_update(context, original_nat_pool, + updated_nat_pool, 'nat_pool') + policy_context = p_context.NatPoolContext( + self, context, updated_nat_pool, original_nat_pool) + (self.policy_driver_manager. + update_nat_pool_precommit(policy_context)) + + self.policy_driver_manager.update_nat_pool_postcommit(policy_context) + return updated_nat_pool + + @log.log + def delete_nat_pool(self, context, nat_pool_id, check_unused=False): + session = context.session + with session.begin(subtransactions=True): + es = self.get_nat_pool(context, nat_pool_id) + policy_context = p_context.NatPoolContext(self, context, es) + (self.policy_driver_manager.delete_nat_pool_precommit( + policy_context)) + super(GroupPolicyPlugin, self).delete_nat_pool(context, + nat_pool_id) + + try: + (self.policy_driver_manager. + delete_nat_pool_postcommit(policy_context)) + except gp_exc.GroupPolicyDriverError: + with excutils.save_and_reraise_exception(): + LOG.error(_("delete_nat_pool_postcommit failed, deleting " + "nat_pool '%s'"), nat_pool_id) + return True + + def get_nat_pool(self, context, nat_pool_id, fields=None): + session = context.session + with session.begin(subtransactions=True): + result = super(GroupPolicyPlugin, self).get_nat_pool( + context, nat_pool_id, None) + self.extension_manager.extend_nat_pool_dict(session, result) + return self._fields(result, fields) + + def get_nat_pools(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + session = context.session + with session.begin(subtransactions=True): + results = super(GroupPolicyPlugin, self).get_nat_pools( + context, filters, None, sorts, limit, marker, page_reverse) + for result in results: + self.extension_manager.extend_nat_pool_dict( + session, result) + return [self._fields(result, fields) for result in results] \ No newline at end of file diff --git a/gbp/neutron/services/grouppolicy/policy_driver_manager.py b/gbp/neutron/services/grouppolicy/policy_driver_manager.py index 0648c90b9..54d118fce 100644 --- a/gbp/neutron/services/grouppolicy/policy_driver_manager.py +++ b/gbp/neutron/services/grouppolicy/policy_driver_manager.py @@ -310,3 +310,70 @@ class PolicyDriverManager(stevedore.named.NamedExtensionManager): def delete_policy_rule_set_postcommit(self, context): self._call_on_drivers("delete_policy_rule_set_postcommit", context, continue_on_failure=True) + + def create_external_segment_precommit(self, context): + self._call_on_drivers("create_external_segment_precommit", + context) + + def create_external_segment_postcommit(self, context): + self._call_on_drivers("create_external_segment_postcommit", + context) + + def update_external_segment_precommit(self, context): + self._call_on_drivers("update_external_segment_precommit", + context) + + def update_external_segment_postcommit(self, context): + self._call_on_drivers("update_external_segment_postcommit", + context) + + def delete_external_segment_precommit(self, context): + self._call_on_drivers("delete_external_segment_precommit", + context) + + def delete_external_segment_postcommit(self, context): + self._call_on_drivers("delete_external_segment_postcommit", + context, continue_on_failure=True) + + def create_external_policy_precommit(self, context): + self._call_on_drivers("create_external_policy_precommit", + context) + + def create_external_policy_postcommit(self, context): + self._call_on_drivers("create_external_policy_postcommit", + context) + + def update_external_policy_precommit(self, context): + self._call_on_drivers("update_external_policy_precommit", + context) + + def update_external_policy_postcommit(self, context): + self._call_on_drivers("update_external_policy_postcommit", + context) + + def delete_external_policy_precommit(self, context): + self._call_on_drivers("delete_external_policy_precommit", + context) + + def delete_external_policy_postcommit(self, context): + self._call_on_drivers("delete_external_policy_postcommit", + context, continue_on_failure=True) + + def create_nat_pool_precommit(self, context): + self._call_on_drivers("create_nat_pool_precommit", context) + + def create_nat_pool_postcommit(self, context): + self._call_on_drivers("create_nat_pool_postcommit", context) + + def update_nat_pool_precommit(self, context): + self._call_on_drivers("update_nat_pool_precommit", context) + + def update_nat_pool_postcommit(self, context): + self._call_on_drivers("update_nat_pool_postcommit", context) + + def delete_nat_pool_precommit(self, context): + self._call_on_drivers("delete_nat_pool_precommit", context) + + def delete_nat_pool_postcommit(self, context): + self._call_on_drivers("delete_nat_pool_postcommit", context, + continue_on_failure=True) \ No newline at end of file diff --git a/gbp/neutron/tests/unit/services/grouppolicy/extensions/test_extension.py b/gbp/neutron/tests/unit/services/grouppolicy/extensions/test_extension.py index 954f409a6..627d09e12 100644 --- a/gbp/neutron/tests/unit/services/grouppolicy/extensions/test_extension.py +++ b/gbp/neutron/tests/unit/services/grouppolicy/extensions/test_extension.py @@ -80,6 +80,27 @@ EXTENDED_ATTRIBUTES_2_0 = { 'is_visible': True, 'enforce_policy': True}, }, + gp.EXTERNAL_SEGMENTS: { + 'es_extension': {'allow_post': True, + 'allow_put': True, + 'default': attr.ATTR_NOT_SPECIFIED, + 'is_visible': True, + 'enforce_policy': True}, + }, + gp.EXTERNAL_POLICIES: { + 'ep_extension': {'allow_post': True, + 'allow_put': True, + 'default': attr.ATTR_NOT_SPECIFIED, + 'is_visible': True, + 'enforce_policy': True}, + }, + gp.NAT_POOLS: { + 'np_extension': {'allow_post': True, + 'allow_put': True, + 'default': attr.ATTR_NOT_SPECIFIED, + 'is_visible': True, + 'enforce_policy': True}, + }, } diff --git a/gbp/neutron/tests/unit/services/grouppolicy/test_extension_driver_api.py b/gbp/neutron/tests/unit/services/grouppolicy/test_extension_driver_api.py index aa66d8b0a..9f516c06a 100644 --- a/gbp/neutron/tests/unit/services/grouppolicy/test_extension_driver_api.py +++ b/gbp/neutron/tests/unit/services/grouppolicy/test_extension_driver_api.py @@ -373,6 +373,55 @@ class ExtensionDriverTestCase( val = res['network_service_policy']['nsp_extension'] self.assertEqual("def", val) + def test_es_attr(self): + self._test_attr('external_segment') + + def test_ep_attr(self): + self._test_attr('external_policy') + + def test_np_attr(self): + self._test_attr('nat_pool') + + def _test_attr(self, type): + # Test create with default value. + acronim = _acronim(type) + plural = self._get_resource_plural(type) + obj = getattr(self, 'create_%s' % type)() + id = obj[type]['id'] + val = obj[type][acronim + '_extension'] + self.assertEqual("", val) + req = self.new_show_request(plural, id) + res = self.deserialize(self.fmt, req.get_response(self.ext_api)) + val = res[type][acronim + '_extension'] + self.assertEqual("", val) + + # Test list. + res = self._list(plural) + val = res[plural][0][acronim + '_extension'] + self.assertEqual("", val) + + # Test create with explict value. + kwargs = {acronim + '_extension': "abc"} + obj = getattr(self, 'create_%s' % type)(**kwargs) + id = obj[type]['id'] + val = obj[type][acronim + '_extension'] + self.assertEqual("abc", val) + req = self.new_show_request(plural, id) + res = self.deserialize(self.fmt, req.get_response(self.ext_api)) + val = res[type][acronim + '_extension'] + self.assertEqual("abc", val) + + # Test update. + data = {type: {acronim + '_extension': "def"}} + req = self.new_update_request(plural, data, id) + res = self.deserialize(self.fmt, req.get_response(self.ext_api)) + val = res[type][acronim + '_extension'] + self.assertEqual("def", val) + req = self.new_show_request(plural, id) + res = self.deserialize(self.fmt, req.get_response(self.ext_api)) + val = res[type][acronim + '_extension'] + self.assertEqual("def", val) + class TestPolicyTargetExtension(model_base.BASEV2): __tablename__ = 'test_policy_target_extension' @@ -455,6 +504,33 @@ class TestNetworkServicePolicyExtension(model_base.BASEV2): value = sa.Column(sa.String(64)) +class TestExternalSegmentExtension(model_base.BASEV2): + __tablename__ = 'test_external_segment_extension' + es_id = sa.Column(sa.String(36), + sa.ForeignKey('gp_external_segments.id', + ondelete="CASCADE"), + primary_key=True) + value = sa.Column(sa.String(64)) + + +class TestExternalPolicyExtension(model_base.BASEV2): + __tablename__ = 'test_external_policy_extension' + ep_id = sa.Column(sa.String(36), + sa.ForeignKey('gp_external_policies.id', + ondelete="CASCADE"), + primary_key=True) + value = sa.Column(sa.String(64)) + + +class TestNatPoolExtension(model_base.BASEV2): + __tablename__ = 'test_nat_pool_extension' + np_id = sa.Column(sa.String(36), + sa.ForeignKey('gp_nat_pools.id', + ondelete="CASCADE"), + primary_key=True) + value = sa.Column(sa.String(64)) + + class TestExtensionDriver(api.ExtensionDriver): _supported_extension_alias = 'test_extension' @@ -677,3 +753,67 @@ class TestExtensionDriver(api.ExtensionDriver): filter_by(nsp_id=result['id']). one()) result['nsp_extension'] = record.value + + def process_create_external_segment(self, session, data, result): + self._process_create(session, data, result, 'external_segment', + TestExternalSegmentExtension) + + def process_update_external_segment(self, session, data, result): + self._process_update(session, data, result, 'external_segment', + TestExternalSegmentExtension) + + def extend_external_segment_dict(self, session, result): + self._extend(session, result, 'external_segment', + TestExternalSegmentExtension) + + def process_create_external_policy(self, session, data, result): + self._process_create(session, data, result, 'external_policy', + TestExternalPolicyExtension) + + def process_update_external_policy(self, session, data, result): + self._process_update(session, data, result, 'external_policy', + TestExternalPolicyExtension) + + def extend_external_policy_dict(self, session, result): + self._extend(session, result, 'external_policy', + TestExternalPolicyExtension) + + def process_create_nat_pool(self, session, data, result): + self._process_create(session, data, result, 'nat_pool', + TestNatPoolExtension) + + def process_update_nat_pool(self, session, data, result): + self._process_update(session, data, result, 'nat_pool', + TestNatPoolExtension) + + def extend_nat_pool_dict(self, session, result): + self._extend(session, result, 'nat_pool', TestNatPoolExtension) + + def _process_create(self, session, data, result, type, klass): + acronim = _acronim(type) + value = data[type][acronim + '_extension'] + if not attributes.is_attr_set(value): + value = '' + kwargs = {acronim + '_id': result['id'], 'value': value} + record = klass(**kwargs) + session.add(record) + result[acronim + '_extension'] = value + + def _process_update(self, session, data, result, type, klass): + acronim = _acronim(type) + kwargs = {acronim + '_id': result['id']} + record = session.query(klass).filter_by(**kwargs).one() + value = data[type].get(acronim + '_extension') + if value and value != record.value: + record.value = value + result[acronim + '_extension'] = record.value + + def _extend(self, session, result, type, klass): + acronim = _acronim(type) + kwargs = {acronim + '_id': result['id']} + record = session.query(klass).filter_by(**kwargs).one() + result[acronim + '_extension'] = record.value + + +def _acronim(type): + return ''.join([x[0] for x in type.split('_')]) \ No newline at end of file diff --git a/gbp/neutron/tests/unit/services/grouppolicy/test_grouppolicy_plugin.py b/gbp/neutron/tests/unit/services/grouppolicy/test_grouppolicy_plugin.py index 578ac2399..09d8c5fe7 100644 --- a/gbp/neutron/tests/unit/services/grouppolicy/test_grouppolicy_plugin.py +++ b/gbp/neutron/tests/unit/services/grouppolicy/test_grouppolicy_plugin.py @@ -90,8 +90,20 @@ class GroupPolicyPluginTestCase(tgpmdb.GroupPolicyMappingDbTestCase): return self.create_policy_rule_set(policy_rules=[pr['id']], **kwargs)['policy_rule_set'] - def _update_gbp_resource(self, id, type, plural, expected_res_status=None, - **kwargs): + def _create_external_policy_on_shared(self, **kwargs): + es = self.create_external_segment(shared=True) + return self.create_external_policy( + external_segments=[es['external_segment']['id']], + **kwargs)['external_policy'] + + def _create_nat_pool_on_shared(self, **kwargs): + es = self.create_external_segment(shared=True) + return self.create_nat_pool( + external_segment_id=es['external_segment']['id'], + **kwargs)['nat_pool'] + + def _update_gbp_resource_full_response( + self, id, type, plural, expected_res_status=None, **kwargs): data = {type: kwargs} # Create PT with bound port req = self.new_update_request(plural, data, id, self.fmt) @@ -101,11 +113,20 @@ class GroupPolicyPluginTestCase(tgpmdb.GroupPolicyMappingDbTestCase): self.assertEqual(res.status_int, expected_res_status) elif res.status_int >= webob.exc.HTTPClientError.code: raise webob.exc.HTTPClientError(code=res.status_int) - return self.deserialize(self.fmt, res).get(type) + return self.deserialize(self.fmt, res) + + def _update_gbp_resource(self, id, type, plural, expected_res_status=None, + **kwargs): + return self._update_gbp_resource_full_response( + id, type, plural, expected_res_status=expected_res_status, + **kwargs).get(type) class TestL3Policy(GroupPolicyPluginTestCase): + def _get_es_dict(self, es, addr=None): + return {es['external_segment']['id']: addr or []} + def test_shared_l3_policy_create(self): # Verify default is False l3p = self.create_l3_policy() @@ -114,6 +135,30 @@ class TestL3Policy(GroupPolicyPluginTestCase): l3p = self.create_l3_policy(shared=True) self.assertEqual(True, l3p['l3_policy']['shared']) + def test_shared_l3p_create_with_es(self): + def combination(l3p, es): + return {'l3p': l3p, 'es': es} + allowed = [combination(False, False), combination(True, True), + combination(False, True)] + for shared in allowed: + es = self.create_external_segment( + cidr='172.0.0.0/8', shared=shared['es']) + es_dict = self._get_es_dict(es, ['172.0.0.2', '172.0.0.3']) + l3p = self.create_l3_policy( + external_segments=es_dict, shared=shared['l3p'], + expected_res_status=201)['l3_policy'] + # Verify create successful + self.assertEqual(es_dict, l3p['external_segments']) + + def test_shared_l3p_create_with_es_negative(self): + # Not allowed: Unshared ES with shared L3P + es = self.create_external_segment(cidr='172.0.0.0/8') + es_dict = self._get_es_dict(es, ['172.0.0.2', '172.0.0.3']) + res = self.create_l3_policy(external_segments=es_dict, + shared=True, expected_res_status=400) + self.assertEqual('SharedResourceReferenceError', + res['NeutronError']['type']) + def test_shared_l3_policy_update(self): l3p = self.create_l3_policy()['l3_policy'] # Accept share if nothing referenced @@ -124,6 +169,36 @@ class TestL3Policy(GroupPolicyPluginTestCase): self.create_l2_policy(l3_policy_id=l3p['id']) self._update_gbp_resource(l3p['id'], 'l3_policy', 'l3_policies', expected_res_status=200, shared=False) + es = self.create_external_segment(cidr='172.0.0.0/8') + es_dict = self._get_es_dict(es, ['172.0.0.2', '172.0.0.3']) + # Set ES + l3p = self._update_gbp_resource(l3p['id'], 'l3_policy', 'l3_policies', + expected_res_status=200, + external_segments=es_dict) + self.assertEqual(es_dict, l3p['external_segments']) + + # Share ES + self._update_gbp_resource( + es['external_segment']['id'], 'external_segment', + 'external_segments', expected_res_status=200, shared=True) + + # Verify sharing/unsharing successful + for shared in [True, False]: + self._update_gbp_resource(l3p['id'], 'l3_policy', 'l3_policies', + expected_res_status=200, shared=shared) + + # Remove ES + l3p = self._update_gbp_resource(l3p['id'], 'l3_policy', 'l3_policies', + expected_res_status=200, + external_segments={}) + self.assertEqual({}, l3p['external_segments']) + # Verify ES update with sharing successful + l3p = self._update_gbp_resource(l3p['id'], 'l3_policy', 'l3_policies', + expected_res_status=200, + external_segments=es_dict, + shared=True) + # Verify ES correctly set + self.assertEqual(es_dict, l3p['external_segments']) def test_shared_l3_policy_update_negative(self): l3p = self.create_l3_policy(shared=True)['l3_policy'] @@ -140,6 +215,69 @@ class TestL3Policy(GroupPolicyPluginTestCase): self._update_gbp_resource(l3p['id'], 'l3_policy', 'l3_policies', expected_res_status=400, shared=False) + es = self.create_external_segment(cidr='172.0.0.0/8') + es_dict = self._get_es_dict(es, ['172.0.0.2', '172.0.0.3']) + res = self._update_gbp_resource_full_response( + l3p['id'], 'l3_policy', 'l3_policies', expected_res_status=400, + external_segments=es_dict, shared=True) + self.assertEqual('SharedResourceReferenceError', + res['NeutronError']['type']) + + def test_create_with_es_negative(self): + attrs = {'external_routes': [{'destination': '10.160.0.0/16', + 'nexthop': '172.1.1.1'}], + 'cidr': '172.1.1.0/24'} + es = self.create_external_segment(**attrs)['external_segment'] + # Overlapping pool + attrs = {'ip_pool': '172.1.1.0/20', + 'external_segments': {es['id']: ['172.1.1.2']}} + res = self.create_l3_policy(expected_res_status=400, **attrs) + self.assertEqual('ExternalSegmentSubnetOverlapsWithL3PIpPool', + res['NeutronError']['type']) + # Overlapping route + attrs['ip_pool'] = '10.160.1.0/24' + res = self.create_l3_policy(expected_res_status=400, **attrs) + self.assertEqual('ExternalRouteOverlapsWithL3PIpPool', + res['NeutronError']['type']) + # Allocated address not in pool + attrs = {'ip_pool': '192.168.0.0/24', + 'external_segments': {es['id']: ['172.1.2.2']}} + res = self.create_l3_policy(expected_res_status=400, **attrs) + self.assertEqual('InvalidL3PExternalIPAddress', + res['NeutronError']['type']) + + def test_update_with_es_negative(self): + attrs = {'external_routes': [{'destination': '10.160.0.0/16', + 'nexthop': '172.1.1.1'}], + 'cidr': '172.1.1.0/24'} + es = self.create_external_segment(**attrs)['external_segment'] + + # Overlapping pool + l3p = self.create_l3_policy(ip_pool='172.1.1.0/20')['l3_policy'] + attrs = {'external_segments': {es['id']: ['172.1.1.2']}} + res = self._update_gbp_resource_full_response( + l3p['id'], 'l3_policy', 'l3_policies', expected_res_status=400, + **attrs) + self.assertEqual('ExternalSegmentSubnetOverlapsWithL3PIpPool', + res['NeutronError']['type']) + + # Overlapping route + l3p = self.create_l3_policy(ip_pool='10.160.1.0/24')['l3_policy'] + res = self._update_gbp_resource_full_response( + l3p['id'], 'l3_policy', 'l3_policies', expected_res_status=400, + **attrs) + self.assertEqual('ExternalRouteOverlapsWithL3PIpPool', + res['NeutronError']['type']) + + # Allocated address not in pool + l3p = self.create_l3_policy(ip_pool='192.168.0.0/24')['l3_policy'] + attrs = {'external_segments': {es['id']: ['172.1.2.2']}} + res = self._update_gbp_resource_full_response( + l3p['id'], 'l3_policy', 'l3_policies', expected_res_status=400, + **attrs) + self.assertEqual('InvalidL3PExternalIPAddress', + res['NeutronError']['type']) + class TestL2Policy(GroupPolicyPluginTestCase): @@ -474,6 +612,184 @@ class TestPolicyTargetGroup(GroupPolicyPluginTestCase): expected_res_status=201) +class TestExternalSegment(GroupPolicyPluginTestCase): + + def test_shared_es_create(self): + # Verify default is False + es = self.create_external_segment() + self.assertEqual(False, es['external_segment']['shared']) + # Verify shared True created without errors + es = self.create_external_segment(shared=True) + self.assertEqual(True, es['external_segment']['shared']) + + def test_shared_es_update(self): + es = self.create_external_segment()['external_segment'] + for shared in [True, False]: + self._update_gbp_resource( + es['id'], 'external_segment', + 'external_segments', expected_res_status=200, + shared=shared) + + def test_create_routes(self): + attrs = {'external_routes': [{'destination': '0.0.0.0/0', + 'nexthop': '172.1.0.1'}], + 'cidr': '172.1.0.0/24'} + self.create_external_segment(expected_res_status=201, **attrs) + + def test_routes_negative(self): + # Verify wrong NH + attrs = {'external_routes': [{'destination': '0.0.0.0/0', + 'nexthop': '172.1.1.1'}], + 'cidr': '172.1.0.0/24'} + res = self.create_external_segment(expected_res_status=400, **attrs) + self.assertEqual('ExternalRouteNextHopNotInExternalSegment', + res['NeutronError']['type']) + attrs['cidr'] = '172.1.1.0/24' + es = self.create_external_segment(**attrs)['external_segment'] + self.create_l3_policy( + ip_pool='192.160.0.0/16', + external_segments={es['id']: ['172.1.1.2']})['l3_policy'] + + # Verify refused because overlapping with L3P + attrs = {'external_routes': [{'destination': '192.168.2.0/0', + 'nexthop': '172.1.1.1'}]} + res = self._update_gbp_resource_full_response( + es['id'], 'external_segment', 'external_segments', + expected_res_status=400, **attrs) + self.assertEqual('ExternalRouteOverlapsWithL3PIpPool', + res['NeutronError']['type']) + + +class TestExternalPolicy(GroupPolicyPluginTestCase): + + def test_shared_ep_create(self): + es = self.create_external_segment( + shared=True)['external_segment'] + esns = self.create_external_segment( + )['external_segment'] + + prs = self._create_policy_rule_set_on_shared(shared=True) + prsns = self._create_policy_rule_set_on_shared() + + # Verify non-shared ep providing and consuming shared and non shared + # policy_rule_sets + ep = self.create_external_policy( + external_segments=[es['id']], expected_res_status=201) + self.assertEqual(False, ep['external_policy']['shared']) + ep = self.create_external_policy( + external_segments=[es['id']], + provided_policy_rule_sets={prs['id']: '', prsns['id']: ''}, + consumed_policy_rule_sets={prs['id']: '', prsns['id']: ''}, + expected_res_status=201) + self.assertEqual(False, ep['external_policy']['shared']) + + # Verify shared True created without errors by providing/consuming + # shared policy_rule_sets + ep = self.create_external_policy( + external_segments=[es['id']], shared=True, + expected_res_status=201) + self.assertEqual(True, ep['external_policy']['shared']) + ep = self.create_external_policy( + external_segments=[es['id']], + provided_policy_rule_sets={prs['id']: ''}, + consumed_policy_rule_sets={prs['id']: ''}, shared=True, + expected_res_status=201) + self.assertEqual(True, ep['external_policy']['shared']) + + # Verify not shared created without error on not shared es + self.create_external_policy( + external_segments=[esns['id']], expected_res_status=201) + + def test_shared_ep_update(self): + ep = self._create_external_policy_on_shared() + self._update_gbp_resource( + ep['id'], 'external_policy', 'external_policies', + expected_res_status=200, shared=True) + self._update_gbp_resource( + ep['id'], 'external_policy', 'external_policies', + expected_res_status=200, shared=False) + + def test_shared_ep_create_negative(self): + es = self.create_external_segment()['external_segment'] + prs = self._create_policy_rule_set_on_shared() + # Verify shared EP fails on non-shared es + res = self.create_external_policy( + external_segments=[es['id']], shared=True, + expected_res_status=400) + self.assertEqual('SharedResourceReferenceError', + res['NeutronError']['type']) + # Verify shared EP fails to provide/consume non shared + # policy_rule_sets + res = self.create_external_policy( + shared=True, provided_policy_rule_sets={prs['id']: ''}, + consumed_policy_rule_sets={prs['id']: ''}, + expected_res_status=400) + self.assertEqual('SharedResourceReferenceError', + res['NeutronError']['type']) + + def test_shared_ep_update_negative(self): + ep = self._create_external_policy_on_shared(shared=True) + # Verify update to non shared ES fails + es = self.create_external_segment()['external_segment'] + self._update_gbp_resource( + ep['id'], 'external_policy', 'external_policies', + expected_res_status=400, external_segments=[es['id']]) + + # Verify update to non shared provided PRS fails + prs = self._create_policy_rule_set_on_shared() + self._update_gbp_resource( + ep['id'], 'external_policy', 'external_policies', + expected_res_status=400, + provided_policy_rule_sets={prs['id']: ''}) + # Verify update to non shared consumed PRS fails + self._update_gbp_resource( + ep['id'], 'external_policy', 'external_policies', + expected_res_status=400, + consumed_policy_rule_sets={prs['id']: ''}) + + +class TestNatPool(GroupPolicyPluginTestCase): + + def test_nat_pool_shared_create(self): + def combination(np, es): + return {'np': np, 'es': es} + allowed = [combination(False, False), combination(True, True), + combination(False, True)] + for shared in allowed: + es = self.create_external_segment( + shared=shared['es'])['external_segment'] + self.create_nat_pool(external_segment_id=es['id'], + shared=shared['np'], expected_res_status=201) + + def test_nat_pool_shared_create_negative(self): + es = self.create_external_segment( + shared=False)['external_segment'] + res = self.create_nat_pool(external_segment_id=es['id'], + shared=True, expected_res_status=400) + self.assertEqual('SharedResourceReferenceError', + res['NeutronError']['type']) + + def test_nat_pool_shared_update(self): + np = self.create_nat_pool(shared=False)['nat_pool'] + for shared in [False, True]: + es = self.create_external_segment( + shared=shared)['external_segment'] + self._update_gbp_resource( + np['id'], 'nat_pool', 'nat_pools', expected_res_status=200, + external_segment_id=es['id']) + np = self.create_nat_pool(shared=True)['nat_pool'] + es = self.create_external_segment( + shared=True)['external_segment'] + # Verify shared NP on shared ES + self._update_gbp_resource( + np['id'], 'nat_pool', 'nat_pools', expected_res_status=200, + external_segment_id=es['id']) + # Verify unshare NP + self._update_gbp_resource( + np['id'], 'nat_pool', 'nat_pools', expected_res_status=200, + shared=False) + + class TestGroupPolicyPluginGroupResources( GroupPolicyPluginTestCase, tgpdb.TestGroupResources):