Skip to content

Commit 9eef87b

Browse files
authored
Merge pull request #192 from ByteInternet/handle_brancher_already_cancelled
Brancher: Handle already cancelled error
2 parents d9bffbc + 146b862 commit 9eef87b

2 files changed

Lines changed: 102 additions & 1 deletion

File tree

src/Brancher/BrancherHypernodeManager.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,20 @@ public function cancel(string ...$brancherHypernodes): void
337337
{
338338
foreach ($brancherHypernodes as $brancherHypernode) {
339339
$this->log->info(sprintf('Stopping brancher Hypernode %s...', $brancherHypernode));
340-
$this->hypernodeClient->brancherApp->cancel($brancherHypernode);
340+
try {
341+
$this->hypernodeClient->brancherApp->cancel($brancherHypernode);
342+
} catch (HypernodeApiClientException $e) {
343+
// If the brancher is already cancelled or not found, that's fine -
344+
// our goal was to cancel it anyway
345+
if ($e->getCode() === 404 || str_contains($e->getMessage(), 'has already been cancelled')) {
346+
$this->log->info(sprintf(
347+
'Brancher Hypernode %s was already cancelled or not found, skipping.',
348+
$brancherHypernode
349+
));
350+
continue;
351+
}
352+
throw $e;
353+
}
341354
}
342355
}
343356
}

tests/Unit/Brancher/BrancherHypernodeManagerTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Hypernode\Api\Exception\HypernodeApiClientException;
88
use Hypernode\Api\HypernodeClient;
99
use Hypernode\Api\Resource\Logbook\Flow;
10+
use Hypernode\Api\Service\BrancherApp;
1011
use Hypernode\Api\Service\Logbook;
1112
use Hypernode\Deploy\Brancher\BrancherHypernodeManager;
1213
use Hypernode\Deploy\Exception\CreateBrancherHypernodeFailedException;
@@ -20,6 +21,7 @@ class BrancherHypernodeManagerTest extends TestCase
2021
{
2122
private LoggerInterface&MockObject $logger;
2223
private HypernodeClient&MockObject $hypernodeClient;
24+
private BrancherApp&MockObject $brancherApp;
2325
private Logbook&MockObject $logbook;
2426
private TestSshPoller $sshPoller;
2527
private BrancherHypernodeManager $manager;
@@ -28,7 +30,9 @@ protected function setUp(): void
2830
{
2931
$this->logger = $this->createMock(LoggerInterface::class);
3032
$this->hypernodeClient = $this->createMock(HypernodeClient::class);
33+
$this->brancherApp = $this->createMock(BrancherApp::class);
3134
$this->logbook = $this->createMock(Logbook::class);
35+
$this->hypernodeClient->brancherApp = $this->brancherApp;
3236
$this->hypernodeClient->logbook = $this->logbook;
3337
$this->sshPoller = new TestSshPoller();
3438
$this->sshPoller->setMicrotime(1000.0);
@@ -258,4 +262,88 @@ private function createFlow(string $name, string $state): Flow
258262
'state' => $state,
259263
]);
260264
}
265+
266+
public function testCancelSucceeds(): void
267+
{
268+
$this->brancherApp->expects($this->once())
269+
->method('cancel')
270+
->with('test-brancher');
271+
272+
$this->manager->cancel('test-brancher');
273+
}
274+
275+
public function testCancelAlreadyCancelledBrancherContinues(): void
276+
{
277+
$response = $this->createMock(ResponseInterface::class);
278+
$response->method('getStatusCode')->willReturn(400);
279+
$response->method('getBody')->willReturn('["Brancher app test-brancher has already been cancelled."]');
280+
$exception = new HypernodeApiClientException($response);
281+
282+
$this->brancherApp->expects($this->once())
283+
->method('cancel')
284+
->with('test-brancher')
285+
->willThrowException($exception);
286+
287+
$this->logger->expects($this->exactly(2))
288+
->method('info');
289+
290+
// Should not throw
291+
$this->manager->cancel('test-brancher');
292+
}
293+
294+
public function testCancelNotFoundBrancherContinues(): void
295+
{
296+
$response = $this->createMock(ResponseInterface::class);
297+
$response->method('getStatusCode')->willReturn(404);
298+
$response->method('getBody')->willReturn('Not Found');
299+
$exception = new HypernodeApiClientException($response);
300+
301+
$this->brancherApp->expects($this->once())
302+
->method('cancel')
303+
->with('test-brancher')
304+
->willThrowException($exception);
305+
306+
$this->logger->expects($this->exactly(2))
307+
->method('info');
308+
309+
// Should not throw
310+
$this->manager->cancel('test-brancher');
311+
}
312+
313+
public function testCancelOtherClientErrorPropagates(): void
314+
{
315+
$response = $this->createMock(ResponseInterface::class);
316+
$response->method('getStatusCode')->willReturn(403);
317+
$response->method('getBody')->willReturn('Forbidden');
318+
$exception = new HypernodeApiClientException($response);
319+
320+
$this->brancherApp->expects($this->once())
321+
->method('cancel')
322+
->with('test-brancher')
323+
->willThrowException($exception);
324+
325+
$this->expectException(HypernodeApiClientException::class);
326+
327+
$this->manager->cancel('test-brancher');
328+
}
329+
330+
public function testCancelMultipleBranchersPartialFailure(): void
331+
{
332+
$response = $this->createMock(ResponseInterface::class);
333+
$response->method('getStatusCode')->willReturn(400);
334+
$response->method('getBody')->willReturn('["Brancher app brancher-1 has already been cancelled."]');
335+
$exception = new HypernodeApiClientException($response);
336+
337+
$this->brancherApp->expects($this->exactly(2))
338+
->method('cancel')
339+
->willReturnCallback(function (string $name) use ($exception) {
340+
if ($name === 'brancher-1') {
341+
throw $exception;
342+
}
343+
// brancher-2 succeeds
344+
});
345+
346+
// Should not throw, should continue to second brancher
347+
$this->manager->cancel('brancher-1', 'brancher-2');
348+
}
261349
}

0 commit comments

Comments
 (0)