Add support for root enable and show

Added an entry in the instance details panel to show whether root has
ever been enabled for the instance.

Added a manage root access screen to allow the user to enable the root
user or reset the root password.

The manage root access screen is accessed from a row menu option in the
database instances table.

Change-Id: I781f55f6184e0b83fe56fce6143269aa48b1c46d
Co-Authored-By: Duk Loi <duk@tesora.com>
Implements: blueprint trove-enable-root-support
This commit is contained in:
Matt Van Dijk 2016-01-19 11:14:52 -05:00
parent 2f1ea93f68
commit b3c0f4dbfd
9 changed files with 287 additions and 1 deletions

View File

@ -203,6 +203,15 @@ def flavor_get(request, flavor_id):
return troveclient(request).flavors.get(flavor_id)
def root_enable(request, instance_ids):
username, password = troveclient(request).root.create(instance_ids[0])
return username, password
def root_show(request, instance_id):
return troveclient(request).root.is_root_enabled(instance_id)
def users_list(request, instance_id):
return troveclient(request).users.list(instance_id)

View File

@ -23,6 +23,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import messages
from horizon import tables
from horizon.templatetags import sizeformat
from horizon.utils import filters
@ -409,6 +410,64 @@ class ResizeInstance(tables.LinkAction):
return urlresolvers.reverse(self.url, args=[instance_id])
class RootAction(tables.Action):
def handle(self, table, request, obj_ids):
try:
username, password = api.trove.root_enable(request, obj_ids)
table.data[0].enabled = True
table.data[0].password = password
except Exception:
messages.error(request, _('There was a problem enabling root.'))
class EnableRootAction(RootAction):
name = "enable_root_action"
verbose_name = _("Enable Root")
def allowed(self, request, instance):
enabled = api.trove.root_show(request, instance.id)
return not enabled.rootEnabled
class ResetRootAction(RootAction):
name = "reset_root_action"
verbose_name = _("Reset Password")
def allowed(self, request, instance):
enabled = api.trove.root_show(request, instance.id)
return enabled.rootEnabled
class ManageRoot(tables.LinkAction):
name = "manage_root_action"
verbose_name = _("Manage Root Access")
url = "horizon:project:databases:manage_root"
def allowed(self, request, instance):
return instance.status in ACTIVE_STATES
def get_link_url(self, datum=None):
instance_id = self.table.get_object_id(datum)
return urlresolvers.reverse(self.url, args=[instance_id])
class ManageRootTable(tables.DataTable):
name = tables.Column('name', verbose_name=_('Instance Name'))
enabled = tables.Column('enabled', verbose_name=_('Root Enabled'),
filters=(d_filters.yesno, d_filters.capfirst),
help_text=_("Status if root was ever enabled "
"for an instance."))
password = tables.Column('password', verbose_name=_('Password'),
help_text=_("Password is only visible "
"immediately after the root is "
"enabled or reset."))
class Meta(object):
name = "manage_root"
verbose_name = _("Manage Root")
row_actions = (EnableRootAction, ResetRootAction,)
class UpdateRow(tables.Row):
ajax = True
@ -531,6 +590,7 @@ class InstancesTable(tables.DataTable):
row_actions = (CreateBackup,
ResizeVolume,
ResizeInstance,
ManageRoot,
RestartInstance,
DetachReplica,
DeleteInstance)

View File

@ -26,7 +26,16 @@ class OverviewTab(tabs.Tab):
slug = "overview"
def get_context_data(self, request):
return {"instance": self.tab_group.kwargs['instance']}
instance = self.tab_group.kwargs['instance']
context = {"instance": instance}
try:
root_show = api.trove.root_show(request, instance.id)
context["root_enabled"] = template.defaultfilters.yesno(
root_show.rootEnabled)
except Exception:
context["root_enabled"] = _('Unable to obtain information on '
'root user')
return context
def get_template_name(self, request):
instance = self.tab_group.kwargs['instance']

View File

@ -12,6 +12,8 @@
<dd>{{ instance.datastore.version }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ instance.status|title }}</dd>
<dt>{% trans "Root Enabled" %}</dt>
<dd>{{ root_enabled|capfirst }}</dd>
</dl>
<h4>{% trans "Specs" %}</h4>

