source: branches/locker-dev/noc/nagios/scripts-plugins/check_svn @ 2239

Last change on this file since 2239 was 900, checked in by quentin, 16 years ago
Check SVN status
  • Property svn:executable set to *
File size: 15.1 KB
Line 
1#!/usr/bin/env python
2#
3#   Copyright Hari Sekhon 2008
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,
11#   but WITHOUT ANY WARRANTY; without even the implied warranty of
12#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#   GNU 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 St, Fifth Floor, Boston, MA  02110-1301  USA
18#
19
20"""Nagios plugin to test the status of a Subversion (SVN) server. Requires
21   the subversion client "svn" to be installed somewhere in the path"""
22
23# Standard Nagios return codes
24OK       = 0
25WARNING  = 1
26CRITICAL = 2
27UNKNOWN  = 3
28
29import os
30import re
31import sys
32import signal
33import time
34try:
35    from subprocess import Popen, PIPE, STDOUT
36except ImportError:
37    print "UNKNOWN: Failed to import python subprocess module.",
38    print "Perhaps you are using a version of python older than 2.4?"
39    sys.exit(CRITICAL)
40from optparse import OptionParser
41
42__author__      = "Hari Sekhon"
43__title__       = "Nagios Plugin for Subversion"
44__version__     = 0.4
45
46DEFAULT_TIMEOUT = 10
47
48
49def end(status, message):
50    """Prints a message and exits. First arg is the status code
51    Second Arg is the string message"""
52   
53    check_name = "SVN "
54    if status == OK:
55        print "%sOK: %s" % (check_name, message)
56        sys.exit(OK)
57    elif status == WARNING:
58        print "%sWARNING: %s" % (check_name, message)
59        sys.exit(WARNING)
60    elif status == CRITICAL:
61        print "%sCRITICAL: %s" % (check_name, message)
62        sys.exit(CRITICAL)
63    else:
64        # This one is intentionally different
65        print "UNKNOWN: %s" % message
66        sys.exit(UNKNOWN)
67
68
69# Pythonic version of "which", inspired by my beloved *nix core utils
70# although I've decided it makes more sense to fetch a non-executable
71# program and alert on it rather than say it wasn't found in the path
72# at all from a user perspective.
73def which(executable):
74    """Takes an executable name as a string and tests if it is in the path.
75    Returns the full path of the executable if it exists in path, or None if it
76    does not"""
77
78    for basepath in os.environ['PATH'].split(os.pathsep):
79        path = os.path.join(basepath, executable)
80        if os.path.isfile(path):
81            if os.access(path, os.X_OK):
82                return path
83            else:
84                #print >> sys.stderr, "Warning: '%s' in path is not executable"
85                end(UNKNOWN, "svn utility '%s' is not executable" % path)
86
87    return None
88
89
90BIN = which("svn")
91if not BIN:
92    end(UNKNOWN, "'svn' cannot be found in path. Please install the " \
93               + "subversion client or fix your PATH environment variable")
94
95
96class SvnTester:
97    """Holds state for the svn test"""
98
99    def __init__(self):
100        """Initializes all variables to their default states"""
101
102        self.directory  = ""
103        self.http       = False
104        self.https      = False
105        self.password   = ""
106        self.port       = ""
107        self.protocol   = "svn"
108        self.server     = ""
109        self.timeout    = DEFAULT_TIMEOUT
110        self.username   = ""
111        self.verbosity  = 0
112
113
114    def validate_variables(self):
115        """Runs through the validation of all test variables
116        Should be called before the main test to perform a sanity check
117        on the environment and settings"""
118
119        self.validate_host()
120        self.validate_protocol()
121        self.validate_port()
122        self.validate_timeout()
123
124
125    def validate_host(self):
126        """Exits with an error if the hostname
127        does not conform to expected format"""
128
129        # Input Validation - Rock my regex ;-)
130        re_hostname = re.compile("^[a-zA-Z0-9]+[a-zA-Z0-9-]*((([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6})?$")
131        re_ipaddr   = re.compile("^((25[0-5]|2[0-4]\d|[01]\d\d|\d?\d)\.){3}(25[0-5]|2[0-4]\d|[01]\d\d|\d?\d)$")
132
133        if self.server == None:
134            end(UNKNOWN, "You must supply a server hostname or ip address. " \
135                       + "See --help for details")
136
137        if not re_hostname.match(self.server) and \
138           not re_ipaddr.match(self.server):
139            end(UNKNOWN, "Server given does not appear to be a valid " \
140                       + "hostname or ip address")
141   
142
143    def validate_protocol(self):
144        """Determines the protocol to use and sets it in the object"""
145
146        if self.http and self.https:
147            end(UNKNOWN, "cannot choose both http and https, they are " \
148                       + "mutually exclusive")
149        elif self.http:   
150            self.protocol = "http"
151        elif self.https:
152            self.protocol = "https"
153        else:
154            self.protocol = "svn"
155
156
157    def validate_port(self):
158        """Exits with an error if the port is not valid"""
159
160        if self.port == None:
161            self.port = ""
162        else:
163            try:
164                self.port = int(self.port)
165                if not 1 <= self.port <= 65535:
166                    raise ValueError
167            except ValueError:
168                end(UNKNOWN, "port number must be a whole number between " \
169                           + "1 and 65535")
170
171
172    def validate_timeout(self):
173        """Exits with an error if the timeout is not valid"""
174
175        if self.timeout == None:
176            self.timeout = DEFAULT_TIMEOUT
177        try:
178            self.timeout = int(self.timeout)
179            if not 1 <= self.timeout <= 65535:
180                end(UNKNOWN, "timeout must be between 1 and 3600 seconds")
181        except ValueError:
182            end(UNKNOWN, "timeout number must be a whole number between " \
183                       + "1 and 3600 seconds")
184
185        if self.verbosity == None:
186            self.verbosity = 0
187
188
189    def run(self, cmd):
190        """runs a system command and returns a tuple containing
191        the return code and the output as a single text block"""
192
193        if cmd == "" or cmd == None:
194            end(UNKNOWN, "Internal python error - " \
195                       + "no cmd supplied for run function")
196       
197        self.vprint(3, "running command: %s" % cmd)
198
199        try:
200            process = Popen( cmd.split(), 
201                             shell=False, 
202                             stdin=PIPE, 
203                             stdout=PIPE, 
204                             stderr=STDOUT )
205        except OSError, error:
206            error = str(error)
207            if error == "No such file or directory":
208                end(UNKNOWN, "Cannot find utility '%s'" % cmd.split()[0])
209            else:
210                end(UNKNOWN, "Error trying to run utility '%s' - %s" \
211                                                      % (cmd.split()[0], error))
212
213        stdout, stderr = process.communicate()
214
215        if stderr == None:
216            pass
217
218        if stdout == None or stdout == "":
219            end(UNKNOWN, "No output from utility '%s'" % cmd.split()[0])
220       
221        returncode = process.returncode
222
223        self.vprint(3, "Returncode: '%s'\nOutput: '%s'" % (returncode, stdout))
224        return (returncode, str(stdout))
225
226
227    def set_timeout(self):
228        """Sets an alarm to time out the test"""
229
230        if self.timeout == 1:
231            self.vprint(2, "setting plugin timeout to 1 second")
232        else:
233            self.vprint(2, "setting plugin timeout to %s seconds"\
234                                                                % self.timeout)
235
236        signal.signal(signal.SIGALRM, self.sighandler)
237        signal.alarm(self.timeout)
238
239
240    def sighandler(self, discarded, discarded2):
241        """Function to be called by signal.alarm to kill the plugin"""
242
243        # Nop for these variables
244        discarded = discarded2
245        discarded2 = discarded
246
247        if self.timeout == 1:
248            timeout = "(1 second)"
249        else:
250            timeout = "(%s seconds)" % self.timeout
251
252        end(CRITICAL, "svn plugin has self terminated after exceeding " \
253                    + "the timeout %s" % timeout)
254
255
256    def generate_uri(self):
257        """Creates the uri and returns it as a string"""
258
259        if self.port == "" or self.port == None:
260            port = ""
261        else:
262            port = ":" + str(self.port)
263
264        if self.directory == None:
265            directory = ""
266        else:
267            directory = "/" + str(self.directory).lstrip("/")
268
269        uri = self.protocol + "://"  \
270              + str(self.server)     \
271              + str(port)            \
272              + str(directory)
273
274        return str(uri)
275
276
277    def test_svn(self):
278        """Performs the test of the subversion server"""
279
280        self.validate_variables()
281        self.set_timeout()
282
283        self.vprint(2, "now running subversion test")
284
285        uri = self.generate_uri()
286
287        self.vprint(3, "subversion server address is '%s'" % uri)
288
289        cmd = BIN + " ls " + uri + " --no-auth-cache --non-interactive"
290        if self.username:
291            cmd += " --username=%s" % self.username
292        if self.password:
293            cmd += " --password=%s" % self.password
294
295        result, output = self.run(cmd)
296       
297        if result == 0:
298            if len(output) == 0:
299                return (WARNING, "Test passed but no output was received " \
300                               + "from svn program, abnormal condition, "  \
301                               + "please check.")
302            else:
303                if self.verbosity >= 1:
304                    return(OK, "svn repository online - directory listing: %s" \
305                                        % output.replace("\n", " ").rstrip(" "))
306                else:
307                    return (OK, "svn repository online - " \
308                              + "directory listing successful")
309        else:
310            if len(output) == 0:
311                return (CRITICAL, "Connection failed. " \
312                                + "There was no output from svn")
313            else:
314                if output == "svn: Can't get password\n":
315                    output = "password required to access this repository but" \
316                           + " none was given or cached"
317                output = output.lstrip("svn: ")
318                return (CRITICAL, "Error connecting to svn server - %s " \
319                                        % output.replace("\n", " ").rstrip(" "))
320 
321
322    def vprint(self, threshold, message):
323        """Prints a message if the first arg is numerically greater than the
324        verbosity level"""
325
326        if self.verbosity >= threshold:
327            print "%s" % message
328
329
330def main():
331    """Parses args and calls func to test svn server"""
332
333    tester = SvnTester()
334    parser = OptionParser()
335    parser.add_option( "-H",
336                       "-S",
337                       "--host",
338                       "--server",
339                       dest="server",
340                       help="The Hostname or IP Address of the subversion "    \
341                          + "server")
342
343    parser.add_option( "-p",
344                       "--port",
345                       dest="port",
346                       help="The port on the server to test if not using the " \
347                          + "default port which is 3690 for svn://, 80 for "   \
348                          + "http:// or 443 for https://.")
349
350    parser.add_option( "--http",
351                       action="store_true",
352                       dest="http",
353                       help="Connect to the server using the http:// " \
354                          + "protocol (Default is svn://)")
355
356    parser.add_option( "--https",
357                       action="store_true",
358                       dest="https",
359                       help="Connect to the server using the https:// " \
360                          + "protocol (Default is svn://)")
361
362    parser.add_option( "--dir",
363                       "--directory",
364                       dest="directory",
365                       help="The directory on the host. Optional but usually " \
366                          + "necessary if using http/https, eg if using an "   \
367                          + "http WebDAV repository "                          \
368                          + "http://somehost.domain.com/repos/svn so this "    \
369                          + "would be --dir /repos/svn. Not usually needed "   \
370                          + "for the default svn:// unless you want to test "  \
371                          + "a specific directory in the repository")
372
373    parser.add_option( "-U",
374                       "--username",
375                       dest="username",
376                       help="The username to use to connect to the subversion" \
377                          + " server.")
378
379    parser.add_option( "-P",
380                       "--password",
381                       dest="password",
382                       help="The password to use to connect to the subversion" \
383                          + " server.")
384
385    parser.add_option( "-t",
386                       "--timeout",
387                       dest="timeout",
388                       help="Sets a timeout after which the the plugin will"   \
389                          + " self terminate. Defaults to %s seconds." \
390                                                              % DEFAULT_TIMEOUT)
391
392    parser.add_option( "-T",
393                       "--timing",
394                       action="store_true",
395                       dest="timing",
396                       help="Enable timer output")
397
398    parser.add_option(  "-v",
399                        "--verbose",
400                        action="count",
401                        dest="verbosity",
402                        help="Verbose mode. Good for testing plugin. By "     \
403                           + "default only one result line is printed as per" \
404                           + " Nagios standards")
405
406    parser.add_option( "-V",
407                        "--version",
408                        action = "store_true",
409                        dest = "version",
410                        help = "Print version number and exit" )
411
412    (options, args) = parser.parse_args()
413
414    if args:
415        parser.print_help()
416        sys.exit(UNKNOWN)
417
418    if options.version:
419        print "%s %s" % (__title__, __version__)
420        sys.exit(UNKNOWN)
421
422    tester.directory  = options.directory
423    tester.http       = options.http
424    tester.https      = options.https
425    tester.password   = options.password
426    tester.port       = options.port
427    tester.server     = options.server
428    tester.timeout    = options.timeout
429    tester.username   = options.username
430    tester.verbosity  = options.verbosity
431
432    if options.timing:
433        start_time = time.time()
434
435    returncode, output = tester.test_svn()
436
437    if options.timing:
438        finish_time = time.time()
439        total_time = finish_time - start_time
440       
441        output += ". Test completed in %.3f seconds" % total_time
442
443    end(returncode, output)
444    sys.exit(UNKNOWN)
445
446
447if __name__ == "__main__":
448    try:
449        main()
450    except KeyboardInterrupt:
451        print "Caught Control-C..."
452        sys.exit(CRITICAL)
Note: See TracBrowser for help on using the repository browser.