~sinzui/juju-ci-tools/upload-retests

979.3.1 by Horacio Durán
Added CI tests for status.
1
from __future__ import print_function
979.3.3 by Horacio Durán
Proper python formatting.
2
979.3.1 by Horacio Durán
Added CI tests for status.
3
import json
4
import re
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
5
from unittest import FunctionTestCase
6
979.3.1 by Horacio Durán
Added CI tests for status.
7
from jujupy import yaml_loads
8
1092.2.2 by Aaron Bentley
Fix lint.
9
10
__metaclass__ = type
11
12
979.3.3 by Horacio Durán
Proper python formatting.
13
# Machine and unit, deprecated in unit
14
AGENT_STATE_KEY = "agent-state"
15
AGENT_VERSION_KEY = "agent-version"
16
DNS_NAME_KEY = "dns-name"
17
INSTANCE_ID_KEY = "instance-id"
18
HARDWARE_KEY = "hardware"
19
SERIES_KEY = "series"
20
STATE_SERVER_MEMBER_STATUS_KEY = "state-server-member-status"
21
# service
22
CHARM_KEY = "charm"
23
EXPOSED_KEY = "exposed"
24
SERVICE_STATUS_KEY = "service-status"
25
# unit
26
WORKLOAD_STATUS_KEY = "workload-status"
27
AGENT_STATUS_KEY = "agent-status"
28
MACHINE_KEY = "machine"
29
PUBLIC_ADDRESS_KEY = "public-address"
30
31
MACHINE_TAB_HEADERS = ['ID', 'STATE', 'VERSION', 'DNS', 'INS-ID', 'SERIES',
32
                       'HARDWARE']
33
UNIT_TAB_HEADERS = ['ID', 'WORKLOAD-STATE', 'AGENT-STATE', 'VERSION',
34
                    'MACHINE', 'PORTS', 'PUBLIC-ADDRESS', 'MESSAGE']
35
SERVICE_TAB_HEADERS = ['NAME', 'STATUS', 'EXPOSED', 'CHARM']
36
979.3.1 by Horacio Durán
Added CI tests for status.
37
38
class StatusTester:
39
    def __init__(self, text="", status_format="yaml"):
40
        self._text = text
41
        self._format = status_format
42
        self.s = globals()["Status%sParser" % status_format.capitalize()](text)
43
44
    @classmethod
45
    def from_text(cls, text, status_format):
46
        return cls(text, status_format)
47
48
    def __unicode__(self):
49
        return self._text
50
51
    def __str__(self):
52
        return self._text
53
979.3.3 by Horacio Durán
Proper python formatting.
54
979.3.1 by Horacio Durán
Added CI tests for status.
55
class ErrNoStatus(Exception):
979.3.6 by Horacio Durán
Addressed extra comments from Curtis.
56
    """An exception for missing juju status."""
979.3.1 by Horacio Durán
Added CI tests for status.
57
979.3.3 by Horacio Durán
Proper python formatting.
58
979.3.1 by Horacio Durán
Added CI tests for status.
59
class ErrMalformedStatus(Exception):
979.3.6 by Horacio Durán
Addressed extra comments from Curtis.
60
    """An exception for unexpected formats of status."""
979.3.1 by Horacio Durán
Added CI tests for status.
61
979.3.3 by Horacio Durán
Proper python formatting.
62
979.3.1 by Horacio Durán
Added CI tests for status.
63
class ErrUntestedStatusOutput(Exception):
979.3.6 by Horacio Durán
Addressed extra comments from Curtis.
64
    """An exception for results returned by status.
65
66
    Status that are known not to be covered by the tests should raise
67
    this exception. """
979.3.1 by Horacio Durán
Added CI tests for status.
68
69
979.3.3 by Horacio Durán
Proper python formatting.
70
class BaseStatusParser:
979.3.1 by Horacio Durán
Added CI tests for status.
71
72
    _expected = set(["environment", "services", "machines"])
73
74
    def __init__(self):
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
75
        self.tc = FunctionTestCase("", description=self.__class__.__name__)