View File

@ -0,0 +1,14 @@
{% extends 'base.html' %}
{% load i18n %}
{% block main %}
<hr>
<div class="help_text">
{% trans "Note: Enable root access on an instance. If the root user is already enabled then a new password is generated." %}
</div>
<div class="row">
<div class="col-sm-12">
{{ table.render }}
</div>
</div>
{% endblock %}

View File

@ -400,6 +400,141 @@ class DatabaseTests(test.TestCase):
res = self.client.post(url, post)
self.assertEqual(res.status_code, 302)
@test.create_stubs({api.trove: ('instance_get', 'root_show')})
def test_show_root(self):
database = self.databases.first()
database.id = u'id'
user = self.database_user_roots.first()
api.trove.instance_get(IsA(http.HttpRequest), IsA(unicode))\
.AndReturn(database)
api.trove.root_show(IsA(http.HttpRequest), database.id) \
.MultipleTimes().AndReturn(user)
self.mox.ReplayAll()
url = reverse('horizon:project:databases:manage_root',
args=['id'])
res = self.client.get(url)
self.assertTemplateUsed(
res, 'project/databases/manage_root.html')
@test.create_stubs({api.trove: ('instance_get', 'root_show')})
def test_show_root_exception(self):
database = self.databases.first()
api.trove.instance_get(IsA(http.HttpRequest), IsA(unicode))\
.AndReturn(database)
api.trove.root_show(IsA(http.HttpRequest), u'id') \
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
url = reverse('horizon:project:databases:manage_root',
args=['id'])
res = self.client.get(url)
self.assertRedirectsNoFollow(res, DETAILS_URL)
@test.create_stubs({api.trove: ('root_enable',)})
def test_enable_root(self):
api.trove.root_enable(IsA(http.HttpRequest), [u'id']) \
.AndReturn(("root", "password"))
self.mox.ReplayAll()
url = reverse('horizon:project:databases:manage_root',
args=['id'])
form_data = {"action": "manage_root__enable_root_action__%s" % 'id'}
req = self.factory.post(url, form_data)
kwargs = {'instance_id': 'id'}
enable_root_info_list = []
enable_root_info = views.EnableRootInfo('id', 'inst1', False, '')
enable_root_info_list.append(enable_root_info)
table = tables.ManageRootTable(req, enable_root_info_list, **kwargs)
table.maybe_handle()
self.assertEqual(table.data[0].enabled, True)
self.assertEqual(table.data[0].password, "password")
@test.create_stubs({api.trove: ('root_enable',)})
def test_enable_root_exception(self):
api.trove.root_enable(IsA(http.HttpRequest), [u'id']) \
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
url = reverse('horizon:project:databases:manage_root',
args=['id'])
form_data = {"action": "manage_root__enable_root_action__%s" % 'id'}
req = self.factory.post(url, form_data)
kwargs = {'instance_id': 'id'}
enable_root_info_list = []
enable_root_info = views.EnableRootInfo('id', 'inst1', False, '')
enable_root_info_list.append(enable_root_info)
table = tables.ManageRootTable(req, enable_root_info_list, **kwargs)
table.maybe_handle()
self.assertNotEqual(table.data[0].enabled, True)
self.assertNotEqual(table.data[0].password, "password")
@test.create_stubs({api.trove: ('root_enable',)})
def test_reset_root(self):
api.trove.root_enable(IsA(http.HttpRequest), [u'id']) \
.AndReturn(("root", "newpassword"))
self.mox.ReplayAll()
url = reverse('horizon:project:databases:manage_root',
args=['id'])
form_data = {"action": "manage_root__reset_root_action__%s" % 'id'}
req = self.factory.post(url, form_data)
kwargs = {'instance_id': 'id'}
enable_root_info_list = []
enable_root_info = views.EnableRootInfo(
'id', 'inst1', True, 'password')
enable_root_info_list.append(enable_root_info)
table = tables.ManageRootTable(req, enable_root_info_list, **kwargs)
table.maybe_handle()
self.assertEqual(table.data[0].enabled, True)
self.assertEqual(table.data[0].password, "newpassword")
@test.create_stubs({api.trove: ('root_enable',)})
def test_reset_root_exception(self):
api.trove.root_enable(IsA(http.HttpRequest), [u'id']) \
.AndRaise(self.exceptions.trove)
self.mox.ReplayAll()
url = reverse('horizon:project:databases:manage_root',
args=['id'])
form_data = {"action": "manage_root__reset_root_action__%s" % 'id'}
req = self.factory.post(url, form_data)
kwargs = {'instance_id': 'id'}
enable_root_info_list = []
enable_root_info = views.EnableRootInfo(
'id', 'inst1', True, 'password')
enable_root_info_list.append(enable_root_info)
table = tables.ManageRootTable(req, enable_root_info_list, **kwargs)
table.maybe_handle()
self.assertEqual(table.data[0].enabled, True)
self.assertNotEqual(table.data[0].password, "newpassword")
@test.create_stubs(
{api.trove: ('instance_get', 'flavor_get', 'users_list',
'user_list_access', 'user_delete')})

View File

@ -39,4 +39,6 @@ urlpatterns = patterns(
name='access_detail'),
url(INSTANCES % 'create_database', views.CreateDatabaseView.as_view(),
name='create_database'),
url(INSTANCES % 'manage_root', views.ManageRootView.as_view(),
name='manage_root'),
)

View File

@ -355,3 +355,50 @@ class ResizeInstanceView(horizon_forms.ModalFormView):
'flavor_name', ''),
'flavors': self.get_flavors()})
return initial
class EnableRootInfo(object):
def __init__(self, instance_id, instance_name, enabled, password=None):
self.id = instance_id
self.name = instance_name
self.enabled = enabled
self.password = password
class ManageRootView(horizon_tables.DataTableView):
table_class = tables.ManageRootTable
template_name = 'project/databases/manage_root.html'
page_title = _("Manage Root Access")
@memoized.memoized_method
def get_data(self):
instance_id = self.kwargs['instance_id']
try:
instance = api.trove.instance_get(self.request, instance_id)
except Exception:
redirect = reverse('horizon:project:databases:detail',
args=[instance_id])
exceptions.handle(self.request,
_('Unable to retrieve instance details.'),
redirect=redirect)
try:
enabled = api.trove.root_show(self.request, instance_id)
except Exception:
redirect = reverse('horizon:project:databases:detail',
args=[instance_id])
exceptions.handle(self.request,
_('Unable to determine if instance root '
'is enabled.'),
redirect=redirect)
root_enabled_list = []
root_enabled_info = EnableRootInfo(instance.id,
instance.name,
enabled.rootEnabled)
root_enabled_list.append(root_enabled_info)
return root_enabled_list
def get_context_data(self, **kwargs):
context = super(ManageRootView, self).get_context_data(**kwargs)
context['instance_id'] = self.kwargs['instance_id']
return context

View File

@ -213,6 +213,10 @@ USER_ONE = {
"databases": [DATABASE_DATA_ONE["name"]],
}
USER_ROOT_ONE = {
"name": "root",
"rootEnabled": True
}
USER_DB_ONE = {
"name": "db1",
@ -335,6 +339,8 @@ def data(TEST):
user1 = users.User(users.Users(None), USER_ONE)
user_db1 = databases.Database(databases.Databases(None),
USER_DB_ONE)
user_root1 = databases.Database(databases.Databases(None),
USER_ROOT_ONE)
datastore1 = datastores.Datastore(datastores.Datastores(None),
DATASTORE_ONE)
@ -368,6 +374,7 @@ def data(TEST):
TEST.database_backups = utils.TestDataContainer()
TEST.database_users = utils.TestDataContainer()
TEST.database_user_dbs = utils.TestDataContainer()
TEST.database_user_roots = utils.TestDataContainer()
TEST.database_flavors = utils.TestDataContainer()
TEST.databases.add(database1)
@ -377,6 +384,7 @@ def data(TEST):
TEST.database_backups.add(bkup3)
TEST.database_users.add(user1)
TEST.database_user_dbs.add(user_db1)
TEST.database_user_roots.add(user_root1)
TEST.datastores = utils.TestDataContainer()
TEST.datastores.add(datastore1)
TEST.datastores.add(datastore_mongodb)