1
from __future__ import print_function
5
from unittest import FunctionTestCase
7
from jujupy import yaml_loads
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"
20
STATE_SERVER_MEMBER_STATUS_KEY = "state-server-member-status"
23
EXPOSED_KEY = "exposed"
24
SERVICE_STATUS_KEY = "service-status"
26
WORKLOAD_STATUS_KEY = "workload-status"
27
AGENT_STATUS_KEY = "agent-status"
28
MACHINE_KEY = "machine"
29
PUBLIC_ADDRESS_KEY = "public-address"
31
MACHINE_TAB_HEADERS = ['ID', 'STATE', 'VERSION', 'DNS', 'INS-ID', 'SERIES',
33
UNIT_TAB_HEADERS = ['ID', 'WORKLOAD-STATE', 'AGENT-STATE', 'VERSION',
34
'MACHINE', 'PORTS', 'PUBLIC-ADDRESS', 'MESSAGE']
35
SERVICE_TAB_HEADERS = ['NAME', 'STATUS', 'EXPOSED', 'CHARM']
39
def __init__(self, text="", status_format="yaml"):
41
self._format = status_format
42
self.s = globals()["Status%sParser" % status_format.capitalize()](text)
45
def from_text(cls, text, status_format):
46
return cls(text, status_format)
48
def __unicode__(self):
55
class ErrNoStatus(Exception):
56
"""An exception for missing juju status."""
59
class ErrMalformedStatus(Exception):
60
"""An exception for unexpected formats of status."""
63
class ErrUntestedStatusOutput(Exception):
64
"""An exception for results returned by status.
66
Status that are known not to be covered by the tests should raise
70
class BaseStatusParser:
72
_expected = set(["environment", "services", "machines"])
75
self.tc = FunctionTestCase("", description=self.__class__.__name__)
76
# expected entity storage
77
self._machines = dict()
78
self._services = dict()
86
def store(self, parsed):
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.
90
self.tc.assertItemsEqual(parsed.keys(), self._expected,
91
"untested items or incomplete status output")
94
for machine_id, machine in parsed.get("machines", {}).iteritems():
95
self.tc.assertNotIn(machine_id, self._machines,
96
"Machine %s is repeated in yaml"
97
" status " % machine_id)
98
self._machines[machine_id] = machine
101
for service_name, service in parsed.get("services", {}).iteritems():
102
self.tc.assertNotIn(service_name, self._services,
103
"Service %s is repeated in yaml "
104
"status " % service_name)
105
self._services[service_name] = service
108
for service_name, service in self._services.iteritems():
109
for unit_name, unit in service.get("units", {}).iteritems():
110
self.tc.assertNotIn(unit_name, self._units,
111
"Unit %s is repeated in yaml "
112
"status " % unit_name)
113
self._units[unit_name] = unit
115
def assert_machines_len(self, expected_len):
116
"""Assert that we got as many machines as we where expecting.
118
:param expected_len: expected quantity of machines.
119
:type expected_len: int
121
self.tc.assertEqual(len(self._machines), expected_len)
123
def assert_machines_ids(self, expected_ids):
124
"""Assert that we got the machines we where expecting.
126
:param expected_ids: expected ids of machines.
127
:type expected_ids: tuple
129
self.tc.assertItemsEqual(self._machines, expected_ids)
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\"" %
137
return self._machines[machine_id][key]
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)
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)
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)
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)
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)
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)
163
def assert_machine_member_status(self, machine_id, member_status):
164
value = self._machine_key_get(machine_id,
165
STATE_SERVER_MEMBER_STATUS_KEY)
166
self.tc.assertEqual(value, member_status)
168
def _service_key_get(self, service_name, key):
169
self.tc.assertIn(service_name, self._services,
170
"Service \"%s\" not present in services." %
172
self.tc.assertIn(key, self._services[service_name],
173
"Key \"%s\" not present in Service \"%s\"" %
175
return self._services[service_name][key]
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)
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)
186
def assert_service_service_status(self, service_name,
187
status={"current": "", "message": ""}):
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"])
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\"" %
198
return self._units[unit_name][key]
201
def assert_unit_workload_status(self, unit_name,
202
status={"current": "", "message": ""}):
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"])
207
def assert_unit_agent_status(self, unit_name,
208
status={"current": "", "message": ""}):
209
value = self._unit_key_get(unit_name, AGENT_STATUS_KEY)
210
self.tc.assertEqual(value["current"], status["current"])
211
# Message is optional for unit agents.
212
self.tc.assertEqual(value.get("message", ""), status["message"])
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)
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)
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)
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)
231
class StatusYamlParser(BaseStatusParser):
232
"""StatusYamlParser handles parsing of status output in yaml format.
234
To be used by status tester.
237
def __init__(self, yaml=""):
240
raise ErrNoStatus("Yaml status was empty")
241
super(StatusYamlParser, self).__init__()
244
parsed = yaml_loads(self._yaml)
248
class StatusJsonParser(BaseStatusParser):
249
"""StatusJSONParser handles parsing of status output in JSON format.
251
To be used by status tester.
254
def __init__(self, json_text=""):
255
self._json = json_text
257
raise ErrNoStatus("JSON status was empty")
258
super(StatusJsonParser, self).__init__()
261
parsed = json.loads(self._json)
265
class StatusTabularParser(BaseStatusParser):
266
"""StatusTabularParser handles parsing of status output in Tabular format.
268
To be used by status tester.
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__()
277
def _normalize_machines(self, header, items):
279
nitems.append(" ".join(items[6:]))
280
self.tc.assertEqual(header, MACHINE_TAB_HEADERS,
281
"Unexpected headers for machine:\n"
283
"got: %s" % (MACHINE_TAB_HEADERS, header))
284
normalized = dict(zip((AGENT_STATE_KEY, AGENT_VERSION_KEY,
285
DNS_NAME_KEY, INSTANCE_ID_KEY,
286
SERIES_KEY, HARDWARE_KEY),
288
return nitems[0], normalized
290
def _normalize_units(self, header, items):
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"}
296
self.tc.assertEqual(header, UNIT_TAB_HEADERS,
297
"Unexpected headers for unit.\n"
299
"got: %s" % (UNIT_TAB_HEADERS, header))
300
normalized = dict(zip((WORKLOAD_STATUS_KEY, AGENT_STATUS_KEY,
301
AGENT_VERSION_KEY, MACHINE_KEY,
303
(wlstatus, astatus, version, machine, paddress)))
305
return eid, normalized
307
def _normalize_services(self, header, items):
308
name, status, exposed, charm = items
309
self.tc.assertEqual(header, SERVICE_TAB_HEADERS,
310
"Unexpected headers for service.\n"
312
"got: %s" % (SERVICE_TAB_HEADERS, header))
313
normalized = dict(zip((CHARM_KEY, EXPOSED_KEY, SERVICE_STATUS_KEY),
314
(charm, exposed == "true", {"current": status,
316
return name, normalized
319
section = re.compile("^\[(\w*)\]")
320
base = {"environment": "not provided"}
323
prev_was_section = False
324
for line in self._tabular.splitlines():
326
is_section = section.findall(line)
327
if len(is_section) == 1:
328
current_parent = is_section[0].lower()
329
if current_parent != "units":
330
base[current_parent] = {}
331
prev_was_section = True
335
prev_was_section = False
336
current_headers = line.split()
340
if current_parent == "" or current_headers == []:
341
raise ErrMalformedStatus("Tabular status is malformed")
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
357
raise ErrUntestedStatusOutput("%s is not an expected tabular"
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
367
base[current_parent][k] = v