979.3.3 by Horacio Durán
Proper python formatting.
76
        # expected entity storage
979.3.1 by Horacio Durán
Added CI tests for status.
77
        self._machines = dict()
78
        self._services = dict()
79
        self._units = dict()
80
81
        self.parse()
82
83
    def parse(self):
84
        return self._parse()
85
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
86
    def store(self, parsed):
979.3.5 by Horacio Durán
Addressed curtis observations.
87
        # Either there are less items than expected and therefore status
88
        # is returning a subset of what it should or there are more and
89
        # this means there are untested keys in status.
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
90
        self.tc.assertItemsEqual(parsed.keys(), self._expected,
91
                                 "untested items or incomplete status output")
979.3.1 by Horacio Durán
Added CI tests for status.
92
93
        # status of machines.
94
        for machine_id, machine in parsed.get("machines", {}).iteritems():
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
95
            self.tc.assertNotIn(machine_id, self._machines,
96
                                "Machine %s is repeated in yaml"
97
                                " status " % machine_id)
979.3.1 by Horacio Durán
Added CI tests for status.
98
            self._machines[machine_id] = machine
99
100
        # status of services
101
        for service_name, service in parsed.get("services", {}).iteritems():
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
102
            self.tc.assertNotIn(service_name, self._services,
103
                                "Service %s is repeated in yaml "
104
                                "status " % service_name)
979.3.1 by Horacio Durán
Added CI tests for status.
105
            self._services[service_name] = service
106
107
        # status of units
108
        for service_name, service in self._services.iteritems():
109
            for unit_name, unit in service.get("units", {}).iteritems():
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
110
                self.tc.assertNotIn(unit_name, self._units,
111
                                    "Unit %s is repeated in yaml "
112
                                    "status " % unit_name)
979.3.1 by Horacio Durán
Added CI tests for status.
113
                self._units[unit_name] = unit
114
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
115
    def assert_machines_len(self, expected_len):
979.3.6 by Horacio Durán
Addressed extra comments from Curtis.
116
        """Assert that we got as many machines as we where expecting.
979.3.1 by Horacio Durán
Added CI tests for status.
117
118
        :param expected_len: expected quantity of machines.
119
        :type expected_len: int
120
        """
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
121
        self.tc.assertEqual(len(self._machines), expected_len)
979.3.1 by Horacio Durán
Added CI tests for status.
122
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
123
    def assert_machines_ids(self, expected_ids):
979.3.6 by Horacio Durán
Addressed extra comments from Curtis.
124
        """Assert that we got the machines we where expecting.
979.3.1 by Horacio Durán
Added CI tests for status.
125
126
        :param expected_ids: expected ids of machines.
127
        :type expected_ids: tuple
128
        """
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
129
        self.tc.assertItemsEqual(self._machines, expected_ids)
979.3.5 by Horacio Durán
Addressed curtis observations.
130
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
131
    def _machine_key_get(self, machine_id, key):
132
        self.tc.assertIn(machine_id, self._machines,
133
                         "Machine \"%s\" not present in machines" % machine_id)
134
        self.tc.assertIn(key, self._machines[machine_id],
135
                         "Key \"%s\" not present in Machine \"%s\"" %
136
                         (key, machine_id))
979.3.5 by Horacio Durán
Addressed curtis observations.
137
        return self._machines[machine_id][key]
138
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
139
    def assert_machine_agent_state(self, machine_id, state):
140
        value = self._machine_key_get(machine_id, AGENT_STATE_KEY)
141
        self.tc.assertEqual(value, state)
142
143
    def assert_machine_agent_version(self, machine_id, version):
144
        value = self._machine_key_get(machine_id, AGENT_VERSION_KEY)
145
        self.tc.assertEqual(value, version)
146
147
    def assert_machine_dns_name(self, machine_id, dns_name):
148
        value = self._machine_key_get(machine_id, DNS_NAME_KEY)
149
        self.tc.assertEqual(value, dns_name)
