mirror of
https://github.com/neondatabase/neon.git
synced 2025-12-26 07:39:58 +00:00
Respect limits for projects for the Random Operations test (#12184)
## Problem The project limits were not respected, resulting in errors. ## Summary of changes Now limits are checked before running an action, and if the action is not possible to run, another random action will be run. --------- Co-authored-by: Peter Bendel <peterbendel@neon.tech>
This commit is contained in:
@@ -129,6 +129,18 @@ class NeonAPI:
|
|||||||
|
|
||||||
return cast("dict[str, Any]", resp.json())
|
return cast("dict[str, Any]", resp.json())
|
||||||
|
|
||||||
|
def get_project_limits(self, project_id: str) -> dict[str, Any]:
|
||||||
|
resp = self.__request(
|
||||||
|
"GET",
|
||||||
|
f"/projects/{project_id}/limits",
|
||||||
|
headers={
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return cast("dict[str, Any]", resp.json())
|
||||||
|
|
||||||
def delete_project(
|
def delete_project(
|
||||||
self,
|
self,
|
||||||
project_id: str,
|
project_id: str,
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ class NeonEndpoint:
|
|||||||
if self.branch.connect_env:
|
if self.branch.connect_env:
|
||||||
self.connect_env = self.branch.connect_env.copy()
|
self.connect_env = self.branch.connect_env.copy()
|
||||||
self.connect_env["PGHOST"] = self.host
|
self.connect_env["PGHOST"] = self.host
|
||||||
|
if self.type == "read_only":
|
||||||
|
self.project.read_only_endpoints_total += 1
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
self.project.delete_endpoint(self.id)
|
self.project.delete_endpoint(self.id)
|
||||||
@@ -228,8 +230,13 @@ class NeonProject:
|
|||||||
self.benchmarks: dict[str, subprocess.Popen[Any]] = {}
|
self.benchmarks: dict[str, subprocess.Popen[Any]] = {}
|
||||||
self.restore_num: int = 0
|
self.restore_num: int = 0
|
||||||
self.restart_pgbench_on_console_errors: bool = False
|
self.restart_pgbench_on_console_errors: bool = False
|
||||||
|
self.limits: dict[str, Any] = self.get_limits()["limits"]
|
||||||
|
self.read_only_endpoints_total: int = 0
|
||||||
|
|
||||||
def delete(self):
|
def get_limits(self) -> dict[str, Any]:
|
||||||
|
return self.neon_api.get_project_limits(self.id)
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
self.neon_api.delete_project(self.id)
|
self.neon_api.delete_project(self.id)
|
||||||
|
|
||||||
def create_branch(self, parent_id: str | None = None) -> NeonBranch | None:
|
def create_branch(self, parent_id: str | None = None) -> NeonBranch | None:
|
||||||
@@ -282,6 +289,7 @@ class NeonProject:
|
|||||||
self.neon_api.delete_endpoint(self.id, endpoint_id)
|
self.neon_api.delete_endpoint(self.id, endpoint_id)
|
||||||
self.endpoints[endpoint_id].branch.endpoints.pop(endpoint_id)
|
self.endpoints[endpoint_id].branch.endpoints.pop(endpoint_id)
|
||||||
self.endpoints.pop(endpoint_id)
|
self.endpoints.pop(endpoint_id)
|
||||||
|
self.read_only_endpoints_total -= 1
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
def start_benchmark(self, target: str, clients: int = 10) -> subprocess.Popen[Any]:
|
def start_benchmark(self, target: str, clients: int = 10) -> subprocess.Popen[Any]:
|
||||||
@@ -369,49 +377,64 @@ def setup_class(
|
|||||||
print(f"::warning::Retried on 524 error {neon_api.retries524} times")
|
print(f"::warning::Retried on 524 error {neon_api.retries524} times")
|
||||||
if neon_api.retries4xx > 0:
|
if neon_api.retries4xx > 0:
|
||||||
print(f"::warning::Retried on 4xx error {neon_api.retries4xx} times")
|
print(f"::warning::Retried on 4xx error {neon_api.retries4xx} times")
|
||||||
log.info("Removing the project")
|
log.info("Removing the project %s", project.id)
|
||||||
project.delete()
|
project.delete()
|
||||||
|
|
||||||
|
|
||||||
def do_action(project: NeonProject, action: str) -> None:
|
def do_action(project: NeonProject, action: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Runs the action
|
Runs the action
|
||||||
"""
|
"""
|
||||||
log.info("Action: %s", action)
|
log.info("Action: %s", action)
|
||||||
if action == "new_branch":
|
if action == "new_branch":
|
||||||
log.info("Trying to create a new branch")
|
log.info("Trying to create a new branch")
|
||||||
|
if 0 <= project.limits["max_branches"] <= len(project.branches):
|
||||||
|
log.info(
|
||||||
|
"Maximum branch limit exceeded (%s of %s)",
|
||||||
|
len(project.branches),
|
||||||
|
project.limits["max_branches"],
|
||||||
|
)
|
||||||
|
return False
|
||||||
parent = project.branches[
|
parent = project.branches[
|
||||||
random.choice(list(set(project.branches.keys()) - project.reset_branches))
|
random.choice(list(set(project.branches.keys()) - project.reset_branches))
|
||||||
]
|
]
|
||||||
log.info("Parent: %s", parent)
|
log.info("Parent: %s", parent)
|
||||||
child = parent.create_child_branch()
|
child = parent.create_child_branch()
|
||||||
if child is None:
|
if child is None:
|
||||||
return
|
return False
|
||||||
log.info("Created branch %s", child)
|
log.info("Created branch %s", child)
|
||||||
child.start_benchmark()
|
child.start_benchmark()
|
||||||
elif action == "delete_branch":
|
elif action == "delete_branch":
|
||||||
if project.leaf_branches:
|
if project.leaf_branches:
|
||||||
target = random.choice(list(project.leaf_branches.values()))
|
target: NeonBranch = random.choice(list(project.leaf_branches.values()))
|
||||||
log.info("Trying to delete branch %s", target)
|
log.info("Trying to delete branch %s", target)
|
||||||
target.delete()
|
target.delete()
|
||||||
else:
|
else:
|
||||||
log.info("Leaf branches not found, skipping")
|
log.info("Leaf branches not found, skipping")
|
||||||
|
return False
|
||||||
elif action == "new_ro_endpoint":
|
elif action == "new_ro_endpoint":
|
||||||
|
if 0 <= project.limits["max_read_only_endpoints"] <= project.read_only_endpoints_total:
|
||||||
|
log.info(
|
||||||
|
"Maximum read only endpoint limit exceeded (%s of %s)",
|
||||||
|
project.read_only_endpoints_total,
|
||||||
|
project.limits["max_read_only_endpoints"],
|
||||||
|
)
|
||||||
|
return False
|
||||||
ep = random.choice(
|
ep = random.choice(
|
||||||
[br for br in project.branches.values() if br.id not in project.reset_branches]
|
[br for br in project.branches.values() if br.id not in project.reset_branches]
|
||||||
).create_ro_endpoint()
|
).create_ro_endpoint()
|
||||||
log.info("Created the RO endpoint with id %s branch: %s", ep.id, ep.branch.id)
|
log.info("Created the RO endpoint with id %s branch: %s", ep.id, ep.branch.id)
|
||||||
ep.start_benchmark()
|
ep.start_benchmark()
|
||||||
elif action == "delete_ro_endpoint":
|
elif action == "delete_ro_endpoint":
|
||||||
|
if project.read_only_endpoints_total == 0:
|
||||||
|
log.info("no read_only endpoints present, skipping")
|
||||||
|
return False
|
||||||
ro_endpoints: list[NeonEndpoint] = [
|
ro_endpoints: list[NeonEndpoint] = [
|
||||||
endpoint for endpoint in project.endpoints.values() if endpoint.type == "read_only"
|
endpoint for endpoint in project.endpoints.values() if endpoint.type == "read_only"
|
||||||
]
|
]
|
||||||
if ro_endpoints:
|
target_ep: NeonEndpoint = random.choice(ro_endpoints)
|
||||||
target_ep: NeonEndpoint = random.choice(ro_endpoints)
|
target_ep.delete()
|
||||||
target_ep.delete()
|
log.info("endpoint %s deleted", target_ep.id)
|
||||||
log.info("endpoint %s deleted", target_ep.id)
|
|
||||||
else:
|
|
||||||
log.info("no read_only endpoints present, skipping")
|
|
||||||
elif action == "restore_random_time":
|
elif action == "restore_random_time":
|
||||||
if project.leaf_branches:
|
if project.leaf_branches:
|
||||||
br: NeonBranch = random.choice(list(project.leaf_branches.values()))
|
br: NeonBranch = random.choice(list(project.leaf_branches.values()))
|
||||||
@@ -419,8 +442,10 @@ def do_action(project: NeonProject, action: str) -> None:
|
|||||||
br.restore_random_time()
|
br.restore_random_time()
|
||||||
else:
|
else:
|
||||||
log.info("No leaf branches found")
|
log.info("No leaf branches found")
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"The action {action} is unknown")
|
raise ValueError(f"The action {action} is unknown")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.timeout(7200)
|
@pytest.mark.timeout(7200)
|
||||||
@@ -457,8 +482,9 @@ def test_api_random(
|
|||||||
pg_bin.run(["pgbench", "-i", "-I", "dtGvp", "-s100"], env=project.main_branch.connect_env)
|
pg_bin.run(["pgbench", "-i", "-I", "dtGvp", "-s100"], env=project.main_branch.connect_env)
|
||||||
for _ in range(num_operations):
|
for _ in range(num_operations):
|
||||||
log.info("Starting action #%s", _ + 1)
|
log.info("Starting action #%s", _ + 1)
|
||||||
do_action(
|
while not do_action(
|
||||||
project, random.choices([a[0] for a in ACTIONS], weights=[w[1] for w in ACTIONS])[0]
|
project, random.choices([a[0] for a in ACTIONS], weights=[w[1] for w in ACTIONS])[0]
|
||||||
)
|
):
|
||||||
|
log.info("Retrying...")
|
||||||
project.check_all_benchmarks()
|
project.check_all_benchmarks()
|
||||||
assert True
|
assert True
|
||||||
|
|||||||
Reference in New Issue
Block a user