@cryptotaxi247 / netdata-1 / commits / d983e0f0f

move MySQLService to a separate file

lgz committed Oct 13, 2017 at 00:16 UTC d983e0f0f26fe2c026c5d8acbad37016388fc90e
1 file changed +158
python.d/python_modules/bases/FrameworkServices/MySQLService.py new
+158
@@ -0,0 +1,158 @@
1 +# -*- coding: utf-8 -*-
2 +# Description:
3 +# Author: Ilya Mashchenko (l2isbad)
4 +
5 +from sys import exc_info
6 +
7 +try:
8 + import MySQLdb
9 +
10 + PY_MYSQL = True
11 +except ImportError:
12 + try:
13 + import pymysql as MySQLdb
14 +
15 + PY_MYSQL = True
16 + except ImportError:
17 + PY_MYSQL = False
18 +
19 +from bases.FrameworkServices.SimpleService import SimpleService
20 +
21 +
22 +class MySQLService(SimpleService):
23 + def __init__(self, configuration=None, name=None):
24 + SimpleService.__init__(self, configuration=configuration, name=name)
25 + self.__connection = None
26 + self.__conn_properties = dict()
27 + self.extra_conn_properties = dict()
28 + self.__queries = self.configuration.get('queries', dict())
29 + self.queries = dict()
30 +
31 + def __connect(self):
32 + try:
33 + connection = MySQLdb.connect(connect_timeout=self.update_every, **self.__conn_properties)
34 + except (MySQLdb.MySQLError, TypeError, AttributeError) as error:
35 + return None, str(error)
36 + else:
37 + return connection, None
38 +
39 + def check(self):
40 + def get_connection_properties(conf, extra_conf):
41 + properties = dict()
42 + if conf.get('user'):
43 + properties['user'] = conf['user']
44 + if conf.get('pass'):
45 + properties['passwd'] = conf['pass']
46 + if conf.get('socket'):
47 + properties['unix_socket'] = conf['socket']
48 + elif conf.get('host'):
49 + properties['host'] = conf['host']
50 + properties['port'] = int(conf.get('port', 3306))
51 + elif conf.get('my.cnf'):
52 + if MySQLdb.__name__ == 'pymysql':
53 + self.error('"my.cnf" parsing is not working for pymysql')
54 + else:
55 + properties['read_default_file'] = conf['my.cnf']
56 + if isinstance(extra_conf, dict) and extra_conf:
57 + properties.update(extra_conf)
58 +
59 + return properties or None
60 +
61 + def is_valid_queries_dict(raw_queries, log_error):
62 + """
63 + :param raw_queries: dict:
64 + :param log_error: function:
65 + :return: dict or None
66 +
67 + raw_queries is valid when: type <dict> and not empty after is_valid_query(for all queries)
68 + """
69 +
70 + def is_valid_query(query):
71 + return all([isinstance(query, str),
72 + query.startswith(('SELECT', 'select', 'SHOW', 'show'))])
73 +
74 + if hasattr(raw_queries, 'keys') and raw_queries:
75 + valid_queries = dict([(n, q) for n, q in raw_queries.items() if is_valid_query(q)])
76 + bad_queries = set(raw_queries) - set(valid_queries)
77 +
78 + if bad_queries:
79 + log_error('Removed query(s): {queries}'.format(queries=bad_queries))
80 + return valid_queries
81 + else:
82 + log_error('Unsupported "queries" format. Must be not empty <dict>')
83 + return None
84 +
85 + if not PY_MYSQL:
86 + self.error('MySQLdb or PyMySQL module is needed to use mysql.chart.py plugin')
87 + return False
88 +
89 + # Preference: 1. "queries" from the configuration file 2. "queries" from the module
90 + self.queries = self.__queries or self.queries
91 + # Check if "self.queries" exist, not empty and all queries are in valid format
92 + self.queries = is_valid_queries_dict(self.queries, self.error)
93 + if not self.queries:
94 + return None
95 +
96 + # Get connection properties
97 + self.__conn_properties = get_connection_properties(self.configuration, self.extra_conn_properties)
98 + if not self.__conn_properties:
99 + self.error('Connection properties are missing')
100 + return False
101 +
102 + # Create connection to the database
103 + self.__connection, error = self.__connect()
104 + if error:
105 + self.error('Can\'t establish connection to MySQL: {error}'.format(error=error))
106 + return False
107 +
108 + try:
109 + data = self._get_data()
110 + except Exception as error:
111 + self.error('_get_data() failed. Error: {error}'.format(error=error))
112 + return False
113 +
114 + if isinstance(data, dict) and data:
115 + return True
116 + self.error("_get_data() returned no data or type is not <dict>")
117 + return False
118 +
119 + def _get_raw_data(self, description=None):
120 + """
121 + Get raw data from MySQL server
122 + :return: dict: fetchall() or (fetchall(), description)
123 + """
124 +
125 + if not self.__connection:
126 + self.__connection, error = self.__connect()
127 + if error:
128 + return None
129 +
130 + raw_data = dict()
131 + queries = dict(self.queries)
132 + try:
133 + with self.__connection as cursor:
134 + for name, query in queries.items():
135 + try:
136 + cursor.execute(query)
137 + except (MySQLdb.ProgrammingError, MySQLdb.OperationalError) as error:
138 + if self.__is_error_critical(err_class=exc_info()[0], err_text=str(error)):
139 + raise RuntimeError
140 + self.error('Removed query: {name}[{query}]. Error: error'.format(name=name,
141 + query=query,
142 + error=error))
143 + self.queries.pop(name)
144 + continue
145 + else:
146 + raw_data[name] = (cursor.fetchall(), cursor.description) if description else cursor.fetchall()
147 + self.__connection.commit()
148 + except (MySQLdb.MySQLError, RuntimeError, TypeError, AttributeError):
149 + self.__connection.close()
150 + self.__connection = None
151 + return None
152 + else:
153 + return raw_data or None
154 +
155 + @staticmethod
156 + def __is_error_critical(err_class, err_text):
157 + return err_class == MySQLdb.OperationalError and all(['denied' not in err_text,
158 + 'Unknown column' not in err_text])