150
151
    def assert_machine_instance_id(self, machine_id, instance_id):
152
        value = self._machine_key_get(machine_id, INSTANCE_ID_KEY)
153
        self.tc.assertEqual(value, instance_id)
154
155
    def assert_machine_series(self, machine_id, series):
156
        value = self._machine_key_get(machine_id, SERIES_KEY)
157
        self.tc.assertEqual(value, series)
158
159
    def assert_machine_hardware(self, machine_id, hardware):
160
        value = self._machine_key_get(machine_id, HARDWARE_KEY)
161
        self.tc.assertEqual(value, hardware)
162
163
    def assert_machine_member_status(self, machine_id, member_status):
164
        value = self._machine_key_get(machine_id,
979.3.5 by Horacio Durán
Addressed curtis observations.
165
                                      STATE_SERVER_MEMBER_STATUS_KEY)
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
166
        self.tc.assertEqual(value, member_status)
979.3.5 by Horacio Durán
Addressed curtis observations.
167
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
168
    def _service_key_get(self, service_name, key):
169
        self.tc.assertIn(service_name, self._services,
170
                         "Service \"%s\" not present in services." %
171
                         service_name)
172
        self.tc.assertIn(key, self._services[service_name],
173
                         "Key \"%s\" not present in Service \"%s\"" %
174
                         (key, service_name))
979.3.5 by Horacio Durán
Addressed curtis observations.
175
        return self._services[service_name][key]
979.3.1 by Horacio Durán
Added CI tests for status.
176
979.3.3 by Horacio Durán
Proper python formatting.
177
    # Service status
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
178
    def assert_service_charm(self, service_name, charm):
179
        value = self._service_key_get(service_name, CHARM_KEY)
180
        self.tc.assertEqual(value, charm)
181
182
    def assert_service_exposed(self, service_name, exposed):
183
        value = self._service_key_get(service_name, EXPOSED_KEY)
184
        self.tc.assertEqual(value, exposed)
185
186
    def assert_service_service_status(self, service_name,
979.3.3 by Horacio Durán
Proper python formatting.
187
                                      status={"current": "", "message": ""}):
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
188
        value = self._service_key_get(service_name, SERVICE_STATUS_KEY)
189
        self.tc.assertEqual(value["current"], status["current"])
190
        self.tc.assertEqual(value["message"], status["message"])
979.3.1 by Horacio Durán
Added CI tests for status.
191
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
192
    def _unit_key_get(self, unit_name, key):
193
        self.tc.assertIn(unit_name, self._units,
194
                         "Unit \"%s\" not present in units" % unit_name)
195
        self.tc.assertIn(key, self._units[unit_name],
196
                         "Key \"%s\" not present in Unit \"%s\"" %
197
                         (key, unit_name))
979.3.5 by Horacio Durán
Addressed curtis observations.
198
        return self._units[unit_name][key]
979.3.1 by Horacio Durán
Added CI tests for status.
199
979.3.3 by Horacio Durán
Proper python formatting.
200
    # Units status
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
201
    def assert_unit_workload_status(self, unit_name,
979.3.3 by Horacio Durán
Proper python formatting.
202
                                    status={"current": "", "message": ""}):
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
203
        value = self._unit_key_get(unit_name, WORKLOAD_STATUS_KEY)
204
        self.tc.assertEqual(value["current"], status["current"])
205
        self.tc.assertEqual(value["message"], status["message"])
979.3.1 by Horacio Durán
Added CI tests for status.
206
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
207
    def assert_unit_agent_status(self, unit_name,
979.3.3 by Horacio Durán
Proper python formatting.
208
                                 status={"current": "", "message": ""}):
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
209
        value = self._unit_key_get(unit_name, AGENT_STATUS_KEY)
210
        self.tc.assertEqual(value["current"], status["current"])
979.3.5 by Horacio Durán
Addressed curtis observations.
211
        # Message is optional for unit agents.
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
212
        self.tc.assertEqual(value.get("message", ""), status["message"])
213
214
    def assert_unit_agent_state(self, unit_name, state):
