Skip to content

Commit

Permalink
tests: fix tests
Browse files Browse the repository at this point in the history
Moved and extended the should_skip function + fixed some tests
Enable parallel execution which removes the need to cleanup things
after a test.

Moved the ACL tests in a specific file.
Fixtures are now function scoped. This *truly* enables
multi-version tests.
  • Loading branch information
Mathias Brulatout authored and mbrulatout committed Mar 20, 2024
1 parent 62c9ea2 commit 102167e
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 287 deletions.
78 changes: 25 additions & 53 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@
import time
import uuid

import aiohttp
import py
import pytest
import requests
from packaging import version

collect_ignore = []

Expand Down Expand Up @@ -47,30 +44,41 @@ def start_consul_instance(binary_name, acl_master_token=None):
returns: a tuple of the instances process object and the http port the
instance is listening on
"""
ports = dict(zip(["http", "serf_lan", "serf_wan", "server", "dns"], get_free_ports(4) + [-1]))
ports = dict(zip(["http", "server", "grpc", "serf_lan", "serf_wan", "https", "dns"], get_free_ports(5) + [-1] * 2))
if "1.13" not in binary_name:
ports["grpc_tls"] = -1

config = {"ports": ports, "performance": {"raft_multiplier": 1}, "enable_script_checks": True}
if acl_master_token:
config["acl_datacenter"] = "dc1"
config["acl_master_token"] = acl_master_token
config["primary_datacenter"] = "dc1"
config["acl"] = {"enabled": True, "tokens": {"initial_management": acl_master_token}}

tmpdir = py.path.local(tempfile.mkdtemp())
tmpdir.join("config.json").write(json.dumps(config))
tmpdir.chdir()
tmpdir = tempfile.mkdtemp()
config_path = os.path.join(tmpdir, "config.json")
print(config_path)
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f)

ext = "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 = f"{binary} agent -dev -bind=127.0.0.1 -config-dir={tmpdir}"
command = shlex.split(command)
log_file_path = os.path.join(tmpdir, "consul.log")

with open("/dev/null", "w") as devnull: # pylint: disable=unspecified-encoding
p = subprocess.Popen(command, stdout=devnull, stderr=devnull) # pylint: disable=consider-using-with
with open(log_file_path, "w", encoding="utf-8") as log_file: # pylint: disable=unspecified-encoding
p = subprocess.Popen(command, stdout=log_file, stderr=subprocess.STDOUT) # pylint: disable=consider-using-with

# wait for consul instance to bootstrap
base_uri = f"http://127.0.0.1:{ports['http']}/v1/"
start_time = time.time()
global_timeout = 5

while True:
# Timeout at some point and read the log file to see what went wrong
if time.time() - start_time > global_timeout:
with open(log_file_path, encoding="utf-8") as log_file:
print(log_file.read())
raise TimeoutError("Global timeout reached")
time.sleep(0.1)
try:
response = requests.get(base_uri + "status/leader", timeout=10)
Expand All @@ -94,43 +102,21 @@ def start_consul_instance(binary_name, acl_master_token=None):
return p, ports["http"]


def clean_consul(port):
# remove all data from the instance, to have a clean start
base_uri = f"http://127.0.0.1:{port}/v1/"
requests.delete(base_uri + "kv/", params={"recurse": 1}, timeout=10)
services = requests.get(base_uri + "agent/services", timeout=10).json().keys()
for s in services:
requests.put(base_uri + f"agent/service/deregister/{s}", timeout=10)


async def async_clean_consul(port):
base_uri = f"http://127.0.0.1:{port}/v1/"
async with aiohttp.ClientSession() as session:
# Delete all key-value pairs
await session.delete(base_uri + "kv/", params={"recurse": 1})

# Deregister all services
async with session.get(base_uri + "agent/services") as response:
services = await response.json()
for s in services:
await session.put(base_uri + f"agent/service/deregister/{s}")


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(scope="module", params=CONSUL_BINARIES.keys())
@pytest.fixture(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", params=CONSUL_BINARIES.keys())
@pytest.fixture(params=CONSUL_BINARIES.keys())
def acl_consul_instance(request):
acl_master_token = uuid.uuid4().hex
p, port = start_consul_instance(binary_name=CONSUL_BINARIES[request.param], acl_master_token=acl_master_token)
Expand All @@ -142,25 +128,11 @@ def acl_consul_instance(request):
@pytest.fixture()
def consul_port(consul_instance):
port, version = consul_instance
yield port, version
clean_consul(port)
return port, version


@pytest.fixture()
def acl_consul(acl_consul_instance):
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
return ACLConsul(port, token, version)
236 changes: 236 additions & 0 deletions tests/test_acl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import pytest

import consul
from tests.utils import find_recursive


class TestConsulAcl:
def test_acl_permission_denied(self, acl_consul):
port, _master_token, _consul_version = acl_consul
c = consul.Consul(port=port)

# No token
pytest.raises(consul.ACLPermissionDenied, c.acl.list)
pytest.raises(consul.ACLPermissionDenied, c.acl.create)
pytest.raises(consul.ACLPermissionDenied, c.acl.update, accessor_id="00000000-0000-0000-0000-000000000002")
pytest.raises(consul.ACLPermissionDenied, c.acl.clone, accessor_id="00000000-0000-0000-0000-000000000002")
pytest.raises(consul.ACLPermissionDenied, c.acl.read, accessor_id="00000000-0000-0000-0000-000000000002")
pytest.raises(consul.ACLPermissionDenied, c.acl.delete, accessor_id="00000000-0000-0000-0000-000000000002")

# Token without the right permission (acl:write or acl:read)
pytest.raises(consul.ACLPermissionDenied, c.acl.list, token="anonymous")
pytest.raises(consul.ACLPermissionDenied, c.acl.create, token="anonymous")
pytest.raises(
consul.ACLPermissionDenied,
c.acl.update,
accessor_id="00000000-0000-0000-0000-000000000002",
token="anonymous",
)
pytest.raises(
consul.ACLPermissionDenied,
c.acl.clone,
accessor_id="00000000-0000-0000-0000-000000000002",
token="anonymous",
)
pytest.raises(
consul.ACLPermissionDenied,
c.acl.read,
accessor_id="00000000-0000-0000-0000-000000000002",
token="anonymous",
)
pytest.raises(
consul.ACLPermissionDenied,
c.acl.delete,
accessor_id="00000000-0000-0000-0000-000000000002",
token="anonymous",
)

def test_acl_list(self, acl_consul):
port, master_token, _consul_version = acl_consul
c = consul.Consul(port=port)

# Make sure both master and anonymous tokens are created
acls = c.acl.list(token=master_token)

master_token_repr = {
"Description": "Initial Management Token",
"Policies": [{"ID": "00000000-0000-0000-0000-000000000001", "Name": "global-management"}],
"SecretID": master_token,
}
anonymous_token_repr = {
"AccessorID": "00000000-0000-0000-0000-000000000002",
"SecretID": "anonymous",
}
assert find_recursive(acls, master_token_repr)
assert find_recursive(acls, anonymous_token_repr)

def test_acl_read(self, acl_consul):
port, master_token, _consul_version = acl_consul
c = consul.Consul(port=port)

# Unknown token
pytest.raises(consul.ConsulException, c.acl.read, accessor_id="unknown", token=master_token)

anonymous_token_repr = {
"AccessorID": "00000000-0000-0000-0000-000000000002",
"SecretID": "anonymous",
}
acl = c.acl.read(accessor_id="00000000-0000-0000-0000-000000000002", token=master_token)
assert find_recursive(acl, anonymous_token_repr)

def test_acl_create(self, acl_consul):
port, master_token, _consul_version = acl_consul
c = consul.Consul(port=port)

c.acl.create(accessor_id="00000000-DEAD-BEEF-0000-000000000000", token=master_token)
c.acl.create(secret_id="DEADBEEF-0000-0000-0000-000000000000", token=master_token)
c.acl.create(
secret_id="00000000-A5A5-0000-0000-000000000000",
accessor_id="00000000-0000-A5A5-0000-000000000000",
description="some token!",
token=master_token,
)

assert c.acl.read(accessor_id="00000000-DEAD-BEEF-0000-000000000000", token=master_token)
assert c.acl.read(accessor_id="00000000-0000-A5A5-0000-000000000000", token=master_token)

expected = [
{
"AccessorID": "00000000-DEAD-BEEF-0000-000000000000",
"Description": "",
},
{
"SecretID": "DEADBEEF-0000-0000-0000-000000000000",
"Description": "",
},
{
"AccessorID": "00000000-0000-A5A5-0000-000000000000",
"SecretID": "00000000-A5A5-0000-0000-000000000000",
"Description": "some token!",
},
]
acl = c.acl.list(token=master_token)
assert find_recursive(acl, expected)

def test_acl_clone(self, acl_consul):
port, master_token, _consul_version = acl_consul
c = consul.Consul(port=port)

assert len(c.acl.list(token=master_token)) == 2

# Unknown token
pytest.raises(consul.ConsulException, c.acl.clone, accessor_id="unknown", token=master_token)

c.acl.create(accessor_id="00000000-DEAD-BEEF-0000-000000000000", token=master_token)
c.acl.clone(accessor_id="00000000-DEAD-BEEF-0000-000000000000", description="cloned", token=master_token)
assert len(c.acl.list(token=master_token)) == 4

expected = [
{
"AccessorID": "00000000-DEAD-BEEF-0000-000000000000",
},
{
"Description": "cloned",
},
]
acl = c.acl.list(token=master_token)
assert find_recursive(acl, expected)

def test_acl_update(self, acl_consul):
port, master_token, _consul_version = acl_consul
c = consul.Consul(port=port)

# Unknown token
pytest.raises(consul.ConsulException, c.acl.update, accessor_id="unknown", token=master_token)

assert len(c.acl.list(token=master_token)) == 2
c.acl.create(accessor_id="00000000-DEAD-BEEF-0000-000000000000", description="original", token=master_token)
assert len(c.acl.list(token=master_token)) == 3
c.acl.update(accessor_id="00000000-DEAD-BEEF-0000-000000000000", description="updated", token=master_token)
assert len(c.acl.list(token=master_token)) == 3

expected = {
"AccessorID": "00000000-DEAD-BEEF-0000-000000000000",
"Description": "updated",
}
acl = c.acl.read(accessor_id="00000000-DEAD-BEEF-0000-000000000000", token=master_token)
assert find_recursive(acl, expected)

def test_acl_delete(self, acl_consul):
port, master_token, _consul_version = acl_consul
c = consul.Consul(port=port)

assert len(c.acl.list(token=master_token)) == 2
c.acl.create(accessor_id="00000000-DEAD-BEEF-0000-000000000000", token=master_token)
assert len(c.acl.list(token=master_token)) == 3
assert c.acl.read(accessor_id="00000000-DEAD-BEEF-0000-000000000000", token=master_token)

# Delete and ensure it doesn't exist anymore
c.acl.delete(accessor_id="00000000-DEAD-BEEF-0000-000000000000", token=master_token)
assert len(c.acl.list(token=master_token)) == 2
pytest.raises(
consul.ConsulException, c.acl.read, accessor_id="00000000-DEAD-BEEF-0000-000000000000", token=master_token
)

#
# def test_acl_implicit_token_use(self, acl_consul):
# # configure client to use the master token by default
# port, _token, _consul_version = acl_consul
# c = consul.Consul(port=port)
# master_token = acl_consul.token
#
# if should_skip(_consul_version, "<", "1.11.0"):
# clean_consul(port)
# pytest.skip("Endpoint /v1/acl/list for the legacy ACL system was removed in Consul 1.11.")
#
#
# acls = c.acl.list()
# assert {x["ID"] for x in acls} == {"anonymous", master_token}
#
# assert c.acl.info("foo") is None
# compare = [c.acl.info(master_token), c.acl.info("anonymous")]
# compare.sort(key=operator.itemgetter("ID"))
# assert acls == compare
#
# rules = """
# key "" {
# policy = "read"
# }
# key "private/" {
# policy = "deny"
# }
# """
# token = c.acl.create(rules=rules)
# assert c.acl.info(token)["Rules"] == rules
#
# token2 = c.acl.clone(token)
# assert c.acl.info(token2)["Rules"] == rules
#
# assert c.acl.update(token2, name="Foo") == token2
# assert c.acl.info(token2)["Name"] == "Foo"
#
# assert c.acl.destroy(token2) is True
# assert c.acl.info(token2) is None
#
# c.kv.put("foo", "bar")
# c.kv.put("private/foo", "bar")
#
# c_limited = consul.Consul(port=acl_consul.port, token=token)
# assert c_limited.kv.get("foo")[1]["Value"] == b"bar"
# pytest.raises(consul.ACLPermissionDenied, c_limited.kv.put, "foo", "bar2")
# pytest.raises(consul.ACLPermissionDenied, c_limited.kv.delete, "foo")
#
# assert c.kv.get("private/foo")[1]["Value"] == b"bar"
# pytest.raises(consul.ACLPermissionDenied, c_limited.kv.get, "private/foo")
# pytest.raises(consul.ACLPermissionDenied, c_limited.kv.put, "private/foo", "bar2")
# pytest.raises(consul.ACLPermissionDenied, c_limited.kv.delete, "private/foo")
#
# # check we can override the client's default token
# pytest.raises(consul.ACLPermissionDenied, c.kv.get, "private/foo", token=token)
# pytest.raises(consul.ACLPermissionDenied, c.kv.put, "private/foo", "bar2", token=token)
# pytest.raises(consul.ACLPermissionDenied, c.kv.delete, "private/foo", token=token)
#
# # clean up
# c.acl.destroy(token)
# acls = c.acl.list()
# assert {x["ID"] for x in acls} == {"anonymous", master_token}
Loading

0 comments on commit 102167e

Please sign in to comment.