Check in MountImage task if /etc/fstab is present
[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(
53         "-f", "--filter", type="string", dest="filter",
54         help="add FILTER to incomint traffice when working on an interface",
55         default=None, metavar="FILTER")
56
57     options, args = parser.parse_args(input_args)
58
59     if len(args) != 1:
60         parser.error('Wrong number of argumets')
61
62     options.fd = args[0]
63
64     if options.filter is not None and options.ifname is None:
65         parser.error('You need to define an interface since filters are'
66                      'defined')
67
68     return options
69
70
71 def error(msg):
72     sys.stderr.write("HELPER-MONITOR ERROR: %s\n" % msg)
73     sys.exit(1)
74
75
76 class HelperMonitor(object):
77     def __init__(self, fd):
78         self.fd = fd
79         self.lines_left = 0
80         self.line_count = 0
81         self.stderr = ""
82         self.line = ""
83
84     def process(self, data):
85         if not data:
86             if not self.line:
87                 return
88             else:
89                 data = '\n'
90
91         while True:
92             split = data.split('\n', 1)
93             self.line += split[0]
94             if len(split) == 1:
95                 if len(self.line) > LINESIZE:
96                     error("Line size exceeded the maximum allowed size")
97                 break
98
99             data = split[1]
100
101             self.line_count += 1
102             if self.line_count >= MAXLINES + 1:
103                 error("Exceeded maximum allowed number of lines: %d." %
104                       MAXLINES)
105
106             if self.lines_left > 0:
107                 self.stderr += "%s\n" % self.line
108                 self.lines_left -= 1
109                 if self.lines_left == 0:
110                     self.send("STDERR", self.stderr)
111                     self.stderr = ""
112                 self.line = ""
113                 continue
114
115             self.line = self.line.strip()
116             if len(self.line) == 0:
117                 continue
118
119             if self.line.startswith("STDERR:"):
120                 m = re.match("STDERR:(\d+):(.*)", self.line)
121                 if not m:
122                     error("Invalid syntax for STDERR line")
123                 try:
124                     self.lines_left = int(m.group(1))
125                 except ValueError:
126                     error("Second field in STDERR line must be an integer")
127
128                 if self.lines_left > STDERR_MAXLINES:
129                     error("Too many lines in the STDERR output")
130                 elif self.lines_left < 0:
131                     error("Second field of STDERR: %d is invalid" %
132                           self.lines_left)
133
134                 if self.lines_left > 0:
135                     self.stderr = m.group(2) + "\n"
136                     self.lines_left -= 1
137
138                 if self.lines_left == 0:
139                     self.send("STDERR", self.stderr)
140                     self.stderr = ""
141             elif self.line.startswith("TASK_START:") \
142                     or self.line.startswith("TASK_END:") \
143                     or self.line.startswith("WARNING:") \
144                     or self.line.startswith("ERROR:"):
145                 (msg_type, _, value) = self.line.partition(':')
146
147                 if self.line.startswith("WARNING:") or \
148                         self.line.startswith("ERROR:"):
149                     value = [value]
150                 self.send(msg_type, value)
151             else:
152                 error("Unknown command!")
153
154             # Remove the processed line
155             self.line = ""
156
157     def send(self, msg_type, value):
158         subtype, value_name = PROTOCOL[msg_type]
159
160         msg = {}
161         msg['type'] = MSG_TYPE
162         msg['subtype'] = subtype
163         msg[value_name] = value
164         msg['timestamp'] = time.time()
165         os.write(self.fd, "%s\n" % json.dumps(msg))
166
167
168 if __name__ == "__main__":
169     options = parse_options(sys.argv[1:])
170
171     try:
172         fd = int(options.fd)
173     except ValueError:
174         error("File descriptor is not an integer")
175
176     try:
177         os.fstat(fd)
178     except OSError:
179         error("File descriptor is not valid")
180
181     monitor = HelperMonitor(fd)
182
183     if options.ifname is not None:
184         try:
185             sniff(filter=options.filter, iface=options.ifname,
186                   prn=lambda x: monitor.process(x.payload.getfieldval("load")))
187         except socket.error as e:
188             # Network is down
189             if e.errno == 100:
190                 monitor.process(None)
191             else:
192                 raise
193     else:
194         while True:
195             data = os.read(sys.stdin.fileno(), BUFSIZE)
196             monitor.process(data)
197             if not data:
198                 break
199
200 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :