Skip to content

Commit 70f1ff3

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add command: router add/remove route --route"
2 parents f01a0f3 + dba57c8 commit 70f1ff3

7 files changed

Lines changed: 296 additions & 8 deletions

File tree

lower-constraints.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ contextlib2==0.4.0
1212
coverage==4.0
1313
cryptography==2.1
1414
debtcollector==1.2.0
15-
decorator==3.4.0
15+
decorator==4.4.1
1616
deprecation==1.0
1717
docker-pycreds==0.2.1
1818
docker==2.4.2
19-
dogpile.cache==0.6.2
19+
dogpile.cache==0.6.5
2020
eventlet==0.18.2
2121
extras==1.0.0
2222
fasteners==0.7.0
@@ -38,7 +38,7 @@ jmespath==0.9.0
3838
jsonpatch==1.16
3939
jsonpointer==1.13
4040
jsonschema==2.6.0
41-
keystoneauth1==3.16.0
41+
keystoneauth1==3.18.0
4242
kombu==4.0.0
4343
linecache2==1.0.0
4444
MarkupSafe==1.1.0
@@ -50,7 +50,7 @@ msgpack-python==0.4.0
5050
munch==2.1.0
5151
netaddr==0.7.18
5252
netifaces==0.10.4
53-
openstacksdk==0.36.0
53+
openstacksdk==0.38.0
5454
os-client-config==1.28.0
5555
os-service-types==1.7.0
5656
os-testr==1.0.0

openstackclient/network/v2/router.py

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,93 @@ def take_action(self, parsed_args):
168168
subnet_id=subnet.id)
169169

170170