215
        value = self._unit_key_get(unit_name, AGENT_STATE_KEY)
216
        self.tc.assertEqual(value, state)
217
218
    def assert_unit_agent_version(self, unit_name, version):
219
        value = self._unit_key_get(unit_name, AGENT_VERSION_KEY)
220
        self.tc.assertEqual(value, version)
221
222
    def assert_unit_machine(self, unit_name, machine):
223
        value = self._unit_key_get(unit_name, MACHINE_KEY)
224
        self.tc.assertEqual(value, machine)
225
226
    def assert_unit_public_address(self, unit_name, address):
227
        value = self._unit_key_get(unit_name, PUBLIC_ADDRESS_KEY)
228
        self.tc.assertEqual(value, address)
979.3.1 by Horacio Durán
Added CI tests for status.
229
230
231
class StatusYamlParser(BaseStatusParser):
979.3.6 by Horacio Durán
Addressed extra comments from Curtis.
232
    """StatusYamlParser handles parsing of status output in yaml format.
233
234
    To be used by status tester.
979.3.1 by Horacio Durán
Added CI tests for status.
235
    """
236
237
    def __init__(self, yaml=""):
238
        self._yaml = yaml
239
        if yaml == "":
240
            raise ErrNoStatus("Yaml status was empty")
241
        super(StatusYamlParser, self).__init__()
242
243
    def _parse(self):
244
        parsed = yaml_loads(self._yaml)
245
        self.store(parsed)
246
247
248
class StatusJsonParser(BaseStatusParser):
979.3.6 by Horacio Durán
Addressed extra comments from Curtis.
249
    """StatusJSONParser handles parsing of status output in JSON format.
250
251
    To be used by status tester.
979.3.1 by Horacio Durán
Added CI tests for status.
252
    """
253
254
    def __init__(self, json_text=""):
255
        self._json = json_text
256
        if json_text == "":
257
            raise ErrNoStatus("JSON status was empty")
258
        super(StatusJsonParser, self).__init__()
259
260
    def _parse(self):
261
        parsed = json.loads(self._json)
262
        self.store(parsed)
263
264
265
class StatusTabularParser(BaseStatusParser):
979.3.6 by Horacio Durán
Addressed extra comments from Curtis.
266
    """StatusTabularParser handles parsing of status output in Tabular format.
267
268
    To be used by status tester.
979.3.1 by Horacio Durán
Added CI tests for status.
269
    """
270
271
    def __init__(self, tabular_text=""):
272
        self._tabular = tabular_text
273
        if tabular_text == "":
274
            raise ErrNoStatus("tabular status was empty")
275
        super(StatusTabularParser, self).__init__()
276
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
277
    def _normalize_machines(self, header, items):
979.3.1 by Horacio Durán
Added CI tests for status.
278
        nitems = items[:6]
279
        nitems.append(" ".join(items[6:]))
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
280
        self.tc.assertEqual(header, MACHINE_TAB_HEADERS,
281
                            "Unexpected headers for machine:\n"
282
                            "wanted: %s"
283
                            "got: %s" % (MACHINE_TAB_HEADERS, header))
979.3.8 by Horacio Durán
Added missing return
284
        normalized = dict(zip((AGENT_STATE_KEY, AGENT_VERSION_KEY,
1025.1.2 by Aaron Bentley
Fix lint.
285
                               DNS_NAME_KEY, INSTANCE_ID_KEY,
286
                               SERIES_KEY, HARDWARE_KEY),
287
                              nitems[1:]))
979.3.8 by Horacio Durán
Added missing return
288
        return nitems[0], normalized
979.3.1 by Horacio Durán
Added CI tests for status.
289
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
290
    def _normalize_units(self, header, items):
979.3.1 by Horacio Durán
Added CI tests for status.
291
        eid, wlstate, astate, version, machine, paddress = items[:6]
292
        message = " ".join(items[6:])
293
        wlstatus = {"current": wlstate, "message": message,
294
                    "since": "bogus date"}
