From 972756f603f72a0026713d98a0d485eb0026094a Mon Sep 17 00:00:00 2001 From: Hirofumi Ichihara Date: Fri, 1 Sep 2017 15:33:01 +0900 Subject: [PATCH] Support 4-Byte AS Numbers Now neutron_dynamic_routing supports 2 byte AS numbers only. This patch expands AS numbers constraint so that it supports 4 byte AS numbers. It expects that operators use asplain notation to set AS numbers[1]. That's backward compatible with existing 2 byte AS numbers. [1]: https://tools.ietf.org/html/rfc5396 Change-Id: I06ae0c42d983e88e1f38c501d5c85a7956f195ad Closes-Bug: #1573092 --- neutron_dynamic_routing/db/bgp_db.py | 4 +- .../alembic_migrations/versions/CONTRACT_HEAD | 2 +- .../a589fdb5724c_change_size_of_as_number.py | 34 +++++ .../extensions/bgp_4byte_asn.py | 72 ++++++++++ .../services/bgp/agent/driver/utils.py | 6 +- .../services/bgp/bgp_plugin.py | 4 +- .../services/bgp/common/constants.py | 1 + .../test_bgp_speaker_extensions_negative.py | 6 +- .../tempest/scenario/4byte_asn/__init__.py | 0 .../scenario/4byte_asn/test_4byte_asn.py | 130 ++++++++++++++++++ .../services/bgp/driver/ryu/test_driver.py | 8 +- .../unit/services/bgp/driver/test_utils.py | 6 +- .../support-4byte-asn-d89d7100c0890ebf.yaml | 3 + 13 files changed, 263 insertions(+), 13 deletions(-) create mode 100644 neutron_dynamic_routing/db/migration/alembic_migrations/versions/queens/contract/a589fdb5724c_change_size_of_as_number.py create mode 100644 neutron_dynamic_routing/extensions/bgp_4byte_asn.py create mode 100644 neutron_dynamic_routing/tests/tempest/scenario/4byte_asn/__init__.py create mode 100644 neutron_dynamic_routing/tests/tempest/scenario/4byte_asn/test_4byte_asn.py create mode 100644 releasenotes/notes/support-4byte-asn-d89d7100c0890ebf.yaml diff --git a/neutron_dynamic_routing/db/bgp_db.py b/neutron_dynamic_routing/db/bgp_db.py index 899d0ddb..0aca0ae4 100644 --- a/neutron_dynamic_routing/db/bgp_db.py +++ b/neutron_dynamic_routing/db/bgp_db.py @@ -93,7 +93,7 @@ class BgpSpeaker(model_base.BASEV2, __tablename__ = 'bgp_speakers' name = sa.Column(sa.String(255), nullable=False) - local_as = sa.Column(sa.Integer, nullable=False, autoincrement=False) + local_as = sa.Column(sa.BigInteger(), nullable=False, autoincrement=False) advertise_floating_ip_host_routes = sa.Column(sa.Boolean, nullable=False) advertise_tenant_networks = sa.Column(sa.Boolean, nullable=False) peers = orm.relationship(BgpSpeakerPeerBinding, @@ -118,7 +118,7 @@ class BgpPeer(model_base.BASEV2, name = sa.Column(sa.String(255), nullable=False) peer_ip = sa.Column(sa.String(64), nullable=False) - remote_as = sa.Column(sa.Integer, nullable=False, autoincrement=False) + remote_as = sa.Column(sa.BigInteger(), nullable=False, autoincrement=False) auth_type = sa.Column(sa.String(16), nullable=False) password = sa.Column(sa.String(255), nullable=True) diff --git a/neutron_dynamic_routing/db/migration/alembic_migrations/versions/CONTRACT_HEAD b/neutron_dynamic_routing/db/migration/alembic_migrations/versions/CONTRACT_HEAD index 2130dc38..4c19f485 100644 --- a/neutron_dynamic_routing/db/migration/alembic_migrations/versions/CONTRACT_HEAD +++ b/neutron_dynamic_routing/db/migration/alembic_migrations/versions/CONTRACT_HEAD @@ -1 +1 @@ -4cf8bc3edb66 +a589fdb5724c diff --git a/neutron_dynamic_routing/db/migration/alembic_migrations/versions/queens/contract/a589fdb5724c_change_size_of_as_number.py b/neutron_dynamic_routing/db/migration/alembic_migrations/versions/queens/contract/a589fdb5724c_change_size_of_as_number.py new file mode 100644 index 00000000..4f661b4e --- /dev/null +++ b/neutron_dynamic_routing/db/migration/alembic_migrations/versions/queens/contract/a589fdb5724c_change_size_of_as_number.py @@ -0,0 +1,34 @@ +# 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. +# + +"""change size of as number + +Revision ID: a589fdb5724c +Revises: 4cf8bc3edb66 +Create Date: 2017-08-31 13:50:28.324422 + +""" + +# revision identifiers, used by Alembic. +revision = 'a589fdb5724c' +down_revision = '4cf8bc3edb66' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.alter_column('bgp_speakers', 'local_as', nullable=False, + type_=sa.BigInteger()) + op.alter_column('bgp_peers', 'remote_as', nullable=False, + type_=sa.BigInteger()) diff --git a/neutron_dynamic_routing/extensions/bgp_4byte_asn.py b/neutron_dynamic_routing/extensions/bgp_4byte_asn.py new file mode 100644 index 00000000..a50cd315 --- /dev/null +++ b/neutron_dynamic_routing/extensions/bgp_4byte_asn.py @@ -0,0 +1,72 @@ +# 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 import extensions as api_extensions + +from neutron_dynamic_routing.extensions import bgp as bgp_ext +from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts + + +BGP_4BYTE_ASN_EXT_ALIAS = 'bgp_4byte_asn' + + +RESOURCE_ATTRIBUTE_MAP = { + 'bgp-speakers': { + 'local_as': {'allow_post': True, 'allow_put': False, + 'validate': {'type:range': (bgp_consts.MIN_ASNUM, + bgp_consts.MAX_4BYTE_ASNUM)}, + 'is_visible': True, 'default': None, + 'required_by_policy': False, + 'enforce_policy': False} + }, + 'bgp-peers': { + 'remote_as': {'allow_post': True, 'allow_put': False, + 'validate': {'type:range': (bgp_consts.MIN_ASNUM, + bgp_consts.MAX_4BYTE_ASNUM)}, + 'is_visible': True, 'default': None, + 'required_by_policy': False, + 'enforce_policy': False} + } +} + + +class Bgp_4byte_asn(api_extensions.ExtensionDescriptor): + """Extension class supporting bgp 4-byte AS numbers. + """ + @classmethod + def get_name(cls): + return "BGP 4-byte AS numbers" + + @classmethod + def get_alias(cls): + return BGP_4BYTE_ASN_EXT_ALIAS + + @classmethod + def get_description(cls): + return "Support bgp 4-byte AS numbers" + + @classmethod + def get_updated(cls): + return "2017-09-07T00:00:00-00:00" + + @classmethod + def get_resources(cls): + return [] + + def get_extended_resources(self, version): + if version == "2.0": + return RESOURCE_ATTRIBUTE_MAP + else: + return {} + + def get_required_extensions(self): + return [bgp_ext.BGP_EXT_ALIAS] diff --git a/neutron_dynamic_routing/services/bgp/agent/driver/utils.py b/neutron_dynamic_routing/services/bgp/agent/driver/utils.py index dce1a8ac..1d5b398a 100644 --- a/neutron_dynamic_routing/services/bgp/agent/driver/utils.py +++ b/neutron_dynamic_routing/services/bgp/agent/driver/utils.py @@ -28,11 +28,11 @@ def validate_as_num(param, as_num): raise bgp_driver_exc.InvalidParamType(param=param, param_type='integer') - if not (bgp_consts.MIN_ASNUM <= as_num <= bgp_consts.MAX_ASNUM): - # Must be in [AS_NUM_MIN, AS_NUM_MAX] range. + if not (bgp_consts.MIN_ASNUM <= as_num <= bgp_consts.MAX_4BYTE_ASNUM): + # Must be in [AS_NUM_MIN, MAX_4BYTE_ASNUM] range. allowed_range = ('[' + str(bgp_consts.MIN_ASNUM) + '-' + - str(bgp_consts.MAX_ASNUM) + + str(bgp_consts.MAX_4BYTE_ASNUM) + ']') raise bgp_driver_exc.InvalidParamRange(param=param, range=allowed_range) diff --git a/neutron_dynamic_routing/services/bgp/bgp_plugin.py b/neutron_dynamic_routing/services/bgp/bgp_plugin.py index 2bdd8d28..22c147fa 100644 --- a/neutron_dynamic_routing/services/bgp/bgp_plugin.py +++ b/neutron_dynamic_routing/services/bgp/bgp_plugin.py @@ -32,6 +32,7 @@ from neutron_dynamic_routing.api.rpc.handlers import bgp_speaker_rpc as bs_rpc from neutron_dynamic_routing.db import bgp_db from neutron_dynamic_routing.db import bgp_dragentscheduler_db from neutron_dynamic_routing.extensions import bgp as bgp_ext +from neutron_dynamic_routing.extensions import bgp_4byte_asn from neutron_dynamic_routing.extensions import bgp_dragentscheduler as dras_ext from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts @@ -44,7 +45,8 @@ class BgpPlugin(service_base.ServicePluginBase, bgp_dragentscheduler_db.BgpDrAgentSchedulerDbMixin): supported_extension_aliases = [bgp_ext.BGP_EXT_ALIAS, - dras_ext.BGP_DRAGENT_SCHEDULER_EXT_ALIAS] + dras_ext.BGP_DRAGENT_SCHEDULER_EXT_ALIAS, + bgp_4byte_asn.BGP_4BYTE_ASN_EXT_ALIAS] def __init__(self): super(BgpPlugin, self).__init__() diff --git a/neutron_dynamic_routing/services/bgp/common/constants.py b/neutron_dynamic_routing/services/bgp/common/constants.py index 42c47bd0..0ca122d4 100644 --- a/neutron_dynamic_routing/services/bgp/common/constants.py +++ b/neutron_dynamic_routing/services/bgp/common/constants.py @@ -25,3 +25,4 @@ SUPPORTED_AUTH_TYPES = ['none', 'md5'] # Supported AS number range MIN_ASNUM = 1 MAX_ASNUM = 65535 +MAX_4BYTE_ASNUM = 4294967295 diff --git a/neutron_dynamic_routing/tests/tempest/api/test_bgp_speaker_extensions_negative.py b/neutron_dynamic_routing/tests/tempest/api/test_bgp_speaker_extensions_negative.py index 8048a03c..5f078d42 100644 --- a/neutron_dynamic_routing/tests/tempest/api/test_bgp_speaker_extensions_negative.py +++ b/neutron_dynamic_routing/tests/tempest/api/test_bgp_speaker_extensions_negative.py @@ -14,6 +14,7 @@ import netaddr +from tempest.common import utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc @@ -27,9 +28,12 @@ class BgpSpeakerTestJSONNegative(test_base.BgpSpeakerTestJSONBase): @decorators.attr(type=['negative', 'smoke']) @decorators.idempotent_id('75e9ee2f-6efd-4320-bff7-ae24741c8b06') def test_create_bgp_speaker_illegal_local_asn(self): + wrong_asn = 65537 + if utils.is_extension_enabled('bgp_4byte_asn', 'network'): + wrong_asn = 4294967296 self.assertRaises(lib_exc.BadRequest, self.create_bgp_speaker, - local_as='65537') + local_as=wrong_asn) @decorators.attr(type=['negative', 'smoke']) @decorators.idempotent_id('6742ec2e-382a-4453-8791-13a19b47cd13') diff --git a/neutron_dynamic_routing/tests/tempest/scenario/4byte_asn/__init__.py b/neutron_dynamic_routing/tests/tempest/scenario/4byte_asn/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/neutron_dynamic_routing/tests/tempest/scenario/4byte_asn/test_4byte_asn.py b/neutron_dynamic_routing/tests/tempest/scenario/4byte_asn/test_4byte_asn.py new file mode 100644 index 00000000..87a60c10 --- /dev/null +++ b/neutron_dynamic_routing/tests/tempest/scenario/4byte_asn/test_4byte_asn.py @@ -0,0 +1,130 @@ +# 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 tempest.common import utils +from tempest import config +from tempest.lib import decorators + +from neutron_dynamic_routing.tests.tempest.scenario import base +from neutron_dynamic_routing.tests.tempest.scenario import base_test_proto as test_base # noqa + +from ryu.tests.integrated.common import docker_base as ctn_base +from ryu.tests.integrated.common import quagga + +CONF = config.CONF + + +class BgpSpeaker4byteASNTest(test_base.BgpSpeakerProtoTestBase): + + RAS_MAX = 3 + ip_version = 4 + public_gw = '192.168.10.1' + MyScope = base.Scope(name='my-scope') + PNet = base.Net(name='', net='172.24.6.0', mask=24, + cidr='172.24.6.0/24', router=None) + PPool = base.Pool(name='test-pool-ext', prefixlen=PNet.mask, + prefixes=[PNet.net + '/8']) + PSubNet = base.SubNet(name='', cidr=PNet.cidr, mask=PNet.mask) + TPool = base.Pool(name='tenant-test-pool', prefixlen=28, + prefixes=['10.10.0.0/16']) + L_AS = base.AS(asn='4200000000', router_id='192.168.0.2', adv_net='') + ras_l = [ + base.AS(asn='4210000000', router_id='192.168.0.12', + adv_net='192.168.162.0/24'), + base.AS(asn='64522', router_id='192.168.0.13', + adv_net='192.168.163.0/24'), + base.AS(asn='4230000000', router_id='192.168.0.14', + adv_net='192.168.164.0/24') + ] + + bgp_speaker_args = { + 'local_as': L_AS.asn, + 'ip_version': ip_version, + 'name': 'my-bgp-speaker1', + 'advertise_floating_ip_host_routes': True, + 'advertise_tenant_networks': True + } + bgp_peer_args = [ + {'remote_as': ras_l[0].asn, + 'name': 'my-bgp-peer1', + 'peer_ip': None, + 'auth_type': 'none'}, + {'remote_as': ras_l[1].asn, + 'name': 'my-bgp-peer2', + 'peer_ip': None, + 'auth_type': 'none'}, + {'remote_as': ras_l[2].asn, + 'name': 'my-bgp-peer3', + 'peer_ip': None, + 'auth_type': 'none'} + ] + + @classmethod + @utils.requires_ext(extension="bgp_4byte_asn", service="network") + def resource_setup(cls): + super(BgpSpeaker4byteASNTest, cls).resource_setup() + + @classmethod + def resource_setup_container(cls): + cls.brdc = ctn_base.Bridge(name='br-docker', + subnet='192.168.10.0/24', + start_ip='192.168.10.128', + end_ip='192.168.10.254', + self_ip=True, + fixed_ip=cls.public_gw + '/24', + br_type=base.BRIDGE_TYPE) + cls.bridges.append(cls.brdc) + # This is dummy container object for a dr service. + # This keeps data which passes to a quagga container. + cls.dr = ctn_base.BGPContainer(name='dr', asn=int(cls.L_AS.asn), + router_id=cls.L_AS.router_id) + cls.dr.set_addr_info(bridge='br-docker', ipv4=cls.public_gw) + # quagga container + cls.dockerimg = ctn_base.DockerImage() + cls.q_img = cls.dockerimg.create_quagga(check_exist=True) + cls.images.append(cls.q_img) + for i in range(cls.RAS_MAX): + qg = quagga.QuaggaBGPContainer(name='q' + str(i + 1), + asn=int(cls.ras_l[i].asn), + router_id=cls.ras_l[i].router_id, + ctn_image_name=cls.q_img) + cls.containers.append(qg) + cls.r_ass.append(qg) + qg.add_route(cls.ras_l[i].adv_net) + qg.run(wait=True) + cls.r_as_ip.append(cls.brdc.addif(qg)) + qg.add_peer(cls.dr, bridge=cls.brdc.name, + peer_info={'passive': True}) + cls.tnet_gen = cls.get_subnet(start='10.10.1.0', end='10.10.255.0', + step=256) + + @decorators.idempotent_id('9f18c931-a59e-4a27-939b-21124995ffe2') + def test_check_neighbor_established(self): + self._test_check_neighbor_established(self.ip_version) + + @decorators.idempotent_id('73466aa5-7d1d-4f9f-8fb4-4100fad2dffe') + def test_check_advertised_tenant_network(self): + self._test_check_advertised_tenant_network(self.ip_version) + + @decorators.idempotent_id('c3158328-2f69-4aa2-b1b7-5a06ab58afaf') + def test_check_advertised_multiple_tenant_network(self): + self._test_check_advertised_multiple_tenant_network(self.ip_version) + + @decorators.idempotent_id('212a3d82-ac50-43dc-b657-030b1133643e') + def test_check_neighbor_established_with_multiple_peers(self): + self._test_check_neighbor_established_with_multiple_peers( + self.ip_version) + + @decorators.idempotent_id('c72411c8-ea79-495d-bdbd-a10159642676') + def test_check_advertised_tenant_network_with_multiple_peers(self): + self._test_check_advertised_tenant_network_with_multiple_peers( + self.ip_version) diff --git a/neutron_dynamic_routing/tests/unit/services/bgp/driver/ryu/test_driver.py b/neutron_dynamic_routing/tests/unit/services/bgp/driver/ryu/test_driver.py index 9bf1810b..18ad94dc 100644 --- a/neutron_dynamic_routing/tests/unit/services/bgp/driver/ryu/test_driver.py +++ b/neutron_dynamic_routing/tests/unit/services/bgp/driver/ryu/test_driver.py @@ -191,7 +191,9 @@ class TestRyuBgpDriver(base.BaseTestCase): self.assertRaises(bgp_driver_exc.InvalidParamRange, self.ryu_bgp_driver.add_bgp_speaker, -1) self.assertRaises(bgp_driver_exc.InvalidParamRange, - self.ryu_bgp_driver.add_bgp_speaker, 65536) + self.ryu_bgp_driver.add_bgp_speaker, 4294967296) + # valid when enables 4 byte AS number + self.ryu_bgp_driver.add_bgp_speaker(65536) def test_add_bgp_peer_with_invalid_paramtype(self): # Test with an invalid asnum data-type @@ -237,7 +239,9 @@ class TestRyuBgpDriver(base.BaseTestCase): FAKE_LOCAL_AS1, FAKE_PEER_IP, -1) self.assertRaises(bgp_driver_exc.InvalidParamRange, self.ryu_bgp_driver.add_bgp_peer, - FAKE_LOCAL_AS1, FAKE_PEER_IP, 65536) + FAKE_LOCAL_AS1, FAKE_PEER_IP, 4294967296) + # valid when enables 4 byte AS number + self.ryu_bgp_driver.add_bgp_peer(FAKE_LOCAL_AS1, FAKE_PEER_IP, 65536) def test_add_bgp_peer_without_adding_speaker(self): self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded, diff --git a/neutron_dynamic_routing/tests/unit/services/bgp/driver/test_utils.py b/neutron_dynamic_routing/tests/unit/services/bgp/driver/test_utils.py index f2544a0c..38889ad5 100644 --- a/neutron_dynamic_routing/tests/unit/services/bgp/driver/test_utils.py +++ b/neutron_dynamic_routing/tests/unit/services/bgp/driver/test_utils.py @@ -52,19 +52,19 @@ class TestValidateMethod(base.BaseTestCase): def test_validate_as_num_with_invalid_max_range(self): allowed_range = ('\[' + str(bgp_consts.MIN_ASNUM) + '-' + - str(bgp_consts.MAX_ASNUM) + + str(bgp_consts.MAX_4BYTE_ASNUM) + '\]') with self.assertRaisesRegex( bgp_driver_exc.InvalidParamRange, EXC_INV_PARAMRANGE % {'param': 'local_as', 'range': allowed_range}): bgp_driver_utils.validate_as_num('local_as', - bgp_consts.MAX_ASNUM + 1) + bgp_consts.MAX_4BYTE_ASNUM + 1) def test_validate_as_num_with_invalid_min_range(self): allowed_range = ('\[' + str(bgp_consts.MIN_ASNUM) + '-' + - str(bgp_consts.MAX_ASNUM) + + str(bgp_consts.MAX_4BYTE_ASNUM) + '\]') with self.assertRaisesRegex( bgp_driver_exc.InvalidParamRange, diff --git a/releasenotes/notes/support-4byte-asn-d89d7100c0890ebf.yaml b/releasenotes/notes/support-4byte-asn-d89d7100c0890ebf.yaml new file mode 100644 index 00000000..a8fc5892 --- /dev/null +++ b/releasenotes/notes/support-4byte-asn-d89d7100c0890ebf.yaml @@ -0,0 +1,3 @@ +--- +features: + - The neutron-dynamic-routing supports 4-byte AS Numbers now. \ No newline at end of file