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