171+
class AddExtraRoutesToRouter(command.ShowOne):
172+
_description = _("Add extra static routes to a router's routing table.")
173+
174+
def get_parser(self, prog_name):
175+
parser = super(AddExtraRoutesToRouter, self).get_parser(prog_name)
176+
parser.add_argument(
177+
'router',
178+
metavar='<router>',
179+
help=_("Router to which extra static routes "
180+
"will be added (name or ID).")
181+
)
182+
parser.add_argument(
183+
'--route',
184+
metavar='destination=<subnet>,gateway=<ip-address>',
185+
action=parseractions.MultiKeyValueAction,
186+
dest='routes',
187+
default=[],
188+
required_keys=['destination', 'gateway'],
189+
help=_("Add extra static route to the router. "
190+
"destination: destination subnet (in CIDR notation), "
191+
"gateway: nexthop IP address. "
192+
"Repeat option to add multiple routes. "
193+
"Trying to add a route that's already present "
194+
"(exactly, including destination and nexthop) "
195+
"in the routing table is allowed and is considered "
196+
"a successful operation.")
197+
)
198+
return parser
199+
200+
def take_action(self, parsed_args):
201+
if parsed_args.routes is not None:
202+
for route in parsed_args.routes:
203+
route['nexthop'] = route.pop('gateway')
204+
client = self.app.client_manager.network
205+
router_obj = client.add_extra_routes_to_router(
206+
client.find_router(parsed_args.router, ignore_missing=False),
207+
body={'router': {'routes': parsed_args.routes}})
208+
display_columns, columns = _get_columns(router_obj)
209+
data = utils.get_item_properties(
210+
router_obj, columns, formatters=_formatters)
211+
return (display_columns, data)
212+
213+
214+
class RemoveExtraRoutesFromRouter(command.ShowOne):
215+
_description = _(
216+
"Remove extra static routes from a router's routing table.")
217+
218+
def get_parser(self, prog_name):
219+
parser = super(RemoveExtraRoutesFromRouter, self).get_parser(prog_name)
220+
parser.add_argument(
221+
'router',
222+
metavar='<router>',
223+
help=_("Router from which extra static routes "
224+
"will be removed (name or ID).")
225+
)
226+
parser.add_argument(
227+
'--route',
228+
metavar='destination=<subnet>,gateway=<ip-address>',
229+
action=parseractions.MultiKeyValueAction,
230+
dest='routes',
231+
default=[],
232+
required_keys=['destination', 'gateway'],
233+
help=_("Remove extra static route from the router. "
234+
"destination: destination subnet (in CIDR notation), "
235+
"gateway: nexthop IP address. "
236+
"Repeat option to remove multiple routes. "
237+
"Trying to remove a route that's already missing "
238+
"(fully, including destination and nexthop) "
239+
"from the routing table is allowed and is considered "
240+
"a successful operation.")
241+
)
242+
return parser
243+
244+
def take_action(self, parsed_args):
245+
if parsed_args.routes is not None:
246+
for route in parsed_args.routes:
247+
route['nexthop'] = route.pop('gateway')
248+
client = self.app.client_manager.network
249+
router_obj = client.remove_extra_routes_from_router(
250+
client.find_router(parsed_args.router, ignore_missing=False),
251+
body={'router': {'routes': parsed_args.routes}})
252+
display_columns, columns = _get_columns(router_obj)
253+
data = utils.get_item_properties(
254+
router_obj, columns, formatters=_formatters)
255+
return (display_columns, data)
256+
257+
171258
# TODO(yanxing'an): Use the SDK resource mapped attribute names once the
172259
# OSC minimum requirements include SDK 1.0.
173260
class CreateRouter(command.ShowOne):
@@ -540,17 +627,21 @@ def get_parser(self, prog_name):
540627
dest='routes',
541628
default=None,
542629
required_keys=['destination', 'gateway'],
543-
help=_("Routes associated with the router "
630+
help=_("Add routes to the router "
544631
"destination: destination subnet (in CIDR notation) "
545632
"gateway: nexthop IP address "
546-
"(repeat option to set multiple routes)")
633+
"(repeat option to add multiple routes). "
634+
"This is deprecated in favor of 'router add/remove route' "
635+
"since it is prone to race conditions between concurrent "
636+
"clients when not used together with --no-route to "
637+
"overwrite the current value of 'routes'.")
547638
)
548639
parser.add_argument(
549640
'--no-route',
550641
action='store_true',
551642
help=_("Clear routes associated with the router. "
552643
"Specify both --route and --no-route to overwrite "
553-
"current value of route.")
644+
"current value of routes.")
554645
)
555646
routes_ha = parser.add_mutually_exclusive_group()
556647
routes_ha.add_argument(

openstackclient/tests/functional/network/v2/test_router.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,46 @@ def test_router_set_show_unset(self):
261261
new_name
262262
))
263263
self.assertIsNone(cmd_output["external_gateway_info"])
264+
265+
def test_router_add_remove_route(self):
266+
network_name = uuid.uuid4().hex
267+
subnet_name = uuid.uuid4().hex
268+
router_name = uuid.uuid4().hex
269+
270+
self.openstack('network create %s' % network_name)
271+
self.addCleanup(self.openstack, 'network delete %s' % network_name)
272+
273+
self.openstack(
274+
'subnet create %s '
275+
'--network %s --subnet-range 10.0.0.0/24' % (
276+
subnet_name, network_name))
277+
278+
self.openstack('router create %s' % router_name)
279+
self.addCleanup(self.openstack, 'router delete %s' % router_name)
280+
281+
self.openstack('router add subnet %s %s' % (router_name, subnet_name))
282+
self.addCleanup(self.openstack, 'router remove subnet %s %s' % (
283+
router_name, subnet_name))
284+
285+
out1 = json.loads(self.openstack(
286+
'router add route -f json %s '
287+
'--route destination=10.0.10.0/24,gateway=10.0.0.10' %
288+
router_name)),
289+
self.assertEqual(1, len(out1[0]['routes']))
290+
291+
self.addCleanup(
292+
self.openstack, 'router set %s --no-route' % router_name)
293+
294+
out2 = json.loads(self.openstack(
295+
'router add route -f json %s '
296+
'--route destination=10.0.10.0/24,gateway=10.0.0.10 '
297+
'--route destination=10.0.11.0/24,gateway=10.0.0.11' %
298+
router_name)),
299+
self.assertEqual(2, len(out2[0]['routes']))
300+
301+
out3 = json.loads(self.openstack(
302+
'router remove route -f json %s '
303+
'--route destination=10.0.11.0/24,gateway=10.0.0.11 '
304+
'--route destination=10.0.12.0/24,gateway=10.0.0.12' %
305+
router_name)),
306+
self.assertEqual(1, len(out3[0]['routes']))

