Root enablement for Vertica clusters/instances

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

Implements blueprint: vertica-cluster-user-features
Depends on: I0a39cae53b371d1a89a6abbf93d026b16a665ed8

Change-Id: Ic9d81ab0e840d699c052c6b0fdccaeec5d69a5b4
This commit is contained in:
sharika
2015-06-30 15:39:52 -07:00
committed by Jonathan Halterman
parent 57bb542d9f
commit 77960eecb6
4 changed files with 127 additions and 22 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_stdout, mock_get_version_map,
mock_find_instance_or_cluster):
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_stdout, mock_get_version_map,
mock_find_instance_or_cluster):
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_stdout,
mock_get_version_map,
mock_find_instance,
mock_find_cluster):
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_stdout, mock_get_version_map,
mock_find_instance):
cmd = 'root-show 1234'
self.shell.main(cmd.split())
self.assert_called('GET', '/instances/1234/root')

View File

@@ -22,22 +22,41 @@ 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_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. """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 or cluster.
""" """
resp, body = self.api.client.post(self.url % base.getid(instance)) body = {"password": root_password} if root_password else {}
common.check_for_exceptions(resp, body, self.url) resp, body = self.api.client.post(uri, body=body)
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_instance_root_enabled(self, instance):
"""Return whether root is enabled for the instance.""" """Returns whether root is enabled for the instance."""
resp, body = self.api.client.get(self.url % base.getid(instance)) return self._is_root_enabled(self.instances_url % base.getid(instance))
common.check_for_exceptions(resp, body, self.url)
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})