| #!/usr/bin/env python |
| """ Protocol class """ |
| |
| import re |
| import zlib |
| import json |
| import binascii |
| import logging |
| |
| from logger import define_logger |
| |
| # protocol contains 2 type of packet: |
| # 1 - header, which contains template schema of counters |
| # 2 - body, which contains only values in order as in template |
| # it uses msgpack (or provided packer) for optimization |
| # |
| # packet has format: |
| # begin_data_prefixSIZE\n\nDATAend_data_postfix |
| # packet part has format: |
| # SIZE\n\rDATA |
| # |
| # DATA use archivation |
| |
| |
| class PacketException(Exception): |
| """ Exceptions from Packet""" |
| pass |
| |
| |
| class Packet(object): |
| """ Class proceed packet by protocol""" |
| |
| prefix = "begin_data_prefix" |
| postfix = "end_data_postfix" |
| header_prefix = "template" |
| # other fields |
| # is_begin |
| # is_end |
| # crc |
| # data |
| # data_len |
| |
| def __init__(self, packer): |
| # preinit |
| self.is_begin = False |
| self.is_end = False |
| self.crc = None |
| self.data = "" |
| self.data_len = None |
| self.srv_template = None |
| self.clt_template = None |
| self.tmpl_size = 0 |
| self.packer = packer |
| |
| |
| def new_packet(self, part): |
| """ New packet adding """ |
| # proceed packet |
| try: |
| # get size |
| local_size_s, _, part = part.partition("\n\r") |
| local_size = int(local_size_s) |
| |
| # find prefix |
| begin = part.find(self.prefix) |
| if begin != -1: |
| # divide data if something before begin and prefix |
| from_i = begin + len(self.prefix) |
| part = part[from_i:] |
| # reset flags |
| self.is_begin = True |
| self.is_end = False |
| self.data = "" |
| # get size |
| data_len_s, _, part = part.partition("\n\r") |
| self.data_len = int(data_len_s) |
| # get crc |
| crc_s, _, part = part.partition("\n\r") |
| self.crc = int(crc_s) |
| |
| # bad size? |
| if local_size != self.data_len: |
| raise PacketException("Part size error") |
| |
| # find postfix |
| end = part.find(self.postfix) |
| if end != -1: |
| # divide postfix |
| part = part[:end] |
| self.is_end = True |
| |
| self.data += part |
| # check if it is end |
| if self.is_end: |
| self.data = zlib.decompress(self.data) |
| if self.data_len != len(self.data): |
| raise PacketException("Total size error") |
| if binascii.crc32(self.data) != self.crc: |
| raise PacketException("CRC error") |
| |
| # check, if it is template |
| if self.data.startswith(self.header_prefix): |
| self.srv_template = self.data |
| # template is for internal use |
| return None |
| |
| # decode values list |
| vals = self.packer.unpack(self.data) |
| dump = self.srv_template % tuple(vals) |
| return dump |
| else: |
| return None |
| |
| |
| except PacketException as e: |
| # if something wrong - skip packet |
| logger = logging.getLogger(__name__) |
| logger.warning("Packet skipped: %s", e) |
| self.is_begin = False |
| self.is_end = False |
| return None |
| |
| except TypeError: |
| # if something wrong - skip packet |
| logger = logging.getLogger(__name__) |
| logger.warning("Packet skipped: doesn't match schema") |
| self.is_begin = False |
| self.is_end = False |
| return None |
| |
| except: |
| # if something at all wrong - skip packet |
| logger = logging.getLogger(__name__) |
| logger.warning("Packet skipped: something is wrong") |
| self.is_begin = False |
| self.is_end = False |
| return None |
| |
| |
| @staticmethod |
| def create_packet(data, part_size): |
| """ Create packet divided by parts with part_size from data |
| No compression here """ |
| # prepare data |
| data_len = "%i\n\r" % len(data) |
| header = "%s%s%s\n\r" % (Packet.prefix, data_len, binascii.crc32(data)) |
| compact_data = zlib.compress(data) |
| packet = "%s%s%s" % (header, compact_data, Packet.postfix) |
| |
| partheader_len = len(data_len) |
| |
| beg = 0 |
| end = part_size - partheader_len |
| |
| result = [] |
| while beg < len(packet): |
| block = packet[beg:beg+end] |
| result.append(data_len + block) |
| beg += end |
| |
| return result |
| |
| |
| def create_packet_v2(self, data, part_size): |
| """ Create packet divided by parts with part_size from data |
| Compressed """ |
| result = [] |
| # create and add to result template header |
| if self.srv_template is None: |
| perf_string = json.dumps(data) |
| self.create_answer_template(perf_string) |
| template = self.header_prefix + self.srv_template |
| header = Packet.create_packet(template, part_size) |
| result.extend(header) |
| |
| vals = self.get_matching_value_list(data) |
| body = self.packer.pack(vals) |
| parts = Packet.create_packet(body, part_size) |
| result.extend(parts) |
| return result |
| |
| |
| def get_matching_value_list(self, data): |
| """ Get values in order server expect""" |
| vals = range(0, self.tmpl_size) |
| |
| try: |
| for node, groups in self.clt_template.items(): |
| for group, counters in groups.items(): |
| for counter, index in counters.items(): |
| if not isinstance(index, dict): |
| vals[index] = data[node][group][counter] |
| else: |
| for k, i in index.items(): |
| vals[i] = data[node][group][counter][k] |
| |
| return vals |
| |
| except (IndexError, KeyError): |
| logger = logging.getLogger(__name__) |
| logger.error("Data don't match last schema") |
| raise PacketException("Data don't match last schema") |
| |
| |
| |
| def create_answer_template(self, perf_string): |
| """ Create template for server to insert counter values |
| Return tuple of server and clien templates + number of replaces""" |
| replacer = re.compile(": [0-9]+\.?[0-9]*") |
| # replace all values by %s |
| finditer = replacer.finditer(perf_string) |
| # server not need know positions |
| self.srv_template = "" |
| # client need positions |
| clt_template = "" |
| beg = 0 |
| k = 0 |
| # this could be done better? |
| for match in finditer: |
| # define input place in server template |
| self.srv_template += perf_string[beg:match.start()] |
| self.srv_template += ": %s" |
| # define match number in client template |
| clt_template += perf_string[beg:match.start()] |
| clt_template += ": %i" % k |
| |
| beg = match.end() |
| k += 1 |
| |
| # add tail |
| self.srv_template += perf_string[beg:] |
| clt_template += perf_string[beg:] |
| |
| self.tmpl_size = k |
| self.clt_template = json.loads(clt_template) |
| |
| |
| |
| define_logger(__name__) |