openstackclient/tests/unit/network/v2/test_router.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,146 @@ def test_remove_subnet_required_options(self):
776776
self.assertIsNone(result)
777777

778778

779+
class TestAddExtraRoutesToRouter(TestRouter):
780+
781+
_router = network_fakes.FakeRouter.create_one_router()
782+
783+
def setUp(self):
784+
super(TestAddExtraRoutesToRouter, self).setUp()
785+
self.network.add_extra_routes_to_router = mock.Mock(
786+
return_value=self._router)
787+
self.cmd = router.AddExtraRoutesToRouter(self.app, self.namespace)
788+
self.network.find_router = mock.Mock(return_value=self._router)
789+
790+
def test_add_no_extra_route(self):
791+
arglist = [
792+
self._router.id,
793+
]
794+
verifylist = [
795+
('router', self._router.id),
796+
]
797+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
798+
799+
result = self.cmd.take_action(parsed_args)
800+
801+
self.network.add_extra_routes_to_router.assert_called_with(
802+
self._router, body={'router': {'routes': []}})
803+
self.assertEqual(2, len(result))
804+
805+
def test_add_one_extra_route(self):
806+
arglist = [
807+
self._router.id,
808+
'--route', 'destination=dst1,gateway=gw1',
809+
]
810+
verifylist = [
811+
('router', self._router.id),
812+
('routes', [{'destination': 'dst1', 'gateway': 'gw1'}]),
813+
]
814+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
815+
816+
result = self.cmd.take_action(parsed_args)
817+
818+
self.network.add_extra_routes_to_router.assert_called_with(
819+
self._router, body={'router': {'routes': [
820+
{'destination': 'dst1', 'nexthop': 'gw1'},
821+
]}})
822+
self.assertEqual(2, len(result))
823+
824+
def test_add_multiple_extra_routes(self):
825+
arglist = [
826+
self._router.id,
827+
'--route', 'destination=dst1,gateway=gw1',
828+
'--route', 'destination=dst2,gateway=gw2',
829+
]
830+
verifylist = [
831+
('router', self._router.id),
832+
('routes', [
833+
{'destination': 'dst1', 'gateway': 'gw1'},
834+
{'destination': 'dst2', 'gateway': 'gw2'},
835+
]),
836+
]
837+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
838+
839+
result = self.cmd.take_action(parsed_args)
840+
841+
self.network.add_extra_routes_to_router.assert_called_with(
842+
self._router, body={'router': {'routes': [
843+
{'destination': 'dst1', 'nexthop': 'gw1'},
844+
{'destination': 'dst2', 'nexthop': 'gw2'},
845+
]}})
846+
self.assertEqual(2, len(result))
847+
848+
849+
class TestRemoveExtraRoutesFromRouter(TestRouter):
850+
851+
_router = network_fakes.FakeRouter.create_one_router()
852+
853+
def setUp(self):
854+
super(TestRemoveExtraRoutesFromRouter, self).setUp()
855+
self.network.remove_extra_routes_from_router = mock.Mock(
856+
return_value=self._router)
857+
self.cmd = router.RemoveExtraRoutesFromRouter(self.app, self.namespace)
858+
self.network.find_router = mock.Mock(return_value=self._router)
859+
860+
def test_remove_no_extra_route(self):
861+
arglist = [
862+
self._router.id,
863+
]
864+
verifylist = [
865+
('router', self._router.id),
866+
]
867+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
868+
869+
result = self.cmd.take_action(parsed_args)
870+
871+
self.network.remove_extra_routes_from_router.assert_called_with(
872+
self._router, body={'router': {'routes': []}})
873+
self.assertEqual(2, len(result))
874+
875+
def test_remove_one_extra_route(self):
876+
arglist = [
877+
self._router.id,
878+
'--route', 'destination=dst1,gateway=gw1',
879+
]
880+
verifylist = [
881+
('router', self._router.id),
882+
('routes', [{'destination': 'dst1', 'gateway': 'gw1'}]),
883+
]
884+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
885+
886+
result = self.cmd.take_action(parsed_args)
887+
888+
self.network.remove_extra_routes_from_router.assert_called_with(
889+
self._router, body={'router': {'routes': [
890+
{'destination': 'dst1', 'nexthop': 'gw1'},
891+
]}})
892+
self.assertEqual(2, len(result))
893+
894+
def test_remove_multiple_extra_routes(self):
895+
arglist = [
896+
self._router.id,
897+
'--route', 'destination=dst1,gateway=gw1',
898+
'--route', 'destination=dst2,gateway=gw2',
899+
]
900+
verifylist = [
901+
('router', self._router.id),
902+
('routes', [
903+
{'destination': 'dst1', 'gateway': 'gw1'},
904+
{'destination': 'dst2', 'gateway': 'gw2'},
905+
]),
906+
]
907+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
908+
909+
result = self.cmd.take_action(parsed_args)
910+
911+
self.network.remove_extra_routes_from_router.assert_called_with(
912+
self._router, body={'router': {'routes': [
913+
{'destination': 'dst1', 'nexthop': 'gw1'},
914+
{'destination': 'dst2', 'nexthop': 'gw2'},
915+
]}})
916+
self.assertEqual(2, len(result))
917+
918+
779919
class TestSetRouter(TestRouter):
780920

