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) 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): def users_list(request, instance_id):
return troveclient(request).users.list(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 django.utils.translation import ungettext_lazy
from horizon import exceptions from horizon import exceptions
from horizon import messages
from horizon import tables from horizon import tables
from horizon.templatetags import sizeformat from horizon.templatetags import sizeformat
from horizon.utils import filters from horizon.utils import filters
@ -409,6 +410,64 @@ class ResizeInstance(tables.LinkAction):
return urlresolvers.reverse(self.url, args=[instance_id]) 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): class UpdateRow(tables.Row):
ajax = True ajax = True
@ -531,6 +590,7 @@ class InstancesTable(tables.DataTable):
row_actions = (CreateBackup, row_actions = (CreateBackup,
ResizeVolume, ResizeVolume,
ResizeInstance, ResizeInstance,
ManageRoot,
RestartInstance, RestartInstance,
DetachReplica, DetachReplica,
DeleteInstance) DeleteInstance)

View File

@ -26,7 +26,16 @@ class OverviewTab(tabs.Tab):
slug = "overview" slug = "overview"
def get_context_data(self, request): 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): def get_template_name(self, request):
instance = self.tab_group.kwargs['instance'] instance = self.tab_group.kwargs['instance']

View File

@ -12,6 +12,8 @@
<dd>{{ instance.datastore.version }}</dd> <dd>{{ instance.datastore.version }}</dd>
<dt>{% trans "Status" %}</dt> <dt>{% trans "Status" %}</dt>
<dd>{{ instance.status|title }}</dd> <dd>{{ instance.status|title }}</dd>
<dt>{% trans "Root Enabled" %}</dt>
<dd>{{ root_enabled|capfirst }}</dd>
</dl> </dl>
<h4>{% trans "Specs" %}</h4> <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) res = self.client.post(url, post)
self.assertEqual(res.status_code, 302) 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( @test.create_stubs(
{api.trove: ('instance_get', 'flavor_get', 'users_list', {api.trove: ('instance_get', 'flavor_get', 'users_list',
'user_list_access', 'user_delete')}) 'user_list_access', 'user_delete')})

View File

@ -39,4 +39,6 @@ urlpatterns = patterns(
name='access_detail'), name='access_detail'),
url(INSTANCES % 'create_database', views.CreateDatabaseView.as_view(), url(INSTANCES % 'create_database', views.CreateDatabaseView.as_view(),
name='create_database'), 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', ''), 'flavor_name', ''),
'flavors': self.get_flavors()}) 'flavors': self.get_flavors()})
return initial 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"]], "databases": [DATABASE_DATA_ONE["name"]],
} }
USER_ROOT_ONE = {
"name": "root",
"rootEnabled": True
}
USER_DB_ONE = { USER_DB_ONE = {
"name": "db1", "name": "db1",
@ -335,6 +339,8 @@ def data(TEST):
user1 = users.User(users.Users(None), USER_ONE) user1 = users.User(users.Users(None), USER_ONE)
user_db1 = databases.Database(databases.Databases(None), user_db1 = databases.Database(databases.Databases(None),
USER_DB_ONE) USER_DB_ONE)
user_root1 = databases.Database(databases.Databases(None),
USER_ROOT_ONE)
datastore1 = datastores.Datastore(datastores.Datastores(None), datastore1 = datastores.Datastore(datastores.Datastores(None),
DATASTORE_ONE) DATASTORE_ONE)
@ -368,6 +374,7 @@ def data(TEST):
TEST.database_backups = utils.TestDataContainer() TEST.database_backups = utils.TestDataContainer()
TEST.database_users = utils.TestDataContainer() TEST.database_users = utils.TestDataContainer()
TEST.database_user_dbs = utils.TestDataContainer() TEST.database_user_dbs = utils.TestDataContainer()
TEST.database_user_roots = utils.TestDataContainer()
TEST.database_flavors = utils.TestDataContainer() TEST.database_flavors = utils.TestDataContainer()
TEST.databases.add(database1) TEST.databases.add(database1)
@ -377,6 +384,7 @@ def data(TEST):
TEST.database_backups.add(bkup3) TEST.database_backups.add(bkup3)
TEST.database_users.add(user1) TEST.database_users.add(user1)
TEST.database_user_dbs.add(user_db1) TEST.database_user_dbs.add(user_db1)
TEST.database_user_roots.add(user_root1)
TEST.datastores = utils.TestDataContainer() TEST.datastores = utils.TestDataContainer()
TEST.datastores.add(datastore1) TEST.datastores.add(datastore1)
TEST.datastores.add(datastore_mongodb) TEST.datastores.add(datastore_mongodb)