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)
This commit is contained in:
parent
c5719a12b5
commit
dba57c85d5
@ -12,11 +12,11 @@ contextlib2==0.4.0
|
|||||||
coverage==4.0
|
coverage==4.0
|
||||||
cryptography==2.1
|
cryptography==2.1
|
||||||
debtcollector==1.2.0
|
debtcollector==1.2.0
|
||||||
decorator==3.4.0
|
decorator==4.4.1
|
||||||
deprecation==1.0
|
deprecation==1.0
|
||||||
docker-pycreds==0.2.1
|
docker-pycreds==0.2.1
|
||||||
docker==2.4.2
|
docker==2.4.2
|
||||||
dogpile.cache==0.6.2
|
dogpile.cache==0.6.5
|
||||||
eventlet==0.18.2
|
eventlet==0.18.2
|
||||||
extras==1.0.0
|
extras==1.0.0
|
||||||
fasteners==0.7.0
|
fasteners==0.7.0
|
||||||
@ -38,7 +38,7 @@ jmespath==0.9.0
|
|||||||
jsonpatch==1.16
|
jsonpatch==1.16
|
||||||
jsonpointer==1.13
|
jsonpointer==1.13
|
||||||
jsonschema==2.6.0
|
jsonschema==2.6.0
|
||||||
keystoneauth1==3.16.0
|
keystoneauth1==3.18.0
|
||||||
kombu==4.0.0
|
kombu==4.0.0
|
||||||
linecache2==1.0.0
|
linecache2==1.0.0
|
||||||
MarkupSafe==1.1.0
|
MarkupSafe==1.1.0
|
||||||
@ -50,7 +50,7 @@ msgpack-python==0.4.0
|
|||||||
munch==2.1.0
|
munch==2.1.0
|
||||||
netaddr==0.7.18
|
netaddr==0.7.18
|
||||||
netifaces==0.10.4
|
netifaces==0.10.4
|
||||||
openstacksdk==0.36.0
|
openstacksdk==0.38.0
|
||||||
os-client-config==1.28.0
|
os-client-config==1.28.0
|
||||||
os-service-types==1.7.0
|
os-service-types==1.7.0
|
||||||
os-testr==1.0.0
|
os-testr==1.0.0
|
||||||
|
@ -168,6 +168,93 @@ class AddSubnetToRouter(command.Command):
|
|||||||
subnet_id=subnet.id)
|
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
|
# TODO(yanxing'an): Use the SDK resource mapped attribute names once the
|
||||||
# OSC minimum requirements include SDK 1.0.
|
# OSC minimum requirements include SDK 1.0.
|
||||||
class CreateRouter(command.ShowOne):
|
class CreateRouter(command.ShowOne):
|
||||||
@ -540,17 +627,21 @@ class SetRouter(command.Command):
|
|||||||
dest='routes',
|
dest='routes',
|
||||||
default=None,
|
default=None,
|
||||||
required_keys=['destination', 'gateway'],
|
required_keys=['destination', 'gateway'],
|
||||||
help=_("Routes associated with the router "
|
help=_("Add routes to the router "
|
||||||
"destination: destination subnet (in CIDR notation) "
|
"destination: destination subnet (in CIDR notation) "
|
||||||
"gateway: nexthop IP address "
|
"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(
|
parser.add_argument(
|
||||||
'--no-route',
|
'--no-route',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help=_("Clear routes associated with the router. "
|
help=_("Clear routes associated with the router. "
|
||||||
"Specify both --route and --no-route to overwrite "
|
"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 = parser.add_mutually_exclusive_group()
|
||||||
routes_ha.add_argument(
|
routes_ha.add_argument(
|
||||||
|
@ -261,3 +261,46 @@ class RouterTests(common.NetworkTagTests):
|
|||||||
new_name
|
new_name
|
||||||
))
|
))
|
||||||
self.assertIsNone(cmd_output["external_gateway_info"])
|
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']))
|
||||||
|
@ -776,6 +776,146 @@ class TestRemoveSubnetFromRouter(TestRouter):
|
|||||||
self.assertIsNone(result)
|
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):
|
class TestSetRouter(TestRouter):
|
||||||
|
|
||||||
# The router to set.
|
# The router to set.
|
||||||
|
@ -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.
|
@ -6,7 +6,7 @@ six>=1.10.0 # MIT
|
|||||||
|
|
||||||
Babel!=2.4.0,>=2.3.4 # BSD
|
Babel!=2.4.0,>=2.3.4 # BSD
|
||||||
cliff!=2.9.0,>=2.8.0 # Apache-2.0
|
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
|
osc-lib>=2.0.0 # Apache-2.0
|
||||||
oslo.i18n>=3.15.3 # Apache-2.0
|
oslo.i18n>=3.15.3 # Apache-2.0
|
||||||
oslo.utils>=3.33.0 # Apache-2.0
|
oslo.utils>=3.33.0 # Apache-2.0
|
||||||
|
@ -480,11 +480,13 @@ openstack.network.v2 =
|
|||||||
port_unset = openstackclient.network.v2.port:UnsetPort
|
port_unset = openstackclient.network.v2.port:UnsetPort
|
||||||
|
|
||||||
router_add_port = openstackclient.network.v2.router:AddPortToRouter
|
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_add_subnet = openstackclient.network.v2.router:AddSubnetToRouter
|
||||||
router_create = openstackclient.network.v2.router:CreateRouter
|
router_create = openstackclient.network.v2.router:CreateRouter
|
||||||
router_delete = openstackclient.network.v2.router:DeleteRouter
|
router_delete = openstackclient.network.v2.router:DeleteRouter
|
||||||
router_list = openstackclient.network.v2.router:ListRouter
|
router_list = openstackclient.network.v2.router:ListRouter
|
||||||
router_remove_port = openstackclient.network.v2.router:RemovePortFromRouter
|
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_remove_subnet = openstackclient.network.v2.router:RemoveSubnetFromRouter
|
||||||
router_set = openstackclient.network.v2.router:SetRouter
|
router_set = openstackclient.network.v2.router:SetRouter
|
||||||
router_show = openstackclient.network.v2.router:ShowRouter
|
router_show = openstackclient.network.v2.router:ShowRouter
|
||||||
|
Loading…
Reference in New Issue
Block a user