Monitor helper through UDP packages in xen
[snf-image] / snf-image-host / helper-monitor.py
1 #!/usr/bin/env python
2
3 # Copyright (C) 2012 GRNET S.A.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 # 02110-1301, USA.
19
20 import sys
21 import os
22 import time
23 import json
24 import re
25 import optparse
26 import socket
27 from scapy.all import sniff
28
29 LINESIZE = 512
30 BUFSIZE = 512
31 PROGNAME = os.path.basename(sys.argv[0])
32 STDERR_MAXLINES = 10
33 MAXLINES = 100
34 MSG_TYPE = 'image-helper'
35
36 PROTOCOL = {
37     'TASK_START': ('task-start', 'task'),
38     'TASK_END': ('task-end', 'task'),
39     'WARNING': ('warning', 'messages'),
40     'STDERR': ('error', 'stderr'),
41     'ERROR': ('error', 'messages')}
42
43
44 def parse_options(input_args):
45     usage = "Usage: %prog [options] <file-sescriptor>"
46     parser = optparse.OptionParser(usage=usage)
47
48     parser.add_option("-i", "--interface", type="string", dest="ifname",
49                       default=None, metavar="IFNAME",
50                       help="listen on interface IFNAME for monitoring data")
51
52     parser.add_option("-f", "--filter", type="string", dest="filter",
53         help="add FILTER to incomint traffice when working on an interface",
54         default=None, metavar="FILTER")
55
56     options, args = parser.parse_args(input_args)
57
58     if len(args) != 1:
59         parser.error('Wrong number of argumets')
60
61     options.fd = args[0]
62
63     if options.filter is not None and options.ifname is None:
64         parser.error('You need to define an interface since filters are' \
65                      'defined')
66
67     return options
68
69
70 def error(msg):
71     sys.stderr.write("HELPER-MONITOR ERROR: %s\n" % msg)
72     sys.exit(1)
73
74
75 class HelperMonitor(object):
76     def __init__(self, fd):
77         self.fd = fd
78         self.lines_left = 0
79         self.line_count = 0
80         self.stderr = ""
81         self.line = ""
82
83     def process(self, data):
84         if not data:
85             if not self.line:
86                 return
87             else:
88                 data = '\n'
89
90         while True:
91             split = data.split('\n', 1)
92             self.line += split[0]
93             if len(split) == 1:
94                 if len(self.line) > LINESIZE:
95                     error("Line size exceeded the maximum allowed size")
96                 break
97
98             data = split[1]
99
100             self.line_count += 1
101             if self.line_count >= MAXLINES + 1:
102                 error("Exceeded maximum allowed number of lines: %d." %
103                       MAXLINES)
104
105             if self.lines_left > 0:
106                 self.stderr += "%s\n" % line
107                 self.lines_left -= 1
108                 if self.lines_left == 0:
109                     self.send("STDERR", stderr)
110                     self.stderr = ""
111                 self.line = ""
112                 continue
113
114             self.line = self.line.strip()
115             if len(self.line) == 0:
116                 continue
117
118             if self.line.startswith("STDERR:"):
119                 m = re.match("STDERR:(\d+):(.*)", line)
120                 if not m:
121                     error("Invalid syntax for STDERR line")
122                 try:
123                     self.lines_left = int(m.group(1))
124                 except ValueError:
125                     error("Second field in STDERR line must be an integer")
126
127                 if self.lines_left > STDERR_MAXLINES:
128                     error("Too many lines in the STDERR output")
129                 elif self.lines_left < 0:
130                     error("Second field of STDERR: %d is invalid" % lines_left)
131
132                 if self.lines_left > 0:
133                     self.stderr = m.group(2) + "\n"
134                     self.lines_left -= 1
135
136                 if self.lines_left == 0:
137                     self.send("STDERR", stderr)
138                     self.stderr = ""
139             elif self.line.startswith("TASK_START:") \
140                 or self.line.startswith("TASK_END:") \
141                 or self.line.startswith("WARNING:") \
142                 or self.line.startswith("ERROR:"):
143                 (msg_type, _, value) = self.line.partition(':')
144
145                 if self.line.startswith("WARNING:") or \
146                     self.line.startswith("ERROR:"):
147                     value = [value]
148                 self.send(msg_type, value)
149             else:
150                 error("Unknown command!")
151
152             # Remove the processed line
153             self.line = ""
154
155     def send(self, msg_type, value):
156         subtype, value_name = PROTOCOL[msg_type]
157
158         msg = {}
159         msg['type'] = MSG_TYPE
160         msg['subtype'] = subtype
161         msg[value_name] = value
162         msg['timestamp'] = time.time()
163         os.write(self.fd, "%s\n" % json.dumps(msg))
164
165
166 if __name__ == "__main__":
167     options = parse_options(sys.argv[1:])
168
169     try:
170         fd = int(options.fd)
171     except ValueError:
172         error("File descriptor is not an integer")
173
174     try:
175         os.fstat(fd)
176     except OSError:
177         error("File descriptor is not valid")
178
179     monitor = HelperMonitor(fd)
180
181     if options.ifname is not None:
182         try:
183             sniff(filter=options.filter, iface=options.ifname,
184                 prn=lambda x: monitor.process(x.payload.getfieldval("load")))
185         except socket.error as e:
186             # Network is down
187             if e.errno == 100:
188                 monitor.process(None)
189             else:
190                 raise
191     else:
192         for data in os.read(sys.stdin.fileno(), BUFSIZE):
193             monitor.process(data)
194
195 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :