Add support for listing folder indexes
If a file isn't found, instead list an index with everything within that folder from both disk and swift. Change-Id: I60ef6c625904de6af098df1906598ef107f74796
This commit is contained in:
parent
f6bd80673b
commit
e1079394e3
|
@ -2,6 +2,7 @@
|
|||
filter = SevFilter
|
||||
view = HTMLView
|
||||
file_conditions = /etc/os-loganalyze/file_conditions.yaml
|
||||
generate_folder_index = true
|
||||
|
||||
[swift]
|
||||
authurl=https://keystone.example.org/v2.0/
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
import re
|
||||
|
||||
import os_loganalyze.generator as generator
|
||||
import os_loganalyze.util as util
|
||||
|
||||
# which logs support severity
|
||||
|
@ -161,6 +162,11 @@ class NoFilter(object):
|
|||
def get_filter_generator(file_generator, environ, root_path, config):
|
||||
"""Return the filter to use as per the config."""
|
||||
|
||||
# Check if the generator is an index page. If so, we don't want to apply
|
||||
# any filters
|
||||
if isinstance(file_generator, generator.IndexIterableBuffer):
|
||||
return NoFilter(file_generator)
|
||||
|
||||
# Check file specific conditions first
|
||||
filter_selected = util.get_file_conditions('filter', file_generator,
|
||||
environ, root_path, config)
|
||||
|
|
|
@ -22,9 +22,10 @@ import os.path
|
|||
import re
|
||||
import sys
|
||||
import types
|
||||
import wsgiref.util
|
||||
import zlib
|
||||
|
||||
import jinja2
|
||||
|
||||
import os_loganalyze.util as util
|
||||
|
||||
try:
|
||||
|
@ -64,13 +65,15 @@ def does_file_exist(fname):
|
|||
|
||||
|
||||
def log_name(environ):
|
||||
path = wsgiref.util.request_uri(environ, include_query=0)
|
||||
path = environ['PATH_INFO']
|
||||
if path[0] == '/':
|
||||
path = path[1:]
|
||||
match = re.search('htmlify/(.*)', path)
|
||||
if match:
|
||||
raw = match.groups(1)[0]
|
||||
return raw
|
||||
|
||||
return None
|
||||
return path
|
||||
|
||||
|
||||
def safe_path(root, log_name):
|
||||
|
@ -80,7 +83,7 @@ def safe_path(root, log_name):
|
|||
remains under the root path. If not, we return None to indicate
|
||||
that we are very sad.
|
||||
"""
|
||||
if log_name:
|
||||
if log_name is not None:
|
||||
newpath = os.path.abspath(os.path.join(root, log_name))
|
||||
if newpath.find(root) == 0:
|
||||
return newpath
|
||||
|
@ -106,12 +109,11 @@ _get_swift_connection.con = None
|
|||
|
||||
|
||||
class SwiftIterableBuffer(collections.Iterable):
|
||||
file_headers = {}
|
||||
|
||||
def __init__(self, logname, config):
|
||||
self.logname = logname
|
||||
self.resp_headers = {}
|
||||
self.obj = None
|
||||
self.file_headers = {}
|
||||
self.file_headers['filename'] = logname
|
||||
|
||||
if not config.has_section('swift'):
|
||||
|
@ -185,14 +187,13 @@ class SwiftIterableBuffer(collections.Iterable):
|
|||
|
||||
|
||||
class DiskIterableBuffer(collections.Iterable):
|
||||
file_headers = {}
|
||||
|
||||
def __init__(self, logname, logpath, config):
|
||||
self.logname = logname
|
||||
self.logpath = logpath
|
||||
self.resp_headers = {}
|
||||
self.obj = fileinput.FileInput(self.logpath,
|
||||
openhook=fileinput.hook_compressed)
|
||||
self.file_headers = {}
|
||||
self.file_headers['filename'] = logname
|
||||
self.file_headers.update(util.get_headers_for_file(logpath))
|
||||
|
||||
|
@ -200,13 +201,78 @@ class DiskIterableBuffer(collections.Iterable):
|
|||
return self.obj
|
||||
|
||||
|
||||
class IndexIterableBuffer(collections.Iterable):
|
||||
def __init__(self, logname, logpath, config):
|
||||
self.logname = logname
|
||||
self.logpath = logpath
|
||||
self.config = config
|
||||
self.resp_headers = {}
|
||||
self.file_headers = {}
|
||||
self.file_headers['Content-type'] = 'text/html'
|
||||
|
||||
# file_list is a list of tuples (relpath, name)
|
||||
self.file_list = self.disk_list() + self.swift_list()
|
||||
self.file_list = sorted(self.file_list, key=lambda tup: tup[0])
|
||||
|
||||
def disk_list(self):
|
||||
file_list = []
|
||||
if os.path.isdir(self.logpath):
|
||||
for f in os.listdir(self.logpath):
|
||||
if os.path.isdir(os.path.join(self.logpath, f)):
|
||||
f = f + '/' if f[-1] != '/' else f
|
||||
file_list.append((
|
||||
os.path.join('/', self.logname, f),
|
||||
f
|
||||
))
|
||||
return file_list
|
||||
|
||||
def swift_list(self):
|
||||
file_list = []
|
||||
if self.config.has_section('swift'):
|
||||
try:
|
||||
swift_config = dict(self.config.items('swift'))
|
||||
con = _get_swift_connection(swift_config)
|
||||
|
||||
prefix = self.logname + '/' if self.logname[-1] != '/' \
|
||||
else self.logname
|
||||
resp, files = con.get_container(swift_config['container'],
|
||||
prefix=prefix,
|
||||
delimiter='/')
|
||||
|
||||
for f in files:
|
||||
if 'subdir' in f:
|
||||
fname = os.path.relpath(f['subdir'], self.logname)
|
||||
fname = fname + '/' if f['subdir'][-1] == '/' else \
|
||||
fname
|
||||
else:
|
||||
fname = os.path.relpath(f['name'], self.logname)
|
||||
file_list.append((
|
||||
os.path.join('/', self.logname, fname),
|
||||
fname
|
||||
))
|
||||
except Exception:
|
||||
import traceback
|
||||
sys.stderr.write("Error fetching index list from swift.\n")
|
||||
sys.stderr.write('logname: %s\n' % self.logname)
|
||||
traceback.print_exc()
|
||||
|
||||
return file_list
|
||||
|
||||
def __iter__(self):
|
||||
env = jinja2.Environment(
|
||||
loader=jinja2.PackageLoader('os_loganalyze', 'templates'))
|
||||
template = env.get_template('file_index.html')
|
||||
gen = template.generate(logname=self.logname,
|
||||
file_list=self.file_list)
|
||||
for l in gen:
|
||||
yield l.encode("utf-8")
|
||||
|
||||
|
||||
def get_file_generator(environ, root_path, config=None):
|
||||
logname = log_name(environ)
|
||||
logpath = safe_path(root_path, logname)
|
||||
file_headers = {}
|
||||
if not logpath:
|
||||
if logpath is None:
|
||||
raise UnsafePath()
|
||||
file_headers['filename'] = os.path.basename(logpath)
|
||||
|
||||
file_generator = None
|
||||
# if we want swift only, we'll skip processing files
|
||||
|
@ -218,7 +284,7 @@ def get_file_generator(environ, root_path, config=None):
|
|||
# NOTE(jhesketh): If the requested URL ends in a trailing slash we
|
||||
# assume that this is meaning to load an index.html from our pseudo
|
||||
# filesystem and attempt that first.
|
||||
if logname[-1] == '/':
|
||||
if logname and logname[-1] == '/':
|
||||
file_generator = SwiftIterableBuffer(
|
||||
os.path.join(logname, 'index.html'), config)
|
||||
if not file_generator.obj:
|
||||
|
@ -229,10 +295,17 @@ def get_file_generator(environ, root_path, config=None):
|
|||
file_generator = SwiftIterableBuffer(logname, config)
|
||||
if not file_generator.obj:
|
||||
# The object doesn't exist. Try again appending index.html
|
||||
logname = os.path.join(logname, 'index.html')
|
||||
file_generator = SwiftIterableBuffer(logname, config)
|
||||
file_generator = SwiftIterableBuffer(
|
||||
os.path.join(logname, 'index.html'), config)
|
||||
|
||||
if not file_generator.obj:
|
||||
if not file_generator or not file_generator.obj:
|
||||
if config.has_section('general'):
|
||||
if config.has_option('general', 'generate_folder_index'):
|
||||
if config.getboolean('general', 'generate_folder_index'):
|
||||
index_generator = IndexIterableBuffer(logname, logpath,
|
||||
config)
|
||||
if len(index_generator.file_list) > 0:
|
||||
return index_generator
|
||||
raise NoSuchFile()
|
||||
|
||||
return file_generator
|
||||
|
|
|
@ -22,7 +22,6 @@ LOG_COLOR=false
|
|||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import wsgiref.simple_server
|
||||
|
@ -53,43 +52,8 @@ def parse_args():
|
|||
|
||||
|
||||
def top_wsgi_app(environ, start_response):
|
||||
req_path = environ.get('PATH_INFO')
|
||||
if bool(re.search('^/$|^/htmlify/?$', req_path)):
|
||||
return gen_links_wsgi_app(environ, start_response)
|
||||
else:
|
||||
return wsgi.application(environ, start_response, root_path=LOG_PATH,
|
||||
wsgi_config=WSGI_CONFIG)
|
||||
|
||||
|
||||
def gen_links_wsgi_app(environ, start_response):
|
||||
start_response('200 OK', [('Content-type', 'text/html')])
|
||||
if environ.get('QUERY_STRING') == 'all':
|
||||
return link_generator(all_files=True)
|
||||
else:
|
||||
return link_generator(all_files=False)
|
||||
|
||||
|
||||
def link_generator(all_files):
|
||||
yield '<head><body>\n'
|
||||
|
||||
filenames = os.listdir(LOG_PATH)
|
||||
if all_files:
|
||||
yield ("Showing all files in %s. "
|
||||
"<a href='/'>Show current logs only</a>\n" % LOG_PATH)
|
||||
else:
|
||||
yield ("Showing current log files in %s. "
|
||||
"<a href='/?all'>Show all files</a>\n" % LOG_PATH)
|
||||
|
||||
filenames = [f for f in filenames if
|
||||
re.search('\.(log|txt\.gz|html.gz)$', f)]
|
||||
# also exclude files with datestamps in their name
|
||||
filenames = [f for f in filenames
|
||||
if not re.search('\d{4}-\d{2}-\d{2}', f)]
|
||||
|
||||
for filename in sorted(filenames):
|
||||
yield "<p><a href='/htmlify/%s'> %s </a>\n" % (filename, filename)
|
||||
|
||||
yield '</body></html>\n'
|
||||
return wsgi.application(environ, start_response, root_path=LOG_PATH,
|
||||
wsgi_config=WSGI_CONFIG)
|
||||
|
||||
|
||||
def my_ip():
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Index of {{ logname }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Index of {{ logname }}</h1>
|
||||
<ul>
|
||||
{% for link, title in file_list %}
|
||||
<li><a href="{{ link }}">{{ title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
[general]
|
||||
generate_folder_index = true
|
||||
|
||||
[swift]
|
||||
authurl=https://keystone.example.org/v2.0/
|
||||
user=example
|
||||
password=example
|
||||
container=logs
|
||||
region=EXP
|
||||
tenant=
|
||||
chunk_size=64
|
|
@ -18,9 +18,10 @@
|
|||
Test the ability to convert files into wsgi generators
|
||||
"""
|
||||
|
||||
import os
|
||||
import types
|
||||
|
||||
import fixtures
|
||||
import mock
|
||||
import swiftclient # noqa needed for monkeypatching
|
||||
|
||||
from os_loganalyze.tests import base
|
||||
|
@ -74,6 +75,52 @@ def compute_total(level, counts):
|
|||
return total
|
||||
|
||||
|
||||
def fake_get_object(self, container, name, resp_chunk_size=None):
|
||||
name = name[len('non-existent/'):]
|
||||
if not os.path.isfile(base.samples_path('samples') + name):
|
||||
return {}, None
|
||||
if resp_chunk_size:
|
||||
|
||||
def _object_body():
|
||||
with open(base.samples_path('samples') + name) as f:
|
||||
|
||||
buf = f.read(resp_chunk_size)
|
||||
while buf:
|
||||
yield buf
|
||||
buf = f.read(resp_chunk_size)
|
||||
|
||||
object_body = _object_body()
|
||||
else:
|
||||
with open(base.samples_path('samples') + name) as f:
|
||||
object_body = f.read()
|
||||
resp_headers = os_loganalyze.util.get_headers_for_file(
|
||||
base.samples_path('samples') + name)
|
||||
return resp_headers, object_body
|
||||
|
||||
|
||||
def fake_get_container_factory(_swift_index_items=[]):
|
||||
def fake_get_container(self, container, prefix=None, delimiter=None):
|
||||
index_items = []
|
||||
if _swift_index_items:
|
||||
for i in _swift_index_items:
|
||||
if i[-1] == '/':
|
||||
index_items.append({'subdir': os.path.join(prefix, i)})
|
||||
else:
|
||||
index_items.append({'name': os.path.join(prefix, i)})
|
||||
else:
|
||||
name = prefix[len('non-existent/'):]
|
||||
p = os.path.join(base.samples_path('samples'), name)
|
||||
for i in os.listdir(p):
|
||||
if os.path.isdir(os.path.join(p, i)):
|
||||
index_items.append(
|
||||
{'subdir': os.path.join(prefix, i + '/')})
|
||||
else:
|
||||
index_items.append({'name': os.path.join(prefix, i)})
|
||||
|
||||
return {}, index_items
|
||||
return fake_get_container
|
||||
|
||||
|
||||
class TestWsgiDisk(base.TestCase):
|
||||
"""Test loading files from samples on disk."""
|
||||
|
||||
|
@ -108,6 +155,8 @@ class TestWsgiDisk(base.TestCase):
|
|||
},
|
||||
}
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_pass_through_all(self):
|
||||
for fname in self.files:
|
||||
gen = self.get_generator(fname, html=False)
|
||||
|
@ -115,6 +164,8 @@ class TestWsgiDisk(base.TestCase):
|
|||
counts = count_types(gen)
|
||||
self.assertEqual(counts['TOTAL'], self.files[fname]['TOTAL'])
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_pass_through_at_levels(self):
|
||||
for fname in self.files:
|
||||
for level in self.files[fname]:
|
||||
|
@ -129,17 +180,23 @@ class TestWsgiDisk(base.TestCase):
|
|||
|
||||
self.assertEqual(counts['TOTAL'], total)
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_invalid_file(self):
|
||||
gen = log_wsgi.application(
|
||||
self.fake_env(), self._start_response)
|
||||
self.fake_env(PATH_INFO='../'), self._start_response)
|
||||
self.assertEqual(gen, ['Invalid file url'])
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_file_not_found(self):
|
||||
gen = log_wsgi.application(
|
||||
self.fake_env(PATH_INFO='/htmlify/foo.txt'),
|
||||
self._start_response)
|
||||
self.assertEqual(gen, ['File Not Found'])
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_plain_text(self):
|
||||
gen = self.get_generator('screen-c-api.txt.gz', html=False)
|
||||
self.assertEqual(type(gen), types.GeneratorType)
|
||||
|
@ -149,11 +206,15 @@ class TestWsgiDisk(base.TestCase):
|
|||
'+ ln -sf /opt/stack/new/screen-logs/screen-c-api.2013-09-27-1815',
|
||||
first)
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_html_gen(self):
|
||||
gen = self.get_generator('screen-c-api.txt.gz')
|
||||
first = gen.next()
|
||||
self.assertIn('<html>', first)
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_plain_non_compressed(self):
|
||||
gen = self.get_generator('screen-c-api.txt', html=False)
|
||||
self.assertEqual(type(gen), types.GeneratorType)
|
||||
|
@ -163,6 +224,8 @@ class TestWsgiDisk(base.TestCase):
|
|||
'+ ln -sf /opt/stack/new/screen-logs/screen-c-api.2013-09-27-1815',
|
||||
first)
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_passthrough_filter(self):
|
||||
# Test the passthrough filter returns an image stream
|
||||
gen = self.get_generator('openstack_logo.png')
|
||||
|
@ -171,6 +234,8 @@ class TestWsgiDisk(base.TestCase):
|
|||
with open(base.samples_path('samples') + 'openstack_logo.png') as f:
|
||||
self.assertEqual(first, f.readline())
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_config_no_filter(self):
|
||||
self.wsgi_config_file = (base.samples_path('samples') +
|
||||
'wsgi_plain.conf')
|
||||
|
@ -186,6 +251,8 @@ class TestWsgiDisk(base.TestCase):
|
|||
# given the header and footer, but we expect to get the full file
|
||||
self.assertNotEqual(12, lines)
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_config_passthrough_view(self):
|
||||
self.wsgi_config_file = (base.samples_path('samples') +
|
||||
'wsgi_plain.conf')
|
||||
|
@ -195,6 +262,8 @@ class TestWsgiDisk(base.TestCase):
|
|||
first = gen.next()
|
||||
self.assertNotIn('<html>', first)
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_file_conditions(self):
|
||||
self.wsgi_config_file = (base.samples_path('samples') +
|
||||
'wsgi_file_conditions.conf')
|
||||
|
@ -217,42 +286,41 @@ class TestWsgiDisk(base.TestCase):
|
|||
with open(base.samples_path('samples') + 'openstack_logo.png') as f:
|
||||
self.assertEqual(first, f.readline())
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_container',
|
||||
fake_get_container_factory())
|
||||
def test_folder_index(self):
|
||||
self.wsgi_config_file = (base.samples_path('samples') +
|
||||
'wsgi_folder_index.conf')
|
||||
gen = self.get_generator('')
|
||||
full = ''
|
||||
for line in gen:
|
||||
full += line
|
||||
|
||||
full_lines = full.split('\n')
|
||||
self.assertEqual('<!DOCTYPE html>', full_lines[0])
|
||||
self.assertIn('samples/</title>', full_lines[3])
|
||||
self.assertEqual(
|
||||
' <li><a href="/samples/console.html.gz">'
|
||||
'console.html.gz</a></li>',
|
||||
full_lines[9])
|
||||
self.assertEqual(
|
||||
' <li><a href="/samples/wsgi_plain.conf">'
|
||||
'wsgi_plain.conf</a></li>',
|
||||
full_lines[-5])
|
||||
self.assertEqual('</html>', full_lines[-1])
|
||||
|
||||
|
||||
class TestWsgiSwift(TestWsgiDisk):
|
||||
"""Test loading files from swift."""
|
||||
def setUp(self):
|
||||
class fake_swiftclient(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def get_object(self, container, name, resp_chunk_size=None):
|
||||
name = name[len('non-existent'):]
|
||||
if resp_chunk_size:
|
||||
|
||||
def _object_body():
|
||||
with open(base.samples_path('samples') + name) as f:
|
||||
|
||||
buf = f.read(resp_chunk_size)
|
||||
while buf:
|
||||
yield buf
|
||||
buf = f.read(resp_chunk_size)
|
||||
|
||||
object_body = _object_body()
|
||||
else:
|
||||
with open(base.samples_path('samples') + name) as f:
|
||||
object_body = f.read()
|
||||
resp_headers = os_loganalyze.util.get_headers_for_file(
|
||||
base.samples_path('samples') + name)
|
||||
return resp_headers, object_body
|
||||
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'swiftclient.client.Connection', fake_swiftclient))
|
||||
super(TestWsgiSwift, self).setUp()
|
||||
|
||||
# Set the samples directory to somewhere non-existent so that swift
|
||||
# is checked for files
|
||||
self.samples_directory = 'non-existent'
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_compare_disk_to_swift_html(self):
|
||||
"""Compare loading logs from disk vs swift."""
|
||||
# Load from disk
|
||||
|
@ -270,6 +338,8 @@ class TestWsgiSwift(TestWsgiDisk):
|
|||
|
||||
self.assertEqual(result_disk, result_swift)
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_compare_disk_to_swift_plain(self):
|
||||
"""Compare loading logs from disk vs swift."""
|
||||
# Load from disk
|
||||
|
@ -287,6 +357,8 @@ class TestWsgiSwift(TestWsgiDisk):
|
|||
|
||||
self.assertEqual(result_disk, result_swift)
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_skip_file(self):
|
||||
# this should generate a TypeError because we're telling it to
|
||||
# skip the filesystem, but we don't have a working swift here.
|
||||
|
@ -294,6 +366,8 @@ class TestWsgiSwift(TestWsgiDisk):
|
|||
TypeError,
|
||||
self.get_generator('screen-c-api.txt.gz', source='swift'))
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_compare_disk_to_swift_no_compression(self):
|
||||
"""Compare loading logs from disk vs swift."""
|
||||
# Load from disk
|
||||
|
@ -311,9 +385,72 @@ class TestWsgiSwift(TestWsgiDisk):
|
|||
|
||||
self.assertEqual(result_disk, result_swift)
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
def test_compare_disk_to_swift_no_chunks(self):
|
||||
self.wsgi_config_file = (base.samples_path('samples') +
|
||||
'wsgi_no_chunks.conf')
|
||||
self.test_compare_disk_to_swift_no_compression()
|
||||
self.test_compare_disk_to_swift_plain()
|
||||
self.test_compare_disk_to_swift_html()
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_object',
|
||||
fake_get_object)
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_container',
|
||||
fake_get_container_factory())
|
||||
def test_folder_index(self):
|
||||
self.wsgi_config_file = (base.samples_path('samples') +
|
||||
'wsgi_folder_index.conf')
|
||||
gen = self.get_generator('')
|
||||
full = ''
|
||||
for line in gen:
|
||||
full += line
|
||||
|
||||
full_lines = full.split('\n')
|
||||
self.assertEqual('<!DOCTYPE html>', full_lines[0])
|
||||
self.assertIn('non-existent/</title>', full_lines[3])
|
||||
self.assertEqual(
|
||||
' <li><a href="/non-existent/console.html.gz">'
|
||||
'console.html.gz</a></li>',
|
||||
full_lines[9])
|
||||
self.assertEqual(
|
||||
' <li><a href="/non-existent/wsgi_plain.conf">'
|
||||
'wsgi_plain.conf</a></li>',
|
||||
full_lines[-5])
|
||||
self.assertEqual('</html>', full_lines[-1])
|
||||
|
||||
@mock.patch.object(swiftclient.client.Connection, 'get_container',
|
||||
fake_get_container_factory(['a', 'b', 'dir/', 'z']))
|
||||
def test_folder_index_dual(self):
|
||||
# Test an index is correctly generated where files may exist on disk as
|
||||
# well as in swift.
|
||||
self.samples_directory = 'samples'
|
||||
self.wsgi_config_file = (base.samples_path('samples') +
|
||||
'wsgi_folder_index.conf')
|
||||
|
||||
gen = self.get_generator('')
|
||||
full = ''
|
||||
for line in gen:
|
||||
full += line
|
||||
|
||||
full_lines = full.split('\n')
|
||||
self.assertEqual('<!DOCTYPE html>', full_lines[0])
|
||||
self.assertIn('samples/</title>', full_lines[3])
|
||||
self.assertEqual(' <li><a href="/samples/a">a</a></li>',
|
||||
full_lines[9])
|
||||
self.assertEqual(' <li><a href="/samples/b">b</a></li>',
|
||||
full_lines[11])
|
||||
self.assertEqual(
|
||||
' <li><a href="/samples/console.html.gz">'
|
||||
'console.html.gz</a></li>',
|
||||
full_lines[13])
|
||||
self.assertEqual(
|
||||
' <li><a href="/samples/dir/">dir/</a></li>',
|
||||
full_lines[17])
|
||||
self.assertEqual(
|
||||
' <li><a href="/samples/wsgi_plain.conf">'
|
||||
'wsgi_plain.conf</a></li>',
|
||||
full_lines[-7])
|
||||
self.assertEqual(' <li><a href="/samples/z">z</a></li>',
|
||||
full_lines[-5])
|
||||
self.assertEqual('</html>', full_lines[-1])
|
||||
|
|
|
@ -17,6 +17,7 @@ import cgi
|
|||
import collections
|
||||
import re
|
||||
|
||||
import os_loganalyze.generator as generator
|
||||
import os_loganalyze.util as util
|
||||
|
||||
HTML_HEADER = """<html>
|
||||
|
@ -117,13 +118,13 @@ NO_ESCAPE_FINISH = re.compile("</pre>")
|
|||
|
||||
|
||||
class HTMLView(collections.Iterable):
|
||||
headers = [('Content-type', 'text/html')]
|
||||
should_escape = True
|
||||
sent_header = False
|
||||
is_html = False
|
||||
no_escape_count = 0
|
||||
|
||||
def __init__(self, filter_generator):
|
||||
self.headers = [('Content-type', 'text/html')]
|
||||
self.filter_generator = filter_generator
|
||||
|
||||
def _discover_html(self, line):
|
||||
|
@ -188,9 +189,8 @@ class HTMLView(collections.Iterable):
|
|||
|
||||
|
||||
class TextView(collections.Iterable):
|
||||
headers = [('Content-type', 'text/plain')]
|
||||
|
||||
def __init__(self, filter_generator):
|
||||
self.headers = [('Content-type', 'text/plain')]
|
||||
self.filter_generator = filter_generator
|
||||
|
||||
def __iter__(self):
|
||||
|
@ -199,9 +199,8 @@ class TextView(collections.Iterable):
|
|||
|
||||
|
||||
class PassthroughView(collections.Iterable):
|
||||
headers = []
|
||||
|
||||
def __init__(self, filter_generator):
|
||||
self.headers = []
|
||||
self.filter_generator = filter_generator
|
||||
for k, v in self.filter_generator.file_generator.file_headers.items():
|
||||
self.headers.append((k, v))
|
||||
|
@ -213,9 +212,16 @@ class PassthroughView(collections.Iterable):
|
|||
|
||||
def get_view_generator(filter_generator, environ, root_path, config):
|
||||
"""Return the view to use as per the config."""
|
||||
# Check if the generator is an index page. If so, we don't want to apply
|
||||
# any additional formatting
|
||||
if isinstance(filter_generator.file_generator,
|
||||
generator.IndexIterableBuffer):
|
||||
return PassthroughView(filter_generator)
|
||||
|
||||
# Determine if html is supported by the client if yes then supply html
|
||||
# otherwise fallback to text.
|
||||
supports_html = util.should_be_html(environ)
|
||||
|
||||
# Check file specific conditions first
|
||||
view_selected = util.get_file_conditions('view',
|
||||
filter_generator.file_generator,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pbr>=0.5.21,<1.0
|
||||
Babel>=0.9.6
|
||||
jinja2
|
||||
python-swiftclient>=1.6
|
||||
python-keystoneclient>=0.4.2
|
||||
python-magic
|
||||
|
|
|
@ -3,6 +3,7 @@ hacking>=0.9.2,<0.10
|
|||
coverage>=3.6
|
||||
discover
|
||||
fixtures>=0.3.14
|
||||
mock
|
||||
python-subunit
|
||||
sphinx>=1.1.2
|
||||
oslosphinx
|
||||
|
|
Loading…
Reference in New Issue