Skip to content

Commit 2560163

Browse files
committed
Modify how deletion service works (actually fixes pterodactyl#2085); cover changes with test
1 parent 7a643be commit 2560163

File tree

8 files changed

+204
-202
lines changed

8 files changed

+204
-202
lines changed

app/Http/Controllers/Admin/ServersController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ public function deleteDatabase($server, $database)
409409
['id', '=', $database],
410410
]);
411411

412-
$this->databaseManagementService->delete($database->id);
412+
$this->databaseManagementService->delete($database);
413413

414414
return response('', 204);
415415
}

app/Http/Controllers/Api/Application/Servers/DatabaseController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public function store(StoreServerDatabaseRequest $request, Server $server): Json
133133
*/
134134
public function delete(ServerDatabaseWriteRequest $request): Response
135135
{
136-
$this->databaseManagementService->delete($request->getModel(Database::class)->id);
136+
$this->databaseManagementService->delete($request->getModel(Database::class));
137137

138138
return response('', 204);
139139
}

app/Http/Controllers/Api/Client/Servers/DatabaseController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public function rotatePassword(RotatePasswordRequest $request, Server $server, D
129129
*/
130130
public function delete(DeleteDatabaseRequest $request, Server $server, Database $database): Response
131131
{
132-
$this->managementService->delete($database->id);
132+
$this->managementService->delete($database);
133133

134134
return Response::create('', Response::HTTP_NO_CONTENT);
135135
}

app/Services/Databases/DatabaseManagementService.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,19 @@ public function create(Server $server, array $data)
151151
/**
152152
* Delete a database from the given host server.
153153
*
154-
* @param int $id
154+
* @param \Pterodactyl\Models\Database $database
155155
* @return bool|null
156156
*
157-
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
157+
* @throws \Exception
158158
*/
159-
public function delete($id)
159+
public function delete(Database $database)
160160
{
161-
$database = $this->repository->find($id);
162161
$this->dynamic->set('dynamic', $database->database_host_id);
163162

164163
$this->repository->dropDatabase($database->database);
165164
$this->repository->dropUser($database->username, $database->remote);
166165
$this->repository->flush();
167166

168-
return $this->repository->delete($id);
167+
return $database->delete();
169168
}
170169
}

app/Services/Servers/ServerDeletionService.php

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
namespace Pterodactyl\Services\Servers;
44

55
use Exception;
6-
use Psr\Log\LoggerInterface;
6+
use Illuminate\Http\Response;
77
use Pterodactyl\Models\Server;
8+
use Illuminate\Support\Facades\Log;
89
use Illuminate\Database\ConnectionInterface;
9-
use Pterodactyl\Repositories\Eloquent\ServerRepository;
10-
use Pterodactyl\Repositories\Eloquent\DatabaseRepository;
1110
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
1211
use Pterodactyl\Services\Databases\DatabaseManagementService;
1312
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
@@ -29,50 +28,26 @@ class ServerDeletionService
2928
*/
3029
private $daemonServerRepository;
3130

32-
/**
33-
* @var \Pterodactyl\Repositories\Eloquent\DatabaseRepository
34-
*/
35-
private $databaseRepository;
36-
3731
/**
3832
* @var \Pterodactyl\Services\Databases\DatabaseManagementService
3933
*/
4034
private $databaseManagementService;
4135

42-
/**
43-
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
44-
*/
45-
private $repository;
46-
47-
/**
48-
* @var \Psr\Log\LoggerInterface
49-
*/
50-
private $writer;
51-
5236
/**
5337
* DeletionService constructor.
5438
*
5539
* @param \Illuminate\Database\ConnectionInterface $connection
5640
* @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository
57-
* @param \Pterodactyl\Repositories\Eloquent\DatabaseRepository $databaseRepository
5841
* @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService
59-
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
60-
* @param \Psr\Log\LoggerInterface $writer
6142
*/
6243
public function __construct(
6344
ConnectionInterface $connection,
6445
DaemonServerRepository $daemonServerRepository,
65-
DatabaseRepository $databaseRepository,
66-
DatabaseManagementService $databaseManagementService,
67-
ServerRepository $repository,
68-
LoggerInterface $writer
46+
DatabaseManagementService $databaseManagementService
6947
) {
7048
$this->connection = $connection;
7149
$this->daemonServerRepository = $daemonServerRepository;
72-
$this->databaseRepository = $databaseRepository;
7350
$this->databaseManagementService = $databaseManagementService;
74-
$this->repository = $repository;
75-
$this->writer = $writer;
7651
}
7752

