summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/Reporting.py
blob: 1ea6637a81ce962e48545891eaf054fe6bb0f9a0 (plain)
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
""" Unified statistics and reporting plugin """

import time
import platform
import traceback
from lxml import etree
from Bcfg2.Reporting.Transport import load_transport_from_config, \
    TransportError
from Bcfg2.Compat import cPickle
from Bcfg2.Options import REPORTING_COMMON_OPTIONS
from Bcfg2.Server.Plugin import Statistics, PullSource, PluginInitError, \
    PluginExecutionError

# required for reporting
try:
    import south  # pylint: disable=W0611
    HAS_SOUTH = True
except ImportError:
    HAS_SOUTH = False


def _rpc_call(method):
    """ Given the name of a Reporting Transport method, get a function
    that defers an XML-RPC call to that method """
    def _real_rpc_call(self, *args, **kwargs):
        """Wrapper for calls to the reporting collector"""
        try:
            return self.transport.rpc(method, *args, **kwargs)
        except TransportError:
            # this is needed for Admin.Pull
            raise PluginExecutionError
    return _real_rpc_call


class Reporting(Statistics, PullSource):  # pylint: disable=W0223
    """ Unified statistics and reporting plugin """
    __rmi__ = ['Ping', 'GetExtra', 'GetCurrentEntry']

    CLIENT_METADATA_FILEDS = ('profile', 'bundles', 'aliases', 'addresses',
                              'groups', 'categories', 'uuid', 'version')

    def __init__(self, core, datastore):
        Statistics.__init__(self, core, datastore)
        PullSource.__init__(self)
        self.core = core
        self.experimental = True

        self.whoami = platform.node()
        self.transport = None

        core.setup.update(REPORTING_COMMON_OPTIONS)
        core.setup.reparse()

        if not HAS_SOUTH:
            msg = "Django south is required for Reporting"
            self.logger.error(msg)
            raise PluginInitError(msg)

        try:
            self.transport = load_transport_from_config(core.setup)
        except TransportError:
            self.logger.error("%s: Failed to load transport: %s" %
                (self.name, traceback.format_exc().splitlines()[-1]))
            raise PluginInitError

    def process_statistics(self, client, xdata):
        stats = xdata.find("Statistics")
        stats.set('time', time.asctime(time.localtime()))

        cdata = {'server': self.whoami}
        for field in self.CLIENT_METADATA_FILEDS:
            try:
                value = getattr(client, field)
            except AttributeError:
                continue
            if value:
                if isinstance(value, set):
                    value = [v for v in value]
                cdata[field] = value

        try:
            interaction_data = cPickle.dumps(dict(
                    hostname=client.hostname,
                    metadata=cdata,
                    stats=etree.tostring(
                        stats,
                        xml_declaration=False).decode('UTF-8')))
        except:  # pylint: disable=W0702
            self.logger.error("%s: Failed to build interaction object: %s" %
                (self.__class__.__name__,
                 traceback.format_exc().splitlines()[-1]))

        # try 3 times to store the data
        for i in [1, 2, 3]:
            try:
                self.transport.store(client.hostname, interaction_data)
                self.logger.debug("%s: Queued statistics data for %s" %
                    (self.__class__.__name__, client.hostname))
                return
            except TransportError:
                continue
            except:
                self.logger.error("%s: Attempt %s: Failed to add statistic %s"
                                  % (self.__class__.__name__, i,
                                     traceback.format_exc().splitlines()[-1]))
        self.logger.error("%s: Retry limit reached for %s" %
                    (self.__class__.__name__, client.hostname))

    def shutdown(self):
        super(Reporting, self).shutdown()
        if self.transport:
            self.transport.shutdown()

    Ping = _rpc_call('Ping')
    GetExtra = _rpc_call('GetExtra')
    GetCurrentEntry = _rpc_call('GetCurrentEntry')