From 01c19ef0bc6abc0dbbd76666bdc0c5dda7ba0196 Mon Sep 17 00:00:00 2001
From: Tang Chen <chen.tang@easystack.cn>
Date: Thu, 4 Feb 2016 13:19:01 +0800
Subject: [PATCH] Router: Add --route and --clear-routes options to "router
 set" command

--route option is used to set routes to the router.

It is used like this:

    --route destination=subnet,gateway=ip-address

destination: destination subnet CIDR
gateway: nexthop IP address

--clear-routes is used to clear all routes on the router.

Change-Id: I97ce4871113c684b29c98cdad4dec9cc80ed20f7
Implements: blueprint neutron-client
Partial-bug: #1519503
---
 doc/source/command-objects/router.rst         | 12 ++++
 openstackclient/network/v2/router.py          | 30 ++++++++--
 .../tests/network/v2/test_router.py           | 57 +++++++++++++++++++
 3 files changed, 95 insertions(+), 4 deletions(-)

diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst
index 2a5bf0539c..6b8b357b1c 100644
--- a/doc/source/command-objects/router.rst
+++ b/doc/source/command-objects/router.rst
@@ -93,6 +93,7 @@ Set router properties
         [--name <name>]
         [--enable | --disable]
         [--distributed | --centralized]
+        [--route destination=<subnet>,gateway=<ip-address> | --clear-routes]
         <router>
 
 .. option:: --name <name>
@@ -115,6 +116,17 @@ Set router properties
 
     Set router to centralized mode (disabled router only)
 
+.. option:: --route destination=<subnet>,gateway=<ip-address>
+
+    Routes associated with the router.
+    Repeat this option to set multiple routes.
+    destination: destination subnet (in CIDR notation).
+    gateway: nexthop IP address.
+
+.. option:: --clear-routes
+
+    Clear routes associated with the router
+
 .. _router_set-router:
 .. describe:: <router>
 
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index 60db816ac6..e4eea3f8ab 100644
--- a/openstackclient/network/v2/router.py
+++ b/openstackclient/network/v2/router.py
@@ -17,6 +17,7 @@ import json
 
 from openstackclient.common import command
 from openstackclient.common import exceptions
+from openstackclient.common import parseractions
 from openstackclient.common import utils
 from openstackclient.identity import common as identity_common
 
@@ -51,6 +52,12 @@ def _get_attrs(client_manager, parsed_args):
     if ('availability_zone_hints' in parsed_args
             and parsed_args.availability_zone_hints is not None):
         attrs['availability_zone_hints'] = parsed_args.availability_zone_hints
+
+    if 'clear_routes' in parsed_args and parsed_args.clear_routes:
+        attrs['routes'] = []
+    elif 'routes' in parsed_args and parsed_args.routes is not None:
+        attrs['routes'] = parsed_args.routes
+
     # "router set" command doesn't support setting project.
     if 'project' in parsed_args and parsed_args.project is not None:
         identity_client = client_manager.identity
@@ -63,7 +70,6 @@ def _get_attrs(client_manager, parsed_args):
 
     # TODO(tangchen): Support getting 'ha' property.
     # TODO(tangchen): Support getting 'external_gateway_info' property.
-    # TODO(tangchen): Support getting 'routes' property.
 
     return attrs
 
@@ -250,6 +256,25 @@ class SetRouter(command.Command):
             action='store_false',
             help="Set router to centralized mode (disabled router only)",
         )
+        routes_group = parser.add_mutually_exclusive_group()
+        routes_group.add_argument(
+            '--route',
+            metavar='destination=<subnet>,gateway=<ip-address>',
+            action=parseractions.MultiKeyValueAction,
+            dest='routes',
+            default=None,
+            required_keys=['destination', 'gateway'],
+            help="Routes associated with the router. "
+                 "Repeat this option to set multiple routes. "
+                 "destination: destination subnet (in CIDR notation). "
+                 "gateway: nexthop IP address.",
+        )
+        routes_group.add_argument(
+            '--clear-routes',
+            dest='clear_routes',
+            action='store_true',
+            help="Clear routes associated with the router",
+        )
 
         # TODO(tangchen): Support setting 'ha' property in 'router set'
         # command. It appears that changing the ha state is supported by
@@ -258,9 +283,6 @@ class SetRouter(command.Command):
         # TODO(tangchen): Support setting 'external_gateway_info' property in
         # 'router set' command.
 
-        # TODO(tangchen): Support setting 'routes' property in 'router set'
-        # command.
-
         return parser
 
     def take_action(self, parsed_args):
diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py
index 05bb7857e2..794f8ab5c6 100644
--- a/openstackclient/tests/network/v2/test_router.py
+++ b/openstackclient/tests/network/v2/test_router.py
@@ -306,6 +306,63 @@ class TestSetRouter(TestRouter):
         self.assertRaises(tests_utils.ParserException, self.check_parser,
                           self.cmd, arglist, verifylist)
 
+    def test_set_route(self):
+        arglist = [
+            self._router.name,
+            '--route', 'destination=10.20.30.0/24,gateway=10.20.30.1',
+        ]
+        verifylist = [
+            ('router', self._router.name),
+            ('routes', [{'destination': '10.20.30.0/24',
+                         'gateway': '10.20.30.1'}]),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        result = self.cmd.take_action(parsed_args)
+
+        attrs = {
+            'routes': [{'destination': '10.20.30.0/24',
+                        'gateway': '10.20.30.1'}],
+        }
+        self.network.update_router.assert_called_with(self._router, **attrs)
+        self.assertIsNone(result)
+
+    def test_set_clear_routes(self):
+        arglist = [
+            self._router.name,
+            '--clear-routes',
+        ]
+        verifylist = [
+            ('router', self._router.name),
+            ('clear_routes', True),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        result = self.cmd.take_action(parsed_args)
+
+        attrs = {
+            'routes': [],
+        }
+        self.network.update_router.assert_called_with(self._router, **attrs)
+        self.assertIsNone(result)
+
+    def test_set_route_clear_routes(self):
+        arglist = [
+            self._router.name,
+            '--route', 'destination=10.20.30.0/24,gateway=10.20.30.1',
+            '--clear-routes',
+        ]
+        verifylist = [
+            ('router', self._router.name),
+            ('routes', [{'destination': '10.20.30.0/24',
+                         'gateway': '10.20.30.1'}]),
+            ('clear_routes', True),
+        ]
+
+        # Argument parse failing should bail here
+        self.assertRaises(tests_utils.ParserException, self.check_parser,
+                          self.cmd, arglist, verifylist)
+
     def test_set_nothing(self):
         arglist = [self._router.name, ]
         verifylist = [('router', self._router.name), ]