7853
/**
@@ -101,27 +76,39 @@ public function handle(Server $server)
10176
try {
10277
$this->daemonServerRepository->setServer($server)->delete();
10378
} catch (DaemonConnectionException $exception) {
104-
if ($this->force) {
105-
$this->writer->warning($exception);
106-
} else {
79+
// If there is an error not caused a 404 error and this isn't a forced delete,
80+
// go ahead and bail out. We specifically ignore a 404 since that can be assumed
81+
// to be a safe error, meaning the server doesn't exist at all on Wings so there
82+
// is no reason we need to bail out from that.
83+
if (! $this->force && $exception->getStatusCode() !== Response::HTTP_NOT_FOUND) {
10784
throw $exception;
10885
}
86+
87+
Log::warning($exception);
10988
}
11089

11190
$this->connection->transaction(function () use ($server) {
112-
$this->databaseRepository->setColumns('id')->findWhere([['server_id', '=', $server->id]])->each(function ($item) {
91+
foreach ($server->databases as $database) {
11392
try {
114-
$this->databaseManagementService->delete($item->id);
93+
$this->databaseManagementService->delete($database);
11594
} catch (Exception $exception) {
116-
if ($this->force) {
117-
$this->writer->warning($exception);
118-
} else {
95+
if (!$this->force) {
11996
throw $exception;
12097
}
98+
99+
// Oh well, just try to delete the database entry we have from the database
100+
// so that the server itself can be deleted. This will leave it dangling on
101+
// the host instance, but we couldn't delete it anyways so not sure how we would
102+
// handle this better anyways.
103+
//
104+
// @see https://github.com/pterodactyl/panel/issues/2085
105+
$database->delete();
106+
107+
Log::warning($exception);
121108
}
122-
});
109+
}
123110

124-
$this->repository->delete($server->id);
111+
$server->delete();
125112
});
126113
}
127114
}

config/logging.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use Monolog\Handler\NullHandler;
34
use Monolog\Handler\StreamHandler;
45

56
return [
@@ -75,5 +76,10 @@
7576
'driver' => 'errorlog',
7677
'level' => 'debug',
7778
],
79+
80+
'null' => [
81+
'driver' => 'monolog',
82+
'handler' => NullHandler::class,
83+
],
7884
],
7985
];
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
namespace Pterodactyl\Tests\Integration\Services\Servers;
4+
5+
use Mockery;
6+
use Exception;
7+
use GuzzleHttp\Psr7\Request;
8+
use GuzzleHttp\Psr7\Response;
9+
use Pterodactyl\Models\Database;
10+
use Pterodactyl\Models\DatabaseHost;
11+
use GuzzleHttp\Exception\BadResponseException;
12+
use Pterodactyl\Tests\Integration\IntegrationTestCase;
13+
use Pterodactyl\Services\Servers\ServerDeletionService;
14+
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
15+
use Pterodactyl\Services\Databases\DatabaseManagementService;
16+
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
17+
18+
class ServerDeletionServiceTest extends IntegrationTestCase
19+
{
20+
/** @var \Mockery\MockInterface */
21+
private $daemonServerRepository;
22+
23+
/** @var \Mockery\MockInterface */
24+
private $databaseManagementService;
25+
26+
private static $defaultLogger;
27+
28+
/**
29+
* Stub out services that we don't want to test in here.
30+
*/
31+
public function setUp(): void
32+
{
33+
parent::setUp();
34+
35+
self::$defaultLogger = config('logging.default');
36+
// There will be some log calls during this test, don't actually write to the disk.
37+
config()->set('logging.default', 'null');
38+
39+
$this->daemonServerRepository = Mockery::mock(DaemonServerRepository::class);
40+
$this->databaseManagementService = Mockery::mock(DatabaseManagementService::class);
41+
42+
$this->app->instance(DaemonServerRepository::class, $this->daemonServerRepository);
43+
$this->app->instance(DatabaseManagementService::class, $this->databaseManagementService);
44+
}
45+
46+
/**
47+
* Reset the log driver.
48+
*/
49+
protected function tearDown(): void
50+
{
51+
config()->set('logging.default', self::$defaultLogger);
52+
self::$defaultLogger = null;
53+
54+
parent::tearDown();
55+
}
56+
57+
/**
58+
* Test that a server is not deleted if the force option is not set and an error
59+
* is returned by wings.
60+
*/
61+
public function testRegularDeleteFailsIfWingsReturnsError()
62+
{
63+
$server = $this->createServerModel();
64+
65+
$this->expectException(DaemonConnectionException::class);
66+
67+
$this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andThrows(
68+
new DaemonConnectionException(new BadResponseException('Bad request', new Request('GET', '/test')))
69+
);
70+
71+
$this->getService()->handle($server);
72+
73+
$this->assertDatabaseHas('servers', ['id' => $server->id]);
74+
}
75+
76+
/**
77+
* Test that a 404 from Wings while deleting a server does not cause the deletion to fail.
78+
*/
79+
public function testRegularDeleteIgnores404FromWings()
80+
{
81+
$server = $this->createServerModel();
82+
83+
$this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andThrows(
84+
new DaemonConnectionException(new BadResponseException('Bad request', new Request('GET', '/test'), new Response(404)))
85+
);
86+
87+
$this->getService()->handle($server);
88+
89+
$this->assertDatabaseMissing('servers', ['id' => $server->id]);
90+
}
91+
92+
/**
93+
* Test that an error from Wings does not cause the deletion to fail if the server is being
94+
* force deleted.
95+
*/
96+
public function testForceDeleteIgnoresExceptionFromWings()
97+
{
98+
$server = $this->createServerModel();
99+
100+
$this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andThrows(
101+
new DaemonConnectionException(new BadResponseException('Bad request', new Request('GET', '/test'), new Response(500)))
102+
);
103+
104+
$this->getService()->withForce(true)->handle($server);
105+
106+
$this->assertDatabaseMissing('servers', ['id' => $server->id]);
107+
}
108+
109+
/**
110+
* Test that a non-force-delete call does not delete the server if one of the databases
111+
* cannot be deleted from the host.
112+
*/
113+
public function testExceptionWhileDeletingStopsProcess()
114+
{
115+
$server = $this->createServerModel();
116+
$host = factory(DatabaseHost::class)->create();
117+
118+
/** @var \Pterodactyl\Models\Database $db */
119+
$db = factory(Database::class)->create(['database_host_id' => $host->id, 'server_id' => $server->id]);
120+
121+
$server->refresh();
122+
123+
$this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andReturnUndefined();
124+
$this->databaseManagementService->expects('delete')->with(Mockery::on(function ($value) use ($db) {
125+
return $value instanceof Database && $value->id === $db->id;
126+
}))->andThrows(new Exception);
127+
128+
$this->expectException(Exception::class);
129+
$this->getService()->handle($server);
130+
131+
$this->assertDatabaseHas('servers', ['id' => $server->id]);
132+
$this->assertDatabaseHas('databases', ['id' => $db->id]);
133+
}
134+
135+
/**
136+
* Test that a server is deleted even if the server databases cannot be deleted from the host.
137+
*/
138+
public function testExceptionWhileDeletingDatabasesDoesNotAbortIfForceDeleted()
139+
{
140+
$server = $this->createServerModel();
141+
$host = factory(DatabaseHost::class)->create();
142+
143+
/** @var \Pterodactyl\Models\Database $db */
144+
$db = factory(Database::class)->create(['database_host_id' => $host->id, 'server_id' => $server->id]);
145+
146+
$server->refresh();
147+
148+
$this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andReturnUndefined();
149+
$this->databaseManagementService->expects('delete')->with(Mockery::on(function ($value) use ($db) {
150+
return $value instanceof Database && $value->id === $db->id;
151+
}))->andThrows(new Exception);
152+
153+
$this->getService()->withForce(true)->handle($server);
154+
155+
$this->assertDatabaseMissing('servers', ['id' => $server->id]);
156+
$this->assertDatabaseMissing('databases', ['id' => $db->id]);
157+
}
158+
159+
/**
160+
* @return \Pterodactyl\Services\Servers\ServerDeletionService
161+
*/
162+
private function getService()
163+
{
164+
return $this->app->make(ServerDeletionService::class);
165+
}
166+
}

0 commit comments

Comments
 (0)