v3bwfile.py 5.14 KB
Newer Older
juga's avatar
juga committed
1
2
3
4
5
# -*- coding: utf-8 -*-
"""Classes and functions that create the bandwidth measurements document
(v3bw) used by bandwidth authorities."""

import logging
6
7
from sbws import __version__
from sbws.globals import SPEC_VERSION
8
from sbws.util.timestamp import now_isodt_str, unixts_to_isodt_str
juga's avatar
juga committed
9
10
11

log = logging.getLogger(__name__)

12
LINE_SEP = '\n'
13
14
15
16
17
18
19
20
21
KEYVALUE_SEP_V110 = '='
KEYVALUE_SEP_V200 = ' '
# List of the extra KeyValues accepted by the class
EXTRA_ARG_KEYVALUES = ['software', 'software_version', 'file_created',
                       'earliest_bandwidth', 'generator_started']
# List of all unordered KeyValues currently being used to generate the file
UNORDERED_KEYVALUES = EXTRA_ARG_KEYVALUES + ['lastest_bandwidth']
# List of all the KeyValues currently being used to generate the file
ALL_KEYVALUES = ['version'] + UNORDERED_KEYVALUES
22
23
24
TERMINATOR = '===='
LINE_TERMINATOR = TERMINATOR + LINE_SEP

juga's avatar
juga committed
25
26
27
28
29
30

class V3BwHeader(object):
    """
    Create a bandwidth measurements (V3bw) header
    following bandwidth measurements document spec version 1.1.0.

31
    :param str timestamp: timestamp in Unix Epoch seconds of the most recent
32
        generator result.
juga's avatar
juga committed
33
34
35
    :param str version: the spec version
    :param str software: the name of the software that generates this
    :param str software_version: the version of the software
36
    :param dict kwargs: extra headers. Currently supported:
37
38
39
40
        - earliest_bandwidth: str, ISO 8601 timestamp in UTC time zone
          when the first bandwidth was obtained
        - generator_started: str, ISO 8601 timestamp in UTC time zone
          when the generator started
juga's avatar
juga committed
41
    """
42
43
44
45
46
47
    def __init__(self, timestamp, **kwargs):
        self.timestamp = str(timestamp)
        # KeyValues with default value when not given by kwargs
        self.version = kwargs.get('version', SPEC_VERSION)
        self.software = kwargs.get('software', 'sbws')
        self.software_version = kwargs.get('software_version', __version__)
48
        self.file_created = kwargs.get('file_created', now_isodt_str())
49
50
        # lastest_bandwidth should not be in kwargs, since it MUST be the
        # same as timestamp
51
        self.lastest_bandwidth = unixts_to_isodt_str(timestamp)
52
53
        [setattr(self, k, v) for k, v in kwargs.items()
         if k in EXTRA_ARG_KEYVALUES]
juga's avatar
juga committed
54

55
    @property
56
57
58
59
60
61
62
    def keyvalue_unordered_tuple_ls(self):
        """Return list of KeyValue tuples that do not have specific order."""
        # sort the list to generate determinist headers
        keyvalue_tuple_ls = sorted([(k, v) for k, v in self.__dict__.items()
                                    if k in UNORDERED_KEYVALUES])
        log.debug('keyvalue_tuple_ls %s', keyvalue_tuple_ls)
        return keyvalue_tuple_ls
63
64

    @property
65
66
67
    def keyvalue_tuple_ls(self):
        """Return list of all KeyValue tuples"""
        return [('version', self.version)] + self.keyvalue_unordered_tuple_ls
68
69

    @property
70
71
72
73
74
75
    def keyvalue_v110str_ls(self):
        """Return KeyValue list of strings following spec v1.1.0."""
        keyvalues = [self.timestamp] + [KEYVALUE_SEP_V110.join([k, v])
                                        for k, v in self.keyvalue_tuple_ls]
        log.debug('keyvalue %s', keyvalues)
        return keyvalues
76
77
78
79

    @property
    def strv110(self):
        """Return header string following spec v1.1.0."""
80
        header_str = LINE_SEP.join(self.keyvalue_v110str_ls) + LINE_SEP + \
81
82
83
84
85
            LINE_TERMINATOR
        log.debug('header_str %s', header_str)
        return header_str

    @property
86
87
88
89
90
91
    def keyvalue_v200_ls(self):
        """Return KeyValue list of strings following spec v2.0.0."""
        keyvalue = [self.timestamp] + [KEYVALUE_SEP_V200.join([k, v])
                                       for k, v in self.keyvalue_tuple_ls]
        log.debug('keyvalue %s', keyvalue)
        return keyvalue
92
93
94
95

    @property
    def strv200(self):
        """Return header string following spec v2.0.0."""
96
        header_str = LINE_SEP.join(self.keyvalue_v200_ls) + LINE_SEP + \
97
98
99
100
            LINE_TERMINATOR
        log.debug('header_str %s', header_str)
        return header_str

101
102
103
104
105
    def __str__(self):
        if self.version == '1.1.0':
            return self.strv110
        return self.strv200

106
107
108
109
110
111
112
113
114
115
116
117
118
    @classmethod
    def from_lines_v110(cls, lines):
        """
        :param list lines: list of lines to parse
        :returns: tuple of V3BwHeader object and non-header lines
        """
        assert isinstance(lines, list)
        try:
            index_terminator = lines.index(TERMINATOR)
        except ValueError as e:
            # is not a bw file or is v100
            log.warn('Terminator is not in lines')
            return None
119
        ts = lines[0]
120
        # not checking order
121
        kwargs = dict([l.split(KEYVALUE_SEP_V110)
122
                       for l in lines[:index_terminator]
123
                       if l.split(KEYVALUE_SEP_V110)[0] in ALL_KEYVALUES])
124
125
126
127
128
129
        h = cls(ts, **kwargs)
        return h, lines[index_terminator + 1:]

    @classmethod
    def from_text_v110(self, text):
        """
130
        :param str text: text to parse
131
132
133
134
        :returns: tuple of V3BwHeader object and non-header lines
        """
        assert isinstance(text, str)
        return self.from_lines_v110(text.split(LINE_SEP))