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:
committed by
Jonathan Halterman
parent
57bb542d9f
commit
77960eecb6
@@ -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": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user