Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php
7460 views
1
<?php
2
3
namespace Pterodactyl\Tests\Integration\Services\Deployment;
4
5
use Pterodactyl\Models\Node;
6
use Pterodactyl\Models\Server;
7
use Pterodactyl\Models\Database;
8
use Pterodactyl\Models\Location;
9
use Illuminate\Support\Collection;
10
use Pterodactyl\Tests\Integration\IntegrationTestCase;
11
use Pterodactyl\Services\Deployment\FindViableNodesService;
12
use Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException;
13
14
class FindViableNodesServiceTest extends IntegrationTestCase
15
{
16
public function setUp(): void
17
{
18
parent::setUp();
19
20
Database::query()->delete();
21
Server::query()->delete();
22
Node::query()->delete();
23
}
24
25
public function testExceptionIsThrownIfNoDiskSpaceHasBeenSet()
26
{
27
$this->expectException(\InvalidArgumentException::class);
28
$this->expectExceptionMessage('Disk space must be an int, got NULL');
29
30
$this->getService()->handle();
31
}
32
33
public function testExceptionIsThrownIfNoMemoryHasBeenSet()
34
{
35
$this->expectException(\InvalidArgumentException::class);
36
$this->expectExceptionMessage('Memory usage must be an int, got NULL');
37
38
$this->getService()->setDisk(10)->handle();
39
}
40
41
/**
42
* Ensure that errors are not thrown back when passing in expected values.
43
*
44
* @see https://github.com/pterodactyl/panel/issues/2529
45
*/
46
public function testNoExceptionIsThrownIfStringifiedIntegersArePassedForLocations()
47
{
48
$this->getService()->setLocations([1, 2, 3]);
49
$this->getService()->setLocations(['1', '2', '3']);
50
$this->getService()->setLocations(['1', 2, 3]);
51
52
try {
53
$this->getService()->setLocations(['a']);
54
$this->fail('This expectation should not be called.');
55
} catch (\Exception $exception) {
56
$this->assertInstanceOf(\InvalidArgumentException::class, $exception);
57
$this->assertSame('An array of location IDs should be provided when calling setLocations.', $exception->getMessage());
58
}
59
60
try {
61
$this->getService()->setLocations(['1.2', '1', 2]);
62
$this->fail('This expectation should not be called.');
63
} catch (\Exception $exception) {
64
$this->assertInstanceOf(\InvalidArgumentException::class, $exception);
65
$this->assertSame('An array of location IDs should be provided when calling setLocations.', $exception->getMessage());
66
}
67
}
68
69
public function testExpectedNodeIsReturnedForLocation()
70
{
71
/** @var \Pterodactyl\Models\Location[] $locations */
72
$locations = Location::factory()->times(2)->create();
73
74
/** @var \Pterodactyl\Models\Node[] $nodes */
75
$nodes = [
76
// This node should never be returned once we've completed the initial test which
77
// runs without a location filter.
78
Node::factory()->create([
79
'location_id' => $locations[0]->id,
80
'memory' => 2048,
81
'disk' => 1024 * 100,
82
]),
83
Node::factory()->create([
84
'location_id' => $locations[1]->id,
85
'memory' => 1024,
86
'disk' => 10240,
87
'disk_overallocate' => 10,
88
]),
89
Node::factory()->create([
90
'location_id' => $locations[1]->id,
91
'memory' => 1024 * 4,
92
'memory_overallocate' => 50,
93
'disk' => 102400,
94
]),
95
];
96
97
// Expect that all the nodes are returned as we're under all of their limits
98
// and there is no location filter being provided.
99
$response = $this->getService()->setDisk(512)->setMemory(512)->handle();
100
$this->assertInstanceOf(Collection::class, $response);
101
$this->assertCount(3, $response);
102
$this->assertInstanceOf(Node::class, $response[0]);
103
104
// Expect that only the last node is returned because it is the only one with enough
105
// memory available to this instance.
106
$response = $this->getService()->setDisk(512)->setMemory(2049)->handle();
107
$this->assertInstanceOf(Collection::class, $response);
108
$this->assertCount(1, $response);
109
$this->assertSame($nodes[2]->id, $response[0]->id);
110
111
// Helper, I am lazy.
112
$base = function () use ($locations) {
113
return $this->getService()->setLocations([$locations[1]->id])->setDisk(512);
114
};
115
116
// Expect that we can create this server on either node since the disk and memory
117
// limits are below the allowed amount.
118
$response = $base()->setMemory(512)->handle();
119
$this->assertCount(2, $response);
120
$this->assertSame(2, $response->where('location_id', $locations[1]->id)->count());
121
122
// Expect that we can only create this server on the second node since the memory
123
// allocated is over the amount of memory available to the first node.
124
$response = $base()->setMemory(2048)->handle();
125
$this->assertCount(1, $response);
126
$this->assertSame($nodes[2]->id, $response[0]->id);
127
128
// Expect that we can only create this server on the second node since the disk
129
// allocated is over the limit assigned to the first node (even with the overallocate).
130
$response = $base()->setDisk(20480)->setMemory(256)->handle();
131
$this->assertCount(1, $response);
132
$this->assertSame($nodes[2]->id, $response[0]->id);
133
134
// Expect that we could create the server on either node since the disk allocated is
135
// right at the limit for Node 1 when the overallocate value is included in the calc.
136
$response = $base()->setDisk(11264)->setMemory(256)->handle();
137
$this->assertCount(2, $response);
138
139
// Create two servers on the first node so that the disk space used is equal to the
140
// base amount available to the node (without overallocation included).
141
$servers = Collection::make([
142
$this->createServerModel(['node_id' => $nodes[1]->id, 'disk' => 5120]),
143
$this->createServerModel(['node_id' => $nodes[1]->id, 'disk' => 5120]),
144
]);
145
146
// Expect that we cannot create a server with a 1GB disk on the first node since there
147
// is not enough space (even with the overallocate) available to the node.
148
$response = $base()->setDisk(1024)->setMemory(256)->handle();
149
$this->assertCount(1, $response);
150
$this->assertSame($nodes[2]->id, $response[0]->id);
151
152
// Cleanup servers since we need to test some other stuff with memory here.
153
$servers->each->delete();
154
155
// Expect that no viable node can be found when the memory limit for the given instance
156
// is greater than either node can support, even with the overallocation limits taken
157
// into account.
158
$this->expectException(NoViableNodeException::class);
159
$base()->setMemory(10000)->handle();
160
161
// Create four servers so that the memory used for the second node is equal to the total
162
// limit for that node (pre-overallocate calculation).
163
Collection::make([
164
$this->createServerModel(['node_id' => $nodes[2]->id, 'memory' => 1024]),
165
$this->createServerModel(['node_id' => $nodes[2]->id, 'memory' => 1024]),
166
$this->createServerModel(['node_id' => $nodes[2]->id, 'memory' => 1024]),
167
$this->createServerModel(['node_id' => $nodes[2]->id, 'memory' => 1024]),
168
]);
169
170
// Expect that either node can support this server when we account for the overallocate
171
// value of the second node.
172
$response = $base()->setMemory(500)->handle();
173
$this->assertCount(2, $response);
174
$this->assertSame(2, $response->where('location_id', $locations[1]->id)->count());
175
176
// Expect that only the first node can support this server when we go over the remaining
177
// memory for the second nodes overallocate calculation.
178
$response = $base()->setMemory(640)->handle();
179
$this->assertCount(1, $response);
180
$this->assertSame($nodes[1]->id, $response[0]->id);
181
}
182
183
private function getService(): FindViableNodesService
184
{
185
return $this->app->make(FindViableNodesService::class);
186
}
187
}
188
189