295
        astatus = {"current": astate, "message": "", "since": "bogus date"}
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
296
        self.tc.assertEqual(header, UNIT_TAB_HEADERS,
297
                            "Unexpected headers for unit.\n"
298
                            "wanted: %s"
299
                            "got: %s" % (UNIT_TAB_HEADERS, header))
979.3.8 by Horacio Durán
Added missing return
300
        normalized = dict(zip((WORKLOAD_STATUS_KEY, AGENT_STATUS_KEY,
979.3.3 by Horacio Durán
Proper python formatting.
301
                              AGENT_VERSION_KEY, MACHINE_KEY,
302
                              PUBLIC_ADDRESS_KEY),
1025.1.2 by Aaron Bentley
Fix lint.
303
                              (wlstatus, astatus, version, machine, paddress)))
979.3.1 by Horacio Durán
Added CI tests for status.
304
979.3.8 by Horacio Durán
Added missing return
305
        return eid, normalized
306
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
307
    def _normalize_services(self, header, items):
979.3.1 by Horacio Durán
Added CI tests for status.
308
        name, status, exposed, charm = items
1031.1.3 by Curtis Hovey
Use tc as a class attribute instead of a decorator to be passed arround.
309
        self.tc.assertEqual(header, SERVICE_TAB_HEADERS,
310
                            "Unexpected headers for service.\n"
311
                            "wanted: %s"
312
                            "got: %s" % (SERVICE_TAB_HEADERS, header))
979.3.8 by Horacio Durán
Added missing return
313
        normalized = dict(zip((CHARM_KEY, EXPOSED_KEY, SERVICE_STATUS_KEY),
979.3.3 by Horacio Durán
Proper python formatting.
314
                              (charm, exposed == "true", {"current": status,
315
                               "message": ""})))
979.3.8 by Horacio Durán
Added missing return
316
        return name, normalized
979.3.1 by Horacio Durán
Added CI tests for status.
317
318
    def _parse(self):
319
        section = re.compile("^\[(\w*)\]")
979.3.3 by Horacio Durán
Proper python formatting.
320
        base = {"environment": "not provided"}
979.3.1 by Horacio Durán
Added CI tests for status.
321
        current_parent = ""
322
        current_headers = []
323
        prev_was_section = False
324
        for line in self._tabular.splitlines():
325
            # parse section
326
            is_section = section.findall(line)
327
            if len(is_section) == 1:
328
                current_parent = is_section[0].lower()
329
                if current_parent != "units":
979.3.3 by Horacio Durán
Proper python formatting.
330
                    base[current_parent] = {}
979.3.1 by Horacio Durán
Added CI tests for status.
331
                prev_was_section = True
332
                continue
333
            # parse headers
334
            if prev_was_section:
335
                prev_was_section = False
336
                current_headers = line.split()
337
                continue
338
339
            # parse content
979.3.3 by Horacio Durán
Proper python formatting.
340
            if current_parent == "" or current_headers == []:
979.3.1 by Horacio Durán
Added CI tests for status.
341
                raise ErrMalformedStatus("Tabular status is malformed")
342
            items = line.split()
343
344
            # separation line
345
            if len(items) == 0:
346
                continue
347
348
            normalize = None
349
            if current_parent == "services":
350
                normalize = self._normalize_services
351
            elif current_parent == "units":
352
                normalize = self._normalize_units
353
            elif current_parent == "machines":
354
                normalize = self._normalize_machines
355
356
            if not normalize:
357
                raise ErrUntestedStatusOutput("%s is not an expected tabular"
979.3.3 by Horacio Durán
Proper python formatting.
358
                                              " status section" %
359
                                              current_parent)
979.3.1 by Horacio Durán
Added CI tests for status.
360
            k, v = normalize(current_headers, items)
361
            if current_parent == "units":
362
                base.setdefault("services", dict())
363
                service = k.split("/")[0]
364
                base["services"][service].setdefault("units", dict())
365
                base["services"][service]["units"][k] = v
366
            else:
367
                base[current_parent][k] = v
368
        self.store(base)