diff --git a/tests/conftest.py b/tests/conftest.py index 1f54cf2..001a11a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,9 +12,17 @@ import py import pytest import requests +from packaging import version collect_ignore = [] +CONSUL_BINARIES = { + "1.1.0": "consul", + "1.13.8": "consul-1.13.8", + "1.15.4": "consul-1.15.4", + "1.16.1": "consul-1.16.1", +} + def get_free_ports(num, host=None): if not host: @@ -31,7 +39,7 @@ def get_free_ports(num, host=None): return ret -def start_consul_instance(acl_master_token=None): +def start_consul_instance(binary_name, acl_master_token=None): """ starts a consul instance. if acl_master_token is None, acl will be disabled for this server, otherwise it will be enabled and the master token will be @@ -52,8 +60,8 @@ def start_consul_instance(acl_master_token=None): tmpdir.chdir() (system, _node, _release, _version, _machine, _processor) = platform.uname() - postfix = "osx" if system == "Darwin" else "linux64" - binary = os.path.join(os.path.dirname(__file__), "consul." + postfix) + ext = "osx" if system == "Darwin" else "linux64" + binary = os.path.join(os.path.dirname(__file__), f"{binary_name}.{ext}") command = "{bin} agent -dev -bind=127.0.0.1 -config-dir=." command = command.format(bin=binary).strip() command = shlex.split(command) @@ -97,31 +105,51 @@ def clean_consul(port): requests.put(base_uri + f"agent/service/deregister/{s}", timeout=10) -@pytest.fixture(scope="module") -def consul_instance(): - p, port = start_consul_instance() - yield port - p.terminate() +def get_consul_version(port): + base_uri = f"http://127.0.0.1:{port}/v1/" + response = requests.get(base_uri + "agent/self", timeout=10) + return response.json()["Config"]["Version"].strip() -@pytest.fixture() -def consul_port(consul_instance): - port = consul_instance - yield port - clean_consul(port) +@pytest.fixture(scope="module", params=CONSUL_BINARIES.keys()) +def consul_instance(request): + p, port = start_consul_instance(binary_name=CONSUL_BINARIES[request.param]) + version = get_consul_version(port) + yield port, version + p.terminate() -@pytest.fixture(scope="module") -def acl_consul_instance(): +@pytest.fixture(scope="module", params=CONSUL_BINARIES.keys()) +def acl_consul_instance(request): acl_master_token = uuid.uuid4().hex - p, port = start_consul_instance(acl_master_token=acl_master_token) - yield port, acl_master_token + p, port = start_consul_instance(binary_name=CONSUL_BINARIES[request.param], acl_master_token=acl_master_token) + version = get_consul_version(port) + yield port, acl_master_token, version p.terminate() +@pytest.fixture() +def consul_port(consul_instance): + port, version = consul_instance + yield port, version + clean_consul(port) + + @pytest.fixture() def acl_consul(acl_consul_instance): - ACLConsul = collections.namedtuple("ACLConsul", ["port", "token"]) - port, token = acl_consul_instance - yield ACLConsul(port, token) + ACLConsul = collections.namedtuple("ACLConsul", ["port", "token", "version"]) + port, token, version = acl_consul_instance + yield ACLConsul(port, token, version) clean_consul(port) + + +def should_skip(version_str, comparator, ref_version_str): + v = version.parse(version_str) + ref_version = version.parse(ref_version_str) + + if comparator == "<" and v >= ref_version: + return f"Requires version {comparator} {ref_version_str}" + if comparator == ">" and v <= ref_version: + return f"Requires version {comparator} {ref_version_str}" + # You can add other comparators if needed + return None diff --git a/tests/consul-1.13.8.linux64 b/tests/consul-1.13.8.linux64 new file mode 100755 index 0000000..f8d11c5 Binary files /dev/null and b/tests/consul-1.13.8.linux64 differ diff --git a/tests/consul-1.15.4.linux64 b/tests/consul-1.15.4.linux64 new file mode 100755 index 0000000..5d5fb10 Binary files /dev/null and b/tests/consul-1.15.4.linux64 differ diff --git a/tests/consul-1.16.1.linux64 b/tests/consul-1.16.1.linux64 new file mode 100755 index 0000000..3680eb0 Binary files /dev/null and b/tests/consul-1.16.1.linux64 differ diff --git a/tests/test_aio.py b/tests/test_aio.py index 60fb212..5db5ee9 100644 --- a/tests/test_aio.py +++ b/tests/test_aio.py @@ -3,30 +3,34 @@ import struct import pytest +from packaging import version import consul import consul.aio +from tests.conftest import should_skip Check = consul.Check @pytest.fixture async def consul_obj(consul_port): + consul_port, consul_version = consul_port c = consul.aio.Consul(port=consul_port) - yield c + yield c, consul_version await c.close() @pytest.fixture async def consul_acl_obj(acl_consul): - c = consul.aio.Consul(port=acl_consul.port, token=acl_consul.token) - yield c + consul_port, token, consul_version = acl_consul + c = consul.aio.Consul(port=consul_port, token=token) + yield c, consul_version await c.close() class TestAsyncioConsul: async def test_kv(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj _index, data = await c.kv.get("foo") assert data is None response = await c.kv.put("foo", "bar") @@ -35,19 +39,19 @@ async def test_kv(self, consul_obj): assert data["Value"] == b"bar" async def test_consul_ctor(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj await c.kv.put("foo", struct.pack("i", 1000)) _index, data = await c.kv.get("foo") assert struct.unpack("i", data["Value"]) == (1000,) async def test_kv_binary(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj await c.kv.put("foo", struct.pack("i", 1000)) _index, data = await c.kv.get("foo") assert struct.unpack("i", data["Value"]) == (1000,) async def test_kv_missing(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj async def put(): await asyncio.sleep(2.0 / 100) @@ -63,7 +67,7 @@ async def put(): await c.close() async def test_kv_put_flags(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj await c.kv.put("foo", "bar") _index, data = await c.kv.get("foo") assert data["Flags"] == 0 @@ -74,7 +78,7 @@ async def test_kv_put_flags(self, consul_obj): assert data["Flags"] == 50 async def test_kv_delete(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj await c.kv.put("foo1", "1") await c.kv.put("foo2", "2") await c.kv.put("foo3", "3") @@ -91,7 +95,7 @@ async def test_kv_delete(self, consul_obj): assert data is None async def test_kv_subscribe(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj async def put(): await asyncio.sleep(1.0 / 100) @@ -106,7 +110,7 @@ async def put(): await fut async def test_transaction(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj value = base64.b64encode(b"1").decode("utf8") d = {"KV": {"Verb": "set", "Key": "asdf", "Value": value}} r = await c.txn.put([d]) @@ -117,32 +121,53 @@ async def test_transaction(self, consul_obj): assert r["Results"][0]["KV"]["Value"] == value async def test_agent_services(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj + EXPECTED = { + "v1": { + "foo": { + "Port": 0, + "ID": "foo", + "CreateIndex": 0, + "ModifyIndex": 0, + "EnableTagOverride": False, + "Service": "foo", + "Tags": [], + "Meta": {}, + "Address": "", + } + }, + "v2": { + "foo": { + "Address": "", + "Datacenter": "dc1", + "EnableTagOverride": False, + "ID": "foo", + "Meta": {}, + "Port": 0, + "Service": "foo", + "Tags": [], + "Weights": {"Passing": 1, "Warning": 1}, + } + }, + } + expected = EXPECTED["v1"] + if version.parse(_consul_version) >= version.parse("1.13.8"): + expected = EXPECTED["v2"] + services = await c.agent.services() assert services == {} response = await c.agent.service.register("foo") assert response is True services = await c.agent.services() - assert services == { - "foo": { - "Port": 0, - "ID": "foo", - "CreateIndex": 0, - "ModifyIndex": 0, - "EnableTagOverride": False, - "Service": "foo", - "Tags": [], - "Meta": {}, - "Address": "", - }, - } + + assert services == expected response = await c.agent.service.deregister("foo") assert response is True services = await c.agent.services() assert services == {} async def test_catalog(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj async def register(): await asyncio.sleep(1.0 / 100) @@ -167,7 +192,7 @@ async def register(): await fut async def test_session(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj async def register(): await asyncio.sleep(1.0 / 100) @@ -188,8 +213,13 @@ async def register(): assert services == [] await fut - async def test_acl(self, consul_acl_obj): - c = consul_acl_obj + async def test_acl_old(self, consul_acl_obj): + c, _consul_version = consul_acl_obj + + # TODO + + if should_skip(_consul_version, "<", "1.11.0"): + pytest.skip("Endpoint /v1/acl/create for the legacy ACL system was removed in Consul 1.11.") rules = """ key "" { diff --git a/tests/test_std.py b/tests/test_std.py index d862d82..2987863 100644 --- a/tests/test_std.py +++ b/tests/test_std.py @@ -4,9 +4,11 @@ import time import pytest +from packaging import version import consul import consul.std +from tests.conftest import should_skip Check = consul.Check @@ -20,13 +22,14 @@ def test_uri(self): @pytest.fixture def consul_obj(consul_port): + consul_port, consul_version = consul_port c = consul.std.Consul(port=consul_port) - yield c + yield c, consul_version class TestConsul: def test_kv(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj _index, data = c.kv.get("foo") assert data is None assert c.kv.put("foo", "bar") is True @@ -34,14 +37,14 @@ def test_kv(self, consul_obj): assert data["Value"] == b"bar" def test_kv_wait(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj assert c.kv.put("foo", "bar") is True index, _data = c.kv.get("foo") check, _data = c.kv.get("foo", index=index, wait="20ms") assert index == check def test_kv_encoding(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj # test binary c.kv.put("foo", struct.pack("i", 1000)) @@ -67,7 +70,7 @@ def test_kv_encoding(self, consul_obj): pytest.raises(AssertionError, c.kv.put, "foo", {1: 2}) def test_kv_put_cas(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj assert c.kv.put("foo", "bar", cas=50) is False assert c.kv.put("foo", "bar", cas=0) is True _index, data = c.kv.get("foo") @@ -78,7 +81,7 @@ def test_kv_put_cas(self, consul_obj): assert data["Value"] == b"bar2" def test_kv_put_flags(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj c.kv.put("foo", "bar") _index, data = c.kv.get("foo") assert data["Flags"] == 0 @@ -88,7 +91,7 @@ def test_kv_put_flags(self, consul_obj): assert data["Flags"] == 50 def test_kv_recurse(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj _index, data = c.kv.get("foo/", recurse=True) assert data is None @@ -104,7 +107,7 @@ def test_kv_recurse(self, consul_obj): assert [x["Value"] for x in data] == [None, b"1", b"2", b"3"] def test_kv_delete(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj c.kv.put("foo1", "1") c.kv.put("foo2", "2") c.kv.put("foo3", "3") @@ -119,7 +122,7 @@ def test_kv_delete(self, consul_obj): assert data is None def test_kv_delete_cas(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj c.kv.put("foo", "bar") index, data = c.kv.get("foo") @@ -132,7 +135,7 @@ def test_kv_delete_cas(self, consul_obj): assert data is None def test_kv_acquire_release(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj pytest.raises(consul.ConsulException, c.kv.put, "foo", "bar", acquire="foo") @@ -150,7 +153,7 @@ def test_kv_acquire_release(self, consul_obj): c.session.destroy(s2) def test_kv_keys_only(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj assert c.kv.put("bar", "4") is True assert c.kv.put("base/foo", "1") is True @@ -160,7 +163,7 @@ def test_kv_keys_only(self, consul_obj): assert data == ["base/base/", "base/foo"] def test_transaction(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj value = base64.b64encode(b"1").decode("utf8") d = {"KV": {"Verb": "set", "Key": "asdf", "Value": value}} r = c.txn.put([d]) @@ -171,7 +174,7 @@ def test_transaction(self, consul_obj): assert r["Results"][0]["KV"]["Value"] == value def test_event(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj assert c.event.fire("fooname", "foobody") _index, events = c.event.list() @@ -179,7 +182,7 @@ def test_event(self, consul_obj): assert [x["Payload"] == "foobody" for x in events] def test_event_targeted(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj assert c.event.fire("fooname", "foobody") _index, events = c.event.list(name="othername") @@ -190,6 +193,7 @@ def test_event_targeted(self, consul_obj): assert [x["Payload"] == "foobody" for x in events] def test_agent_checks(self, consul_port): + consul_port, _consul_version = consul_port c = consul.Consul(port=consul_port) def verify_and_dereg_check(check_id): @@ -246,6 +250,7 @@ def verify_check_status(check_id, status, notes=None): verify_and_dereg_check("ttl_check") def test_service_dereg_issue_156(self, consul_port): + consul_port, _consul_version = consul_port # https://github.com/cablehead/python-consul/issues/156 service_name = "app#127.0.0.1#3000" c = consul.Consul(port=consul_port) @@ -265,7 +270,7 @@ def test_service_dereg_issue_156(self, consul_port): assert [node["Service"]["ID"] for node in nodes] == [] def test_agent_checks_service_id(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj c.agent.service.register("foo1") time.sleep(40 / 1000.0) @@ -291,13 +296,12 @@ def test_agent_checks_service_id(self, consul_obj): time.sleep(40 / 1000.0) def test_agent_register_check_no_service_id(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj _index, nodes = c.health.service("foo1") assert nodes == [] - pytest.raises( - consul.std.base.ConsulException, c.agent.check.register, "foo", Check.ttl("100ms"), service_id="foo1" - ) + with pytest.raises(consul.std.base.ConsulException): + c.agent.check.register("foo", Check.ttl("100ms"), service_id="foo1") time.sleep(40 / 1000.0) @@ -309,7 +313,7 @@ def test_agent_register_check_no_service_id(self, consul_obj): time.sleep(40 / 1000.0) def test_agent_register_enable_tag_override(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj _index, nodes = c.health.service("foo1") assert nodes == [] @@ -320,7 +324,7 @@ def test_agent_register_enable_tag_override(self, consul_obj): c.agent.check.deregister("foo") def test_agent_service_maintenance(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj c.agent.service.register("foo", check=Check.ttl("100ms")) @@ -347,7 +351,7 @@ def test_agent_service_maintenance(self, consul_obj): time.sleep(40 / 1000.0) def test_agent_node_maintenance(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj c.agent.maintenance("true", "test") @@ -365,7 +369,7 @@ def test_agent_node_maintenance(self, consul_obj): assert "_node_maintenance" not in checks_post def test_agent_members(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj members = c.agent.members() for x in members: assert x["Status"] == 1 @@ -378,11 +382,19 @@ def test_agent_members(self, consul_obj): assert "dc1" in x["Name"] def test_agent_self(self, consul_obj): - c = consul_obj - assert set(c.agent.self().keys()) == {"Member", "Stats", "Config", "Coord", "DebugConfig", "Meta"} + c, _consul_version = consul_obj + + EXPECTED = { + "v1": {"Member", "Stats", "Config", "Coord", "DebugConfig", "Meta"}, + "v2": {"Member", "xDS", "Stats", "Config", "Coord", "DebugConfig", "Meta"}, + } + expected = EXPECTED["v1"] + if version.parse(_consul_version) >= version.parse("1.13.8"): + expected = EXPECTED["v2"] + assert set(c.agent.self().keys()) == expected def test_agent_services(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj assert c.agent.service.register("foo") is True assert set(c.agent.services().keys()) == {"foo"} assert c.agent.service.deregister("foo") is True @@ -394,7 +406,7 @@ def test_agent_services(self, consul_obj): assert c.agent.service.deregister("foo") is True def test_catalog(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj # grab the node our server created, so we can ignore it _, nodes = c.catalog.nodes() @@ -456,7 +468,7 @@ def test_catalog(self, consul_obj): assert [x["Node"] for x in nodes] == [] def test_health_service(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj # check there are no nodes for the service 'foo' _index, nodes = c.health.service("foo") @@ -516,7 +528,7 @@ def test_health_service(self, consul_obj): assert nodes == [] def test_health_state(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj # The empty string is for the Serf Health Status check, which has an # empty ServiceID @@ -573,14 +585,14 @@ def test_health_state(self, consul_obj): assert [node["ServiceID"] for node in nodes] == [""] def test_health_node(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj # grab local node name node = c.agent.self()["Config"]["NodeName"] _index, checks = c.health.node(node) assert node in [check["Node"] for check in checks] def test_health_checks(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj c.agent.service.register("foobar", service_id="foobar", check=Check.ttl("10s")) @@ -599,7 +611,7 @@ def test_health_checks(self, consul_obj): assert len(checks) == 0 def test_session(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj # session.create pytest.raises(consul.ConsulException, c.session.create, node="n2") @@ -631,7 +643,7 @@ def test_session(self, consul_obj): assert sessions == [] def test_session_delete_ttl_renew(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj s = c.session.create(behavior="delete", ttl=20) @@ -651,8 +663,12 @@ def test_session_delete_ttl_renew(self, consul_obj): _index, data = c.kv.get("foo") assert data is None + # TODO test newer versions def test_acl_disabled(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj + if should_skip(_consul_version, "<", "1.11.0"): + pytest.skip("Endpoint /v1/acl/create for the legacy ACL system was removed in Consul 1.11.") + pytest.raises(consul.ACLDisabled, c.acl.list) pytest.raises(consul.ACLDisabled, c.acl.info, "1" * 36) pytest.raises(consul.ACLDisabled, c.acl.create) @@ -661,7 +677,10 @@ def test_acl_disabled(self, consul_obj): pytest.raises(consul.ACLDisabled, c.acl.destroy, "foo") def test_acl_permission_denied(self, acl_consul): - c = consul.Consul(port=acl_consul.port) + port, _token, _consul_version = acl_consul + c = consul.Consul(port=port) + if should_skip(_consul_version, "<", "1.11.0"): + pytest.skip("Endpoint /v1/acl/create for the legacy ACL system was removed in Consul 1.11.") pytest.raises(consul.ACLPermissionDenied, c.acl.list) pytest.raises(consul.ACLPermissionDenied, c.acl.create) pytest.raises(consul.ACLPermissionDenied, c.acl.update, "anonymous") @@ -792,7 +811,7 @@ def test_acl_implicit_token_use(self, acl_consul): assert {x["ID"] for x in acls} == {"anonymous", master_token} def test_status_leader(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj agent_self = c.agent.self() leader = c.status.leader() @@ -801,7 +820,7 @@ def test_status_leader(self, consul_obj): assert leader == addr_port, f"Leader value was {leader}, expected value was {addr_port}" def test_status_peers(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj agent_self = c.agent.self() @@ -811,7 +830,7 @@ def test_status_peers(self, consul_obj): assert addr_port in peers, f"Expected value '{addr_port}' in peer list but it was not present" def test_query(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj # check that query list is empty queries = c.query.list() @@ -841,15 +860,20 @@ def test_query(self, consul_obj): assert c.query.delete(query["ID"]) def test_coordinate(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj c.coordinate.nodes() c.coordinate.datacenters() assert set(c.coordinate.datacenters()[0].keys()) == {"Datacenter", "Coordinates", "AreaID"} def test_operator(self, consul_obj): - c = consul_obj + c, _consul_version = consul_obj config = c.operator.raft_config() - assert config["Index"] == 1 + + expected_index = 1 + if version.parse(_consul_version) >= version.parse("1.13.8"): + expected_index = 0 + + assert config["Index"] == expected_index leader = False voter = False for server in config["Servers"]: