blob: 1b6993c0be583a114b0635d48e33267f5922ca46 [file] [log] [blame]
Ved-vampir0c7e2d42015-03-18 17:18:47 +03001#!/usr/bin/env python
2""" Protocol class """
3
4import re
5import zlib
6import json
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03007import logging
Ved-vampir0c7e2d42015-03-18 17:18:47 +03008import binascii
Ved-vampir0c7e2d42015-03-18 17:18:47 +03009
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030010
koder aka kdanilov168f6092015-04-19 02:33:38 +030011logger = logging.getLogger("wally.sensors")
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030012
Ved-vampir0c7e2d42015-03-18 17:18:47 +030013
14# protocol contains 2 type of packet:
15# 1 - header, which contains template schema of counters
16# 2 - body, which contains only values in order as in template
Ved-vampir2c2f2e92015-03-18 18:02:25 +030017# it uses msgpack (or provided packer) for optimization
Ved-vampir0c7e2d42015-03-18 17:18:47 +030018#
19# packet has format:
20# begin_data_prefixSIZE\n\nDATAend_data_postfix
21# packet part has format:
22# SIZE\n\rDATA
23#
24# DATA use archivation
25
26
27class PacketException(Exception):
28 """ Exceptions from Packet"""
29 pass
30
31
32class Packet(object):
33 """ Class proceed packet by protocol"""
34
35 prefix = "begin_data_prefix"
36 postfix = "end_data_postfix"
37 header_prefix = "template"
38 # other fields
39 # is_begin
40 # is_end
41 # crc
42 # data
43 # data_len
44
Ved-vampir2c2f2e92015-03-18 18:02:25 +030045 def __init__(self, packer):
Ved-vampir0c7e2d42015-03-18 17:18:47 +030046 # preinit
47 self.is_begin = False
48 self.is_end = False
49 self.crc = None
50 self.data = ""
51 self.data_len = None
52 self.srv_template = None
53 self.clt_template = None
54 self.tmpl_size = 0
Ved-vampir2c2f2e92015-03-18 18:02:25 +030055 self.packer = packer
Ved-vampir0c7e2d42015-03-18 17:18:47 +030056
Ved-vampir0c7e2d42015-03-18 17:18:47 +030057 def new_packet(self, part):
58 """ New packet adding """
59 # proceed packet
60 try:
61 # get size
62 local_size_s, _, part = part.partition("\n\r")
63 local_size = int(local_size_s)
64
65 # find prefix
66 begin = part.find(self.prefix)
67 if begin != -1:
68 # divide data if something before begin and prefix
69 from_i = begin + len(self.prefix)
70 part = part[from_i:]
71 # reset flags
72 self.is_begin = True
73 self.is_end = False
74 self.data = ""
75 # get size
76 data_len_s, _, part = part.partition("\n\r")
77 self.data_len = int(data_len_s)
78 # get crc
79 crc_s, _, part = part.partition("\n\r")
80 self.crc = int(crc_s)
81
82 # bad size?
83 if local_size != self.data_len:
84 raise PacketException("Part size error")
85
86 # find postfix
87 end = part.find(self.postfix)
88 if end != -1:
89 # divide postfix
90 part = part[:end]
91 self.is_end = True
92
93 self.data += part
94 # check if it is end
95 if self.is_end:
96 self.data = zlib.decompress(self.data)
97 if self.data_len != len(self.data):
98 raise PacketException("Total size error")
99 if binascii.crc32(self.data) != self.crc:
100 raise PacketException("CRC error")
101
102 # check, if it is template
103 if self.data.startswith(self.header_prefix):
104 self.srv_template = self.data
105 # template is for internal use
106 return None
107
108 # decode values list
Ved-vampir2c2f2e92015-03-18 18:02:25 +0300109 vals = self.packer.unpack(self.data)
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300110 dump = self.srv_template % tuple(vals)
111 return dump
112 else:
113 return None
114
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300115 except PacketException as e:
116 # if something wrong - skip packet
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300117 logger.warning("Packet skipped: %s", e)
118 self.is_begin = False
119 self.is_end = False
120 return None
121
122 except TypeError:
123 # if something wrong - skip packet
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300124 logger.warning("Packet skipped: doesn't match schema")
125 self.is_begin = False
126 self.is_end = False
127 return None
128
129 except:
130 # if something at all wrong - skip packet
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300131 logger.warning("Packet skipped: something is wrong")
132 self.is_begin = False
133 self.is_end = False
134 return None
135
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300136 @staticmethod
137 def create_packet(data, part_size):
138 """ Create packet divided by parts with part_size from data
139 No compression here """
140 # prepare data
141 data_len = "%i\n\r" % len(data)
142 header = "%s%s%s\n\r" % (Packet.prefix, data_len, binascii.crc32(data))
143 compact_data = zlib.compress(data)
144 packet = "%s%s%s" % (header, compact_data, Packet.postfix)
145
146 partheader_len = len(data_len)
147
148 beg = 0
149 end = part_size - partheader_len
150
151 result = []
152 while beg < len(packet):
153 block = packet[beg:beg+end]
154 result.append(data_len + block)
155 beg += end
156
157 return result
158
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300159 def create_packet_v2(self, data, part_size):
160 """ Create packet divided by parts with part_size from data
161 Compressed """
162 result = []
163 # create and add to result template header
164 if self.srv_template is None:
165 perf_string = json.dumps(data)
166 self.create_answer_template(perf_string)
167 template = self.header_prefix + self.srv_template
168 header = Packet.create_packet(template, part_size)
169 result.extend(header)
170
171 vals = self.get_matching_value_list(data)
Ved-vampir2c2f2e92015-03-18 18:02:25 +0300172 body = self.packer.pack(vals)
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300173 parts = Packet.create_packet(body, part_size)
174 result.extend(parts)
175 return result
176
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300177 def get_matching_value_list(self, data):
178 """ Get values in order server expect"""
179 vals = range(0, self.tmpl_size)
180
181 try:
182 for node, groups in self.clt_template.items():
183 for group, counters in groups.items():
184 for counter, index in counters.items():
koder aka kdanilove06762a2015-03-22 23:32:09 +0200185 if not isinstance(index, dict):
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300186 vals[index] = data[node][group][counter]
187 else:
188 for k, i in index.items():
189 vals[i] = data[node][group][counter][k]
190
191 return vals
192
193 except (IndexError, KeyError):
194 logger = logging.getLogger(__name__)
195 logger.error("Data don't match last schema")
196 raise PacketException("Data don't match last schema")
197
Ved-vampir0c7e2d42015-03-18 17:18:47 +0300198 def create_answer_template(self, perf_string):
199 """ Create template for server to insert counter values
200 Return tuple of server and clien templates + number of replaces"""
201 replacer = re.compile(": [0-9]+\.?[0-9]*")
202 # replace all values by %s
203 finditer = replacer.finditer(perf_string)
204 # server not need know positions
205 self.srv_template = ""
206 # client need positions
207 clt_template = ""
208 beg = 0
209 k = 0
210 # this could be done better?
211 for match in finditer:
212 # define input place in server template
213 self.srv_template += perf_string[beg:match.start()]
214 self.srv_template += ": %s"
215 # define match number in client template
216 clt_template += perf_string[beg:match.start()]
217 clt_template += ": %i" % k
218
219 beg = match.end()
220 k += 1
221
222 # add tail
223 self.srv_template += perf_string[beg:]
224 clt_template += perf_string[beg:]
225
226 self.tmpl_size = k
227 self.clt_template = json.loads(clt_template)