Add source_ip_prefix and destination_ip_prefix to metering label rules

As proposed in the RFE and then approved in the spec, we are adding to
the neutron metering rules two new parameters. The source IP prefix, and
destination IP prefix.

Partially-Implements: https://bugs.launchpad.net/neutron/+bug/1889431
RFE: https://bugs.launchpad.net/neutron/+bug/1889431

Depends-On: https://review.opendev.org/#/c/746203/
Depends-On: https://review.opendev.org/#/c/744702/
Depends-On: https://review.opendev.org/#/c/743828/
Depends-On: https://review.opendev.org/#/c/746142/

Change-Id: I38991de2b4937becd0f1f14f3a32dc39c590e0d9
(cherry picked from commit 10091f9346)
This commit is contained in:
Rafael Weingärtner 2020-08-14 17:00:28 -03:00 committed by Slawek Kaplonski
parent a88f5438b9
commit 341401d916
11 changed files with 573 additions and 110 deletions

View File

@ -17,7 +17,9 @@ from neutron_lib import constants
from neutron_lib.db import api as db_api
from neutron_lib.db import utils as db_utils
from neutron_lib.exceptions import metering as metering_exc
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import uuidutils
from neutron.api.rpc.agentnotifiers import metering_rpc_agent_api
@ -27,6 +29,8 @@ from neutron.objects import base as base_obj
from neutron.objects import metering as metering_objs
from neutron.objects import router as l3_obj
LOG = logging.getLogger(__name__)
class MeteringDbMixin(metering.MeteringPluginBase):
@ -84,7 +88,10 @@ class MeteringDbMixin(metering.MeteringPluginBase):
res = {'id': metering_label_rule['id'],
'metering_label_id': metering_label_rule['metering_label_id'],
'direction': metering_label_rule['direction'],
'remote_ip_prefix': metering_label_rule['remote_ip_prefix'],
'remote_ip_prefix': metering_label_rule.get('remote_ip_prefix'),
'source_ip_prefix': metering_label_rule.get('source_ip_prefix'),
'destination_ip_prefix': metering_label_rule.get(
'destination_ip_prefix'),
'excluded': metering_label_rule['excluded']}
return db_utils.resource_fields(res, fields)
@ -109,45 +116,33 @@ class MeteringDbMixin(metering.MeteringPluginBase):
return self._make_metering_label_rule_dict(
self._get_metering_label_rule(context, rule_id), fields)
def _validate_cidr(self, context, label_id, remote_ip_prefix,
direction, excluded):
r_ips = self.get_metering_label_rules(context,
filters={'metering_label_id':
[label_id],
'direction':
[direction],
'excluded':
[excluded]},
fields=['remote_ip_prefix'])
cidrs = [r['remote_ip_prefix'] for r in r_ips]
new_cidr_ipset = netaddr.IPSet([remote_ip_prefix])
if (netaddr.IPSet(cidrs) & new_cidr_ipset):
raise metering_exc.MeteringLabelRuleOverlaps(
remote_ip_prefix=remote_ip_prefix)
def create_metering_label_rule(self, context, metering_label_rule):
m = metering_label_rule['metering_label_rule']
label_id = metering_label_rule['metering_label_id']
try:
with db_api.CONTEXT_WRITER.using(context):
label_id = m['metering_label_id']
ip_prefix = m['remote_ip_prefix']
direction = m['direction']
excluded = m['excluded']
self._validate_cidr(context, label_id, ip_prefix, direction,
excluded)
rule = metering_objs.MeteringLabelRule(
context, id=uuidutils.generate_uuid(),
metering_label_id=label_id, direction=direction,
excluded=m['excluded'],
remote_ip_prefix=netaddr.IPNetwork(ip_prefix))
metering_label_id=label_id,
direction=metering_label_rule['direction'],
excluded=metering_label_rule['excluded'],
)
if metering_label_rule.get('remote_ip_prefix'):
rule.remote_ip_prefix = netaddr.IPNetwork(
metering_label_rule['remote_ip_prefix'])
if metering_label_rule.get('source_ip_prefix'):
rule.source_ip_prefix = netaddr.IPNetwork(
metering_label_rule['source_ip_prefix'])
if metering_label_rule.get('destination_ip_prefix'):
rule.destination_ip_prefix = netaddr.IPNetwork(
metering_label_rule['destination_ip_prefix'])
rule.create()
return self._make_metering_label_rule_dict(rule)
except db_exc.DBReferenceError:
raise metering_exc.MeteringLabelNotFound(label_id=label_id)
return self._make_metering_label_rule_dict(rule)
def delete_metering_label_rule(self, context, rule_id):
with db_api.CONTEXT_WRITER.using(context):
rule = self._get_metering_label_rule(context, rule_id)

View File

@ -1 +1 @@
49d8622c5221
I38991de2b4

View File

@ -0,0 +1,38 @@
# Copyright 2020 OpenStack Foundation
#
# 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
#
# 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 alembic import op
import sqlalchemy as sa
"""Add source and destination IP prefixes to neutron metering system
Revision ID: I38991de2b4
Revises: fd6107509ccd
Create Date: 2020-08-20 10:00:00.000000
"""
# revision identifiers, used by Alembic.
revision = 'I38991de2b4'
down_revision = '49d8622c5221'
metering_label_rules_table_name = 'meteringlabelrules'
def upgrade():
op.add_column(metering_label_rules_table_name,
sa.Column('source_ip_prefix', sa.String(64)))
op.add_column(metering_label_rules_table_name,
sa.Column('destination_ip_prefix', sa.String(64)))

View File

@ -23,6 +23,8 @@ class MeteringLabelRule(model_base.BASEV2, model_base.HasId):
direction = sa.Column(sa.Enum('ingress', 'egress',
name='meteringlabels_direction'))
remote_ip_prefix = sa.Column(sa.String(64))
source_ip_prefix = sa.Column(sa.String(64))
destination_ip_prefix = sa.Column(sa.String(64))
metering_label_id = sa.Column(sa.String(36),
sa.ForeignKey("meteringlabels.id",
ondelete="CASCADE"),

View File

@ -0,0 +1,42 @@
# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
#
# 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
#
# 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_lib.api.definitions import metering_source_and_destination_filters
from neutron_lib.api import extensions
class Metering_source_and_destination_fields(
extensions.APIExtensionDescriptor):
api_definition = metering_source_and_destination_filters
@classmethod
def get_extended_resources(cls, version):
sub_resource_map = super(Metering_source_and_destination_fields, cls
).get_extended_resources(version)
processed_sub_resource_map = {}
for value in sub_resource_map.values():
parent_def = value['parent']
collection_name = parent_def['collection_name']
member_name = parent_def['member_name']
if collection_name == member_name:
processed_sub_resource_map[
collection_name] = value['parameters']
else:
processed_sub_resource_map[
collection_name] = {member_name: value['parameters']}
return processed_sub_resource_map

View File

@ -23,7 +23,8 @@ from neutron.objects import base
@base.NeutronObjectRegistry.register
class MeteringLabelRule(base.NeutronDbObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 2.0: Source and destination field for the metering label rule
VERSION = '2.0'
db_model = metering_models.MeteringLabelRule
@ -33,6 +34,8 @@ class MeteringLabelRule(base.NeutronDbObject):
'id': common_types.UUIDField(),
'direction': common_types.FlowDirectionEnumField(nullable=True),
'remote_ip_prefix': common_types.IPNetworkField(nullable=True),
'source_ip_prefix': common_types.IPNetworkField(nullable=True),
'destination_ip_prefix': common_types.IPNetworkField(nullable=True),
'metering_label_id': common_types.UUIDField(),
'excluded': obj_fields.BooleanField(default=False),
}
@ -42,19 +45,34 @@ class MeteringLabelRule(base.NeutronDbObject):
@classmethod
def modify_fields_from_db(cls, db_obj):
result = super(MeteringLabelRule, cls).modify_fields_from_db(db_obj)
if 'remote_ip_prefix' in result:
result['remote_ip_prefix'] = net_utils.AuthenticIPNetwork(
result['remote_ip_prefix'])
cls.ip_field_from_db(result, "remote_ip_prefix")
cls.ip_field_from_db(result, "source_ip_prefix")
cls.ip_field_from_db(result, "destination_ip_prefix")
return result
@classmethod
def ip_field_from_db(cls, result, attribute_name):
if attribute_name in result:
result[attribute_name] = net_utils.AuthenticIPNetwork(
result[attribute_name])
@classmethod
def modify_fields_to_db(cls, fields):
result = super(MeteringLabelRule, cls).modify_fields_to_db(fields)
if 'remote_ip_prefix' in result:
result['remote_ip_prefix'] = cls.filter_to_str(
result['remote_ip_prefix'])
cls.ip_field_to_db(result, "remote_ip_prefix")
cls.ip_field_to_db(result, "source_ip_prefix")
cls.ip_field_to_db(result, "destination_ip_prefix")
return result
@classmethod
def ip_field_to_db(cls, result, attribute_name):
if attribute_name in result:
result[attribute_name] = cls.filter_to_str(result[attribute_name])
@base.NeutronObjectRegistry.register
class MeteringLabel(base.NeutronDbObject):

View File

@ -213,6 +213,10 @@ class IptablesMeteringDriver(abstract_driver.MeteringAbstractDriver):
def _add_rule_to_chain(self, ext_dev, rule, im,
label_chain, rules_chain):
ipt_rule = self._prepare_rule(ext_dev, rule, label_chain)
LOG.debug("Adding IPtables rule [%s] for configurations [%s].",
ipt_rule, rule)
if rule['excluded']:
im.ipv4['filter'].add_rule(rules_chain, ipt_rule,
wrap=False, top=True)
@ -223,6 +227,10 @@ class IptablesMeteringDriver(abstract_driver.MeteringAbstractDriver):
def _remove_rule_from_chain(self, ext_dev, rule, im,
label_chain, rules_chain):
ipt_rule = self._prepare_rule(ext_dev, rule, label_chain)
LOG.debug("Removing IPtables rule [%s] for configurations [%s].",
ipt_rule, rule)
if rule['excluded']:
im.ipv4['filter'].remove_rule(rules_chain, ipt_rule,
wrap=False, top=True)
@ -231,16 +239,43 @@ class IptablesMeteringDriver(abstract_driver.MeteringAbstractDriver):
wrap=False, top=False)
def _prepare_rule(self, ext_dev, rule, label_chain):
remote_ip = rule['remote_ip_prefix']
if rule['direction'] == 'egress':
dir_opt = '-s %s -o %s' % (remote_ip, ext_dev)
if rule.get('remote_ip_prefix'):
ipt_rule = IptablesMeteringDriver.\
prepare_source_and_destination_rule_legacy(ext_dev, rule)
else:
dir_opt = '-d %s -i %s' % (remote_ip, ext_dev)
ipt_rule = IptablesMeteringDriver.\
prepare_source_and_destination_rule(ext_dev, rule)
if rule['excluded']:
ipt_rule = '%s -j RETURN' % dir_opt
ipt_rule = '%s -j RETURN' % ipt_rule
else:
ipt_rule = '%s -j %s' % (dir_opt, label_chain)
ipt_rule = '%s -j %s' % (ipt_rule, label_chain)
return ipt_rule
@staticmethod
def prepare_source_and_destination_rule(ext_dev, rule):
if rule['direction'] == 'egress':
iptables_rule = '-o %s' % ext_dev
else:
iptables_rule = '-i %s' % ext_dev
source_ip_prefix = rule.get('source_ip_prefix')
if source_ip_prefix:
iptables_rule = "-s %s %s" % (source_ip_prefix, iptables_rule)
destination_ip_prefix = rule.get('destination_ip_prefix')
if destination_ip_prefix:
iptables_rule = "-d %s %s" % (destination_ip_prefix, iptables_rule)
return iptables_rule
@staticmethod
def prepare_source_and_destination_rule_legacy(ext_dev, rule):
remote_ip = rule['remote_ip_prefix']
if rule['direction'] == 'egress':
ipt_rule = '-s %s -o %s' % (remote_ip, ext_dev)
else:
ipt_rule = '-d %s -i %s' % (remote_ip, ext_dev)
return ipt_rule
def _process_ns_specific_metering_label(self, router, ext_dev, im):

View File

@ -12,8 +12,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import ipaddress
import netaddr
from neutron_lib.agent import topics
from neutron_lib.api.definitions import metering as metering_apidef
from neutron_lib.api.definitions import metering_source_and_destination_filters
from neutron_lib.exceptions import metering as metering_exc
from neutron_lib import exceptions as neutron_exc
from neutron_lib import rpc as n_rpc
from neutron.api.rpc.agentnotifiers import metering_rpc_agent_api
@ -28,7 +35,8 @@ LOG = logging.getLogger(__name__)
class MeteringPlugin(metering_db.MeteringDbMixin):
"""Implementation of the Neutron Metering Service Plugin."""
supported_extension_aliases = [metering_apidef.ALIAS]
supported_extension_aliases = [
metering_apidef.ALIAS, metering_source_and_destination_filters.ALIAS]
path_prefix = "/metering"
__filter_validation_support = True
@ -66,6 +74,10 @@ class MeteringPlugin(metering_db.MeteringDbMixin):
return label
def create_metering_label_rule(self, context, metering_label_rule):
metering_label_rule = metering_label_rule['metering_label_rule']
MeteringPlugin.validate_metering_label_rule(metering_label_rule)
self.check_for_rule_overlaps(context, metering_label_rule)
rule = super(MeteringPlugin, self).create_metering_label_rule(
context, metering_label_rule)
@ -82,6 +94,80 @@ class MeteringPlugin(metering_db.MeteringDbMixin):
return rule
@staticmethod
def validate_metering_label_rule(metering_label_rule):
MeteringPlugin.validate_metering_rule_ip_address(
metering_label_rule, "remote_ip_prefix")
MeteringPlugin.validate_metering_rule_ip_address(
metering_label_rule, "source_ip_prefix")
MeteringPlugin.validate_metering_rule_ip_address(
metering_label_rule, "destination_ip_prefix")
if metering_label_rule.get("remote_ip_prefix"):
if metering_label_rule.get("source_ip_prefix") or \
metering_label_rule.get("destination_ip_prefix"):
raise neutron_exc.Invalid(
"Cannot use 'remote-ip-prefix' in conjunction "
"with 'source-ip-prefix' or 'destination-ip-prefix'.")
none_ip_prefix_informed = not metering_label_rule.get(
'remote_ip_prefix') and not metering_label_rule.get(
'source_ip_prefix') and not metering_label_rule.get(
'destination_ip_prefix')
if none_ip_prefix_informed:
raise neutron_exc.Invalid(
"You must define at least one of the following parameters "
"'remote_ip_prefix', or 'source_ip_prefix' or "
"'destination_ip_prefix'.")
@staticmethod
def validate_metering_rule_ip_address(metering_label_rule,
ip_address_field):
try:
if metering_label_rule.get(ip_address_field):
ipaddress.ip_interface(
metering_label_rule.get(ip_address_field))
except ValueError as exception:
raise neutron_exc.Invalid(
"%s: %s is invalid [%s]." %
(ip_address_field,
metering_label_rule.get(ip_address_field),
exception))
def check_for_rule_overlaps(self, context, metering_label_rule):
label_id = metering_label_rule['metering_label_id']
direction = metering_label_rule['direction']
excluded = metering_label_rule['excluded']
db_metering_rules = self.get_metering_label_rules(
context, filters={
'metering_label_id': [label_id],
'direction': [direction],
'excluded': [excluded]}
)
for db_metering_rule in db_metering_rules:
MeteringPlugin.verify_rule_overlap(
db_metering_rule, metering_label_rule, "remote_ip_prefix")
@staticmethod
def verify_rule_overlap(db_metering_rule, metering_label_rule,
attribute_name):
if db_metering_rule.get(
attribute_name) and metering_label_rule.get(attribute_name):
remote_ip_prefix = metering_label_rule[attribute_name]
cidr = [db_metering_rule.get(attribute_name)]
new_cidr_ipset = netaddr.IPSet([remote_ip_prefix])
if netaddr.IPSet(cidr) & new_cidr_ipset:
LOG.warning("The metering rule [%s] overlaps with"
" previously created rule [%s]. It is not an"
" expected use case, and people should use"
" it wisely.", metering_label_rule,
db_metering_rule)
raise metering_exc.MeteringLabelRuleOverlaps(
remote_ip_prefix=remote_ip_prefix)
def delete_metering_label_rule(self, context, rule_id):
rule = super(MeteringPlugin, self).delete_metering_label_rule(
context, rule_id)

