@cryptotaxi247 / netdata-1 / commits / 4bdf8543f

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