From e776382c93c2eb9abcf6e6f5f84d43e97f9ca642 Mon Sep 17 00:00:00 2001
From: Duk Loi <duk@tesora.com>
Date: Tue, 9 Jun 2015 14:07:46 -0400
Subject: [PATCH] Add root-disable api

Add api entry points for root-disable to the Trove client.

Added unit tests.

Change-Id: I27831eb361c2b219a9623f152b9def73a2865d67
Partially implements: blueprint root-disable
DocImpact: added new root-disable CLI command
---
 troveclient/compat/cli.py          |  5 +++
 troveclient/tests/fakes.py         |  3 ++
 troveclient/tests/test_root.py     | 56 ++++++++++++++++++++++++++++++
 troveclient/tests/test_v1_shell.py |  4 +++
 troveclient/v1/root.py             | 15 ++++++++
 troveclient/v1/shell.py            |  9 +++++
 6 files changed, 92 insertions(+)
 create mode 100644 troveclient/tests/test_root.py

diff --git a/troveclient/compat/cli.py b/troveclient/compat/cli.py
index 5064c877..86b31098 100644
--- a/troveclient/compat/cli.py
+++ b/troveclient/compat/cli.py
@@ -268,6 +268,11 @@ class RootCommands(common.AuthedCommandsBase):
         except Exception:
             print(sys.exc_info()[1])
 
+    def delete(self):
+        """Disable the instance's root user."""
+        self._require('id')
+        print(self.dbaas.root.delete(self.id))
+
     def enabled(self):
         """Check the instance for root access."""
         self._require('id')
diff --git a/troveclient/tests/fakes.py b/troveclient/tests/fakes.py
index 76cb2225..820a4332 100644
--- a/troveclient/tests/fakes.py
+++ b/troveclient/tests/fakes.py
@@ -508,6 +508,9 @@ class FakeHTTPClient(base_client.HTTPClient):
     def post_clusters_cls_1234_root(self, **kw):
         return (202, {}, {"user": {"password": "password", "name": "root"}})
 
+    def delete_instances_1234_root(self, **kw):
+        return (202, {}, None)
+
     def get_instances_1234_root(self, **kw):
         return (200, {}, {"rootEnabled": 'True'})
 
diff --git a/troveclient/tests/test_root.py b/troveclient/tests/test_root.py
new file mode 100644
index 00000000..a4e9f38f
--- /dev/null
+++ b/troveclient/tests/test_root.py
@@ -0,0 +1,56 @@
+# Copyright 2015 Tesora Inc.
+#
+#    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 mock
+import testtools
+
+from troveclient.v1 import root
+
+"""
+Unit tests for root.py
+"""
+
+
+class RootTest(testtools.TestCase):
+    def setUp(self):
+        super(RootTest, self).setUp()
+        self.orig__init = root.Root.__init__
+        root.Root.__init__ = mock.Mock(return_value=None)
+        self.root = root.Root()
+        self.root.api = mock.Mock()
+        self.root.api.client = mock.Mock()
+
+    def tearDown(self):
+        super(RootTest, self).tearDown()
+        root.Root.__init__ = self.orig__init
+
+    def _get_mock_method(self):
+        self._resp = mock.Mock()
+        self._body = None
+        self._url = None
+
+        def side_effect_func(url, body=None):
+            self._body = body
+            self._url = url
+            return (self._resp, body)
+
+        return mock.Mock(side_effect=side_effect_func)
+
+    def test_delete(self):
+        self.root.api.client.delete = self._get_mock_method()
+        self._resp.status_code = 200
+        self.root.delete(1234)
+        self.assertEqual('/instances/1234/root', self._url)
+        self._resp.status_code = 400
+        self.assertRaises(Exception, self.root.delete, 1234)
diff --git a/troveclient/tests/test_v1_shell.py b/troveclient/tests/test_v1_shell.py
index b4a10072..161a3083 100644
--- a/troveclient/tests/test_v1_shell.py
+++ b/troveclient/tests/test_v1_shell.py
@@ -539,6 +539,10 @@ class ShellTest(utils.TestCase):
         self.run_command_clusters('root-enable cls-1234')
         self.assert_called_anytime('POST', '/clusters/cls-1234/root')
 
+    def test_root_disable_instance(self):
+        self.run_command('root-disable 1234')
+        self.assert_called_anytime('DELETE', '/instances/1234/root')
+
     def test_root_show_instance(self):
         self.run_command('root-show 1234')
         self.assert_called('GET', '/instances/1234/root')
diff --git a/troveclient/v1/root.py b/troveclient/v1/root.py
index 99c6672b..47acd192 100644
--- a/troveclient/v1/root.py
+++ b/troveclient/v1/root.py
@@ -55,6 +55,21 @@ class Root(base.ManagerWithFind):
         common.check_for_exceptions(resp, body, uri)
         return body['user']['name'], body['user']['password']
 
+    def delete(self, instance):
+        """Implements root-disable API.
+        Disables access to the root user for the specified db instance.
+        :param instance: The instance on which the root user is enabled
+        """
+        self.disable_instance_root(instance)
+
+    def disable_instance_root(self, instance):
+        """Implements root-disable for instances."""
+        self._disable_root(self.instances_url % base.getid(instance))
+
+    def _disable_root(self, url):
+        resp, body = self.api.client.delete(url)
+        common.check_for_exceptions(resp, body, url)
+
     def is_root_enabled(self, instance):
         """Return whether root is enabled for the instance."""
         return self.is_instance_root_enabled(instance)
diff --git a/troveclient/v1/shell.py b/troveclient/v1/shell.py
index 7a64f176..3eb17c12 100644
--- a/troveclient/v1/shell.py
+++ b/troveclient/v1/shell.py
@@ -1024,6 +1024,15 @@ def do_root_enable(cs, args):
     utils.print_dict({'name': root[0], 'password': root[1]})
 
 
+@utils.arg('instance', metavar='<instance>',
+           help='ID or name of the instance.')
+@utils.service_type('database')
+def do_root_disable(cs, args):
+    """Disables root for an instance."""
+    instance = _find_instance(cs, args.instance)
+    cs.root.disable_instance_root(instance)
+
+
 @utils.arg('instance_or_cluster', metavar='<instance_or_cluster>',
            help='ID or name of the instance or cluster.')
 @utils.service_type('database')