Root enablement for Vertica clusters/instances

Adds support for root-enable for Vertica clusters,
with optional user-provided passwords.

Fixes backward compatibility issue.

Implements blueprint: vertica-cluster-user-features
Change-Id: I3e23c87c0e48a7f910e54207116fc75b5ce0ad0c
This commit is contained in:
sharika
2015-08-28 20:34:26 -07:00
committed by Saurabh Surana
parent 7ec45dba07
commit 153353ce9a
4 changed files with 137 additions and 18 deletions

View File

@@ -502,9 +502,15 @@ class FakeHTTPClient(base_client.HTTPClient):
def post_instances_1234_root(self, **kw): def post_instances_1234_root(self, **kw):
return (202, {}, {"user": {"password": "password", "name": "root"}}) return (202, {}, {"user": {"password": "password", "name": "root"}})
def post_clusters_cls_1234_root(self, **kw):
return (202, {}, {"user": {"password": "password", "name": "root"}})
def get_instances_1234_root(self, **kw): def get_instances_1234_root(self, **kw):
return (200, {}, {"rootEnabled": 'True'}) return (200, {}, {"rootEnabled": 'True'})
def get_clusters_cls_1234_root(self, **kw):
return (200, {}, {"rootEnabled": 'True'})
def get_security_groups(self, **kw): def get_security_groups(self, **kw):
return (200, {}, {"security_groups": [ return (200, {}, {"security_groups": [
{ {

View File

@@ -56,7 +56,23 @@ class ShellTest(utils.TestCase):
@mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('troveclient.client.get_version_map', @mock.patch('troveclient.client.get_version_map',
return_value=fakes.get_version_map()) return_value=fakes.get_version_map())
def run_command(self, cmd, mock_stdout, mock_get_version_map): @mock.patch('troveclient.v1.shell._find_instance_or_cluster',
return_value=('1234', 'instance'))
def run_command(self, cmd, mock_find_instance_or_cluster,
mock_get_version_map, mock_stdout):
if isinstance(cmd, list):
self.shell.main(cmd)
else:
self.shell.main(cmd.split())
return mock_stdout.getvalue()
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('troveclient.client.get_version_map',
return_value=fakes.get_version_map())
@mock.patch('troveclient.v1.shell._find_instance_or_cluster',
return_value=('cls-1234', 'cluster'))
def run_command_clusters(self, cmd, mock_find_instance_or_cluster,
mock_get_version_map, mock_stdout):
if isinstance(cmd, list): if isinstance(cmd, list):
self.shell.main(cmd) self.shell.main(cmd)
else: else:
@@ -448,14 +464,22 @@ class ShellTest(utils.TestCase):
self.assert_called('DELETE', self.assert_called('DELETE',
'/instances/1234/users/jacob/databases/db1') '/instances/1234/users/jacob/databases/db1')
def test_root_enable(self): def test_root_enable_instance(self):
self.run_command('root-enable 1234') self.run_command('root-enable 1234')
self.assert_called_anytime('POST', '/instances/1234/root') self.assert_called_anytime('POST', '/instances/1234/root')
def test_root_show(self): def test_root_enable_cluster(self):
self.run_command_clusters('root-enable cls-1234')
self.assert_called_anytime('POST', '/clusters/cls-1234/root')
def test_root_show_instance(self):
self.run_command('root-show 1234') self.run_command('root-show 1234')
self.assert_called('GET', '/instances/1234/root') self.assert_called('GET', '/instances/1234/root')
def test_root_show_cluster(self):
self.run_command_clusters('root-show cls-1234')
self.assert_called('GET', '/clusters/cls-1234/root')
def test_secgroup_list(self): def test_secgroup_list(self):
self.run_command('secgroup-list') self.run_command('secgroup-list')
self.assert_called('GET', '/security-groups') self.assert_called('GET', '/security-groups')
@@ -480,3 +504,29 @@ class ShellTest(utils.TestCase):
'cidr': '15.0.0.0/24', 'cidr': '15.0.0.0/24',
'group_id': '2', 'group_id': '2',
}}) }})
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('troveclient.client.get_version_map',
return_value=fakes.get_version_map())
@mock.patch('troveclient.v1.shell._find_instance',
side_effect=exceptions.CommandError)
@mock.patch('troveclient.v1.shell._find_cluster',
return_value='cls-1234')
def test_find_instance_or_cluster_find_cluster(self, mock_find_cluster,
mock_find_instance,
mock_get_version_map,
mock_stdout):
cmd = 'root-show cls-1234'
self.shell.main(cmd.split())
self.assert_called('GET', '/clusters/cls-1234/root')
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('troveclient.client.get_version_map',
return_value=fakes.get_version_map())
@mock.patch('troveclient.v1.shell._find_instance',
return_value='1234')
def test_find_instance_or_cluster(self, mock_find_instance,
mock_get_version_map, mock_stdout):
cmd = 'root-show 1234'
self.shell.main(cmd.split())
self.assert_called('GET', '/instances/1234/root')

View File

@@ -22,22 +22,55 @@ from troveclient.v1 import users
class Root(base.ManagerWithFind): class Root(base.ManagerWithFind):
"""Manager class for Root resource.""" """Manager class for Root resource."""
resource_class = users.User resource_class = users.User
url = "/instances/%s/root" instances_url = "/instances/%s/root"
clusters_url = "/clusters/%s/root"
def create(self, instance): def create(self, instance):
"""Implements root-enable API. """Implements root-enable API.
Enable the root user and return the root password for the Enable the root user and return the root password for the
specified db instance. specified db instance.
""" """
resp, body = self.api.client.post(self.url % base.getid(instance)) return self.create_instance_root(instance)
common.check_for_exceptions(resp, body, self.url)
def create_instance_root(self, instance, root_password=None):
"""Implements root-enable for instances."""
return self._enable_root(self.instances_url % base.getid(instance),
root_password)
def create_cluster_root(self, cluster, root_password=None):
"""Implements root-enable for clusters."""
return self._enable_root(self.clusters_url % base.getid(cluster),
root_password)
def _enable_root(self, uri, root_password=None):
"""Implements root-enable API.
Enable the root user and return the root password for the
specified db instance or cluster.
"""
if root_password:
resp, body = self.api.client.post(uri,
body={"password": root_password})
else:
resp, body = self.api.client.post(uri)
common.check_for_exceptions(resp, body, uri)
return body['user']['name'], body['user']['password'] return body['user']['name'], body['user']['password']
def is_root_enabled(self, instance): def is_root_enabled(self, instance):
"""Return whether root is enabled for the instance.""" """Return whether root is enabled for the instance."""
resp, body = self.api.client.get(self.url % base.getid(instance)) return self.is_instance_root_enabled(instance)
common.check_for_exceptions(resp, body, self.url)
def is_instance_root_enabled(self, instance):
"""Returns whether root is enabled for the instance."""
return self._is_root_enabled(self.instances_url % base.getid(instance))
def is_cluster_root_enabled(self, cluster):
"""Returns whether root is enabled for the cluster."""
return self._is_root_enabled(self.clusters_url % base.getid(cluster))
def _is_root_enabled(self, uri):
"""Return whether root is enabled for the instance or the cluster."""
resp, body = self.api.client.get(uri)
common.check_for_exceptions(resp, body, uri)
return self.resource_class(self, body, loaded=True) return self.resource_class(self, body, loaded=True)
# Appease the abc gods # Appease the abc gods

View File

@@ -99,6 +99,22 @@ def _print_object(obj):
utils.print_dict(obj._info) utils.print_dict(obj._info)
def _find_instance_or_cluster(cs, instance_or_cluster):
"""Returns an instance or cluster, found by id, along with the type of
resource, instance or cluster, that was found.
Raises CommandError if none is found.
"""
try:
return _find_instance(cs, instance_or_cluster), 'instance'
except exceptions.CommandError:
try:
return _find_cluster(cs, instance_or_cluster), 'cluster'
except Exception:
raise exceptions.CommandError(
"No instance or cluster with a name or ID of '%s' exists."
% instance_or_cluster)
def _find_instance(cs, instance): def _find_instance(cs, instance):
"""Get an instance by ID.""" """Get an instance by ID."""
return utils.find_resource(cs.instances, instance) return utils.find_resource(cs.instances, instance)
@@ -840,23 +856,37 @@ def do_limit_list(cs, args):
# Root related commands # Root related commands
@utils.arg('instance', metavar='<instance>', @utils.arg('instance_or_cluster', metavar='<instance_or_cluster>',
help='ID or name of the instance.') help='ID or name of the instance or cluster.')
@utils.arg('--root_password',
metavar='<root_password>',
default=None,
help='Root password to set.')
@utils.service_type('database') @utils.service_type('database')
def do_root_enable(cs, args): def do_root_enable(cs, args):
"""Enables root for an instance and resets if already exists.""" """Enables root for an instance and resets if already exists."""
instance = _find_instance(cs, args.instance) instance_or_cluster, resource_type = _find_instance_or_cluster(
root = cs.root.create(instance) cs, args.instance_or_cluster)
if resource_type == 'instance':
root = cs.root.create_instance_root(instance_or_cluster,
args.root_password)
else:
root = cs.root.create_cluster_root(instance_or_cluster,
args.root_password)
utils.print_dict({'name': root[0], 'password': root[1]}) utils.print_dict({'name': root[0], 'password': root[1]})
@utils.arg('instance', metavar='<instance>', @utils.arg('instance_or_cluster', metavar='<instance_or_cluster>',
help='ID or name of the instance.') help='ID or name of the instance or cluster.')
@utils.service_type('database') @utils.service_type('database')
def do_root_show(cs, args): def do_root_show(cs, args):
"""Gets status if root was ever enabled for an instance.""" """Gets status if root was ever enabled for an instance or cluster."""
instance = _find_instance(cs, args.instance) instance_or_cluster, resource_type = _find_instance_or_cluster(
root = cs.root.is_root_enabled(instance) cs, args.instance_or_cluster)
if resource_type == 'instance':
root = cs.root.is_instance_root_enabled(instance_or_cluster)
else:
root = cs.root.is_cluster_root_enabled(instance_or_cluster)
utils.print_dict({'is_root_enabled': root.rootEnabled}) utils.print_dict({'is_root_enabled': root.rootEnabled})