202 lines
7.9 KiB
Python
202 lines
7.9 KiB
Python
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
import reactive.etcd
|
|
|
|
from etcdctl import (
|
|
EtcdCtl,
|
|
etcdctl_command,
|
|
get_connection_string,
|
|
) # noqa
|
|
|
|
from etcd_databag import EtcdDatabag
|
|
|
|
from reactive.etcd import (
|
|
clear_flag,
|
|
endpoint_from_flag,
|
|
force_rejoin_requested,
|
|
force_rejoin,
|
|
GRAFANA_DASHBOARD_NAME,
|
|
host,
|
|
pre_series_upgrade,
|
|
post_series_upgrade,
|
|
register_grafana_dashboard,
|
|
register_prometheus_jobs,
|
|
status,
|
|
)
|
|
|
|
|
|
class TestEtcdCtl:
|
|
@pytest.fixture
|
|
def etcdctl(self):
|
|
return EtcdCtl()
|
|
|
|
def test_register(self, etcdctl):
|
|
with patch("etcdctl.EtcdCtl.run") as spcm:
|
|
etcdctl.register(
|
|
{
|
|
"cluster_address": "127.0.0.1",
|
|
"unit_name": "etcd0",
|
|
"management_port": "1313",
|
|
"leader_address": "http://127.1.1.1:1212",
|
|
}
|
|
)
|
|
spcm.assert_called_with(
|
|
"member add etcd0 https://127.0.0.1:1313",
|
|
api=2,
|
|
endpoints="http://127.1.1.1:1212",
|
|
)
|
|
|
|
def test_unregister(self, etcdctl):
|
|
with patch("etcdctl.EtcdCtl.run") as spcm:
|
|
etcdctl.unregister("br1212121212")
|
|
|
|
spcm.assert_called_with(
|
|
["member", "remove", "br1212121212"], api=2, endpoints=None
|
|
)
|
|
|
|
def test_member_list(self, etcdctl):
|
|
with patch("etcdctl.EtcdCtl.run") as comock:
|
|
comock.return_value = "7dc8404daa2b8ca0: name=etcd22 peerURLs=https://10.113.96.220:2380 clientURLs=https://10.113.96.220:2379\n" # noqa
|
|
members = etcdctl.member_list()
|
|
assert members["etcd22"]["unit_id"] == "7dc8404daa2b8ca0"
|
|
assert members["etcd22"]["peer_urls"] == "https://10.113.96.220:2380"
|
|
assert members["etcd22"]["client_urls"] == "https://10.113.96.220:2379"
|
|
|
|
def test_member_list_with_unstarted_member(self, etcdctl):
|
|
"""Validate we receive information only about members we can parse
|
|
from the current status string"""
|
|
# 57fa5c39949c138e[unstarted]: peerURLs=http://10.113.96.80:2380
|
|
# bb0f83ebb26386f7: name=etcd9 peerURLs=https://10.113.96.178:2380 clientURLs=https://10.113.96.178:2379
|
|
with patch("etcdctl.EtcdCtl.run") as comock:
|
|
comock.return_value = "57fa5c39949c138e[unstarted]: peerURLs=http://10.113.96.80:2380]\nbb0f83ebb26386f7: name=etcd9 peerURLs=https://10.113.96.178:2380 clientURLs=https://10.113.96.178:2379\n" # noqa
|
|
members = etcdctl.member_list()
|
|
assert members["etcd9"]["unit_id"] == "bb0f83ebb26386f7"
|
|
assert members["etcd9"]["peer_urls"] == "https://10.113.96.178:2380"
|
|
assert members["etcd9"]["client_urls"] == "https://10.113.96.178:2379"
|
|
assert "unstarted" in members.keys()
|
|
assert members["unstarted"]["unit_id"] == "57fa5c39949c138e"
|
|
assert "10.113.96.80:2380" in members["unstarted"]["peer_urls"]
|
|
|
|
def test_etcd_v2_version(self, etcdctl):
|
|
"""Validate that etcdctl can parse versions for both etcd v2 and
|
|
etcd v3"""
|
|
# Define fixtures of what we expect for the version output
|
|
etcdctl_2_version = b"etcdctl version 2.3.8\n"
|
|
with patch("etcdctl.check_output") as comock:
|
|
comock.return_value = etcdctl_2_version
|
|
ver = etcdctl.version()
|
|
assert ver == "2.3.8"
|
|
|
|
def test_etcd_v3_version(self, etcdctl):
|
|
"""Validate that etcdctl can parse version for etcdctl v3"""
|
|
etcdctl_3_version = b"etcdctl version: 3.0.17\nAPI version: 2\n"
|
|
with patch("etcdctl.check_output") as comock:
|
|
comock.return_value = etcdctl_3_version
|
|
ver = etcdctl.version()
|
|
assert ver == "3.0.17"
|
|
|
|
def test_etcdctl_command(self):
|
|
"""Validate sane results from etcdctl_command"""
|
|
assert isinstance(etcdctl_command(), str)
|
|
|
|
def test_etcdctl_environment_with_version_2(self, etcdctl):
|
|
"""Validate that environment gets set correctly
|
|
spoiler alert; it shouldn't be set when passing --version"""
|
|
with patch("etcdctl.check_output") as comock:
|
|
etcdctl.run("member list", api=2)
|
|
api_version = comock.call_args[1].get("env").get("ETCDCTL_API")
|
|
assert api_version == "2"
|
|
|
|
def test_etcdctl_environment_with_version_3(self, etcdctl):
|
|
"""Validate that environment gets set correctly
|
|
spoiler alert; it shouldn't be set when passing --version"""
|
|
with patch("etcdctl.check_output") as comock:
|
|
etcdctl.run("member list", api=3)
|
|
api_version = comock.call_args[1].get("env").get("ETCDCTL_API")
|
|
assert api_version == "3"
|
|
|
|
def test_get_connection_string(self):
|
|
"""Validate the get_connection_string function
|
|
gives a sane return.
|
|
"""
|
|
assert get_connection_string(["1.1.1.1"], "1111") == "https://1.1.1.1:1111"
|
|
|
|
@patch("reactive.etcd.render_grafana_dashboard")
|
|
def test_register_grafana_dashboard(self, mock_dashboard_render):
|
|
"""Register grafana dashboard."""
|
|
dashboard_json = {"foo": "bar"}
|
|
mock_dashboard_render.return_value = dashboard_json
|
|
grafana = MagicMock()
|
|
endpoint_from_flag.return_value = grafana
|
|
|
|
register_grafana_dashboard()
|
|
|
|
mock_dashboard_render.assert_called_once()
|
|
grafana.register_dashboard.assert_called_with(
|
|
name=GRAFANA_DASHBOARD_NAME, dashboard=dashboard_json
|
|
)
|
|
reactive.etcd.set_flag.assert_called_with("grafana.configured")
|
|
|
|
def test_register_prometheus_job(self, mocker):
|
|
"""Test successful registration of prometheus job."""
|
|
ingress_address = "10.0.0.1"
|
|
port = "2379"
|
|
targets = ["{}:{}".format(ingress_address, port)]
|
|
prometheus_mock = MagicMock()
|
|
etcd_cluster_mock = MagicMock()
|
|
job_data = {"scheme": "https", "static_configs": [{"targets": targets}]}
|
|
|
|
etcd_cluster_mock.get_db_ingress_addresses.return_value = []
|
|
endpoint_from_flag.side_effect = [prometheus_mock, etcd_cluster_mock]
|
|
mocker.patch.object(
|
|
reactive.etcd, "get_ingress_address", return_value=ingress_address
|
|
)
|
|
reactive.etcd.config.return_value = port
|
|
|
|
register_prometheus_jobs()
|
|
|
|
prometheus_mock.register_job.assert_called_with(
|
|
job_name="etcd", job_data=job_data
|
|
)
|
|
reactive.etcd.set_flag.assert_called_with("prometheus.configured")
|
|
|
|
def test_series_upgrade(self):
|
|
assert host.service_pause.call_count == 0
|
|
assert host.service_resume.call_count == 0
|
|
assert status.blocked.call_count == 0
|
|
pre_series_upgrade()
|
|
assert host.service_pause.call_count == 1
|
|
assert host.service_resume.call_count == 0
|
|
assert status.blocked.call_count == 1
|
|
post_series_upgrade()
|
|
assert host.service_pause.call_count == 1
|
|
assert host.service_resume.call_count == 1
|
|
assert status.blocked.call_count == 1
|
|
|
|
@patch("reactive.etcd.force_rejoin")
|
|
@patch("reactive.etcd.check_cluster_health")
|
|
def test_rejoin_trigger(self, cluster_health_mock, rejoin_mock):
|
|
"""Test that unit will trigger force_rejoin on new request"""
|
|
force_rejoin_requested()
|
|
|
|
rejoin_mock.assert_called_once()
|
|
cluster_health_mock.assert_called_once()
|
|
|
|
@patch("reactive.etcd.register_node_with_leader")
|
|
@patch("os.path.exists")
|
|
@patch("shutil.rmtree")
|
|
@patch("os.path.join")
|
|
@patch("time.sleep")
|
|
def test_force_rejoin(self, sleep, path_join, rmtree, path_exists, register_node):
|
|
"""Test that force_rejoin performs required steps."""
|
|
data_dir = "/foo/bar"
|
|
path_exists.return_value = True
|
|
path_join.return_value = data_dir
|
|
force_rejoin()
|
|
|
|
host.service_stop.assert_called_with(EtcdDatabag().etcd_daemon)
|
|
clear_flag.assert_called_with("etcd.registered")
|
|
rmtree.assert_called_with(data_dir)
|
|
register_node.assert_called()
|