Charmed-Kubernetes/nrpe/mod/charmhelpers/tests/contrib/charmhelpers/test_charmhelpers.py

275 lines
13 KiB
Python

# Tests for Python charm helpers.
import unittest
import yaml
from testtools import TestCase
from six import StringIO
import sys
# Path hack to ensure we test the local code, not a version installed in
# /usr/local/lib. This is necessary since /usr/local/lib is prepended before
# what is specified in PYTHONPATH.
sys.path.insert(0, 'helpers/python')
from charmhelpers.contrib import charmhelpers # noqa
class CharmHelpersTestCase(TestCase):
"""A basic test case for Python charm helpers."""
def _patch_command(self, replacement_command):
"""Monkeypatch charmhelpers.command for testing purposes.
:param replacement_command: The replacement Callable for
command().
"""
self.patch(charmhelpers, 'command', lambda *args: replacement_command)
def _make_juju_status_dict(self, num_units=1,
service_name='test-service',
unit_state='pending',
machine_state='not-started'):
"""Generate valid juju status dict and return it."""
machine_data = {}
# The 0th machine is the Zookeeper.
machine_data[0] = {'dns-name': 'zookeeper.example.com',
'instance-id': 'machine0',
'state': 'not-started'}
service_data = {'charm': 'local:precise/{}-1'.format(service_name),
'relations': {},
'units': {}}
for i in range(num_units):
# The machine is always going to be i+1 because there
# will always be num_units+1 machines.
machine_number = i + 1
unit_machine_data = {
'dns-name': 'machine{}.example.com'.format(machine_number),
'instance-id': 'machine{}'.format(machine_number),
'state': machine_state,
'instance-state': machine_state}
machine_data[machine_number] = unit_machine_data
unit_data = {
'machine': machine_number,
'public-address':
'{}-{}.example.com'.format(service_name, i),
'relations': {'db': {'state': 'up'}},
'agent-state': unit_state}
service_data['units']['{}/{}'.format(service_name, i)] = (
unit_data)
juju_status_data = {'machines': machine_data,
'services': {service_name: service_data}}
return juju_status_data
def _make_juju_status_yaml(self, num_units=1,
service_name='test-service',
unit_state='pending',
machine_state='not-started'):
"""Convert the dict returned by `_make_juju_status_dict` to YAML."""
return yaml.dump(
self._make_juju_status_dict(
num_units, service_name, unit_state, machine_state))
def test_make_charm_config_file(self):
# make_charm_config_file() writes the passed configuration to a
# temporary file as YAML.
charm_config = {'foo': 'bar',
'spam': 'eggs',
'ham': 'jam'}
# make_charm_config_file() returns the file object so that it
# can be garbage collected properly.
charm_config_file = charmhelpers.make_charm_config_file(charm_config)
with open(charm_config_file.name) as config_in:
written_config = config_in.read()
self.assertEqual(yaml.dump(charm_config), written_config)
def test_unit_info(self):
# unit_info returns requested data about a given service.
juju_yaml = self._make_juju_status_yaml()
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
self.assertEqual(
'pending',
charmhelpers.unit_info('test-service', 'agent-state'))
def test_unit_info_returns_empty_for_nonexistent_service(self):
# If the service passed to unit_info() has not yet started (or
# otherwise doesn't exist), unit_info() will return an empty
# string.
juju_yaml = "services: {}"
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
self.assertEqual(
'', charmhelpers.unit_info('test-service', 'state'))
def test_unit_info_accepts_data(self):
# It's possible to pass a `data` dict, containing the parsed
# result of juju status, to unit_info().
juju_status_data = yaml.safe_load(
self._make_juju_status_yaml())
self.patch(charmhelpers, 'juju_status', lambda: None)
service_data = juju_status_data['services']['test-service']
unit_info_dict = service_data['units']['test-service/0']
for key, value in unit_info_dict.items():
item_info = charmhelpers.unit_info(
'test-service', key, data=juju_status_data)
self.assertEqual(value, item_info)
def test_unit_info_returns_first_unit_by_default(self):
# By default, unit_info() just returns the value of the
# requested item for the first unit in a service.
juju_yaml = self._make_juju_status_yaml(num_units=2)
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
unit_address = charmhelpers.unit_info(
'test-service', 'public-address')
self.assertEqual('test-service-0.example.com', unit_address)
def test_unit_info_accepts_unit_name(self):
# By default, unit_info() just returns the value of the
# requested item for the first unit in a service. However, it's
# possible to pass a unit name to it, too.
juju_yaml = self._make_juju_status_yaml(num_units=2)
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
unit_address = charmhelpers.unit_info(
'test-service', 'public-address', unit='test-service/1')
self.assertEqual('test-service-1.example.com', unit_address)
def test_get_machine_data(self):
# get_machine_data() returns a dict containing the machine data
# parsed from juju status.
juju_yaml = self._make_juju_status_yaml()
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
machine_0_data = charmhelpers.get_machine_data()[0]
self.assertEqual('zookeeper.example.com', machine_0_data['dns-name'])
def test_wait_for_machine_returns_if_machine_up(self):
# If wait_for_machine() is called and the machine(s) it is
# waiting for are already up, it will return.
juju_yaml = self._make_juju_status_yaml(machine_state='running')
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
machines, time_taken = charmhelpers.wait_for_machine(timeout=1)
self.assertEqual(1, machines)
def test_wait_for_machine_times_out(self):
# If the machine that wait_for_machine is waiting for isn't
# 'running' before the passed timeout is reached,
# wait_for_machine will raise an error.
juju_yaml = self._make_juju_status_yaml()
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
self.assertRaises(
RuntimeError, charmhelpers.wait_for_machine, timeout=0)
def test_wait_for_machine_always_returns_if_running_locally(self):
# If juju is actually running against a local LXC container,
# wait_for_machine will always return.
juju_status_dict = self._make_juju_status_dict()
# We'll update the 0th machine to make it look like it's an LXC
# container.
juju_status_dict['machines'][0]['dns-name'] = 'localhost'
juju_yaml = yaml.dump(juju_status_dict)
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
machines, time_taken = charmhelpers.wait_for_machine(timeout=1)
# wait_for_machine will always return 1 machine started here,
# since there's only one machine to start.
self.assertEqual(1, machines)
# time_taken will be 0, since no actual waiting happened.
self.assertEqual(0, time_taken)
def test_wait_for_machine_waits_for_multiple_machines(self):
# wait_for_machine can be told to wait for multiple machines.
juju_yaml = self._make_juju_status_yaml(
num_units=2, machine_state='running')
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
machines, time_taken = charmhelpers.wait_for_machine(num_machines=2)
self.assertEqual(2, machines)
def test_wait_for_unit_returns_if_unit_started(self):
# wait_for_unit() will return if the service it's waiting for is
# already up.
juju_yaml = self._make_juju_status_yaml(
unit_state='started', machine_state='running')
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
charmhelpers.wait_for_unit('test-service', timeout=0)
def test_wait_for_unit_raises_error_on_error_state(self):
# If the unit is in some kind of error state, wait_for_unit will
# raise a RuntimeError.
juju_yaml = self._make_juju_status_yaml(
unit_state='start-error', machine_state='running')
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
self.assertRaises(RuntimeError, charmhelpers.wait_for_unit,
'test-service', timeout=0)
def test_wait_for_unit_raises_error_on_timeout(self):
# If the unit does not start before the timeout is reached,
# wait_for_unit will raise a RuntimeError.
juju_yaml = self._make_juju_status_yaml(
unit_state='pending', machine_state='running')
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
self.assertRaises(RuntimeError, charmhelpers.wait_for_unit,
'test-service', timeout=0)
def test_wait_for_relation_returns_if_relation_up(self):
# wait_for_relation() waits for relations to come up. If a
# relation is already 'up', wait_for_relation() will return
# immediately.
juju_yaml = self._make_juju_status_yaml(
unit_state='started', machine_state='running')
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
charmhelpers.wait_for_relation('test-service', 'db', timeout=0)
def test_wait_for_relation_times_out_if_relation_not_present(self):
# If a relation does not exist at all before a timeout is
# reached, wait_for_relation() will raise a RuntimeError.
juju_dict = self._make_juju_status_dict(
unit_state='started', machine_state='running')
units = juju_dict['services']['test-service']['units']
# We'll remove all the relations for test-service for this test.
units['test-service/0']['relations'] = {}
juju_dict['services']['test-service']['units'] = units
juju_yaml = yaml.dump(juju_dict)
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
self.assertRaises(
RuntimeError, charmhelpers.wait_for_relation, 'test-service',
'db', timeout=0)
def test_wait_for_relation_times_out_if_relation_not_up(self):
# If a relation does not transition to an 'up' state, before a
# timeout is reached, wait_for_relation() will raise a
# RuntimeError.
juju_dict = self._make_juju_status_dict(
unit_state='started', machine_state='running')
units = juju_dict['services']['test-service']['units']
units['test-service/0']['relations']['db']['state'] = 'down'
juju_dict['services']['test-service']['units'] = units
juju_yaml = yaml.dump(juju_dict)
self.patch(charmhelpers, 'juju_status', lambda: juju_yaml)
self.assertRaises(
RuntimeError, charmhelpers.wait_for_relation, 'test-service',
'db', timeout=0)
def test_wait_for_page_contents_returns_if_contents_available(self):
# wait_for_page_contents() will wait until a given string is
# contained within the results of a given url and will return
# once it does.
# We need to patch the charmhelpers instance of urlopen so that
# it doesn't try to connect out.
test_content = "Hello, world."
self.patch(charmhelpers, 'urlopen',
lambda *args: StringIO(test_content))
charmhelpers.wait_for_page_contents(
'http://example.com', test_content, timeout=0)
def test_wait_for_page_contents_times_out(self):
# If the desired contents do not appear within the page before
# the specified timeout, wait_for_page_contents() will raise a
# RuntimeError.
# We need to patch the charmhelpers instance of urlopen so that
# it doesn't try to connect out.
self.patch(charmhelpers, 'urlopen',
lambda *args: StringIO("This won't work."))
self.assertRaises(
RuntimeError, charmhelpers.wait_for_page_contents,
'http://example.com', "This will error", timeout=0)
if __name__ == '__main__':
unittest.main()