MySQLService class added
Ilya committed
Mar 9, 2017 at 14:38 UTC
4bdf8543f617f8df20f5c2be289accc03bbb631a
1 file changed
+141
-6
python.d/python_modules/base.py
+141
-6
@@ -18,20 +18,29 @@
18
# using ".encode()" in one thread can block other threads as well (only in python2)
19
20
import time
21
-# import sys
21
import os
22
import socket
23
import select
24
+import threading
25
+import msg
26
+import ssl
27
+from subprocess import Popen, PIPE
28
+from sys import exc_info
29
+
30
try:
31
import urllib.request as urllib2
32
except ImportError:
33
import urllib2
34
30
-from subprocess import Popen, PIPE
31
-
32
-import threading
33
-import msg
34
-import ssl
35
+try:
36
+ import MySQLdb
37
+ PYMYSQL = True
38
+except ImportError:
39
+ try:
40
+ import pymysql as MySQLdb
41
+ PYMYSQL = True
42
+ except ImportError:
43
+ PYMYSQL = False
44
45
try:
46
PATH = os.getenv('PATH').split(':')
@@ -930,3 +939,129 @@ class ExecutableService(SimpleService):
939
else:
940
self.error("Command", str(self.command), "returned no data")
941
return False
942
+
943
+
944
+class MySQLService(SimpleService):
945
+
946
+ def __init__(self, configuration=None, name=None):
947
+ SimpleService.__init__(self, configuration=configuration, name=name)
948
+ self.__connection = None
949
+ self.conn_properties = dict()
950
+ self.queries = dict()
951
+
952
+ def __connect(self):
953
+ try:
954
+ connection = MySQLdb.connect(connect_timeout=self.update_every, **self.conn_properties)
955
+ except (MySQLdb.MySQLError, TypeError) as error:
956
+ return None, str(error)
957
+ else:
958
+ return connection, None
959
+
960
+ def check(self):
961
+ def get_connection_properties(conf):
962
+ properties = dict()
963
+ if 'user' in conf and conf['user']:
964
+ properties['user'] = conf['user']
965
+ if 'pass' in conf and conf['pass']:
966
+ properties['passwd'] = conf['pass']
967
+ if 'socket' in conf and conf['socket']:
968
+ properties['unix_socket'] = conf['socket']
969
+ elif 'host' in conf and conf['host']:
970
+ properties['host'] = conf['host']
971
+ properties['port'] = int(conf['port']) if conf.get('port') else 3306
972
+ elif 'my.cnf' in conf and conf['my.cnf']:
973
+ properties['read_default_file'] = conf['my.cnf']
974
+
975
+ return properties or None
976
+
977
+ def is_valid_queries_dict(raw_queries, log_error):
978
+ """
979
+ :param raw_queries: dict:
980
+ :param log_error: function:
981
+ :return: dict or None
982
+
983
+ raw_queries is valid when: type <dict> and not empty after is_valid_query(for all queries)
984
+ """
985
+ def is_valid_query(query):
986
+ return all([isinstance(query, str),
987
+ query.startswith(('SELECT', 'select', 'SHOW', 'show'))])
988
+
989
+ if isinstance(raw_queries, dict) and raw_queries:
990
+ valid_queries = dict([(n, q) for n, q in raw_queries.items() if is_valid_query(q)])
991
+ bad_queries = set(raw_queries) - set(valid_queries)
992
+
993
+ if bad_queries:
994
+ log_error('Removed query(s): %s' % bad_queries)
995
+ return valid_queries
996
+ else:
997
+ log_error('Unsupported "queries" format. Must be not empty <dict>')
998
+ return None
999
+
1000
+ if not PYMYSQL:
1001
+ self.error('MySQLdb or PyMySQL module is needed to use mysql.chart.py plugin')
1002
+ return False
1003
+
1004
+ # Check if "self.queries" exist, not empty and all queries are in valid format
1005
+ self.queries = is_valid_queries_dict(self.queries, self.error)
1006
+ if not self.queries:
1007
+ return None
1008
+
1009
+ # Get connection properties
1010
+ self.conn_properties = get_connection_properties(self.configuration)
1011
+ if not self.conn_properties:
1012
+ self.error('Connection properties are missing')
1013
+ return False
1014
+
1015
+ # Create connection to the database
1016
+ self.__connection, error = self.__connect()
1017
+ if error:
1018
+ self.error('Can\'t establish connection to MySQL: %s' % error)
1019
+ return False
1020
+
1021
+ try:
1022
+ data = self._get_data()
1023
+ except Exception as error:
1024
+ self.error('_get_data() failed. Error: %s' % error)
1025
+ return False
1026
+
1027
+ if isinstance(data, dict) and data:
1028
+ # We need this for create() method
1029
+ self._data_from_check = data
1030
+ return True
1031
+ else:
1032
+ self.error("_get_data() returned no data or type is not <dict>")
1033
+ return False
1034
+
1035
+ def _get_raw_data(self, description=None):
1036
+ """
1037
+ Get raw data from MySQL server
1038
+ :return: dict: fetchall() or (fetchall(), description)
1039
+ """
1040
+
1041
+ if not self.__connection:
1042
+ self.__connection, error = self.__connect()
1043
+ if error:
1044
+ return None
1045
+
1046
+ raw_data = dict()
1047
+ try:
1048
+ with self.__connection as cursor:
1049
+ for name, query in self.queries.items():
1050
+ try:
1051
+ cursor.execute(query)
1052
+ except (MySQLdb.ProgrammingError, MySQLdb.OperationalError) as error:
1053
+ if exc_info()[0] == MySQLdb.OperationalError and 'denied' not in str(error):
1054
+ raise RuntimeError
1055
+ self.error('Removed query: %s[%s]. Error: %s'
1056
+ % (name, query, error))
1057
+ self.queries.pop(name)
1058
+ continue
1059
+ else:
1060
+ raw_data[name] = (cursor.fetchall(), cursor.description) if description else cursor.fetchall()
1061
+ self.__connection.commit()
1062
+ except (MySQLdb.MySQLError, RuntimeError, TypeError, AttributeError):
1063
+ self.__connection.close()
1064
+ self.__connection = None
1065
+ return None
1066
+ else:
1067
+ return raw_data or None