Trove add support for log retrieval

Add new log tab table to instance details to display the list of logs
that can be retrieved.  The table has row options to view the log
in a separate panel, publish new log data and stop collection of
log data.

The log data is displayed in a new panel.  In this panel the number
of lines to display can be changed.  Also checking the publish
checkbox and refreshing the log data will publish any new log data.
There are three buttons that perform the following actions:
* view the entire log in a separate window
* download the entire log
* return to the log list panel

The log code is placed in a subdirectory of database to keep the
code more manageable.

Added the supporting log commands to the api.

Change-Id: I577bcb598932d4a4568e22176671f1b67aceb787
Closes-Bug: #1548514
This commit is contained in:
Duk Loi 2016-02-23 16:26:45 -05:00
parent 9ad5737ea2
commit 55bd481865
14 changed files with 845 additions and 3 deletions

View File

@ -0,0 +1,5 @@
---
features:
- Added support for logging features in the dashboard.
This includes listing logs that can be retrieved and
viewing, publishing and stop collection of log contents.

View File

@ -297,3 +297,31 @@ def datastore_list(request):
def datastore_version_list(request, datastore):
return troveclient(request).datastore_versions.list(datastore)
def log_list(request, instance_id):
return troveclient(request).instances.log_list(instance_id)
def log_enable(request, instance_id, log_name):
return troveclient(request).instances.log_enable(instance_id, log_name)
def log_disable(request, instance_id, log_name):
return troveclient(request).instances.log_disable(instance_id, log_name)
def log_publish(request, instance_id, log_name):
return troveclient(request).instances.log_publish(instance_id, log_name)
def log_discard(request, instance_id, log_name):
return troveclient(request).instances.log_discard(instance_id, log_name)
def log_tail(request, instance_id, log_name, publish, lines, swift=None):
return troveclient(request).instances.log_generator(instance_id,
log_name,
publish=publish,
lines=lines,
swift=swift)

View File

@ -0,0 +1,156 @@
# Copyright 2016 Tesora Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.core import urlresolvers
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import tables
from trove_dashboard import api
class PublishLog(tables.BatchAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Publish Log",
u"Publish Logs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Published Log",
u"Published Logs",
count
)
name = "publish_log"
def action(self, request, obj_id):
instance_id = self.table.kwargs['instance_id']
api.trove.log_publish(request, instance_id, obj_id)
class DiscardLog(tables.BatchAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Discard Log",
u"Discard Logs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Discarded Log",
u"Discarded Logs",
count
)
name = "discard_log"
def action(self, request, obj_id):
instance_id = self.table.kwargs['instance_id']
api.trove.log_discard(request, instance_id, obj_id)
class EnableLog(tables.BatchAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Enable Log",
u"Enable Logs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Enabled Log",
u"Enabled Logs",
count
)
name = "enable_log"
def action(self, request, obj_id):
instance_id = self.table.kwargs['instance_id']
api.trove.log_enable(request, instance_id, obj_id)
class DisableLog(tables.BatchAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Disable Log",
u"Disable Logs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Disabled Log",
u"Disabled Logs",
count
)
name = "disable_log"
def action(self, request, obj_id):
instance_id = self.table.kwargs['instance_id']
api.trove.log_disable(request, instance_id, obj_id)
def allowed(self, request, datum=None):
if datum:
return datum.type != "SYS"
return False
class ViewLog(tables.LinkAction):
name = "view_log"
verbose_name = _("View Log")
url = "horizon:project:databases:logs:log_contents"
def get_link_url(self, datum):
instance_id = self.table.kwargs['instance_id']
return urlresolvers.reverse(self.url,
args=(instance_id,
datum.name))
def allowed(self, request, datum=None):
if datum:
return datum.published > 0
return False
class LogsTable(tables.DataTable):
name = tables.Column('name', verbose_name=_('Name'))
type = tables.Column('type', verbose_name=_("Type"))
status = tables.Column('status', verbose_name=_("Status"))
published = tables.Column('published', verbose_name=_('Published (bytes)'))
pending = tables.Column('pending', verbose_name=_('Publishable (bytes)'))
container = tables.Column('container', verbose_name=_('Container'))
class Meta(object):
name = "logs"
verbose_name = _("Logs")
row_actions = (ViewLog, PublishLog, EnableLog, DisableLog, DiscardLog)
def get_object_id(self, datum):
return datum.name

View File

@ -0,0 +1,361 @@
# Copyright 2016 Tesora Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.core.urlresolvers import reverse
from django import http
from mox3 import mox
from mox3.mox import IsA # noqa
import six
from trove_dashboard import api
from trove_dashboard.test import helpers as test
from swiftclient import client as swift_client
LINES = 50
class LogsTests(test.TestCase):
def stub_swiftclient(self, expected_calls=1):
if not hasattr(self, "swiftclient"):
self.mox.StubOutWithMock(swift_client, 'Connection')
self.swiftclient = self.mox.CreateMock(swift_client.Connection)
while expected_calls:
(swift_client.Connection(None,
mox.IgnoreArg(),
None,
preauthtoken=mox.IgnoreArg(),
preauthurl=mox.IgnoreArg(),
cacert=None,
insecure=False,
auth_version="2.0")
.AndReturn(self.swiftclient))
expected_calls -= 1
return self.swiftclient
@test.create_stubs({
api.trove: ('flavor_get', 'instance_get', 'log_list', 'root_show')})
def test_log_tab(self):
database = self.databases.first()
database_id = database.id
(api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))
.AndReturn(database))
(api.trove.log_list(IsA(http.HttpRequest), database_id)
.AndReturn(self.logs.list()))
(api.trove.flavor_get(IsA(http.HttpRequest), database.flavor["id"])
.AndReturn(self.flavors.first()))
(api.trove.root_show(IsA(http.HttpRequest), database.id)
.AndReturn(self.database_user_roots.first()))
self.mox.ReplayAll()
detail_url = reverse('horizon:project:databases:detail',
args=[database_id])
url = detail_url + '?tab=instance_details__logs_tab'
res = self.client.get(url)
table_data = res.context['logs_table'].data
self.assertItemsEqual(self.logs.list(), table_data)
self.assertTemplateUsed(
res, 'horizon/common/_detail_table.html')
@test.create_stubs({
api.trove: ('flavor_get', 'instance_get', 'log_list', 'root_show')})
def test_log_tab_exception(self):
database = self.databases.first()
database_id = database.id
(api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))
.AndReturn(database))
(api.trove.log_list(IsA(http.HttpRequest), database_id)
.AndRaise(self.exceptions.trove))
(api.trove.flavor_get(IsA(http.HttpRequest), database.flavor["id"])
.AndReturn(self.flavors.first()))
(api.trove.root_show(IsA(http.HttpRequest), database.id)
.AndReturn(self.database_user_roots.first()))
self.mox.ReplayAll()
detail_url = reverse('horizon:project:databases:detail',
args=[database_id])
url = detail_url + '?tab=instance_details__logs_tab'
toSuppress = ["trove_dashboard.content.databases.tabs"]
loggers = []
for cls in toSuppress:
logger = logging.getLogger(cls)
loggers.append((logger, logger.getEffectiveLevel()))
logger.setLevel(logging.CRITICAL)
try:
res = self.client.get(url)
table_data = res.context['logs_table'].data
self.assertNotEqual(len(self.logs.list()), len(table_data))
self.assertTemplateUsed(
res, 'horizon/common/_detail_table.html')
finally:
# Restore the previous log levels
for (log, level) in loggers:
log.setLevel(level)
@test.create_stubs({
api.trove: ('flavor_get', 'instance_get', 'log_list', 'log_publish',)
})
def test_log_publish(self):
database = self.databases.first()
database_id = database.id
log = self.logs.first()
(api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))
.AndReturn(database))
(api.trove.log_list(IsA(http.HttpRequest), database_id)
.AndReturn(self.logs.list()))
(api.trove.flavor_get(IsA(http.HttpRequest), database.flavor["id"])
.AndReturn(self.flavors.first()))
(api.trove.log_publish(IsA(http.HttpRequest), database_id, log.name)
.AndReturn(None))
self.mox.ReplayAll()
detail_url = reverse('horizon:project:databases:detail',
args=[database_id])
url = detail_url + '?tab=instance_details__logs_tab'
action_string = u"logs__%s_log__%s" % ('publish', log.name)
form_data = {'action': action_string}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({
api.trove: ('flavor_get', 'instance_get', 'log_list', 'log_publish',)
})
def test_log_publish_exception(self):
database = self.databases.first()
database_id = database.id
log = self.logs.first()
(api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))
.AndReturn(database))
(api.trove.log_list(IsA(http.HttpRequest), database_id)
.AndReturn(self.logs.list()))
(api.trove.flavor_get(IsA(http.HttpRequest), database.flavor["id"])
.AndReturn(self.flavors.first()))
(api.trove.log_publish(IsA(http.HttpRequest), database_id, log.name)
.AndRaise(self.exceptions.trove))
self.mox.ReplayAll()
detail_url = reverse('horizon:project:databases:detail',
args=[database_id])
url = detail_url + '?tab=instance_details__logs_tab'
action_string = u"logs__%s_log__%s" % ('publish', log.name)
form_data = {'action': action_string}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({
api.trove: ('flavor_get', 'instance_get', 'log_list', 'log_enable',)
})
def test_log_enable(self):
database = self.databases.first()
database_id = database.id
log = self.logs.first()
(api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))
.AndReturn(database))
(api.trove.log_list(IsA(http.HttpRequest), database_id)
.AndReturn(self.logs.list()))
(api.trove.flavor_get(IsA(http.HttpRequest), database.flavor["id"])
.AndReturn(self.flavors.first()))
(api.trove.log_enable(IsA(http.HttpRequest), database_id, log.name)
.AndReturn(None))
self.mox.ReplayAll()
detail_url = reverse('horizon:project:databases:detail',
args=[database_id])
url = detail_url + '?tab=instance_details__logs_tab'
action_string = u"logs__%s_log__%s" % ('enable', log.name)
form_data = {'action': action_string}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({
api.trove: ('flavor_get', 'instance_get', 'log_list', 'log_enable',)
})
def test_log_enable_exception(self):
database = self.databases.first()
database_id = database.id
log = self.logs.first()
(api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))
.AndReturn(database))
(api.trove.log_list(IsA(http.HttpRequest), database_id)
.AndReturn(self.logs.list()))
(api.trove.flavor_get(IsA(http.HttpRequest), database.flavor["id"])
.AndReturn(self.flavors.first()))
(api.trove.log_enable(IsA(http.HttpRequest), database_id, log.name)
.AndRaise(self.exceptions.trove))
self.mox.ReplayAll()
detail_url = reverse('horizon:project:databases:detail',
args=[database_id])
url = detail_url + '?tab=instance_details__logs_tab'
action_string = u"logs__%s_log__%s" % ('enable', log.name)
form_data = {'action': action_string}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({
api.trove: ('flavor_get', 'instance_get', 'log_list', 'log_discard',)
})
def test_log_discard(self):
database = self.databases.first()
database_id = database.id
log = self.logs.first()
(api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))
.AndReturn(database))
(api.trove.log_list(IsA(http.HttpRequest), database_id)
.AndReturn(self.logs.list()))
(api.trove.flavor_get(IsA(http.HttpRequest), database.flavor["id"])
.AndReturn(self.flavors.first()))
(api.trove.log_discard(IsA(http.HttpRequest), database_id, log.name)
.AndReturn(None))
self.mox.ReplayAll()
detail_url = reverse('horizon:project:databases:detail',
args=[database_id])
url = detail_url + '?tab=instance_details__logs_tab'
action_string = u"logs__%s_log__%s" % ('discard', log.name)
form_data = {'action': action_string}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({
api.trove: ('flavor_get', 'instance_get', 'log_list', 'log_discard',)
})
def test_log_discard_exception(self):
database = self.databases.first()
database_id = database.id
log = self.logs.first()
(api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))
.AndReturn(database))
(api.trove.log_list(IsA(http.HttpRequest), database_id)
.AndReturn(self.logs.list()))
(api.trove.flavor_get(IsA(http.HttpRequest), database.flavor["id"])
.AndReturn(self.flavors.first()))
(api.trove.log_discard(IsA(http.HttpRequest), database_id, log.name)
.AndRaise(self.exceptions.trove))
self.mox.ReplayAll()
detail_url = reverse('horizon:project:databases:detail',
args=[database_id])
url = detail_url + '?tab=instance_details__logs_tab'
action_string = u"logs__%s_log__%s" % ('discard', log.name)
form_data = {'action': action_string}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({
api.trove: ('flavor_get', 'instance_get', 'log_list', 'log_disable',)
})
def test_log_disable(self):
database = self.databases.first()
database_id = database.id
log = self.logs.list()[3]
(api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))
.AndReturn(database))
(api.trove.log_list(IsA(http.HttpRequest), database_id)
.AndReturn(self.logs.list()))
(api.trove.flavor_get(IsA(http.HttpRequest), database.flavor["id"])
.AndReturn(self.flavors.first()))
(api.trove.log_disable(IsA(http.HttpRequest), database_id, log.name)
.AndReturn(None))
self.mox.ReplayAll()
detail_url = reverse('horizon:project:databases:detail',
args=[database_id])
url = detail_url + '?tab=instance_details__logs_tab'
action_string = u"logs__%s_log__%s" % ('disable', log.name)
form_data = {'action': action_string}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({
api.trove: ('flavor_get', 'instance_get', 'log_list', 'log_disable',)
})
def test_log_disable_exception(self):
database = self.databases.first()
database_id = database.id
log = self.logs.list()[3]
(api.trove.instance_get(IsA(http.HttpRequest), IsA(six.text_type))
.AndReturn(database))
(api.trove.log_list(IsA(http.HttpRequest), database_id)
.AndReturn(self.logs.list()))
(api.trove.flavor_get(IsA(http.HttpRequest), database.flavor["id"])
.AndReturn(self.flavors.first()))
(api.trove.log_disable(IsA(http.HttpRequest), database_id, log.name)
.AndRaise(self.exceptions.trove))
self.mox.ReplayAll()
detail_url = reverse('horizon:project:databases:detail',
args=[database_id])
url = detail_url + '?tab=instance_details__logs_tab'
action_string = u"logs__%s_log__%s" % ('disable', log.name)
form_data = {'action': action_string}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({api.trove: ('log_tail',)})
def test_view_log(self):
CONSOLE_OUTPUT = 'superspecialuniquetext'
(api.trove.log_tail(IsA(http.HttpRequest),
IsA(six.string_types),
'guest.log',
False,
LINES,
self.stub_swiftclient())
.AndReturn(lambda: [CONSOLE_OUTPUT]))
self.mox.ReplayAll()
url = reverse('horizon:project:databases:logs:log_contents',
args=('id', 'guest.log'))
res = self.client.get(url)
self.assertNoMessages()
self.assertIsInstance(res, http.HttpResponse)
self.assertContains(res, CONSOLE_OUTPUT)
@test.create_stubs({api.trove: ('log_tail',)})
def test_view_log_exception(self):
(api.trove.log_tail(IsA(http.HttpRequest),
IsA(six.string_types),
'guest.log',
False,
LINES,
self.stub_swiftclient())
.AndRaise(self.exceptions.trove))
self.mox.ReplayAll()
url = reverse('horizon:project:databases:logs:log_contents',
args=('id', 'guest.log'))
res = self.client.get(url)
self.assertContains(res, "Unable to load")

View File

@ -0,0 +1,32 @@
# Copyright 2016 Tesora Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import patterns
from django.conf.urls import url
from trove_dashboard.content.databases.logs import views
VIEWS_MOD = ('trove_dashboard.content.databases.logs.views')
LOGS = r'^(?P<filename>[^/]+)/%s$'
urlpatterns = patterns(
VIEWS_MOD,
url(LOGS % 'console', 'console', name='console'),
url(LOGS % 'download_log', 'download_log', name='download_log'),
url(LOGS % 'full_log', 'full_log', name='full_log'),
url(LOGS % 'log_contents',
views.LogContentsView.as_view(), name='log_contents'),
)

View File

@ -0,0 +1,129 @@
# Copyright 2016 Tesora Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django import http
from django import shortcuts
from django.utils.translation import ugettext_lazy as _
from django.views import generic
from horizon import exceptions
from horizon import messages
from openstack_dashboard import api as dash_api
from trove_dashboard import api
FULL_LOG_VALUE = 0
DEFAULT_LINES = 50
class LogContentsView(generic.TemplateView):
template_name = 'project/databases/logs/view_log.html'
preload = False
def get_context_data(self, **kwargs):
context = super(LogContentsView, self).get_context_data(**kwargs)
context["instance_id"] = kwargs['instance_id']
context["filename"] = kwargs['filename']
context["publish"] = ''
try:
log_length = int(kwargs['lines'])
except Exception:
log_length = DEFAULT_LINES
context["log_length"] = log_length
context["log_contents"] = get_contents(self.request,
kwargs['instance_id'],
kwargs['filename'],
False,
log_length)
return context
def get_contents(request, instance_id, filename, publish, lines):
try:
log_generator = api.trove.log_tail(request,
instance_id,
filename,
publish,
lines,
dash_api.swift.swift_api(request))
data = ""
for log_part in log_generator():
data += log_part
except Exception as e:
data = _('Unable to load {0} log\n{1}').format(filename, e.message)
return data
def build_response(request, instance_id, filename, tail):
data = (_('Unable to load {0} log for instance "{1}".')
.format(filename, instance_id))
if request.GET.get('publish'):
publish = True
else:
publish = False
try:
data = get_contents(request,
instance_id,
filename,
publish,
int(tail))
except Exception:
exceptions.handle(request, ignore=True)
return http.HttpResponse(data.encode('utf-8'), content_type='text/plain')
def console(request, instance_id, filename):
tail = request.GET.get('length')
if not tail or (tail and not tail.isdigit()):
msg = _('Log length must be a nonnegative integer.')
messages.warning(request, msg)
data = (_('Unable to load {0} log for instance "{1}".')
.format(filename, instance_id))
return http.HttpResponse(data.encode('utf-8'),
content_type='text/plain')
return build_response(request, instance_id, filename, tail)
def full_log(request, instance_id, filename):
return build_response(request, instance_id, filename, FULL_LOG_VALUE)
def download_log(request, instance_id, filename):
try:
publish_value = request.GET.get('publish')
if publish_value:
publish = True
else:
publish = False
data = get_contents(request,
instance_id,
filename,
publish,
FULL_LOG_VALUE)
response = http.HttpResponse()
response.write(data)
response['Content-Disposition'] = ('attachment; '
'filename="%s.log"' % filename)
response['Content-Length'] = str(len(response.content))
return response
except Exception as e:
messages.error(request, _('Error downloading log file: %s') % e)
return shortcuts.redirect(request.build_absolute_uri())

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django import template
from django.utils.translation import ugettext_lazy as _
@ -19,9 +21,13 @@ from horizon import exceptions
from horizon import tabs
from trove_dashboard import api
from trove_dashboard.content.databases import db_capability
from trove_dashboard.content.databases.logs import tables as log_tables
from trove_dashboard.content.databases import tables
LOG = logging.getLogger(__name__)
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "overview"
@ -136,7 +142,26 @@ class BackupsTab(tabs.TableTab):
return request.user.has_perm('openstack.services.object-store')
class LogsTab(tabs.TableTab):
table_classes = [log_tables.LogsTable]
name = _("Logs")
slug = "logs_tab"
template_name = "horizon/common/_detail_table.html"
preload = False
def get_logs_data(self):
instance = self.tab_group.kwargs['instance']
try:
logs = api.trove.log_list(self.request, instance.id)
return logs
except Exception as e:
LOG.exception(
_('Unable to retrieve list of logs.\n%s') % e.message)
logs = []
return logs
class InstanceDetailTabs(tabs.TabGroup):
slug = "instance_details"
tabs = (OverviewTab, UserTab, DatabaseTab, BackupsTab)
tabs = (OverviewTab, UserTab, DatabaseTab, BackupsTab, LogsTab)
sticky = True

View File

@ -0,0 +1,22 @@
{% load i18n %}
{% load url from future %}
<div class="col-sm-12">
<div class="clearfix">
<form id="tail_length" action="{% url 'horizon:project:databases:logs:console' instance_id filename %}" class="form-inline pull-right">
<label for="tail_length_select">{% trans "Log Length" %}</label>
<input class="span1" type="text" name="length" value="{{ log_length }}" />
<label for="publish_check">{% trans "Publish" %}</label>
<input type="checkbox" name="publish" value="publish" {{ publish }}>
<button class="btn btn-default btn-sm btn-primary always-enabled" type="submit">{% trans "Go" %}</button>
<a href="{% url 'horizon:project:databases:detail' instance_id %}" class="btn btn-default btn-sm pull-right secondary">{% trans "Return to Log List" %}</a>
<a href="{% url 'horizon:project:databases:logs:download_log' instance_id filename %}" class="btn btn-default btn-sm pull-right">{% trans "Download" %}</a>
<a href="{% url 'horizon:project:databases:logs:full_log' instance_id filename %}" class="btn btn-default btn-sm pull-right" target="_blank">{% trans "View Full Log" %}</a>
</form>
</div>
<br>
<pre class="logs">
{{ log_contents }}
</pre>
</div>

View File

@ -0,0 +1,15 @@
{% extends "horizon/common/_modal.html" %}
{% load i18n %}
{% load url from future %}
{% block modal-header %}{% trans "Log Contents" %}{% endblock %}
{% block modal-body %}
<pre class="logs">
{{ log.data }}
</pre>
{% endblock %}
{% block modal-footer %}
<a href="{% url 'horizon:project:databases:index' %}" class="btn btn-default secondary cancel close">{% trans "Close" %}</a>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Log Contents" %}{% endblock %}
{% block main %}
<div class="row-fluid">
<div class="col-sm-12">
{% include "project/databases/logs/_log_contents.html" %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Log" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Log: ")|add:filename %}
{% endblock page_header %}
{% block main %}
{% include "project/databases/logs/_detail_log.html" %}
{% endblock %}

View File

@ -12,13 +12,15 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import include
from django.conf.urls import patterns
from django.conf.urls import url
from trove_dashboard.content.databases.logs import urls as logs_urls
from trove_dashboard.content.databases import views
INSTANCES = r'^(?P<instance_id>[^/]+)/%s$'
BASEINSTANCES = r'^(?P<instance_id>[^/]+)/%s'
INSTANCES = BASEINSTANCES + '$'
USERS = r'^(?P<instance_id>[^/]+)/(?P<user_name>[^/]+)/%s$'
@ -44,4 +46,5 @@ urlpatterns = patterns(
name='promote_to_replica_source'),
url(INSTANCES % 'manage_root', views.ManageRootView.as_view(),
name='manage_root'),
url(BASEINSTANCES % 'logs/', include(logs_urls, namespace='logs')),
)

View File

@ -367,6 +367,42 @@ VERSION_VERTICA_7_1 = {
"id": "600a6d52-8347-4e00-8e4c-f4fa9cf96af1"
}
LOG_1 = {
"name": "guest",
"type": "SYS",
"status": "Partial",
"published": 5000,
"pending": 100,
"container": "guest_container"
}
LOG_2 = {
"name": "slow_query",
"type": "USER",
"status": "Disabled",
"published": 0,
"pending": 0,
"container": "None"
}
LOG_3 = {
"name": "error",
"type": "SYS",
"status": "Unavailable",
"published": 0,
"pending": 0,
"container": "None"
}
LOG_4 = {
"name": "general",
"type": "USER",
"status": "Ready",
"published": 0,
"pending": 1000,
"container": "None"
}
def data(TEST):
cluster1 = clusters.Cluster(clusters.Clusters(None),
@ -413,6 +449,11 @@ def data(TEST):
DatastoreVersion(datastores.DatastoreVersions(None),
VERSION_VERTICA_7_1)
log1 = instances.DatastoreLog(instances.Instances(None), LOG_1)
log2 = instances.DatastoreLog(instances.Instances(None), LOG_2)
log3 = instances.DatastoreLog(instances.Instances(None), LOG_3)
log4 = instances.DatastoreLog(instances.Instances(None), LOG_4)
TEST.trove_clusters = utils.TestDataContainer()
TEST.trove_clusters.add(cluster1)
TEST.trove_clusters.add(cluster2)
@ -443,3 +484,6 @@ def data(TEST):
TEST.datastore_versions.add(version_redis_3_0)
TEST.datastore_versions.add(version_mongodb_2_6)
TEST.datastore_versions.add(version1)
TEST.logs = utils.TestDataContainer()
TEST.logs.add(log1, log2, log3, log4)