2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
27 # DB2 support requires installation of the IBM Data Server Client:
28 # http://www-01.ibm.com/support/docview.wss?uid=swg27016878
29 # as well as the ibm_db2 Python DBI driver for DB2:
30 # https://pypi.python.org/pypi/ibm_db
31 """Check_MK SQL Test"""
37 import cmk
.utils
.password_store
38 cmk
.utils
.password_store
.replace_passwords()
40 LOG
= logging
.getLogger(__name__
)
50 MP_INF
= (float('-inf'), float('+inf'))
52 # . parse commandline argumens
56 lower
, upper
= values
.split(':')
57 lower
= float(lower
) if lower
else MP_INF
[0]
58 upper
= float(upper
) if upper
else MP_INF
[1]
62 def sql_cmd_piece(values
):
63 """Parse every piece of the SQL command (replace \\n and \\;)"""
64 return values
.replace(r
"\n", "\n").replace(r
"\;", ";")
68 """Parse commandline arguments (incl password store and logging set up)"""
69 this
= str(os
.path
.basename(argv
[0]))
70 fmt
= argparse
.RawDescriptionHelpFormatter
71 parser
= argparse
.ArgumentParser(prog
=this
, description
=__doc__
, formatter_class
=fmt
)
78 help='''Verbose mode: print SQL statement and levels
79 (for even more output use -vv''',
84 help='''Debug mode: let Python exceptions come through''',
90 help='''Add performance data to the output''',
96 help='''treat the main argument as a procedure instead
97 of an SQL-Statement''',
104 type=lambda s
: s
.split(','),
105 help='''comma separated list if values of input variables
106 if required by the procedure''',
113 choices
=['postgres', 'mysql', 'mssql', 'oracle', 'db2'],
114 help='''Name of the database management system.
115 Default is "postgres"''',
122 help='''Hostname or IP-Address where the database lives.
123 Default is "127.0.0.1"''',
130 help='''Port used to connect to the database.
131 Default depends on DBMS''',
139 help='''lower and upper level for the warning state,
140 separated by a colon''',
148 help='''lower and upper level for the critical state,
149 separated by a colon''',
155 help='''Additional text prefixed to the output''',
163 help='''Name of the database on the DBMS''',
169 help='''Username for database access''',
175 help='''Password for database access''',
179 metavar
="SQL-Statement|Procedure",
182 help='''Valid SQL-Statement for the selected database.
183 The statement must return at least a number and a
184 string, plus optional performance data.
186 Alternatively: If the the "-o" option is given,
187 treat the argument as a procedure name.
189 The procedure must return one output variable,
190 which content is evaluated the same way as the
191 output of the SQL-Statement''',
193 args
= parser
.parse_args(argv
[1:])
194 args
.cmd
= ' '.join(args
.cmd
)
199 fmt
= "%(levelname)s: %(lineno)s: " + fmt
200 if args
.dbms
== "mssql":
201 os
.environ
["TDSDUMP"] = "stdout"
202 logging
.basicConfig(level
=max(30 - 10 * args
.verbose
, 0), format
=fmt
)
205 for key
, val
in args
.__dict
__.items():
206 if key
in ('user', 'password'):
208 LOG
.debug('argparse: %s = %r', key
, val
)
215 def bail_out(exit_code
, output
):
216 state_txt
= ["OK", "WARN", "CRIT", "UNKNOWN"][exit_code
]
217 sys
.stdout
.write("%s - %s\n" % (state_txt
, output
))
221 # . DBMS specific code here!
223 # For every DBMS specify a connect and execute function.
224 # Add them in the dict in the 'main' connect and execute functions
226 def _default_execute(cursor
, cmd
, inpt
, procedure
):
228 LOG
.info("SQL Procedure Name: %s", cmd
)
229 LOG
.info("Input Values: %s", inpt
)
230 cursor
.callproc(cmd
, inpt
)
231 LOG
.debug("inpt after 'callproc' = %r", inpt
)
233 LOG
.info("SQL Statement: %s", cmd
)
236 return cursor
.fetchall()
239 def postgres_connect(host
, port
, db_name
, user
, pwd
):
241 return psycopg2
.connect(host
=host
, port
=port
, \
242 database
=db_name
, user
=user
, password
=pwd
)
245 def postgres_execute(cursor
, cmd
, inpt
, procedure
):
246 return _default_execute(cursor
, cmd
, inpt
, procedure
)
249 def mysql_connect(host
, port
, db_name
, user
, pwd
):
251 return MySQLdb
.connect(host
=host
, port
=port
, db
=db_name
, \
252 user
=user
, passwd
=pwd
)
255 def mysql_execute(cursor
, cmd
, inpt
, procedure
):
256 return _default_execute(cursor
, cmd
, inpt
, procedure
)
259 def mssql_connect(host
, port
, db_name
, user
, pwd
):
261 return pymssql
.connect(host
=host
, port
=port
, \
262 database
=db_name
, user
=user
, password
=pwd
)
265 def mssql_execute(cursor
, cmd
, _inpt
, procedure
):
267 LOG
.info("SQL Procedure Name: %s", cmd
)
270 LOG
.info("SQL Statement: %s", cmd
)
274 return cursor
.fetchall()
277 def oracle_connect(host
, port
, db_name
, user
, pwd
):
279 '/usr/lib/python%s.%s/site-packages' % (sys
.version_info
.major
, sys
.version_info
.minor
))
281 import cx_Oracle
# pylint: disable=import-error
282 except ImportError as exc
:
283 bail_out(3, "%s. Please install it via 'pip install cx_Oracle'." % exc
)
285 cstring
= "%s/%s@%s:%s/%s" % (user
, pwd
, host
, port
, db_name
)
286 return cx_Oracle
.connect(cstring
)
289 def oracle_execute(cursor
, cmd
, inpt
, procedure
):
291 import cx_Oracle
# pylint: disable=import-error
292 except ImportError as exc
:
293 bail_out(3, "%s. Please install it via 'pip install cx_Oracle'." % exc
)
296 LOG
.info("SQL Procedure Name: %s", cmd
)
297 LOG
.info("Input Values: %s", inpt
)
299 # In an earlier version, this code-branch
300 # had been executed regardles of the dbms.
301 # clearly this is oracle specific.
302 outvar
= cursor
.var(cx_Oracle
.STRING
) # pylint:disable=undefined-variable
303 # However, I have not been able to test it.
306 cursor
.callproc(cmd
, inpt
)
308 LOG
.debug("inpt after 'callproc' = %r", inpt
)
309 LOG
.debug("outvar = %r", outvar
)
311 # for empty input this is just
312 # _res = outvar.getvalue()
313 _res
= ','.join(i
.getvalue() for i
in inpt
)
314 LOG
.debug("outvar.getvalue() = %r", _res
)
315 params_result
= _res
.split(",")
316 LOG
.debug("params_result = %r", params_result
)
319 LOG
.info("SQL Statement: %s", cmd
)
322 return cursor
.fetchall()
325 def db2_connect(host
, port
, db_name
, user
, pwd
):
326 # IBM data server driver
328 import ibm_db
# pylint: disable=import-error
329 import ibm_db_dbi
# pylint: disable=import-error
330 except ImportError as exc
:
331 bail_out(3, "%s. Please install it via pip." % exc
)
333 cstring
= 'DRIVER={IBM DB2 ODBC DRIVER};DATABASE=%s;' \
334 'HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;UID=%s;PWD=%s;' \
335 % (db_name
, host
, port
, user
, pwd
)
336 ibm_db_conn
= ibm_db
.connect(cstring
, '', '')
337 return ibm_db_dbi
.Connection(ibm_db_conn
)
340 def db2_execute(cursor
, cmd
, inpt
, procedure
):
341 return _default_execute(cursor
, cmd
, inpt
, procedure
)
347 def connect(dbms
, host
, port
, db_name
, user
, pwd
):
348 """Connect to the correct database
350 A python library is imported depending on the value of dbms.
351 Return the created connection object.
354 port
= DEFAULT_PORTS
[dbms
]
357 'postgres': postgres_connect
,
358 'mysql': mysql_connect
,
359 'mssql': mssql_connect
,
360 'oracle': oracle_connect
,
362 }[dbms
](host
, port
, db_name
, user
, pwd
)
365 def execute(dbms
, connection
, cmd
, inpt
, procedure
=False):
366 """Execute the sql statement, or call the procedure.
368 Some corrections are made for libraries that do not adhere to the
369 python SQL API: https://www.python.org/dev/peps/pep-0249/
371 cursor
= connection
.cursor()
375 'postgres': postgres_execute
,
376 'mysql': mysql_execute
,
377 'mssql': mssql_execute
,
378 'oracle': oracle_execute
,
380 }[dbms
](cursor
, cmd
, inpt
, procedure
)
385 LOG
.info("SQL Result:\n%r", result
)
389 def process_result(result
, warn
, crit
, metrics
=False, debug
=False):
390 """Process the first row (!) of the result of the SQL command.
392 Only the first row of the result (result[0]) is considered.
393 It is assumed to be an sequence of length 3, consisting of of
394 [numerical_value, text, performance_data].
395 The full result is returned as muliline output.
398 bail_out(3, "SQL statement/procedure returned no data")
401 number
= float(row0
[0])
403 # handle case where sql query only results in one column
405 text
= "%s" % row0
[0]
407 text
= "%s" % row0
[1]
412 perf
= " | performance_data=%s" % str(row0
[2])
418 if warn
!= MP_INF
or crit
!= MP_INF
:
419 if not warn
[0] < number
< warn
[1]:
421 if not crit
[0] < number
< crit
[1]:
423 text
+= ": %s" % number
424 else: # no levels were given
425 if number
in (0, 1, 2, 3):
428 bail_out(3, "<%d> is not a state, and no levels given" % number
)
430 return state
, text
+ perf
435 args
= parse_args(argv
or sys
.argv
)
438 msg
= "connecting to database"
439 conn
= connect(args
.dbms
, args
.hostname
, args
.port
, args
.name
, args
.user
, args
.password
)
441 msg
= "executing SQL command"
442 result
= execute(args
.dbms
, conn
, args
.cmd
, args
.input, procedure
=args
.procedure
)
444 msg
= "processing result of SQL statement/procedure"
445 state
, text
= process_result(
449 metrics
=args
.metrics
,
452 except () if args
.debug
else Exception as exc
:
453 errmsg
= str(exc
).strip('()').replace(r
'\n', ' ')
454 bail_out(3, "Error while %s: %s" % (msg
, errmsg
))
456 bail_out(state
, args
.text
+ text
)
459 if __name__
== '__main__':