summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBence Romsics <bence.romsics@gmail.com>2019-07-31 15:44:27 +0200
committerBence Romsics <bence.romsics@gmail.com>2020-03-30 16:19:05 +0200
commitdba57c85d5536369d600ae98599c08b921713bd5 (patch)
tree16607cbb4c70c312d166ffd91b785b3d3aaf74cf
parentc5719a12b5b84b2efd989030f17c0eacc9faf7ad (diff)
downloadpython-openstackclient-dba57c85d5536369d600ae98599c08b921713bd5.tar.gz
Add command: router add/remove route --route
Add commands to osc to call the two new API methods introduced by new Neutron extension: extraroute-atomic. Bump our openstacksdk requirement to >=0.38.0 which contains the corresponding sdk change. The lower-constraints of dogpile.cache and keystoneauth1 are bumped because of requirements bumps in openstacksdk. The lower-constraint of decorator is bumped because of problem already fixed by amotoki here: https://review.opendev.org/701706 Change-Id: Ia9b9c216f1d1161ebedac31594a2c464d77f4ae2 Depends-On: https://review.opendev.org/674324 Partial-Bug: #1826396 (rfe) Related-Change: https://review.opendev.org/655680 (spec)
-rw-r--r--lower-constraints.txt8
-rw-r--r--openstackclient/network/v2/router.py97
-rw-r--r--openstackclient/tests/functional/network/v2/test_router.py43
-rw-r--r--openstackclient/tests/unit/network/v2/test_router.py140
-rw-r--r--releasenotes/notes/router-extraroute-atomic-d6d406ffb15695f2.yaml12
-rw-r--r--requirements.txt2
-rw-r--r--setup.cfg2
7 files changed, 296 insertions, 8 deletions
diff --git a/lower-constraints.txt b/lower-constraints.txt
index 1fa30674..83e19fe6 100644
--- a/lower-constraints.txt
+++ b/lower-constraints.txt
@@ -12,11 +12,11 @@ contextlib2==0.4.0
coverage==4.0
cryptography==2.1
debtcollector==1.2.0
-decorator==3.4.0
+decorator==4.4.1
deprecation==1.0
docker-pycreds==0.2.1
docker==2.4.2
-dogpile.cache==0.6.2
+dogpile.cache==0.6.5
eventlet==0.18.2
extras==1.0.0
fasteners==0.7.0
@@ -38,7 +38,7 @@ jmespath==0.9.0
jsonpatch==1.16
jsonpointer==1.13
jsonschema==2.6.0
-keystoneauth1==3.16.0
+keystoneauth1==3.18.0
kombu==4.0.0
linecache2==1.0.0
MarkupSafe==1.1.0
@@ -50,7 +50,7 @@ msgpack-python==0.4.0
munch==2.1.0
netaddr==0.7.18
netifaces==0.10.4
-openstacksdk==0.36.0
+openstacksdk==0.38.0
os-client-config==1.28.0
os-service-types==1.7.0
os-testr==1.0.0
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index 464dbbec..81b81f98 100644
--- a/openstackclient/network/v2/router.py
+++ b/openstackclient/network/v2/router.py
@@ -168,6 +168,93 @@ class AddSubnetToRouter(command.Command):
subnet_id=subnet.id)
+class AddExtraRoutesToRouter(command.ShowOne):
+ _description = _("Add extra static routes to a router's routing table.")
+
+ def get_parser(self, prog_name):
+ parser = super(AddExtraRoutesToRouter, self).get_parser(prog_name)
+ parser.add_argument(
+ 'router',
+ metavar='<router>',
+ help=_("Router to which extra static routes "
+ "will be added (name or ID).")
+ )
+ parser.add_argument(
+ '--route',
+ metavar='destination=<subnet>,gateway=<ip-address>',
+ action=parseractions.MultiKeyValueAction,
+ dest='routes',
+ default=[],
+ required_keys=['destination', 'gateway'],
+ help=_("Add extra static route to the router. "
+ "destination: destination subnet (in CIDR notation), "
+ "gateway: nexthop IP address. "
+ "Repeat option to add multiple routes. "
+ "Trying to add a route that's already present "
+ "(exactly, including destination and nexthop) "
+ "in the routing table is allowed and is considered "
+ "a successful operation.")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ if parsed_args.routes is not None:
+ for route in parsed_args.routes:
+ route['nexthop'] = route.pop('gateway')
+ client = self.app.client_manager.network
+ router_obj = client.add_extra_routes_to_router(
+ client.find_router(parsed_args.router, ignore_missing=False),
+ body={'router': {'routes': parsed_args.routes}})
+ display_columns, columns = _get_columns(router_obj)
+ data = utils.get_item_properties(
+ router_obj, columns, formatters=_formatters)
+ return (display_columns, data)
+
+
+class RemoveExtraRoutesFromRouter(command.ShowOne):
+ _description = _(
+ "Remove extra static routes from a router's routing table.")
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveExtraRoutesFromRouter, self).get_parser(prog_name)
+ parser.add_argument(
+ 'router',
+ metavar='<router>',
+ help=_("Router from which extra static routes "
+ "will be removed (name or ID).")
+ )
+ parser.add_argument(
+ '--route',
+ metavar='destination=<subnet>,gateway=<ip-address>',
+ action=parseractions.MultiKeyValueAction,
+ dest='routes',
+ default=[],
+ required_keys=['destination', 'gateway'],
+ help=_("Remove extra static route from the router. "
+ "destination: destination subnet (in CIDR notation), "
+ "gateway: nexthop IP address. "
+ "Repeat option to remove multiple routes. "
+ "Trying to remove a route that's already missing "
+ "(fully, including destination and nexthop) "
+ "from the routing table is allowed and is considered "
+ "a successful operation.")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ if parsed_args.routes is not None:
+ for route in parsed_args.routes:
+ route['nexthop'] = route.pop('gateway')
+ client = self.app.client_manager.network
+ router_obj = client.remove_extra_routes_from_router(
+ client.find_router(parsed_args.router, ignore_missing=False),
+ body={'router': {'routes': parsed_args.routes}})
+ display_columns, columns = _get_columns(router_obj)
+ data = utils.get_item_properties(
+ router_obj, columns, formatters=_formatters)
+ return (display_columns, data)
+
+
# TODO(yanxing'an): Use the SDK resource mapped attribute names once the
# OSC minimum requirements include SDK 1.0.
class CreateRouter(command.ShowOne):
@@ -540,17 +627,21 @@ class SetRouter(command.Command):
dest='routes',
default=None,
required_keys=['destination', 'gateway'],
- help=_("Routes associated with the router "
+ help=_("Add routes to the router "
"destination: destination subnet (in CIDR notation) "
"gateway: nexthop IP address "
- "(repeat option to set multiple routes)")
+ "(repeat option to add multiple routes). "
+ "This is deprecated in favor of 'router add/remove route' "
+ "since it is prone to race conditions between concurrent "
+ "clients when not used together with --no-route to "
+ "overwrite the current value of 'routes'.")
)
parser.add_argument(
'--no-route',
action='store_true',
help=_("Clear routes associated with the router. "
"Specify both --route and --no-route to overwrite "
- "current value of route.")
+ "current value of routes.")
)
routes_ha = parser.add_mutually_exclusive_group()
routes_ha.add_argument(
diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py
index 05aad7a0..0769dca6 100644
--- a/openstackclient/tests/functional/network/v2/test_router.py
+++ b/openstackclient/tests/functional/network/v2/test_router.py
@@ -261,3 +261,46 @@ class RouterTests(common.NetworkTagTests):
new_name
))
self.assertIsNone(cmd_output["external_gateway_info"])
+
+ def test_router_add_remove_route(self):
+ network_name = uuid.uuid4().hex
+ subnet_name = uuid.uuid4().hex
+ router_name = uuid.uuid4().hex
+
+ self.openstack('network create %s' % network_name)
+ self.addCleanup(self.openstack, 'network delete %s' % network_name)
+
+ self.openstack(
+ 'subnet create %s '
+ '--network %s --subnet-range 10.0.0.0/24' % (
+ subnet_name, network_name))
+
+ self.openstack('router create %s' % router_name)
+ self.addCleanup(self.openstack, 'router delete %s' % router_name)
+
+ self.openstack('router add subnet %s %s' % (router_name, subnet_name))
+ self.addCleanup(self.openstack, 'router remove subnet %s %s' % (
+ router_name, subnet_name))
+
+ out1 = json.loads(self.openstack(
+ 'router add route -f json %s '
+ '--route destination=10.0.10.0/24,gateway=10.0.0.10' %
+ router_name)),
+ self.assertEqual(1, len(out1[0]['routes']))
+
+ self.addCleanup(
+ self.openstack, 'router set %s --no-route' % router_name)
+
+ out2 = json.loads(self.openstack(
+ 'router add route -f json %s '
+ '--route destination=10.0.10.0/24,gateway=10.0.0.10 '
+ '--route destination=10.0.11.0/24,gateway=10.0.0.11' %
+ router_name)),
+ self.assertEqual(2, len(out2[0]['routes']))
+
+ out3 = json.loads(self.openstack(
+ 'router remove route -f json %s '
+ '--route destination=10.0.11.0/24,gateway=10.0.0.11 '
+ '--route destination=10.0.12.0/24,gateway=10.0.0.12' %
+ router_name)),
+ self.assertEqual(1, len(out3[0]['routes']))
diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py
index 38861b0a..09b4957c 100644
--- a/openstackclient/tests/unit/network/v2/test_router.py
+++ b/openstackclient/tests/unit/network/v2/test_router.py
@@ -776,6 +776,146 @@ class TestRemoveSubnetFromRouter(TestRouter):
self.assertIsNone(result)
+class TestAddExtraRoutesToRouter(TestRouter):
+
+ _router = network_fakes.FakeRouter.create_one_router()
+
+ def setUp(self):
+ super(TestAddExtraRoutesToRouter, self).setUp()
+ self.network.add_extra_routes_to_router = mock.Mock(
+ return_value=self._router)
+ self.cmd = router.AddExtraRoutesToRouter(self.app, self.namespace)
+ self.network.find_router = mock.Mock(return_value=self._router)
+
+ def test_add_no_extra_route(self):
+ arglist = [
+ self._router.id,
+ ]
+ verifylist = [
+ ('router', self._router.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.add_extra_routes_to_router.assert_called_with(
+ self._router, body={'router': {'routes': []}})
+ self.assertEqual(2, len(result))
+
+ def test_add_one_extra_route(self):
+ arglist = [
+ self._router.id,
+ '--route', 'destination=dst1,gateway=gw1',
+ ]
+ verifylist = [
+ ('router', self._router.id),
+ ('routes', [{'destination': 'dst1', 'gateway': 'gw1'}]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.add_extra_routes_to_router.assert_called_with(
+ self._router, body={'router': {'routes': [
+ {'destination': 'dst1', 'nexthop': 'gw1'},
+ ]}})
+ self.assertEqual(2, len(result))
+
+ def test_add_multiple_extra_routes(self):
+ arglist = [
+ self._router.id,
+ '--route', 'destination=dst1,gateway=gw1',
+ '--route', 'destination=dst2,gateway=gw2',
+ ]
+ verifylist = [
+ ('router', self._router.id),
+ ('routes', [
+ {'destination': 'dst1', 'gateway': 'gw1'},
+ {'destination': 'dst2', 'gateway': 'gw2'},
+ ]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.add_extra_routes_to_router.assert_called_with(
+ self._router, body={'router': {'routes': [
+ {'destination': 'dst1', 'nexthop': 'gw1'},
+ {'destination': 'dst2', 'nexthop': 'gw2'},
+ ]}})
+ self.assertEqual(2, len(result))
+
+
+class TestRemoveExtraRoutesFromRouter(TestRouter):
+
+ _router = network_fakes.FakeRouter.create_one_router()
+
+ def setUp(self):
+ super(TestRemoveExtraRoutesFromRouter, self).setUp()
+ self.network.remove_extra_routes_from_router = mock.Mock(
+ return_value=self._router)
+ self.cmd = router.RemoveExtraRoutesFromRouter(self.app, self.namespace)
+ self.network.find_router = mock.Mock(return_value=self._router)
+
+ def test_remove_no_extra_route(self):
+ arglist = [
+ self._router.id,
+ ]
+ verifylist = [
+ ('router', self._router.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.remove_extra_routes_from_router.assert_called_with(
+ self._router, body={'router': {'routes': []}})
+ self.assertEqual(2, len(result))
+
+ def test_remove_one_extra_route(self):
+ arglist = [
+ self._router.id,
+ '--route', 'destination=dst1,gateway=gw1',
+ ]
+ verifylist = [
+ ('router', self._router.id),
+ ('routes', [{'destination': 'dst1', 'gateway': 'gw1'}]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.remove_extra_routes_from_router.assert_called_with(
+ self._router, body={'router': {'routes': [
+ {'destination': 'dst1', 'nexthop': 'gw1'},
+ ]}})
+ self.assertEqual(2, len(result))
+
+ def test_remove_multiple_extra_routes(self):
+ arglist = [
+ self._router.id,
+ '--route', 'destination=dst1,gateway=gw1',
+ '--route', 'destination=dst2,gateway=gw2',
+ ]
+ verifylist = [
+ ('router', self._router.id),
+ ('routes', [
+ {'destination': 'dst1', 'gateway': 'gw1'},
+ {'destination': 'dst2', 'gateway': 'gw2'},
+ ]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.remove_extra_routes_from_router.assert_called_with(
+ self._router, body={'router': {'routes': [
+ {'destination': 'dst1', 'nexthop': 'gw1'},
+ {'destination': 'dst2', 'nexthop': 'gw2'},
+ ]}})
+ self.assertEqual(2, len(result))
+
+
class TestSetRouter(TestRouter):
# The router to set.
diff --git a/releasenotes/notes/router-extraroute-atomic-d6d406ffb15695f2.yaml b/releasenotes/notes/router-extraroute-atomic-d6d406ffb15695f2.yaml
new file mode 100644
index 00000000..33b5ba7a
--- /dev/null
+++ b/releasenotes/notes/router-extraroute-atomic-d6d406ffb15695f2.yaml
@@ -0,0 +1,12 @@
+---
+features:
+ - |
+ Add new commands ``router add route`` and ``router remove route`` to
+ support new Neutron extension: ``extraroute-atomic`` (see `Neutron RFE
+ <https://bugs.launchpad.net/neutron/+bug/1826396>`_).
+deprecations:
+ - |
+ The use of ``router set --route`` to add extra routes next to already
+ existing extra routes is deprecated in favor of ``router add route
+ --route``, because ``router set --route`` if used from multiple clients
+ concurrently may lead to lost updates.
diff --git a/requirements.txt b/requirements.txt
index b17b6a55..f7a12dae 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@ six>=1.10.0 # MIT
Babel!=2.4.0,>=2.3.4 # BSD
cliff!=2.9.0,>=2.8.0 # Apache-2.0
-openstacksdk>=0.36.0 # Apache-2.0
+openstacksdk>=0.38.0 # Apache-2.0
osc-lib>=2.0.0 # Apache-2.0
oslo.i18n>=3.15.3 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 60caf5db..1e5e36b8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -480,11 +480,13 @@ openstack.network.v2 =
port_unset = openstackclient.network.v2.port:UnsetPort
router_add_port = openstackclient.network.v2.router:AddPortToRouter
+ router_add_route = openstackclient.network.v2.router:AddExtraRoutesToRouter
router_add_subnet = openstackclient.network.v2.router:AddSubnetToRouter
router_create = openstackclient.network.v2.router:CreateRouter
router_delete = openstackclient.network.v2.router:DeleteRouter
router_list = openstackclient.network.v2.router:ListRouter
router_remove_port = openstackclient.network.v2.router:RemovePortFromRouter
+ router_remove_route = openstackclient.network.v2.router:RemoveExtraRoutesFromRouter
router_remove_subnet = openstackclient.network.v2.router:RemoveSubnetFromRouter
router_set = openstackclient.network.v2.router:SetRouter
router_show = openstackclient.network.v2.router:ShowRouter