diff --git a/neutronclient/neutron/v2_0/bgp/__init__.py b/neutronclient/neutron/v2_0/bgp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutronclient/neutron/v2_0/bgp/dragentscheduler.py b/neutronclient/neutron/v2_0/bgp/dragentscheduler.py new file mode 100644 index 000000000..81ce8f1ec --- /dev/null +++ b/neutronclient/neutron/v2_0/bgp/dragentscheduler.py @@ -0,0 +1,117 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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 +# +# 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 __future__ import print_function + +from neutronclient._i18n import _ +from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker + + +def add_common_args(parser): + parser.add_argument('dragent_id', + metavar='BGP_DRAGENT_ID', + help=_('ID of the Dynamic Routing agent.')) + parser.add_argument('bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + + +class AddBGPSpeakerToDRAgent(neutronV20.NeutronCommand): + """Add a BGP speaker to a Dynamic Routing agent.""" + + def get_parser(self, prog_name): + parser = super(AddBGPSpeakerToDRAgent, self).get_parser(prog_name) + add_common_args(parser) + return parser + + def take_action(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = bgp_speaker.get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + neutron_client.add_bgp_speaker_to_dragent( + parsed_args.dragent_id, {'bgp_speaker_id': _speaker_id}) + print(_('Associated BGP speaker %s to the Dynamic Routing agent.') + % parsed_args.bgp_speaker, file=self.app.stdout) + + +class RemoveBGPSpeakerFromDRAgent(neutronV20.NeutronCommand): + """Removes a BGP speaker from a Dynamic Routing agent.""" + + def get_parser(self, prog_name): + parser = super(RemoveBGPSpeakerFromDRAgent, self).get_parser( + prog_name) + add_common_args(parser) + return parser + + def take_action(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = bgp_speaker.get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + neutron_client.remove_bgp_speaker_from_dragent(parsed_args.dragent_id, + _speaker_id) + print(_('Disassociated BGP speaker %s from the ' + 'Dynamic Routing agent.') + % parsed_args.bgp_speaker, file=self.app.stdout) + + +class ListBGPSpeakersOnDRAgent(neutronV20.ListCommand): + """List BGP speakers hosted by a Dynamic Routing agent.""" + + list_columns = ['id', 'name', 'local_as', 'ip_version'] + resource = 'bgp_speaker' + + def get_parser(self, prog_name): + parser = super(ListBGPSpeakersOnDRAgent, + self).get_parser(prog_name) + parser.add_argument( + 'dragent_id', + metavar='BGP_DRAGENT_ID', + help=_('ID of the Dynamic Routing agent.')) + return parser + + def call_server(self, neutron_client, search_opts, parsed_args): + data = neutron_client.list_bgp_speaker_on_dragent( + parsed_args.dragent_id, **search_opts) + return data + + +class ListDRAgentsHostingBGPSpeaker(neutronV20.ListCommand): + """List Dynamic Routing agents hosting a BGP speaker.""" + + resource = 'agent' + _formatters = {} + list_columns = ['id', 'host', 'admin_state_up', 'alive'] + unknown_parts_flag = False + + def get_parser(self, prog_name): + parser = super(ListDRAgentsHostingBGPSpeaker, + self).get_parser(prog_name) + parser.add_argument('bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + return parser + + def extend_list(self, data, parsed_args): + for agent in data: + agent['alive'] = ":-)" if agent['alive'] else 'xxx' + + def call_server(self, neutron_client, search_opts, parsed_args): + _speaker_id = bgp_speaker.get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + search_opts['bgp_speaker'] = _speaker_id + data = neutron_client.list_dragents_hosting_bgp_speaker(**search_opts) + return data diff --git a/neutronclient/neutron/v2_0/bgp/peer.py b/neutronclient/neutron/v2_0/bgp/peer.py new file mode 100644 index 000000000..8fefb660a --- /dev/null +++ b/neutronclient/neutron/v2_0/bgp/peer.py @@ -0,0 +1,127 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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 +# +# 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 neutronclient._i18n import _ +from neutronclient.common import exceptions +from neutronclient.common import utils +from neutronclient.common import validators +from neutronclient.neutron import v2_0 as neutronv20 + + +def get_bgp_peer_id(client, id_or_name): + return neutronv20.find_resourceid_by_name_or_id(client, + 'bgp_peer', + id_or_name) + + +def validate_peer_attributes(parsed_args): + # Validate AS number + validators.validate_int_range(parsed_args, 'remote_as', + neutronv20.bgp.speaker.MIN_AS_NUM, + neutronv20.bgp.speaker.MAX_AS_NUM) + # Validate password + if parsed_args.auth_type != 'none' and parsed_args.password is None: + raise exceptions.CommandError(_('Must provide password if auth-type ' + 'is specified.')) + if parsed_args.auth_type == 'none' and parsed_args.password: + raise exceptions.CommandError(_('Must provide auth-type if password ' + 'is specified.')) + + +class ListPeers(neutronv20.ListCommand): + """List BGP peers.""" + + resource = 'bgp_peer' + list_columns = ['id', 'name', 'peer_ip', 'remote_as'] + pagination_support = True + sorting_support = True + + +class ShowPeer(neutronv20.ShowCommand): + """Show information of a given BGP peer.""" + + resource = 'bgp_peer' + + +class CreatePeer(neutronv20.CreateCommand): + """Create a BGP Peer.""" + + resource = 'bgp_peer' + + def add_known_arguments(self, parser): + parser.add_argument( + 'name', + metavar='NAME', + help=_('Name of the BGP peer to create.')) + parser.add_argument( + '--peer-ip', + metavar='PEER_IP_ADDRESS', + required=True, + help=_('Peer IP address.')) + parser.add_argument( + '--remote-as', + required=True, + metavar='PEER_REMOTE_AS', + help=_('Peer AS number. (Integer in [%(min_val)s, %(max_val)s] ' + 'is allowed.)') % + {'min_val': neutronv20.bgp.speaker.MIN_AS_NUM, + 'max_val': neutronv20.bgp.speaker.MAX_AS_NUM}) + parser.add_argument( + '--auth-type', + metavar='PEER_AUTH_TYPE', + choices=['none', 'md5'], + default='none', + type=utils.convert_to_lowercase, + help=_('Authentication algorithm. Supported algorithms: ' + 'none(default), md5')) + parser.add_argument( + '--password', + metavar='AUTH_PASSWORD', + help=_('Authentication password.')) + + def args2body(self, parsed_args): + body = {} + validate_peer_attributes(parsed_args) + neutronv20.update_dict(parsed_args, body, + ['name', 'peer_ip', + 'remote_as', 'auth_type', 'password']) + return {self.resource: body} + + +class UpdatePeer(neutronv20.UpdateCommand): + """Update BGP Peer's information.""" + + resource = 'bgp_peer' + + def add_known_arguments(self, parser): + parser.add_argument( + '--name', + help=_('Updated name of the BGP peer.')) + parser.add_argument( + '--password', + metavar='AUTH_PASSWORD', + help=_('Updated authentication password.')) + + def args2body(self, parsed_args): + body = {} + neutronv20.update_dict(parsed_args, body, ['name', 'password']) + return {self.resource: body} + + +class DeletePeer(neutronv20.DeleteCommand): + """Delete a BGP peer.""" + + resource = 'bgp_peer' diff --git a/neutronclient/neutron/v2_0/bgp/speaker.py b/neutronclient/neutron/v2_0/bgp/speaker.py new file mode 100755 index 000000000..f2a3df7db --- /dev/null +++ b/neutronclient/neutron/v2_0/bgp/speaker.py @@ -0,0 +1,277 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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 +# +# 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 __future__ import print_function + +from neutronclient._i18n import _ +from neutronclient.common import utils +from neutronclient.common import validators +from neutronclient.neutron import v2_0 as neutronv20 +from neutronclient.neutron.v2_0.bgp import peer as bgp_peer + +# Allowed BGP Autonomous number range +MIN_AS_NUM = 1 +MAX_AS_NUM = 65535 + + +def get_network_id(client, id_or_name): + return neutronv20.find_resourceid_by_name_or_id(client, + 'network', + id_or_name) + + +def get_bgp_speaker_id(client, id_or_name): + return neutronv20.find_resourceid_by_name_or_id(client, + 'bgp_speaker', + id_or_name) + + +def validate_speaker_attributes(parsed_args): + # Validate AS number + validators.validate_int_range(parsed_args, 'local_as', + MIN_AS_NUM, MAX_AS_NUM) + + +def add_common_arguments(parser): + utils.add_boolean_argument( + parser, '--advertise-floating-ip-host-routes', + help=_('Whether to enable or disable the advertisement ' + 'of floating-ip host routes by the BGP speaker. ' + 'By default floating ip host routes will be ' + 'advertised by the BGP speaker.')) + utils.add_boolean_argument( + parser, '--advertise-tenant-networks', + help=_('Whether to enable or disable the advertisement ' + 'of tenant network routes by the BGP speaker. ' + 'By default tenant network routes will be ' + 'advertised by the BGP speaker.')) + + +def args2body_common_arguments(body, parsed_args): + neutronv20.update_dict(parsed_args, body, + ['name', + 'advertise_floating_ip_host_routes', + 'advertise_tenant_networks']) + + +class ListSpeakers(neutronv20.ListCommand): + """List BGP speakers.""" + + resource = 'bgp_speaker' + list_columns = ['id', 'name', 'local_as', 'ip_version'] + pagination_support = True + sorting_support = True + + +class ShowSpeaker(neutronv20.ShowCommand): + """Show information of a given BGP speaker.""" + + resource = 'bgp_speaker' + + +class CreateSpeaker(neutronv20.CreateCommand): + """Create a BGP Speaker.""" + + resource = 'bgp_speaker' + + def add_known_arguments(self, parser): + parser.add_argument( + 'name', + metavar='NAME', + help=_('Name of the BGP speaker to create.')) + parser.add_argument( + '--local-as', + metavar='LOCAL_AS', + required=True, + help=_('Local AS number. (Integer in [%(min_val)s, %(max_val)s] ' + 'is allowed.)') % {'min_val': MIN_AS_NUM, + 'max_val': MAX_AS_NUM}) + parser.add_argument( + '--ip-version', + type=int, choices=[4, 6], + default=4, + help=_('IP version for the BGP speaker (default is 4).')) + add_common_arguments(parser) + + def args2body(self, parsed_args): + body = {} + validate_speaker_attributes(parsed_args) + body['local_as'] = parsed_args.local_as + body['ip_version'] = parsed_args.ip_version + args2body_common_arguments(body, parsed_args) + return {self.resource: body} + + +class UpdateSpeaker(neutronv20.UpdateCommand): + """Update BGP Speaker's information.""" + + resource = 'bgp_speaker' + + def add_known_arguments(self, parser): + parser.add_argument( + '--name', + help=_('Name of the BGP speaker to update.')) + add_common_arguments(parser) + + def args2body(self, parsed_args): + body = {} + args2body_common_arguments(body, parsed_args) + return {self.resource: body} + + +class DeleteSpeaker(neutronv20.DeleteCommand): + """Delete a BGP speaker.""" + + resource = 'bgp_speaker' + + +class AddPeerToSpeaker(neutronv20.NeutronCommand): + """Add a peer to the BGP speaker.""" + + def get_parser(self, prog_name): + parser = super(AddPeerToSpeaker, self).get_parser(prog_name) + parser.add_argument( + 'bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + parser.add_argument( + 'bgp_peer', + metavar='BGP_PEER', + help=_('ID or name of the BGP peer to add.')) + return parser + + def run(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + _peer_id = bgp_peer.get_bgp_peer_id(neutron_client, + parsed_args.bgp_peer) + neutron_client.add_peer_to_bgp_speaker(_speaker_id, + {'bgp_peer_id': _peer_id}) + print(_('Added BGP peer %(peer)s to BGP speaker %(speaker)s.') % + {'peer': parsed_args.bgp_peer, + 'speaker': parsed_args.bgp_speaker}, + file=self.app.stdout) + + +class RemovePeerFromSpeaker(neutronv20.NeutronCommand): + """Remove a peer from the BGP speaker.""" + + def get_parser(self, prog_name): + parser = super(RemovePeerFromSpeaker, self).get_parser(prog_name) + parser.add_argument( + 'bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + parser.add_argument( + 'bgp_peer', + metavar='BGP_PEER', + help=_('ID or name of the BGP peer to remove.')) + return parser + + def run(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + _peer_id = bgp_peer.get_bgp_peer_id(neutron_client, + parsed_args.bgp_peer) + neutron_client.remove_peer_from_bgp_speaker(_speaker_id, + {'bgp_peer_id': _peer_id}) + print(_('Removed BGP peer %(peer)s from BGP speaker %(speaker)s.') % + {'peer': parsed_args.bgp_peer, + 'speaker': parsed_args.bgp_speaker}, + file=self.app.stdout) + + +class AddNetworkToSpeaker(neutronv20.NeutronCommand): + """Add a network to the BGP speaker.""" + + def get_parser(self, prog_name): + parser = super(AddNetworkToSpeaker, self).get_parser(prog_name) + parser.add_argument( + 'bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + parser.add_argument( + 'network', + metavar='NETWORK', + help=_('ID or name of the network to add.')) + return parser + + def run(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + _net_id = get_network_id(neutron_client, + parsed_args.network) + neutron_client.add_network_to_bgp_speaker(_speaker_id, + {'network_id': _net_id}) + print(_('Added network %(net)s to BGP speaker %(speaker)s.') % + {'net': parsed_args.network, 'speaker': parsed_args.bgp_speaker}, + file=self.app.stdout) + + +class RemoveNetworkFromSpeaker(neutronv20.NeutronCommand): + """Remove a network from the BGP speaker.""" + + def get_parser(self, prog_name): + parser = super(RemoveNetworkFromSpeaker, self).get_parser(prog_name) + parser.add_argument( + 'bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + parser.add_argument( + 'network', + metavar='NETWORK', + help=_('ID or name of the network to remove.')) + return parser + + def run(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + _net_id = get_network_id(neutron_client, + parsed_args.network) + neutron_client.remove_network_from_bgp_speaker(_speaker_id, + {'network_id': _net_id}) + print(_('Removed network %(net)s from BGP speaker %(speaker)s.') % + {'net': parsed_args.network, 'speaker': parsed_args.bgp_speaker}, + file=self.app.stdout) + + +class ListRoutesAdvertisedBySpeaker(neutronv20.ListCommand): + """List routes advertised by a given BGP speaker.""" + + list_columns = ['id', 'destination', 'next_hop'] + resource = 'advertised_route' + pagination_support = True + sorting_support = True + + def get_parser(self, prog_name): + parser = super(ListRoutesAdvertisedBySpeaker, + self).get_parser(prog_name) + parser.add_argument( + 'bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + return parser + + def call_server(self, neutron_client, search_opts, parsed_args): + _speaker_id = get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + data = neutron_client.list_route_advertised_from_bgp_speaker( + _speaker_id, **search_opts) + return data diff --git a/neutronclient/shell.py b/neutronclient/shell.py index c1f6b0aaf..795571ad4 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -45,6 +45,9 @@ from neutronclient.neutron.v2_0 import agent from neutronclient.neutron.v2_0 import agentscheduler from neutronclient.neutron.v2_0 import auto_allocated_topology from neutronclient.neutron.v2_0 import availability_zone +from neutronclient.neutron.v2_0.bgp import dragentscheduler as bgp_drsched +from neutronclient.neutron.v2_0.bgp import peer as bgp_peer +from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker from neutronclient.neutron.v2_0 import extension from neutronclient.neutron.v2_0.flavor import flavor from neutronclient.neutron.v2_0.flavor import flavor_profile @@ -395,6 +398,35 @@ COMMAND_V2 = { 'availability-zone-list': availability_zone.ListAvailabilityZone, 'auto-allocated-topology-show': ( auto_allocated_topology.ShowAutoAllocatedTopology), + 'bgp-dragent-speaker-add': ( + bgp_drsched.AddBGPSpeakerToDRAgent + ), + 'bgp-dragent-speaker-remove': ( + bgp_drsched.RemoveBGPSpeakerFromDRAgent + ), + 'bgp-speaker-list-on-dragent': ( + bgp_drsched.ListBGPSpeakersOnDRAgent + ), + 'bgp-dragent-list-hosting-speaker': ( + bgp_drsched.ListDRAgentsHostingBGPSpeaker + ), + 'bgp-speaker-list': bgp_speaker.ListSpeakers, + 'bgp-speaker-advertiseroute-list': ( + bgp_speaker.ListRoutesAdvertisedBySpeaker + ), + 'bgp-speaker-show': bgp_speaker.ShowSpeaker, + 'bgp-speaker-create': bgp_speaker.CreateSpeaker, + 'bgp-speaker-update': bgp_speaker.UpdateSpeaker, + 'bgp-speaker-delete': bgp_speaker.DeleteSpeaker, + 'bgp-speaker-peer-add': bgp_speaker.AddPeerToSpeaker, + 'bgp-speaker-peer-remove': bgp_speaker.RemovePeerFromSpeaker, + 'bgp-speaker-network-add': bgp_speaker.AddNetworkToSpeaker, + 'bgp-speaker-network-remove': bgp_speaker.RemoveNetworkFromSpeaker, + 'bgp-peer-list': bgp_peer.ListPeers, + 'bgp-peer-show': bgp_peer.ShowPeer, + 'bgp-peer-create': bgp_peer.CreatePeer, + 'bgp-peer-update': bgp_peer.UpdatePeer, + 'bgp-peer-delete': bgp_peer.DeletePeer, } COMMANDS = {'2.0': COMMAND_V2} diff --git a/neutronclient/tests/unit/bgp/__init__.py b/neutronclient/tests/unit/bgp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutronclient/tests/unit/bgp/test_cli20_dragentscheduler.py b/neutronclient/tests/unit/bgp/test_cli20_dragentscheduler.py new file mode 100644 index 000000000..cbe85d92f --- /dev/null +++ b/neutronclient/tests/unit/bgp/test_cli20_dragentscheduler.py @@ -0,0 +1,66 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import sys + +from neutronclient.neutron.v2_0.bgp import dragentscheduler as bgp_drsched +from neutronclient.tests.unit import test_cli20 +from neutronclient.tests.unit import test_cli20_agentschedulers as test_as + + +BGP_DRAGENT_ID = 'bgp_dragent_id1' +BGP_SPEAKER = 'bgp_speaker_id1' + + +class CLITestV20DRAgentScheduler(test_as.CLITestV20AgentScheduler): + + def test_add_bgp_speaker_to_dragent(self): + resource = 'agent' + cmd = bgp_drsched.AddBGPSpeakerToDRAgent( + test_cli20.MyApp(sys.stdout), None) + args = (BGP_DRAGENT_ID, BGP_SPEAKER) + body = {'bgp_speaker_id': BGP_SPEAKER} + result = {'bgp_speaker_id': 'bgp_speaker_id', } + self._test_add_to_agent(resource, cmd, args, + self.client.BGP_DRINSTANCES, + body, result) + + def test_remove_bgp_speaker_from_dragent(self): + resource = 'agent' + cmd = bgp_drsched.RemoveBGPSpeakerFromDRAgent( + test_cli20.MyApp(sys.stdout), None) + args = (BGP_DRAGENT_ID, BGP_SPEAKER) + self._test_remove_from_agent(resource, cmd, args, + self.client.BGP_DRINSTANCES) + + def test_list_bgp_speakers_on_dragent(self): + resources = 'bgp_speakers' + cmd = bgp_drsched.ListBGPSpeakersOnDRAgent( + test_cli20.MyApp(sys.stdout), None) + path = ((self.client.agent_path + self.client.BGP_DRINSTANCES) % + BGP_DRAGENT_ID) + self._test_list_resources(resources, cmd, base_args=[BGP_DRAGENT_ID], + path=path) + + def test_list_dragents_hosting_bgp_speaker(self): + resources = 'agent' + cmd = bgp_drsched.ListDRAgentsHostingBGPSpeaker( + test_cli20.MyApp(sys.stdout), None) + path = ((self.client.bgp_speaker_path + self.client.BGP_DRAGENTS) % + BGP_DRAGENT_ID) + contents = {self.id_field: 'myid1', 'alive': True} + self._test_list_resources(resources, cmd, base_args=[BGP_DRAGENT_ID], + path=path, response_contents=contents) diff --git a/neutronclient/tests/unit/bgp/test_cli20_peer.py b/neutronclient/tests/unit/bgp/test_cli20_peer.py new file mode 100644 index 000000000..b16532704 --- /dev/null +++ b/neutronclient/tests/unit/bgp/test_cli20_peer.py @@ -0,0 +1,223 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import sys + +from neutronclient.common import exceptions +from neutronclient.neutron.v2_0.bgp import peer as bgp_peer +from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker +from neutronclient.tests.unit import test_cli20 + + +class CLITestV20BGPPeerJSON(test_cli20.CLITestV20Base): + + non_admin_status_resources = ['bgp_peer'] + + def test_create_bgp_peer_with_mandatory_params(self): + # Create BGP peer with mandatory params. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '1' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, ] + position_names = ['name', 'peer_ip', 'remote_as', + 'auth_type'] + position_values = [name, peerip, remote_asnum, 'none'] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_bgp_peer_with_all_params(self): + # Create BGP peer with all params. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '65535' + authType = 'md5' + password = 'abc' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, + '--auth-type', authType, + '--password', password] + position_names = ['name', 'peer_ip', 'remote_as', + 'auth_type', 'password'] + position_values = [name, peerip, remote_asnum, authType, password] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_bgp_peer_with_invalid_min_remote_asnum(self): + # Create BGP peer with invalid minimum remote-asnum. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '0' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, ] + position_names = ['name', 'peer_ip', 'remote_as', ] + position_values = [name, peerip, remote_asnum, ] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('remote-as "0" should be an integer [%s:%s].' % + (bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM), + str(exc)) + + def test_create_bgp_peer_with_invalid_max_remote_asnum(self): + # Create BGP peer with invalid maximum remote-asnum. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '65536' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, ] + position_names = ['name', 'peer_ip', 'remote_as', + 'auth_type', 'password'] + position_values = [name, peerip, remote_asnum, 'none', ''] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('remote-as "65536" should be an integer [%s:%s].' % + (bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM), + str(exc)) + + def test_create_authenticated_bgp_peer_without_authtype(self): + # Create authenticated BGP peer without auth-type. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '2048' + password = 'abc' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, + '--password', password] + position_names = ['name', 'peer_ip', 'remote_as', 'password'] + position_values = [name, peerip, remote_asnum, password] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('Must provide auth-type if password is specified.', + str(exc)) + + def test_create_authenticated_bgp_peer_without_password(self): + # Create authenticated BGP peer without password. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '2048' + authType = 'md5' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, + '--auth-type', authType] + position_names = ['name', 'peer_ip', 'remote_as', 'auth-type'] + position_values = [name, peerip, remote_asnum, authType] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('Must provide password if auth-type is specified.', + str(exc)) + + def test_update_bgp_peer(self): + # Update BGP peer: + # myid --advertise-tenant-networks True + # --advertise-floating-ip-host-routes False + resource = 'bgp_peer' + cmd = bgp_peer.UpdatePeer(test_cli20.MyApp(sys.stdout), + None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'new-name', + '--password', 'abc'], + {'name': 'new-name', 'password': 'abc'}) + + def test_update_bgp_peer_exception(self): + # Update BGP peer: myid. + resource = 'bgp_peer' + cmd = bgp_peer.UpdatePeer(test_cli20.MyApp(sys.stdout), + None) + self.assertRaises(exceptions.CommandError, + self._test_update_resource, + resource, cmd, 'myid', ['myid'], {}) + + def test_list_bgp_peer(self): + # List all BGP peers. + resources = "bgp_peers" + cmd = bgp_peer.ListPeers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, True) + + # TODO(Vikram): Add test_list_bgp_peer_pagination + + def test_list_bgp_peer_sort(self): + # sorted list: bgp-peer-list --sort-key name --sort-key id + # --sort-key asc --sort-key desc + resources = "bgp_peers" + cmd = bgp_peer.ListPeers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_bgp_peer_limit(self): + # size (1000) limited list: bgp-peer-list -P. + resources = "bgp_peers" + cmd = bgp_peer.ListPeers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, page_size=1000) + + def test_show_bgp_peer(self): + # Show BGP peer: --fields id --fields name myid. + resource = 'bgp_peer' + cmd = bgp_peer.ShowPeer(test_cli20.MyApp(sys.stdout), + None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args, + ['id', 'name']) + + def test_delete_bgp_peer(self): + # Delete BGP peer: bgp_peer_id. + resource = 'bgp_peer' + cmd = bgp_peer.DeletePeer(test_cli20.MyApp(sys.stdout), + None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) diff --git a/neutronclient/tests/unit/bgp/test_cli20_speaker.py b/neutronclient/tests/unit/bgp/test_cli20_speaker.py new file mode 100644 index 000000000..63ce5fc82 --- /dev/null +++ b/neutronclient/tests/unit/bgp/test_cli20_speaker.py @@ -0,0 +1,267 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import sys + +from mox3 import mox + +from neutronclient.common import exceptions +from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker +from neutronclient.tests.unit import test_cli20 + + +class CLITestV20BGPSpeakerJSON(test_cli20.CLITestV20Base): + + non_admin_status_resources = ['bgp_speaker'] + + def test_create_bgp_speaker_with_minimal_options(self): + # Create BGP Speaker with mandatory params. + resource = 'bgp_speaker' + cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + local_asnum = '1' + args = [name, '--local-as', local_asnum, ] + position_names = ['name', 'local_as', 'ip_version'] + position_values = [name, local_asnum, 4] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_ipv4_bgp_speaker_with_all_params(self): + # Create BGP Speaker with all params. + resource = 'bgp_speaker' + cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + local_asnum = '1' + args = [name, + '--local-as', local_asnum, + '--ip-version', '4', + '--advertise-floating-ip-host-routes', 'True', + '--advertise-tenant-networks', 'True'] + position_names = ['name', 'local_as', 'ip_version', + 'advertise_floating_ip_host_routes', + 'advertise_tenant_networks'] + position_values = [name, local_asnum, 4, 'True', 'True'] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_ipv6_bgp_speaker_with_all_params(self): + # Create BGP Speaker with all params. + resource = 'bgp_speaker' + cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + local_asnum = '65535' + args = [name, + '--local-as', local_asnum, + '--ip-version', '6', + '--advertise-floating-ip-host-routes', 'True', + '--advertise-tenant-networks', 'True'] + position_names = ['name', 'local_as', 'ip_version', + 'advertise_floating_ip_host_routes', + 'advertise_tenant_networks'] + position_values = [name, local_asnum, 6, 'True', 'True'] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_bgp_speaker_with_invalid_min_local_asnum(self): + # Create BGP Speaker with invalid minimum local-asnum. + resource = 'bgp_speaker' + cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + local_asnum = '0' + args = [name, + '--local-as', local_asnum] + position_names = ['name', 'local_as'] + position_values = [name, local_asnum] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('local-as "0" should be an integer [%s:%s].' % + (bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM), + str(exc)) + + def test_create_bgp_speaker_with_invalid_max_local_asnum(self): + # Create BGP Speaker with invalid maximum local-asnum. + resource = 'bgp_speaker' + cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + local_asnum = '65536' + args = [name, + '--local-as', local_asnum] + position_names = ['name', 'local_as', ] + position_values = [name, local_asnum, ] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('local-as "65536" should be an integer [%s:%s].' % + (bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM), + str(exc)) + + def test_update_bgp_speaker(self): + # Update BGP Speaker: + # myid --advertise-tenant-networks True + # --advertise-floating-ip-host-routes False + resource = 'bgp_speaker' + cmd = bgp_speaker.UpdateSpeaker(test_cli20.MyApp(sys.stdout), + None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', + '--name', 'new-name', + '--advertise-tenant-networks', 'True', + '--advertise-floating-ip-host-routes', + 'False'], + {'name': 'new-name', + 'advertise_tenant_networks': 'True', + 'advertise_floating_ip_host_routes': + 'False'}) + + def test_update_bgp_speaker_exception(self): + # Update BGP Speaker: myid. + resource = 'bgp_speaker' + cmd = bgp_speaker.UpdateSpeaker(test_cli20.MyApp(sys.stdout), + None) + self.assertRaises(exceptions.CommandError, + self._test_update_resource, + resource, cmd, 'myid', ['myid'], {}) + + def test_list_bgp_speaker(self): + # List all BGP Speakers. + resources = "bgp_speakers" + cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, True) + + def test_list_bgp_speaker_pagination(self): + # List all BGP Speakers with pagination support. + cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout), + None) + self.mox.StubOutWithMock(bgp_speaker.ListSpeakers, + "extend_list") + bgp_speaker.ListSpeakers.extend_list(mox.IsA(list), + mox.IgnoreArg()) + self._test_list_resources_with_pagination("bgp_speakers", + cmd) + self.mox.VerifyAll() + self.mox.UnsetStubs() + + def test_list_bgp_speaker_sort(self): + # sorted list: bgp-speaker-list --sort-key name --sort-key id + # --sort-key asc --sort-key desc + resources = "bgp_speakers" + cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_bgp_speaker_limit(self): + # size (1000) limited list: bgp-speaker-list -P. + resources = "bgp_speakers" + cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, page_size=1000) + + def test_show_bgp_speaker(self): + # Show BGP Speaker: --fields id --fields name myid. + resource = 'bgp_speaker' + cmd = bgp_speaker.ShowSpeaker(test_cli20.MyApp(sys.stdout), + None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args, + ['id', 'name']) + + def test_delete_bgp_speaker(self): + # Delete BGP Speaker: bgp_speaker_id. + resource = 'bgp_speaker' + cmd = bgp_speaker.DeleteSpeaker(test_cli20.MyApp(sys.stdout), + None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) + + def _test_add_remove_peer(self, action, cmd, args): + """Add or Remove BGP Peer to/from a BGP Speaker.""" + resource = 'bgp_speaker' + subcmd = '%s_bgp_peer' % action + body = {'bgp_peer_id': 'peerid'} + if action == 'add': + retval = {'bgp_peer': 'peerid'} + else: + retval = None + self._test_update_resource_action(resource, cmd, 'myid', + subcmd, args, body, retval) + + def test_add_peer_to_bgp_speaker(self): + # Add peer to BGP speaker: myid peer_id=peerid + cmd = bgp_speaker.AddPeerToSpeaker(test_cli20.MyApp(sys.stdout), + None) + args = ['myid', 'peerid'] + self._test_add_remove_peer('add', cmd, args) + + def test_remove_peer_from_bgp_speaker(self): + # Remove peer from BGP speaker: myid peer_id=peerid + cmd = bgp_speaker.RemovePeerFromSpeaker(test_cli20.MyApp(sys.stdout), + None) + args = ['myid', 'peerid'] + self._test_add_remove_peer('remove', cmd, args) + + def _test_add_remove_network(self, action, cmd, args): + # Add or Remove network to/from a BGP Speaker. + resource = 'bgp_speaker' + subcmd = '%s_gateway_network' % action + body = {'network_id': 'netid'} + if action == 'add': + retval = {'network': 'netid'} + else: + retval = None + self._test_update_resource_action(resource, cmd, 'myid', + subcmd, args, body, retval) + + def test_add_network_to_bgp_speaker(self): + # Add peer to BGP speaker: myid network_id=netid + cmd = bgp_speaker.AddNetworkToSpeaker(test_cli20.MyApp(sys.stdout), + None) + args = ['myid', 'netid'] + self._test_add_remove_network('add', cmd, args) + + def test_remove_network_from_bgp_speaker(self): + # Remove network from BGP speaker: myid network_id=netid + cmd = bgp_speaker.RemoveNetworkFromSpeaker( + test_cli20.MyApp(sys.stdout), None) + args = ['myid', 'netid'] + self._test_add_remove_network('remove', cmd, args) + + def test_list_routes_advertised_by_a_bgp_speaker(self): + # Retrieve advertised route list + resources = 'advertised_routes' + cmd = bgp_speaker.ListRoutesAdvertisedBySpeaker( + test_cli20.MyApp(sys.stdout), None) + bs_id = 'bgp_speaker_id1' + path = ((self.client.bgp_speaker_path + '/get_advertised_routes') % + bs_id) + self._test_list_resources(resources, cmd, base_args=[bs_id], + path=path) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index c078fb160..2c4f10520 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -404,6 +404,14 @@ class Client(ClientBase): flavor_profile_binding_path = flavor_path + service_profile_path availability_zones_path = "/availability_zones" auto_allocated_topology_path = "/auto-allocated-topology/%s" + BGP_DRINSTANCES = "/bgp-drinstances" + BGP_DRINSTANCE = "/bgp-drinstance/%s" + BGP_DRAGENTS = "/bgp-dragents" + BGP_DRAGENT = "/bgp-dragents/%s" + bgp_speakers_path = "/bgp-speakers" + bgp_speaker_path = "/bgp-speakers/%s" + bgp_peers_path = "/bgp-peers" + bgp_peer_path = "/bgp-peers/%s" # API has no way to report plurals, so we have to hard code them EXTED_PLURALS = {'routers': 'router', @@ -441,6 +449,8 @@ class Client(ClientBase): 'bandwidth_limit_rules': 'bandwidth_limit_rule', 'rule_types': 'rule_type', 'flavors': 'flavor', + 'bgp_speakers': 'bgp_speaker', + 'bgp_peers': 'bgp_peer', } @APIParamsCall @@ -1340,6 +1350,30 @@ class Client(ClientBase): return self.post((self.agent_path + self.L3_ROUTERS) % l3_agent, body=body) + @APIParamsCall + def list_dragents_hosting_bgp_speaker(self, bgp_speaker, **_params): + """Fetches a list of Dynamic Routing agents hosting a BGP speaker.""" + return self.get((self.bgp_speaker_path + self.BGP_DRAGENTS) + % bgp_speaker, params=_params) + + @APIParamsCall + def add_bgp_speaker_to_dragent(self, bgp_dragent, body): + """Adds a BGP speaker to Dynamic Routing agent.""" + return self.post((self.agent_path + self.BGP_DRINSTANCES) + % bgp_dragent, body=body) + + @APIParamsCall + def remove_bgp_speaker_from_dragent(self, bgp_dragent, bgpspeaker_id): + """Removes a BGP speaker from Dynamic Routing agent.""" + return self.delete((self.agent_path + self.BGP_DRINSTANCES + "/%s") + % (bgp_dragent, bgpspeaker_id)) + + @APIParamsCall + def list_bgp_speaker_on_dragent(self, bgp_dragent, **_params): + """Fetches a list of BGP speakers hosted by Dynamic Routing agent.""" + return self.get((self.agent_path + self.BGP_DRINSTANCES) + % bgp_dragent, params=_params) + @APIParamsCall def list_firewall_rules(self, retrieve_all=True, **_params): """Fetches a list of all firewall rules for a tenant.""" @@ -1695,6 +1729,89 @@ class Client(ClientBase): self.auto_allocated_topology_path % tenant_id, params=_params) + @APIParamsCall + def list_bgp_speakers(self, retrieve_all=True, **_params): + """Fetches a list of all BGP speakers for a tenant.""" + return self.list('bgp_speakers', self.bgp_speakers_path, retrieve_all, + **_params) + + @APIParamsCall + def show_bgp_speaker(self, bgp_speaker_id, **_params): + """Fetches information of a certain BGP speaker.""" + return self.get(self.bgp_speaker_path % (bgp_speaker_id), + params=_params) + + @APIParamsCall + def create_bgp_speaker(self, body=None): + """Creates a new BGP speaker.""" + return self.post(self.bgp_speakers_path, body=body) + + @APIParamsCall + def update_bgp_speaker(self, bgp_speaker_id, body=None): + """Update a BGP speaker.""" + return self.put(self.bgp_speaker_path % bgp_speaker_id, body=body) + + @APIParamsCall + def delete_bgp_speaker(self, speaker_id): + """Deletes the specified BGP speaker.""" + return self.delete(self.bgp_speaker_path % (speaker_id)) + + @APIParamsCall + def add_peer_to_bgp_speaker(self, speaker_id, body=None): + """Adds a peer to BGP speaker.""" + return self.put((self.bgp_speaker_path % speaker_id) + + "/add_bgp_peer", body=body) + + @APIParamsCall + def remove_peer_from_bgp_speaker(self, speaker_id, body=None): + """Removes a peer from BGP speaker.""" + return self.put((self.bgp_speaker_path % speaker_id) + + "/remove_bgp_peer", body=body) + + @APIParamsCall + def add_network_to_bgp_speaker(self, speaker_id, body=None): + """Adds a network to BGP speaker.""" + return self.put((self.bgp_speaker_path % speaker_id) + + "/add_gateway_network", body=body) + + @APIParamsCall + def remove_network_from_bgp_speaker(self, speaker_id, body=None): + """Removes a network from BGP speaker.""" + return self.put((self.bgp_speaker_path % speaker_id) + + "/remove_gateway_network", body=body) + + @APIParamsCall + def list_route_advertised_from_bgp_speaker(self, speaker_id, **_params): + """Fetches a list of all routes advertised by BGP speaker.""" + return self.get((self.bgp_speaker_path % speaker_id) + + "/get_advertised_routes", params=_params) + + @APIParamsCall + def list_bgp_peers(self, **_params): + """Fetches a list of all BGP peers.""" + return self.get(self.bgp_peers_path, params=_params) + + @APIParamsCall + def show_bgp_peer(self, peer_id, **_params): + """Fetches information of a certain BGP peer.""" + return self.get(self.bgp_peer_path % peer_id, + params=_params) + + @APIParamsCall + def create_bgp_peer(self, body=None): + """Create a new BGP peer.""" + return self.post(self.bgp_peers_path, body=body) + + @APIParamsCall + def update_bgp_peer(self, bgp_peer_id, body=None): + """Update a BGP peer.""" + return self.put(self.bgp_peer_path % bgp_peer_id, body=body) + + @APIParamsCall + def delete_bgp_peer(self, peer_id): + """Deletes the specified BGP peer.""" + return self.delete(self.bgp_peer_path % peer_id) + def __init__(self, **kwargs): """Initialize a new client for the Neutron v2.0 API.""" super(Client, self).__init__(**kwargs) diff --git a/releasenotes/notes/bgp-dynamic-routing-b97a1c81d3007049.yaml b/releasenotes/notes/bgp-dynamic-routing-b97a1c81d3007049.yaml new file mode 100644 index 000000000..2fa2e8e6e --- /dev/null +++ b/releasenotes/notes/bgp-dynamic-routing-b97a1c81d3007049.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + CLI support for the BGP dynamic routing functionality will help + advertising neutron fixed-ips and dvr host routes via BGP. \ No newline at end of file