Fix AtributeError when canceling merge requests
When trying to cancel a merge request, Zuul raises the following exception: Traceback (most recent call last): File "/opt/zuul/lib/python3.11/site-packages/zuul/configloader.py", line 2500, in _cacheTenantYAML self.merger.cancel(cancel_job) File "/opt/zuul/lib/python3.11/site-packages/zuul/merger/client.py", line 264, in cancel request = self.merger_api.get(job.request_path) ^^^^^^^^^^^^^^^^^^^ AttributeError: 'MergerApi' object has no attribute 'get' The MergerApi.get() method was renamed to MergerApi.getRequest() in Ie3d62dadd9990c847df8302d4da801a990394d2c, but apparently this call in the cancel procedure was forgotten. Since getRequest is using the job UUID rather than the request_path, we first have to extract the UUID from the path. This enhances the TestingMergerApi to allow explicitly failing merge jobs for a given job type. To make this work properly we also have to use the TestingMergerApi on the executor server that is used within the Zuul tests. Change-Id: Ib2f81959202adc08aff5eb905f3d73b10cd2891c
This commit is contained in:
@ -866,6 +866,10 @@ class TestingMergerApi(HoldableMergerApi):
|
||||
|
||||
log = logging.getLogger("zuul.test.TestingMergerApi")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.job_types_to_fail = []
|
||||
|
||||
def _test_getMergeJobsInState(self, *states):
|
||||
# As this method is used for assertions in the tests, it should look up
|
||||
# the merge requests directly from ZooKeeper and not from a cache
|
||||
@ -879,6 +883,29 @@ class TestingMergerApi(HoldableMergerApi):
|
||||
|
||||
return sorted(all_merge_requests)
|
||||
|
||||
def failJobs(self, job_type):
|
||||
"""
|
||||
Lets all merge jobs of the given type fail.
|
||||
|
||||
The actual implementation is done by overriding reportResult().
|
||||
"""
|
||||
# As the params of a merge job are cleared directly after it's
|
||||
# picked up by a merger, we don't have much information to
|
||||
# identify a running merge job. The easiest solution is to
|
||||
# fail all merge jobs of a given type, which should be fine
|
||||
# for test purposes.
|
||||
# We are failing merge jobs by type as the params are directly
|
||||
# cleared when the merge job is picked up by a merger. Thus, we
|
||||
# cannot compare information like project or branch in
|
||||
# reportResult() where we set the merge job's result to failed.
|
||||
self.job_types_to_fail.append(job_type)
|
||||
|
||||
def reportResult(self, request, result):
|
||||
# Set updated to False to mark a merge job as failed
|
||||
if request.job_type in self.job_types_to_fail:
|
||||
result["updated"] = False
|
||||
return super().reportResult(request, result)
|
||||
|
||||
def release(self, merge_request=None):
|
||||
"""
|
||||
Releases a merge request which was previously put on hold for testing.
|
||||
@ -1016,6 +1043,7 @@ class RecordingExecutorServer(zuul.executor.server.ExecutorServer):
|
||||
"""
|
||||
|
||||
_job_class = RecordingAnsibleJob
|
||||
_merger_api_class = TestingMergerApi
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
self._run_ansible = kw.pop('_run_ansible', False)
|
||||
@ -3128,6 +3156,7 @@ class ZuulTestCase(BaseTestCase):
|
||||
self.merger_api.hold_in_queue = hold_in_queue
|
||||
for app in self.scheds:
|
||||
app.sched.merger.merger_api.hold_in_queue = hold_in_queue
|
||||
self.executor_server.merger_api.hold_in_queue = hold_in_queue
|
||||
|
||||
@property
|
||||
def hold_nodeset_requests_in_queue(self):
|
||||
|
@ -66,6 +66,42 @@ class TestConfigLoader(ZuulTestCase):
|
||||
|
||||
self.assertEqual(tenant.name, tenant_name)
|
||||
|
||||
@okay_tracebacks(' _processCatJob')
|
||||
def test_merge_job_cleanup(self):
|
||||
# When issuing a full reconfiguration or a tenant validation, we
|
||||
# cannot hold the merge jobs in queue as the configloader is
|
||||
# explicitly waiting for the cat jobs to complete. Thus, instead
|
||||
# of holding the merge jobs we directly fail those cat jobs to
|
||||
# trigger the merge request cleanup.
|
||||
self.merger_api.failJobs(MergeRequest.CAT)
|
||||
self.executor_server.merger_api.failJobs(MergeRequest.CAT)
|
||||
self.assertRaises(
|
||||
Exception,
|
||||
lambda: self.scheds.execute(lambda app: app.sched.validateTenants(
|
||||
app.config, {'tenant-one'})),
|
||||
)
|
||||
|
||||
self.waitUntilSettled()
|
||||
|
||||
# Even if the merge jobs are canceled, there could be a race
|
||||
# condition where an executor picks up a merge request and
|
||||
# re-creates the result znode in ZK after the request got
|
||||
# deleted. To prevent assertCleanZooKeeper to fail this test
|
||||
# even if the cancelation was successful, we have to manually
|
||||
# clean up lost merge requests.
|
||||
self.merger_api.cleanup()
|
||||
|
||||
# As we are explicitly failing cat jobs we are expecting
|
||||
# validateTenants() to fail with an exception.
|
||||
# As this catches other exceptions that might be raised when
|
||||
# canceling the merge jobs, there is no proper way to validate
|
||||
# this apart from checking the test/exception logs for specific
|
||||
# messages.
|
||||
for record in self._exception_logs:
|
||||
if "Unable to cancel job" in record.msg:
|
||||
self.fail(
|
||||
f"Exception while canceling merge jobs: {record.msg}")
|
||||
|
||||
|
||||
class TenantParserTestCase(ZuulTestCase):
|
||||
create_project_keys = True
|
||||
|
@ -261,7 +261,8 @@ class MergeClient(object):
|
||||
def cancel(self, job):
|
||||
try:
|
||||
# Try to remove the request first
|
||||
request = self.merger_api.get(job.request_path)
|
||||
request_uuid = job.request_path.split('/')[-1]
|
||||
request = self.merger_api.getRequest(request_uuid)
|
||||
if request:
|
||||
if self.merger_api.lock(request, blocking=False):
|
||||
try:
|
||||
|
@ -70,6 +70,7 @@ class BaseMergeServer(metaclass=ABCMeta):
|
||||
log = logging.getLogger("zuul.BaseMergeServer")
|
||||
|
||||
_repo_locks_class = BaseRepoLocks
|
||||
_merger_api_class = MergerApi
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -110,7 +111,7 @@ class BaseMergeServer(metaclass=ABCMeta):
|
||||
|
||||
self.merger_loop_wake_event = threading.Event()
|
||||
|
||||
self.merger_api = MergerApi(
|
||||
self.merger_api = self._merger_api_class(
|
||||
self.zk_client,
|
||||
merge_request_callback=self.merger_loop_wake_event.set,
|
||||
)
|
||||
|
Reference in New Issue
Block a user