File: cron.py

package info (click to toggle)
zeekctl 2.2.0%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 2,544 kB
  • sloc: python: 5,639; sh: 1,374; makefile: 71; awk: 24
file content (250 lines) | stat: -rw-r--r-- 8,997 bytes parent folder | download
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# Tasks which are to be done on a regular basis from cron.
from __future__ import print_function
import os
import time
import shutil

from ZeekControl import execute
from ZeekControl import py3zeek
from ZeekControl import node as node_mod

class CronUI:
    def __init__(self):
        self.buffer = None

    def info(self, txt):
        if self.buffer:
            self.buffer.write("%s\n" % txt)
        else:
            print(txt)
    error = info
    warn = info

    def buffer_output(self):
        self.buffer = py3zeek.io.StringIO()

    def get_buffered_output(self):
        buf = self.buffer.getvalue()
        self.buffer.close()
        self.buffer = None
        return buf


class CronTasks:
    def __init__(self, ui, config, controller, executor, pluginregistry):
        self.ui = ui
        self.config = config
        self.controller = controller
        self.executor = executor
        self.pluginregistry = pluginregistry

    def log_stats(self, interval):
        if not self.config.statslogenable:
            return

        nodes = self.config.nodes()
        top = self.controller.get_top_output(nodes)

        have_capstats = self.config.capstatspath
        capstats = []

        if have_capstats:
            capstats = self.controller.get_capstats_output(nodes, interval)

        t = time.time()

        try:
            with open(self.config.statslog, "a") as out:
                for (node, error, vals) in top:
                    if not error:
                        for (val, key) in sorted(vals.items()):
                            out.write("%s %s parent %s %s\n" % (t, node, val, key))
                    else:
                        out.write("%s %s error error %s\n" % (t, node, error))

                for (node, netif, success, vals) in capstats:
                    if not success:
                        out.write("%s %s error error %s\n" % (t, node, vals))
                        continue

                    for (key, val) in sorted(vals.items()):
                        out.write("%s %s interface %s %s\n" % (t, node, key, val))

                        if key == "pkts" and str(node) != "$total":
                            # Report if we don't see packets on an interface.
                            tag = "lastpkts-%s" % node.name

                            last = self.config.get_state(tag, default=-1.0)

                            if self.config.mailreceivingpackets:
                                if val == 0.0 and last != 0.0:
                                    self.ui.info("%s is not seeing any packets on interface %s" % (node.host, netif))

                                if val != 0.0 and last == 0.0:
                                    self.ui.info("%s is seeing packets again on interface %s" % (node.host, netif))

                            self.config.set_state(tag, val)

        except IOError as err:
            self.ui.error("failed to append to file: %s" % err)
            return

    def check_disk_space(self):
        minspace = self.config.mindiskspace
        if minspace == 0:
            return

        results = self.controller.df(self.config.hosts())
        for (node, _, dfs) in results.get_node_data():
            host = node.host

            for key, df in dfs.items():
                if key == "FAIL":
                    # A failure here is normally caused by a host that is down,
                    # so we don't need to output the error message.
                    continue

                fs = df.fs
                perc = df.percent
                key = ("disk-space-%s%s" % (host, fs.replace("/", "-")))

                if perc > 100 - minspace:
                    last = self.config.get_state(key, default=-1)
                    if last > 100 - minspace:
                        # Already reported.
                        continue

                    self.ui.warn("Disk space low on %s:%s - %.1f%% used." % (host, fs, perc))

                self.config.set_state(key, perc)

    def expire_logs(self):
        if self.config.logexpireminutes == 0 and self.config.statslogexpireinterval == 0:
            return

        if self.config.standalone:
            success, output = execute.run_localcmd(os.path.join(self.config.scriptsdir, "expire-logs"))

            if not success:
                self.ui.error("expire-logs failed\n%s" % output)
        else:
            nodes = self.config.hosts(tag=node_mod.logger_group())

            if not nodes:
                nodes = self.config.hosts(tag=node_mod.manager_group())

            expirelogs = os.path.join(self.config.scriptsdir, "expire-logs")
            cmds = [(node, expirelogs, []) for node in nodes]

            for (node, success, output) in self.executor.run_cmds(cmds):
                if not success:
                    self.ui.error("expire-logs failed for node %s\n" % node)
                    if output:
                        self.ui.error(output)


    def expire_crash(self):
        if self.config.crashexpireinterval == 0:
            return

        expirecrash = os.path.join(self.config.scriptsdir, "expire-crash")
        cmds = [(node, expirecrash, []) for node in self.config.hosts()]

        for (node, success, output) in self.executor.run_cmds(cmds):
            if not success:
                self.ui.error("expire-crash failed for node %s\n" % node)
                if output:
                    self.ui.error(output)

    def check_hosts(self):
        for host, status in self.executor.host_status():
            tag = "alive-%s" % host
            alive = status

            previous = self.config.get_state(tag)
            if previous is not None:
                if alive != previous:
                    self.pluginregistry.hostStatusChanged(host, alive)
                    if self.config.mailhostupdown:
                        up_or_down = "up" if alive else "down"
                        self.ui.info("host %s %s" % (host, up_or_down))

            self.config.set_state(tag, alive)

    def update_http_stats(self):
        if not self.config.statslogenable:
            return

        # Create meta file.
        if not os.path.exists(self.config.statsdir):
            try:
                os.makedirs(self.config.statsdir)
            except OSError as err:
                self.ui.error("failure creating directory in zeekctl option statsdir: %s" % err)
                return

            self.ui.info("creating directory for stats file: %s" % self.config.statsdir)

        metadat = os.path.join(self.config.statsdir, "meta.dat")
        try:
            with open(metadat, "w") as meta:
                for node in self.config.hosts():
                    meta.write("node %s %s %s\n" % (node, node.type, node.host))

                meta.write("time %s\n" % time.asctime())
                meta.write("version %s\n" % self.config.version)

                success, output = execute.run_localcmd("uname -a")
                if success and output:
                    # Note: "output" already has a '\n'
                    meta.write("os %s" % output)
                else:
                    meta.write("os <error>\n")

                success, output = execute.run_localcmd("hostname")
                if success and output:
                    # Note: "output" already has a '\n'
                    meta.write("host %s" % output)
                else:
                    meta.write("host <error>\n")

        except IOError as err:
            self.ui.error("failure creating file: %s" % err)
            return

        wwwdir = os.path.join(self.config.statsdir, "www")
        if not os.path.isdir(wwwdir):
            try:
                os.makedirs(wwwdir)
            except OSError as err:
                self.ui.error("failed to create directory: %s" % err)
                return

        # Update the WWW data
        statstocsv = os.path.join(self.config.scriptsdir, "stats-to-csv")

        success, output = execute.run_localcmd("%s %s %s %s" % (statstocsv, self.config.statslog, metadat, wwwdir))
        if success:
            shutil.copy(metadat, wwwdir)
        else:
            self.ui.error("error reported by stats-to-csv\n%s" % output)

        # Append the current stats.log in spool to the one in ${statsdir}
        dst = os.path.join(self.config.statsdir, os.path.basename(self.config.statslog))
        try:
            with open(self.config.statslog, "r") as fsrc:
                with open(dst, "a") as fdst:
                    shutil.copyfileobj(fsrc, fdst)
        except IOError as err:
            self.ui.error("failed to append file: %s" % err)
            return

        os.unlink(self.config.statslog)


    def run_cron_cmd(self):
        # Run external command if we have one.
        if self.config.croncmd:
            success, output = execute.run_localcmd(self.config.croncmd)
            if not success:
                self.ui.error("failure running croncmd: %s" % self.config.croncmd)