View File

@ -64,13 +64,29 @@ class MeteringPluginDbTestCaseMixin(object):
return self.deserialize(fmt, res)
def _create_metering_label_rule(self, fmt, metering_label_id, direction,
remote_ip_prefix, excluded, **kwargs):
data = {'metering_label_rule':
{'metering_label_id': metering_label_id,
'tenant_id': kwargs.get('tenant_id', 'test-tenant'),
'direction': direction,
'excluded': excluded,
'remote_ip_prefix': remote_ip_prefix}}
excluded, remote_ip_prefix=None,
source_ip_prefix=None,
destination_ip_prefix=None,
**kwargs):
data = {
'metering_label_rule': {
'metering_label_id': metering_label_id,
'tenant_id': kwargs.get('tenant_id', 'test-tenant'),
'direction': direction,
'excluded': excluded,
}
}
if remote_ip_prefix:
data['metering_label_rule']['remote_ip_prefix'] = remote_ip_prefix
if source_ip_prefix:
data['metering_label_rule']['source_ip_prefix'] = source_ip_prefix
if destination_ip_prefix:
data['metering_label_rule']['destination_ip_prefix'] =\
destination_ip_prefix
req = self.new_create_request('metering-label-rules',
data, fmt)
@ -82,10 +98,9 @@ class MeteringPluginDbTestCaseMixin(object):
return req.get_response(self.ext_api)
def _make_metering_label_rule(self, fmt, metering_label_id, direction,
remote_ip_prefix, excluded, **kwargs):
excluded, **kwargs):
res = self._create_metering_label_rule(fmt, metering_label_id,
direction, remote_ip_prefix,
excluded, **kwargs)
direction, excluded, **kwargs)
if res.status_int >= 400:
raise webob.exc.HTTPClientError(code=res.status_int)
return self.deserialize(fmt, res)
@ -101,15 +116,14 @@ class MeteringPluginDbTestCaseMixin(object):
@contextlib.contextmanager
def metering_label_rule(self, metering_label_id=None, direction='ingress',
remote_ip_prefix='10.0.0.0/24',
excluded='false', fmt=None):
excluded=False, fmt=None, **kwargs):
if not fmt:
fmt = self.fmt
metering_label_rule = self._make_metering_label_rule(fmt,
metering_label_id,
direction,
remote_ip_prefix,
excluded)
excluded,
**kwargs)
yield metering_label_rule
@ -212,10 +226,9 @@ class TestMetering(MeteringPluginDbTestCase):
('direction', direction),
('excluded', excluded),
('remote_ip_prefix', remote_ip_prefix)]
with self.metering_label_rule(metering_label_id,
direction,
remote_ip_prefix,
excluded) as label_rule:
with self.metering_label_rule(
metering_label_id, direction, excluded,
remote_ip_prefix=remote_ip_prefix) as label_rule:
for k, v, in keys:
self.assertEqual(label_rule['metering_label_rule'][k], v)
@ -224,11 +237,9 @@ class TestMetering(MeteringPluginDbTestCase):
remote_ip_prefix = '192.168.0.0/24'
excluded = True
res = self._create_metering_label_rule(self.fmt,
_fake_uuid(),
direction,
remote_ip_prefix,
excluded)
res = self._create_metering_label_rule(
self.fmt, _fake_uuid(), direction, excluded,
remote_ip_prefix=remote_ip_prefix)
self.assertEqual(webob.exc.HTTPNotFound.code, res.status_int)
def test_update_metering_label_rule(self):
@ -239,8 +250,8 @@ class TestMetering(MeteringPluginDbTestCase):
data = {'metering_label_rule': {}}
with self.metering_label(name, description) as metering_label, \
self.metering_label_rule(
metering_label['metering_label']['id'],
direction, remote_ip_prefix) as label_rule:
metering_label['metering_label']['id'], direction,
remote_ip_prefix=remote_ip_prefix) as label_rule:
rule_id = label_rule['metering_label_rule']['id']
self._update('metering-label-rules', rule_id, data,
webob.exc.HTTPNotImplemented.code)
@ -256,10 +267,9 @@ class TestMetering(MeteringPluginDbTestCase):
remote_ip_prefix = '192.168.0.0/24'
excluded = True
with self.metering_label_rule(metering_label_id,
direction,
remote_ip_prefix,
excluded) as label_rule:
with self.metering_label_rule(
metering_label_id, direction, excluded,
remote_ip_prefix=remote_ip_prefix) as label_rule:
rule_id = label_rule['metering_label_rule']['id']
self._delete('metering-label-rules', rule_id, 204)
@ -274,14 +284,12 @@ class TestMetering(MeteringPluginDbTestCase):
remote_ip_prefix = '192.168.0.0/24'
excluded = True
with self.metering_label_rule(metering_label_id,
direction,
remote_ip_prefix,
excluded) as v1,\
self.metering_label_rule(metering_label_id,
'ingress',
remote_ip_prefix,
excluded) as v2:
with self.metering_label_rule(
metering_label_id, direction, excluded,
remote_ip_prefix=remote_ip_prefix) as v1,\
self.metering_label_rule(
metering_label_id, 'ingress', excluded,
remote_ip_prefix=remote_ip_prefix) as v2:
metering_label_rule = (v1, v2)
self._test_list_resources('metering-label-rule',
@ -298,14 +306,12 @@ class TestMetering(MeteringPluginDbTestCase):
remote_ip_prefix = '192.168.0.0/24'
excluded = True
with self.metering_label_rule(metering_label_id,
direction,
remote_ip_prefix,
excluded) as v1,\
self.metering_label_rule(metering_label_id,
direction,
n_consts.IPv4_ANY,
False) as v2:
with self.metering_label_rule(
metering_label_id, direction, excluded,
remote_ip_prefix=remote_ip_prefix) as v1,\
self.metering_label_rule(
metering_label_id, direction, False,
remote_ip_prefix=n_consts.IPv4_ANY) as v2:
metering_label_rule = (v1, v2)
self._test_list_resources('metering-label-rule',
@ -323,15 +329,12 @@ class TestMetering(MeteringPluginDbTestCase):
remote_ip_prefix2 = '192.168.0.0/16'
excluded = True
with self.metering_label_rule(metering_label_id,
direction,
remote_ip_prefix1,
excluded):
res = self._create_metering_label_rule(self.fmt,
metering_label_id,
direction,
remote_ip_prefix2,
excluded)
with self.metering_label_rule(
metering_label_id, direction, excluded,
remote_ip_prefix=remote_ip_prefix1):
res = self._create_metering_label_rule(
self.fmt, metering_label_id, direction, excluded,
remote_ip_prefix=remote_ip_prefix2)
self.assertEqual(webob.exc.HTTPConflict.code, res.status_int)
def test_create_metering_label_rule_two_labels(self):
@ -349,14 +352,12 @@ class TestMetering(MeteringPluginDbTestCase):
remote_ip_prefix = '192.168.0.0/24'
excluded = True
with self.metering_label_rule(metering_label_id1,
direction,
remote_ip_prefix,
excluded) as v1,\
self.metering_label_rule(metering_label_id2,
direction,
remote_ip_prefix,
excluded) as v2:
with self.metering_label_rule(
metering_label_id1, direction, excluded,
remote_ip_prefix=remote_ip_prefix) as v1,\
self.metering_label_rule(
metering_label_id2, direction, excluded,
remote_ip_prefix=remote_ip_prefix) as v2:
metering_label_rule = (v1, v2)
self._test_list_resources('metering-label-rule',

View File

@ -59,7 +59,7 @@ object_data = {
'L3HARouterNetwork': '1.0-87acea732853f699580179a94d2baf91',
'L3HARouterVRIdAllocation': '1.0-37502aebdbeadc4f9e3bd5e9da714ab9',
'MeteringLabel': '1.0-cc4b620a3425222447cbe459f62de533',
'MeteringLabelRule': '1.0-b5c5717e7bab8d1af1623156012a5842',
'MeteringLabelRule': '2.0-0ad09894c62e1ce6e868f725158959ba',
'Log': '1.0-6391351c0f34ed34375a19202f361d24',
'Network': '1.1-c3e9ecc0618ee934181d91b143a48901',
'NetworkDhcpAgentBinding': '1.1-d9443c88809ffa4c45a0a5a48134b54a',

View File

@ -22,6 +22,8 @@ from neutron_lib.plugins import constants
from neutron_lib.plugins import directory
from neutron_lib.tests import tools
from neutron_lib.utils import net as net_utils
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from neutron.api.rpc.agentnotifiers import metering_rpc_agent_api
@ -275,6 +277,8 @@ class TestMeteringPlugin(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
'remote_ip_prefix':
net_utils.AuthenticIPNetwork(
'10.0.0.0/24'),
'destination_ip_prefix': None,
'source_ip_prefix': None,
'direction': 'ingress',
'metering_label_id': self.uuid,
'excluded': False,
@ -293,6 +297,8 @@ class TestMeteringPlugin(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
'remote_ip_prefix':
net_utils.AuthenticIPNetwork(
'10.0.0.0/24'),
'destination_ip_prefix': None,
'source_ip_prefix': None,
'direction': 'ingress',
'metering_label_id': self.uuid,
'excluded': False,
@ -300,18 +306,258 @@ class TestMeteringPlugin(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
'id': self.uuid}],
'id': self.uuid}]
remote_ip_prefix = {'remote_ip_prefix': '10.0.0.0/24'}
with self.router(tenant_id=self.tenant_id, set_context=True):
with self.metering_label(tenant_id=self.tenant_id,
set_context=True) as label:
la = label['metering_label']
self.mock_uuid.return_value = second_uuid
with self.metering_label_rule(la['id']):
with self.metering_label_rule(la['id'], **remote_ip_prefix):
self.mock_add_rule.assert_called_with(self.ctx,
expected_add)
self._delete('metering-label-rules', second_uuid)
self.mock_remove_rule.assert_called_with(self.ctx,
expected_del)
def test_add_and_remove_metering_label_rule_source_ip_only(self):
second_uuid = 'e27fe2df-376e-4ac7-ae13-92f050a21f84'
expected_add = [{'status': 'ACTIVE',
'name': 'router1',
'gw_port_id': None,
'admin_state_up': True,
'distributed': False,
'tenant_id': self.tenant_id,
'_metering_labels': [
{'rule': {
'source_ip_prefix':
net_utils.AuthenticIPNetwork(
'10.0.0.0/24'),
'destination_ip_prefix': None,
'remote_ip_prefix': None,
'direction': 'ingress',
'metering_label_id': self.uuid,
'excluded': False,
'id': second_uuid},
'id': self.uuid}],
'id': self.uuid}]
expected_del = [{'status': 'ACTIVE',
'name': 'router1',
'gw_port_id': None,
'admin_state_up': True,
'distributed': False,
'tenant_id': self.tenant_id,
'_metering_labels': [
{'rule': {
'source_ip_prefix':
net_utils.AuthenticIPNetwork(
'10.0.0.0/24'),
'destination_ip_prefix': None,
'remote_ip_prefix': None,
'direction': 'ingress',
'metering_label_id': self.uuid,
'excluded': False,
'id': second_uuid},
'id': self.uuid}],
'id': self.uuid}]
source_ip_prefix = {'source_ip_prefix': '10.0.0.0/24'}
with self.router(tenant_id=self.tenant_id, set_context=True):
with self.metering_label(tenant_id=self.tenant_id,
set_context=True) as label:
la = label['metering_label']
self.mock_uuid.return_value = second_uuid
with self.metering_label_rule(la['id'],
**source_ip_prefix):
self.mock_add_rule.assert_called_with(self.ctx,
expected_add)
self._delete('metering-label-rules', second_uuid)
self.mock_remove_rule.assert_called_with(self.ctx,
expected_del)
def test_add_and_remove_metering_label_rule_dest_ip_only(self):
second_uuid = 'e27fe2df-376e-4ac7-ae13-92f050a21f84'
expected_add = [{'status': 'ACTIVE',
'name': 'router1',
'gw_port_id': None,
'admin_state_up': True,
'distributed': False,
'tenant_id': self.tenant_id,
'_metering_labels': [
{'rule': {
'destination_ip_prefix':
net_utils.AuthenticIPNetwork(
'10.0.0.0/24'),
'source_ip_prefix': None,
'remote_ip_prefix': None,
'direction': 'ingress',
'metering_label_id': self.uuid,
'excluded': False,
'id': second_uuid},
'id': self.uuid}],
'id': self.uuid}]
expected_del = [{'status': 'ACTIVE',
'name': 'router1',
'gw_port_id': None,
'admin_state_up': True,
'distributed': False,
'tenant_id': self.tenant_id,
'_metering_labels': [
{'rule': {
'destination_ip_prefix':
net_utils.AuthenticIPNetwork(
'10.0.0.0/24'),
'source_ip_prefix': None,
'remote_ip_prefix': None,
'direction': 'ingress',
'metering_label_id': self.uuid,
'excluded': False,
'id': second_uuid},
'id': self.uuid}],
'id': self.uuid}]
source_ip_prefix = {'destination_ip_prefix': '10.0.0.0/24'}
with self.router(tenant_id=self.tenant_id, set_context=True):
with self.metering_label(tenant_id=self.tenant_id,
set_context=True) as label:
la = label['metering_label']
self.mock_uuid.return_value = second_uuid
with self.metering_label_rule(la['id'],
**source_ip_prefix):
self.mock_add_rule.assert_called_with(self.ctx,
expected_add)
self._delete('metering-label-rules', second_uuid)
self.mock_remove_rule.assert_called_with(self.ctx,
expected_del)
def test_add_and_remove_metering_label_rule_src_and_dest_ip_only(self):
second_uuid = 'e27fe2df-376e-4ac7-ae13-92f050a21f84'
expected_add = [{'status': 'ACTIVE',
'name': 'router1',
'gw_port_id': None,
'admin_state_up': True,
'distributed': False,
'tenant_id': self.tenant_id,
'_metering_labels': [
{'rule': {
'destination_ip_prefix':
net_utils.AuthenticIPNetwork('0.0.0.0/0'),
'source_ip_prefix':
net_utils.AuthenticIPNetwork(
'10.0.0.0/24'),
'remote_ip_prefix': None,
'direction': 'ingress',
'metering_label_id': self.uuid,
'excluded': False,
'id': second_uuid},
'id': self.uuid}],
'id': self.uuid}]
expected_del = [{'status': 'ACTIVE',
'name': 'router1',
'gw_port_id': None,
'admin_state_up': True,
'distributed': False,
'tenant_id': self.tenant_id,
'_metering_labels': [
{'rule': {
'destination_ip_prefix':
net_utils.AuthenticIPNetwork('0.0.0.0/0'),
'source_ip_prefix':
net_utils.AuthenticIPNetwork(
'10.0.0.0/24'),
'remote_ip_prefix': None,
'direction': 'ingress',
'metering_label_id': self.uuid,
'excluded': False,
'id': second_uuid},
'id': self.uuid}],
'id': self.uuid}]
ip_prefixes = {'source_ip_prefix': '10.0.0.0/24',
'destination_ip_prefix': '00.0.0.0/0'}
with self.router(tenant_id=self.tenant_id, set_context=True):
with self.metering_label(tenant_id=self.tenant_id,
set_context=True) as label:
la = label['metering_label']
self.mock_uuid.return_value = second_uuid
with self.metering_label_rule(la['id'],
**ip_prefixes):
self.mock_add_rule.assert_called_with(self.ctx,
expected_add)
self._delete('metering-label-rules', second_uuid)
self.mock_remove_rule.assert_called_with(self.ctx,
expected_del)
def test_add_and_remove_metering_label_rule_src_and_remote_ip(self):
with self.router(tenant_id=self.tenant_id, set_context=True):
with self.metering_label(tenant_id=self.tenant_id,
set_context=True) as label:
la = label['metering_label']
res = self._create_metering_label_rule(
self.fmt, la['id'], 'ingress', False,
remote_ip_prefix='0.0.0.0/0',
source_ip_prefix='10.0.0.0/24')
expected_error_code = 500
self.assertEqual(expected_error_code, res.status_int)
expected_error_message = "Cannot use 'remote-ip-prefix' in " \
"conjunction with " \
"'source-ip-prefix' or " \
"'destination-ip-prefix'."
self.assertEqual(
expected_error_message, jsonutils.loads(res.body)[
"NeutronError"]["message"])
def test_add_and_remove_metering_label_rule_dest_and_remote_ip(self):
with self.router(tenant_id=self.tenant_id, set_context=True):
with self.metering_label(tenant_id=self.tenant_id,
set_context=True) as label:
la = label['metering_label']
res = self._create_metering_label_rule(
self.fmt, la['id'], 'ingress', False,
remote_ip_prefix='0.0.0.0/0',
destination_ip_prefix='8.8.8.8/32')
expected_error_code = 500
self.assertEqual(expected_error_code, res.status_int)
expected_error_message = "Cannot use 'remote-ip-prefix' in " \
"conjunction with " \
"'source-ip-prefix' or " \
"'destination-ip-prefix'."
self.assertEqual(
expected_error_message, jsonutils.loads(res.body)[
"NeutronError"]["message"])
def test_add_and_remove_metering_label_rule_no_ip_prefix_entered(self):
with self.router(tenant_id=self.tenant_id, set_context=True):
with self.metering_label(tenant_id=self.tenant_id,
set_context=True) as label:
la = label['metering_label']
res = self._create_metering_label_rule(
self.fmt, la['id'], 'ingress', False)
expected_error_code = 500
self.assertEqual(expected_error_code, res.status_int)
expected_error_message = "You must define at least one of " \
"the following parameters " \
"'remote_ip_prefix', or " \
"'source_ip_prefix' or " \
"'destination_ip_prefix'."
self.assertEqual(
expected_error_message, jsonutils.loads(res.body)[
"NeutronError"]["message"])
def test_delete_metering_label_does_not_clear_router_tenant_id(self):
tenant_id = '654f6b9d-0f36-4ae5-bd1b-01616794ca60'
with self.metering_label(tenant_id=tenant_id) as metering_label: