Merge "Trove add support for log retrieval"
This commit is contained in:
commit
1c05349cb0
5
releasenotes/notes/logging-support-f999a1b1b342eb4d.yaml
Normal file
5
releasenotes/notes/logging-support-f999a1b1b342eb4d.yaml
Normal 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.
|
@ -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)
|
||||
|
0
trove_dashboard/content/databases/logs/__init__.py
Normal file
0
trove_dashboard/content/databases/logs/__init__.py
Normal file
156
trove_dashboard/content/databases/logs/tables.py
Normal file
156
trove_dashboard/content/databases/logs/tables.py
Normal 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
|
361
trove_dashboard/content/databases/logs/tests.py
Normal file
361
trove_dashboard/content/databases/logs/tests.py
Normal 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")
|
32
trove_dashboard/content/databases/logs/urls.py
Normal file
32
trove_dashboard/content/databases/logs/urls.py
Normal 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'),
|
||||
)
|
129
trove_dashboard/content/databases/logs/views.py
Normal file
129
trove_dashboard/content/databases/logs/views.py
Normal 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())
|
@ -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
|
||||
|
@ -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>
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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')),
|
||||
)
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user