1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
'''This file manages the statistics collected by the BCFG2 Server'''
__revision__ = '$Revision$'
import binascii
import copy
import difflib
import logging
from lxml.etree import XML, SubElement, Element, XMLSyntaxError
import lxml.etree
import os
import Queue
from time import asctime, localtime, time, strptime, mktime
import threading
import Bcfg2.Server.Plugin
class StatisticsStore(object):
"""Manages the memory and file copy of statistics collected about client runs."""
__min_write_delay__ = 0
def __init__(self, filename):
self.filename = filename
self.element = Element('Dummy')
self.dirty = 0
self.lastwrite = 0
self.logger = logging.getLogger('Bcfg2.Server.Statistics')
self.ReadFromFile()
def WriteBack(self, force=0):
"""Write statistics changes back to persistent store."""
if (self.dirty and (self.lastwrite + self.__min_write_delay__ <= time())) \
or force:
try:
fout = open(self.filename + '.new', 'w')
except IOError, ioerr:
self.logger.error("Failed to open %s for writing: %s" % (self.filename + '.new', ioerr))
else:
fout.write(lxml.etree.tostring(self.element, encoding='UTF-8', xml_declaration=True))
fout.close()
os.rename(self.filename + '.new', self.filename)
self.dirty = 0
self.lastwrite = time()
def ReadFromFile(self):
"""Reads current state regarding statistics."""
try:
fin = open(self.filename, 'r')
data = fin.read()
fin.close()
self.element = XML(data)
self.dirty = 0
except (IOError, XMLSyntaxError):
self.logger.error("Creating new statistics file %s"%(self.filename))
self.element = Element('ConfigStatistics')
self.WriteBack()
self.dirty = 0
def updateStats(self, xml, client):
"""Updates the statistics of a current node with new data."""
# Current policy:
# - Keep anything less than 24 hours old
# - Keep latest clean run for clean nodes
# - Keep latest clean and dirty run for dirty nodes
newstat = xml.find('Statistics')
if newstat.get('state') == 'clean':
node_dirty = 0
else:
node_dirty = 1
# Find correct node entry in stats data
# The following list comprehension should be guarenteed to return at
# most one result
nodes = [elem for elem in self.element.findall('Node') \
if elem.get('name') == client]
nummatch = len(nodes)
if nummatch == 0:
# Create an entry for this node
node = SubElement(self.element, 'Node', name=client)
elif nummatch == 1 and not node_dirty:
# Delete old instance
node = nodes[0]
[node.remove(elem) for elem in node.findall('Statistics') \
if self.isOlderThan24h(elem.get('time'))]
elif nummatch == 1 and node_dirty:
# Delete old dirty statistics entry
node = nodes[0]
[node.remove(elem) for elem in node.findall('Statistics') \
if (elem.get('state') == 'dirty' \
and self.isOlderThan24h(elem.get('time')))]
else:
# Shouldn't be reached
self.logger.error("Duplicate node entry for %s"%(client))
# Set current time for stats
newstat.set('time', asctime(localtime()))
# Add statistic
node.append(copy.deepcopy(newstat))
# Set dirty
self.dirty = 1
self.WriteBack(force=1)
def isOlderThan24h(self, testTime):
"""Helper function to determine if <time> string is older than 24 hours."""
now = time()
utime = mktime(strptime(testTime))
secondsPerDay = 60*60*24
return (now-utime) > secondsPerDay
class Statistics(Bcfg2.Server.Plugin.Plugin,
Bcfg2.Server.Plugin.ThreadedStatistics,
Bcfg2.Server.Plugin.PullSource):
name = 'Statistics'
__version__ = '$Id$'
def __init__(self, core, datastore):
Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
Bcfg2.Server.Plugin.ThreadedStatistics.__init__(self, core, datastore)
Bcfg2.Server.Plugin.PullSource.__init__(self)
fpath = "%s/etc/statistics.xml" % datastore
self.data_file = StatisticsStore(fpath)
def handle_statistic(self, metadata, data):
self.data_file.updateStats(data, metadata.hostname)
def FindCurrent(self, client):
rt = self.data_file.element.xpath('//Node[@name="%s"]' % client)[0]
maxtime = max([strptime(stat.get('time')) for stat \
in rt.findall('Statistics')])
return [stat for stat in rt.findall('Statistics') \
if strptime(stat.get('time')) == maxtime][0]
def GetExtra(self, client):
return [(entry.tag, entry.get('name')) for entry \
in self.FindCurrent(client).xpath('.//Extra/*')]
def GetCurrentEntry(self, client, e_type, e_name):
curr = self.FindCurrent(client)
entry = curr.xpath('.//Bad/%s[@name="%s"]' % (e_type, e_name))
if not entry:
raise Bcfg2.Server.Plugin.PluginExecutionError
cfentry = entry[-1]
owner = cfentry.get('current_owner', cfentry.get('owner'))
group = cfentry.get('current_group', cfentry.get('group'))
perms = cfentry.get('current_perms', cfentry.get('perms'))
if 'current_bfile' in cfentry.attrib:
contents = binascii.a2b_base64(cfentry.get('current_bfile'))
elif 'current_bdiff' in cfentry.attrib:
diff = binascii.a2b_base64(cfentry.get('current_bdiff'))
contents = '\n'.join(difflib.restore(diff.split('\n'), 1))
else:
contents = None
return (owner, group, perms, contents)
|