summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Reporting
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Reporting')
-rw-r--r--src/lib/Bcfg2/Reporting/Collector.py81
-rwxr-xr-xsrc/lib/Bcfg2/Reporting/Reports.py3
-rw-r--r--src/lib/Bcfg2/Reporting/Storage/DjangoORM.py11
-rw-r--r--src/lib/Bcfg2/Reporting/models.py3
-rw-r--r--src/lib/Bcfg2/Reporting/templates/config_items/item.html2
-rw-r--r--src/lib/Bcfg2/Reporting/templatetags/bcfg2_tags.py77
-rwxr-xr-xsrc/lib/Bcfg2/Reporting/utils.py12
7 files changed, 112 insertions, 77 deletions
diff --git a/src/lib/Bcfg2/Reporting/Collector.py b/src/lib/Bcfg2/Reporting/Collector.py
index 12c9cdaa8..153809a35 100644
--- a/src/lib/Bcfg2/Reporting/Collector.py
+++ b/src/lib/Bcfg2/Reporting/Collector.py
@@ -1,3 +1,4 @@
+import os
import sys
import atexit
import daemon
@@ -5,13 +6,12 @@ import logging
import time
import threading
-# pylint: disable=E0611
from lockfile import LockFailed, LockTimeout
+# pylint: disable=E0611
try:
- from lockfile.pidlockfile import PIDLockFile
- from lockfile import Error as PIDFileError
+ from daemon.pidfile import TimeoutPIDLockFile
except ImportError:
- from daemon.pidlockfile import PIDLockFile, PIDFileError
+ from daemon.pidlockfile import TimeoutPIDLockFile
# pylint: enable=E0611
import Bcfg2.Logger
@@ -30,7 +30,7 @@ class ReportingError(Exception):
class ReportingStoreThread(threading.Thread):
"""Thread for calling the storage backend"""
def __init__(self, interaction, storage, group=None, target=None,
- name=None, args=(), kwargs=None):
+ name=None, semaphore=None, args=(), kwargs=None):
"""Initialize the thread with a reference to the interaction
as well as the storage engine to use"""
threading.Thread.__init__(self, group, target, name, args,
@@ -38,26 +38,37 @@ class ReportingStoreThread(threading.Thread):
self.interaction = interaction
self.storage = storage
self.logger = logging.getLogger('bcfg2-report-collector')
+ self.semaphore = semaphore
def run(self):
"""Call the database storage procedure (aka import)"""
try:
- start = time.time()
- self.storage.import_interaction(self.interaction)
- self.logger.info("Imported interaction for %s in %ss" %
- (self.interaction.get('hostname', '<unknown>'),
- time.time() - start))
- except:
- #TODO requeue?
- self.logger.error("Unhandled exception in import thread %s" %
- sys.exc_info()[1])
+ try:
+ start = time.time()
+ self.storage.import_interaction(self.interaction)
+ self.logger.info("Imported interaction for %s in %ss" %
+ (self.interaction.get('hostname',
+ '<unknown>'),
+ time.time() - start))
+ except:
+ #TODO requeue?
+ self.logger.error("Unhandled exception in import thread %s" %
+ sys.exc_info()[1])
+ finally:
+ if self.semaphore:
+ self.semaphore.release()
class ReportingCollector(object):
"""The collecting process for reports"""
options = [Bcfg2.Options.Common.reporting_storage,
Bcfg2.Options.Common.reporting_transport,
- Bcfg2.Options.Common.daemon]
+ Bcfg2.Options.Common.daemon,
+ Bcfg2.Options.Option(
+ '--max-children', dest="children",
+ cf=('reporting', 'max_children'), type=int,
+ default=0,
+ help='Maximum number of children for the reporting collector')]
def __init__(self):
"""Setup the collector. This may be called by the daemon or though
@@ -67,6 +78,11 @@ class ReportingCollector(object):
self.children = []
self.cleanup_threshold = 25
+ self.semaphore = None
+ if Bcfg2.Options.setup.children > 0:
+ self.semaphore = threading.Semaphore(
+ value=Bcfg2.Options.setup.children)
+
if Bcfg2.Options.setup.debug:
level = logging.DEBUG
elif Bcfg2.Options.setup.verbose:
@@ -113,25 +129,31 @@ class ReportingCollector(object):
if Bcfg2.Options.setup.daemon:
self.logger.debug("Daemonizing")
+ self.context.pidfile = TimeoutPIDLockFile(
+ Bcfg2.Options.setup.daemon, acquire_timeout=5)
+ # Attempt to ensure lockfile is able to be created and not stale
try:
- self.context.pidfile = PIDLockFile(Bcfg2.Options.setup.daemon)
- self.context.open()
+ self.context.pidfile.acquire()
except LockFailed:
self.logger.error("Failed to daemonize: %s" %
sys.exc_info()[1])
self.shutdown()
return
except LockTimeout:
- self.logger.error("Failed to daemonize: "
- "Failed to acquire lock on %s" %
- self.setup['daemon'])
- self.shutdown()
- return
- except PIDFileError:
- self.logger.error("Error writing pid file: %s" %
- sys.exc_info()[1])
- self.shutdown()
- return
+ try: # attempt to break the lock
+ os.kill(self.context.pidfile.read_pid(), 0)
+ except (OSError, TypeError): # No process with locked PID
+ self.context.pidfile.break_lock()
+ else:
+ self.logger.error("Failed to daemonize: "
+ "Failed to acquire lock on %s" %
+ Bcfg2.Options.setup.daemon)
+ self.shutdown()
+ return
+ else:
+ self.context.pidfile.release()
+
+ self.context.open()
self.logger.info("Starting daemon")
self.transport.start_monitor(self)
@@ -141,7 +163,10 @@ class ReportingCollector(object):
interaction = self.transport.fetch()
if not interaction:
continue
- store_thread = ReportingStoreThread(interaction, self.storage)
+ if self.semaphore:
+ self.semaphore.acquire()
+ store_thread = ReportingStoreThread(interaction, self.storage,
+ semaphore=self.semaphore)
store_thread.start()
self.children.append(store_thread)
diff --git a/src/lib/Bcfg2/Reporting/Reports.py b/src/lib/Bcfg2/Reporting/Reports.py
index 219d74584..3b9c83433 100755
--- a/src/lib/Bcfg2/Reporting/Reports.py
+++ b/src/lib/Bcfg2/Reporting/Reports.py
@@ -267,10 +267,11 @@ class CLI(Bcfg2.Options.CommandRegistry):
def __init__(self):
Bcfg2.Options.CommandRegistry.__init__(self)
- Bcfg2.Options.register_commands(self.__class__, globals().values())
+ self.register_commands(globals().values())
parser = Bcfg2.Options.get_parser(
description="Query the Bcfg2 reporting subsystem",
components=[self])
+ parser.add_options(self.subcommand_options)
parser.parse()
def run(self):
diff --git a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
index 406216861..96226c424 100644
--- a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
+++ b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
@@ -168,7 +168,7 @@ class DjangoORM(StorageBase):
# TODO - vcs output
act_dict['detail_type'] = PathEntry.DETAIL_UNUSED
if path_type == 'directory' and entry.get('prune', 'false') == 'true':
- unpruned_elist = [e.get('path') for e in entry.findall('Prune')]
+ unpruned_elist = [e.get('name') for e in entry.findall('Prune')]
if unpruned_elist:
act_dict['detail_type'] = PathEntry.DETAIL_PRUNED
act_dict['details'] = "\n".join(unpruned_elist)
@@ -367,10 +367,11 @@ class DjangoORM(StorageBase):
def import_interaction(self, interaction):
"""Import the data into the backend"""
try:
- self._import_interaction(interaction)
- except:
- self.logger.error("Failed to import interaction: %s" %
- traceback.format_exc().splitlines()[-1])
+ try:
+ self._import_interaction(interaction)
+ except:
+ self.logger.error("Failed to import interaction: %s" %
+ traceback.format_exc().splitlines()[-1])
finally:
self.logger.debug("%s: Closing database connection" %
self.__class__.__name__)
diff --git a/src/lib/Bcfg2/Reporting/models.py b/src/lib/Bcfg2/Reporting/models.py
index 2d96990b1..ae6f6731b 100644
--- a/src/lib/Bcfg2/Reporting/models.py
+++ b/src/lib/Bcfg2/Reporting/models.py
@@ -717,9 +717,6 @@ class PathEntry(SuccessEntry):
def has_detail(self):
return self.detail_type != PathEntry.DETAIL_UNUSED
- def is_sensitive(self):
- return self.detail_type == PathEntry.DETAIL_SENSITIVE
-
def is_diff(self):
return self.detail_type == PathEntry.DETAIL_DIFF
diff --git a/src/lib/Bcfg2/Reporting/templates/config_items/item.html b/src/lib/Bcfg2/Reporting/templates/config_items/item.html
index b03d48045..c6e6df020 100644
--- a/src/lib/Bcfg2/Reporting/templates/config_items/item.html
+++ b/src/lib/Bcfg2/Reporting/templates/config_items/item.html
@@ -107,7 +107,7 @@ div.entry_list h3 {
{{ item.details|syntaxhilight }}
</div>
{% else %}
- {{ item.details }}
+ {{ item.details|linebreaks }}
{% endif %}
</div>
{% endif %}
diff --git a/src/lib/Bcfg2/Reporting/templatetags/bcfg2_tags.py b/src/lib/Bcfg2/Reporting/templatetags/bcfg2_tags.py
index 4a93e77e0..09aebc7fd 100644
--- a/src/lib/Bcfg2/Reporting/templatetags/bcfg2_tags.py
+++ b/src/lib/Bcfg2/Reporting/templatetags/bcfg2_tags.py
@@ -111,47 +111,58 @@ def filter_navigator(context):
try:
path = context['request'].META['PATH_INFO']
view, args, kwargs = resolve(path)
+ except (Resolver404, KeyError):
+ return dict()
- # Strip any page limits and numbers
- if 'page_number' in kwargs:
- del kwargs['page_number']
- if 'page_limit' in kwargs:
- del kwargs['page_limit']
-
- # get a query string
- qs = context['request'].GET.urlencode()
- if qs:
- qs = '?' + qs
-
- filters = []
- for filter in filter_list:
- if filter == 'group':
- continue
- if filter in kwargs:
- myargs = kwargs.copy()
- del myargs[filter]
+ # Strip any page limits and numbers
+ if 'page_number' in kwargs:
+ del kwargs['page_number']
+ if 'page_limit' in kwargs:
+ del kwargs['page_limit']
+
+ # get a query string
+ qs = context['request'].GET.urlencode()
+ if qs:
+ qs = '?' + qs
+
+ filters = []
+ for filter in filter_list:
+ if filter == 'group':
+ continue
+ if filter in kwargs:
+ myargs = kwargs.copy()
+ del myargs[filter]
+ try:
filters.append((filter,
reverse(view, args=args, kwargs=myargs) + qs))
- filters.sort(key=lambda x: x[0])
-
- myargs = kwargs.copy()
- selected = True
- if 'group' in myargs:
- del myargs['group']
- selected = False
- groups = [('---',
- reverse(view, args=args, kwargs=myargs) + qs,
- selected)]
- for group in Group.objects.values('name'):
+ except NoReverseMatch:
+ pass
+ filters.sort(key=lambda x: x[0])
+
+ myargs = kwargs.copy()
+ selected = True
+ if 'group' in myargs:
+ del myargs['group']
+ selected = False
+
+ groups = []
+ try:
+ groups.append(('---',
+ reverse(view, args=args, kwargs=myargs) + qs,
+ selected))
+ except NoReverseMatch:
+ pass
+
+ for group in Group.objects.values('name'):
+ try:
myargs['group'] = group['name']
groups.append((group['name'],
reverse(view, args=args, kwargs=myargs) + qs,
group['name'] == kwargs.get('group', '')))
+ except NoReverseMatch:
+ pass
- return {'filters': filters, 'groups': groups}
- except (Resolver404, NoReverseMatch, ValueError, KeyError):
- pass
- return dict()
+ return {'filters': filters, 'groups': groups}
def _subtract_or_na(mdict, x, y):
diff --git a/src/lib/Bcfg2/Reporting/utils.py b/src/lib/Bcfg2/Reporting/utils.py
index 0d394fcd8..694f38824 100755
--- a/src/lib/Bcfg2/Reporting/utils.py
+++ b/src/lib/Bcfg2/Reporting/utils.py
@@ -96,12 +96,12 @@ def filteredUrls(pattern, view, kwargs=None, name=None):
tail = mtail.group(1)
pattern = pattern[:len(pattern) - len(tail)]
for filter in ('/state/(?P<state>\w+)',
- '/group/(?P<group>[\w\-\.]+)',
- '/group/(?P<group>[\w\-\.]+)/(?P<state>[A-Za-z]+)',
- '/server/(?P<server>[\w\-\.]+)',
- '/server/(?P<server>[\w\-\.]+)/(?P<state>[A-Za-z]+)',
- '/server/(?P<server>[\w\-\.]+)/group/(?P<group>[\w\-\.]+)',
- '/server/(?P<server>[\w\-\.]+)/group/(?P<group>[\w\-\.]+)/(?P<state>[A-Za-z]+)'):
+ '/group/(?P<group>[^/]+)',
+ '/group/(?P<group>[^/]+)/(?P<state>[A-Za-z]+)',
+ '/server/(?P<server>[^/]+)',
+ '/server/(?P<server>[^/]+)/(?P<state>[A-Za-z]+)',
+ '/server/(?P<server>[^/]+)/group/(?P<group>[^/]+)',
+ '/server/(?P<server>[^/]+)/group/(?P<group>[^/]+)/(?P<state>[A-Za-z]+)'):
results += [(pattern + filter + tail, view, kwargs)]
return results