[FAF PATCH] provide emergency wsgi application
by Richard Marko
Provides emergency WSGI application
capable of saving reports to disk
to work as a backup in case of server
issues.
Closes: #162 #175
Signed-off-by: Richard Marko <rmarko(a)redhat.com>
---
configure.ac | 1 +
faf.spec.in | 23 ++++++++-
pyfaf/Makefile.am | 2 +-
pyfaf/emergency/Makefile.am | 10 ++++
pyfaf/emergency/__init__.py | 0
pyfaf/emergency/emergency.wsgi | 88 +++++++++++++++++++++++++++++++++++
pyfaf/emergency/faf-emergency.conf.in | 16 +++++++
7 files changed, 137 insertions(+), 3 deletions(-)
create mode 100644 pyfaf/emergency/Makefile.am
create mode 100644 pyfaf/emergency/__init__.py
create mode 100644 pyfaf/emergency/emergency.wsgi
create mode 100644 pyfaf/emergency/faf-emergency.conf.in
diff --git a/configure.ac b/configure.ac
index b0117a9..4e39ba1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -52,6 +52,7 @@ AC_CONFIG_FILES([
tests/utils/Makefile
pyfaf/client/Makefile
pyfaf/client/commands/Makefile
+ pyfaf/emergency/Makefile
pyfaf/hub/Makefile
pyfaf/hub/common/Makefile
pyfaf/hub/problems/Makefile
diff --git a/faf.spec.in b/faf.spec.in
index 6effe3e..5ffa84c 100644
--- a/faf.spec.in
+++ b/faf.spec.in
@@ -93,6 +93,20 @@ Requires(postun): systemd-units httpd
%description hub
Kobo hub for %{name} tasks
+%package emergency
+Summary: Emergency WSGI application
+Group: System Environment/Libraries
+Requires: %{name} = %{version}
+Requires: httpd
+Requires: mod_wsgi
+BuildArch: noarch
+
+%description emergency
+Provides emergency WSGI application
+capable of saving reports to disk
+to work as a backup in case of server
+issues.
+
%package worker
Summary: Kobo worker for %{name} tasks
Group: System Environment/Libraries
@@ -145,9 +159,10 @@ mkdir -p %{buildroot}%{_localstatedir}/log/faf/build
mkdir -p %{buildroot}%{_datadir}/faf/hub/static
# On Fedora, the Python WSGI module is loaded by wsgi.conf in the
# conf.d HTTPD configuration directory. faf-hub.conf requires WSGI to
-# be already active. Here we rename the faf-hub.conf to be loaded
-# after wsgi.conf and not before.
+# be already active. Here we rename the faf-hub.conf and
+# faf-emergency.conf to be loaded after wsgi.conf and not before.
mv %{buildroot}%{_sysconfdir}/httpd/conf.d/{,wsgi-}faf-hub.conf
+mv %{buildroot}%{_sysconfdir}/httpd/conf.d/{,wsgi-}faf-emergency.conf
%check
@@ -297,6 +312,10 @@ fi
%dir %attr(0770, faf, faf) %{_localstatedir}/spool/faf/hub/upload
%{_datadir}/faf/hub
+%files emergency
+%config(noreplace) %{_sysconfdir}/httpd/conf.d/wsgi-faf-emergency.conf
+%{python_sitelib}/pyfaf/emergency
+
%files worker
%config(noreplace) %{_sysconfdir}/faf/worker.conf
%{python_sitelib}/pyfaf/worker
diff --git a/pyfaf/Makefile.am b/pyfaf/Makefile.am
index 533ccd4..c97b1a8 100644
--- a/pyfaf/Makefile.am
+++ b/pyfaf/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = client hub worker storage
+SUBDIRS = client emergency hub worker storage
pyfaf_PYTHON = \
__init__.py \
diff --git a/pyfaf/emergency/Makefile.am b/pyfaf/emergency/Makefile.am
new file mode 100644
index 0000000..26bfc90
--- /dev/null
+++ b/pyfaf/emergency/Makefile.am
@@ -0,0 +1,10 @@
+emergency_PYTHON = emergency.wsgi __init__.py
+emergencydir = $(pythondir)/pyfaf/emergency
+
+httpdconf_DATA = faf-emergency.conf
+httpdconfdir = ${sysconfdir}/httpd/conf.d
+
+EXTRA_DIST = faf-emergency.conf.in
+
+faf-emergency.conf: faf-emergency.conf.in
+ sed -e "s|@PYTHONDIR@|$(pythondir)|g" $< > $@
diff --git a/pyfaf/emergency/__init__.py b/pyfaf/emergency/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pyfaf/emergency/emergency.wsgi b/pyfaf/emergency/emergency.wsgi
new file mode 100644
index 0000000..205d796
--- /dev/null
+++ b/pyfaf/emergency/emergency.wsgi
@@ -0,0 +1,88 @@
+#! /usr/bin/env python
+
+import os
+import cgi
+import json
+import uuid
+import logging
+
+from wsgiref.simple_server import make_server
+
+import pyfaf
+
+
+def application(environ, start_response):
+ """
+ WSGI application capable of saving incoming uReports to
+ directory, to be used as a backup handler.
+ """
+
+ def is_post_request(environ):
+ """
+ Return True if this request is valid POST request
+ with correct content type
+ """
+
+ if environ["REQUEST_METHOD"].upper() != "POST":
+ return False
+
+ content_type = environ.get("CONTENT_TYPE", "application/x-www-form-urlencoded")
+ return content_type.startswith("multipart/form-data")
+
+ def bad_request():
+ """
+ Respond with HTTP 400 Bad Reqeust.
+ """
+
+ start_response("400 BAD REQUEST",
+ [("Content-Type", "application/json")])
+ return [""]
+
+ def method_not_allowed():
+ """
+ Respond with HTTP 405 Method Not Allowed.
+ """
+
+ start_response("405 METHOD NOT ALLOWED",
+ [("Content-Type", "application/json"),
+ ("Allow", "POST")])
+ return [""]
+
+ spool_dir = pyfaf.config.get("Report.SpoolDirectory")
+
+ if not is_post_request(environ):
+ logging.debug('Not a POST request or content type'
+ ' is not mulitpart/form-data.')
+
+ return method_not_allowed()
+
+ inp = environ["wsgi.input"]
+ fs = cgi.FieldStorage(fp=inp, environ=environ)
+
+ try:
+ fs = fs.list.pop()
+ ureport = fs.file.read()
+ except (IndexError, AttributeError):
+ logging.debug('Unable to parse input.')
+ return bad_request()
+
+ fname = str(uuid.uuid4())
+ with open(os.path.join(spool_dir, "incoming", fname), "w") as fil:
+ fil.write(ureport)
+ logging.info('Report saved as {0}'.format(fname))
+
+ status = "202 ACCEPTED"
+ response_body = json.dumps({"result": False})
+ response_headers = [("Content-Type", "application/json"),
+ ("Content-Length", str(len(response_body)))]
+ start_response(status, response_headers)
+
+ return [response_body]
+
+if __name__ == "__main__":
+ # possible to run as standalone server,
+ # mainly for debugging purposes
+
+ logging.basicConfig(level=logging.DEBUG)
+ server = make_server("0.0.0.0", 8000, application)
+ server.serve_forever()
diff --git a/pyfaf/emergency/faf-emergency.conf.in b/pyfaf/emergency/faf-emergency.conf.in
new file mode 100644
index 0000000..1a14c15
--- /dev/null
+++ b/pyfaf/emergency/faf-emergency.conf.in
@@ -0,0 +1,16 @@
+#WSGISocketPrefix /var/spool/faf/wsgi
+#WSGIDaemonProcess faf user=faf group=faf processes=1 threads=5
+#WSGIProcessGroup faf
+#WSGIScriptAlias /faf @PYTHONDIR(a)/pyfaf/emergency/emergency.wsgi
+
+#<Directory "@PYTHONDIR@/pyfaf/emergency/">
+# <IfModule mod_authz_core.c>
+# # Apache 2.4
+# Require all granted
+# </IfModule>
+# <IfModule !mod_authz_core.c>
+# # Apache 2.2
+# Order allow,deny
+# Allow from all
+# </IfModule>
+#</Directory>
--
1.8.1.4
11 years, 2 months
[FAF PATCH 0/3] bugzilla
by Richard Marko
Patchset adding update only functionality, pep8
and coding style compliance.
Richard Marko (3):
bugzilla: add update-only functionality
bugzilla: fix log messages
bugzilla: pep8 compliance & coding style
bin/faf-bugzilla-pull-bugs | 27 +-
pyfaf/bugzilla.py | 639 ++++++++++++++++++++++++---------------------
2 files changed, 356 insertions(+), 310 deletions(-)
--
1.8.1.4
11 years, 2 months
[FAF PATCH] hub: make graph points on summary graph clickable
by Richard Marko
Closes #9.
Signed-off-by: Richard Marko <rmarko(a)redhat.com>
---
pyfaf/hub/media/report_graph.js | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/pyfaf/hub/media/report_graph.js b/pyfaf/hub/media/report_graph.js
index f5fa3c2..787e197 100644
--- a/pyfaf/hub/media/report_graph.js
+++ b/pyfaf/hub/media/report_graph.js
@@ -5,6 +5,10 @@ function showTooltip(x, y, contents) {
}).appendTo("body").fadeIn(200);
}
+function slugify(input_string) {
+ return input_string.replace(/\s+/g,'-').replace(/[^a-zA-Z0-9\-]/g,'').toLowerCase()
+}
+
function plotReportGraph(data, tick_unit) {
var msday = 86400000; // one day in ms
var temp_data = data[0].data;
@@ -92,4 +96,25 @@ function plotReportGraph(data, tick_unit) {
previousPoint = null;
}
});
+ $("#placeholder").bind("plotclick", function (event, pos, item) {
+ if(!item) return;
+
+ var today_stats_url = $('a[href="/stats/today/"]').attr('href');
+
+ // clicked on todays point which has no label
+ // -> redirect to todays stats
+ if(!item.series.label) {
+ window.location = today_stats_url;
+
+ } else {
+ var date = new Date(item.datapoint[0]);
+ var datestr = ('date'
+ + '/' + date.getFullYear()
+ + '/' + (date.getMonth()+1)
+ + '/' + date.getDate()
+ + '/#' + slugify(item.series.label));
+
+ window.location = today_stats_url.replace('today/', datestr);
+ }
+ });
}
--
1.8.1.4
11 years, 2 months
[FAF PATCH v2 1/2] bugzilla: pick report with highest count
by Richard Marko
When creating new ticket make sure we pick
report with highest count so we don't end up
linking to unrelated report if clustering
splits the problem.
Closes #172.
Signed-off-by: Richard Marko <rmarko(a)redhat.com>
---
pyfaf/bugzilla.py | 2 +-
pyfaf/storage/problem.py | 8 ++++++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/pyfaf/bugzilla.py b/pyfaf/bugzilla.py
index f135655..a8db6ec 100644
--- a/pyfaf/bugzilla.py
+++ b/pyfaf/bugzilla.py
@@ -713,7 +713,7 @@ class Bugzilla(object):
# pick first and assign this bug to it
data['component'] = components.pop()
- report = problem.reports[0]
+ report = problem.sorted_reports[0]
if not report.backtraces:
logging.warning('Refusing to process report with no backtrace.')
continue
diff --git a/pyfaf/storage/problem.py b/pyfaf/storage/problem.py
index 7242517..e8ee1a5 100644
--- a/pyfaf/storage/problem.py
+++ b/pyfaf/storage/problem.py
@@ -92,3 +92,11 @@ class Problem(GenericTable):
sorted by quality.
'''
return sorted(self.backtraces, key=lambda bt: bt.quality, reverse=True)
+
+ @property
+ def sorted_reports(self):
+ """
+ Return list of all reports sorted by report count.
+ """
+
+ return sorted(self.reports, key=lambda report: report.count, reverse=True)
--
1.8.1.4
11 years, 2 months