781921
# The router to set.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
features:
3+
- |
4+
Add new commands ``router add route`` and ``router remove route`` to
5+
support new Neutron extension: ``extraroute-atomic`` (see `Neutron RFE
6+
<https://bugs.launchpad.net/neutron/+bug/1826396>`_).
7+
deprecations:
8+
- |
9+
The use of ``router set --route`` to add extra routes next to already
10+
existing extra routes is deprecated in favor of ``router add route
11+
--route``, because ``router set --route`` if used from multiple clients
12+
concurrently may lead to lost updates.

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ six>=1.10.0 # MIT
66

77
Babel!=2.4.0,>=2.3.4 # BSD
88
cliff!=2.9.0,>=2.8.0 # Apache-2.0
9-
openstacksdk>=0.36.0 # Apache-2.0
9+
openstacksdk>=0.38.0 # Apache-2.0
1010
osc-lib>=2.0.0 # Apache-2.0
1111
oslo.i18n>=3.15.3 # Apache-2.0
1212
oslo.utils>=3.33.0 # Apache-2.0

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,11 +481,13 @@ openstack.network.v2 =
481481
port_unset = openstackclient.network.v2.port:UnsetPort
482482

483483
router_add_port = openstackclient.network.v2.router:AddPortToRouter
484+
router_add_route = openstackclient.network.v2.router:AddExtraRoutesToRouter
484485
router_add_subnet = openstackclient.network.v2.router:AddSubnetToRouter
485486
router_create = openstackclient.network.v2.router:CreateRouter
486487
router_delete = openstackclient.network.v2.router:DeleteRouter
487488
router_list = openstackclient.network.v2.router:ListRouter
488489
router_remove_port = openstackclient.network.v2.router:RemovePortFromRouter
490+
router_remove_route = openstackclient.network.v2.router:RemoveExtraRoutesFromRouter
489491
router_remove_subnet = openstackclient.network.v2.router:RemoveSubnetFromRouter
490492
router_set = openstackclient.network.v2.router:SetRouter
491493
router_show = openstackclient.network.v2.router:ShowRouter

0 commit comments

Comments
 (0)