Skip to content

Commit 73caae1

Browse files
authored
Merge pull request #35 from renoki-co/feature/scaling
[feature] Scaling for StatefulSet & Deployment
2 parents 8981324 + a86c087 commit 73caae1

11 files changed

+258
-5
lines changed

docs/kinds/Deployment.md

+18
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,24 @@ foreach ($dep->getPods() as $pod) {
111111
}
112112
```
113113

114+
### Scaling
115+
116+
The Scaling API is available via a `K8sScale` resource:
117+
118+
```php
119+
$scaler = $dep->scaler();
120+
121+
$scaler->setReplicas(3)->update(); // autoscale the Deployment to 3 replicas
122+
```
123+
124+
Shorthand, you can use `scale()` directly from the Deployment
125+
126+
```php
127+
$scaler = $dep->scale(3);
128+
129+
$pods = $dep->getPods(); // Expecting 3 pods
130+
```
131+
114132
### Deployment Status
115133

116134
The Status API is available to be accessed for fresh instances:

docs/kinds/StatefulSet.md

+18
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,24 @@ foreach ($sts->getPods() as $pod) {
127127
}
128128
```
129129

130+
### Scaling
131+
132+
The Scaling API is available via a `K8sScale` resource:
133+
134+
```php
135+
$scaler = $sts->scaler();
136+
137+
$scaler->setReplicas(3)->update(); // autoscale the StatefulSet to 3 replicas
138+
```
139+
140+
Shorthand, you can use `scale()` directly from the StatefulSet
141+
142+
```php
143+
$scaler = $sts->scale(3);
144+
145+
$pods = $sts->getPods(); // Expecting 3 pods
146+
```
147+
130148
### StatefulSet Status
131149

132150
The Status API is available to be accessed for fresh instances:

src/Contracts/Scalable.php

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace RenokiCo\PhpK8s\Contracts;
4+
5+
interface Scalable
6+
{
7+
/**
8+
* Get the path, prefixed by '/', that points to the resource scale.
9+
*
10+
* @return string
11+
*/
12+
public function resourceScalePath(): string;
13+
}

src/Kinds/K8sDeployment.php

+14-2
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@
44

55
use RenokiCo\PhpK8s\Contracts\InteractsWithK8sCluster;
66
use RenokiCo\PhpK8s\Contracts\Podable;
7+
use RenokiCo\PhpK8s\Contracts\Scalable;
78
use RenokiCo\PhpK8s\Contracts\Watchable;
9+
use RenokiCo\PhpK8s\Traits\CanScale;
810
use RenokiCo\PhpK8s\Traits\HasAnnotations;
911
use RenokiCo\PhpK8s\Traits\HasLabels;
1012
use RenokiCo\PhpK8s\Traits\HasPods;
1113
use RenokiCo\PhpK8s\Traits\HasSelector;
1214
use RenokiCo\PhpK8s\Traits\HasSpec;
1315
use RenokiCo\PhpK8s\Traits\HasTemplate;
1416

15-
class K8sDeployment extends K8sResource implements InteractsWithK8sCluster, Podable, Watchable
17+
class K8sDeployment extends K8sResource implements InteractsWithK8sCluster, Podable, Scalable, Watchable
1618
{
17-
use HasAnnotations, HasLabels, HasPods, HasSelector, HasSpec, HasTemplate;
19+
use CanScale, HasAnnotations, HasLabels, HasPods, HasSelector, HasSpec, HasTemplate;
1820

1921
/**
2022
* The resource Kind parameter.
@@ -98,6 +100,16 @@ public function resourceWatchPath(): string
98100
return "/apis/{$this->getApiVersion()}/watch/namespaces/{$this->getNamespace()}/deployments/{$this->getIdentifier()}";
99101
}
100102

103+
/**
104+
* Get the path, prefixed by '/', that points to the resource scale.
105+
*
106+
* @return string
107+
*/
108+
public function resourceScalePath(): string
109+
{
110+
return "/apis/{$this->getApiVersion()}/namespaces/{$this->getNamespace()}/deployments/{$this->getIdentifier()}/scale";
111+
}
112+
101113
/**
102114
* Get the selector for the pods that are owned by this resource.
103115
*

src/Kinds/K8sResource.php

+28
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Contracts\Support\Arrayable;
77
use Illuminate\Contracts\Support\Jsonable;
88
use RenokiCo\PhpK8s\Contracts\Loggable;
9+
use RenokiCo\PhpK8s\Contracts\Scalable;
910
use RenokiCo\PhpK8s\Contracts\Watchable;
1011
use RenokiCo\PhpK8s\Exceptions\KubernetesAPIException;
1112
use RenokiCo\PhpK8s\Exceptions\KubernetesWatchException;
@@ -728,4 +729,31 @@ public function watchContainerLogsByName(string $name, string $container, Closur
728729
{
729730
return $this->whereName($name)->watchContainerLogs($container, $callback, $query);
730731
}
732+
733+
/**
734+
* Get a specific resource scaling data.
735+
*
736+
* @return \RenokiCo\PhpK8s\Kinds\K8sScale
737+
*/
738+
public function scaler(): K8sScale
739+
{
740+
if (! $this instanceof Scalable) {
741+
throw new KubernetesScalingException(
742+
'The resource '.get_class($this).' does not support scaling.'
743+
);
744+
}
745+
746+
$scaler = $this->cluster
747+
->setResourceClass(K8sScale::class)
748+
->runOperation(
749+
KubernetesCluster::GET_OP,
750+
$this->resourceScalePath(),
751+
$this->toJsonPayload(),
752+
['pretty' => 1]
753+
);
754+
755+
$scaler->setScalableResource($this);
756+
757+
return $scaler;
758+
}
731759
}

src/Kinds/K8sScale.php

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
namespace RenokiCo\PhpK8s\Kinds;
4+
5+
use RenokiCo\PhpK8s\Contracts\InteractsWithK8sCluster;
6+
use RenokiCo\PhpK8s\Traits\HasSpec;
7+
8+
class K8sScale extends K8sResource implements InteractsWithK8sCluster
9+
{
10+
use HasSpec;
11+
12+
/**
13+
* The resource Kind parameter.
14+
*
15+
* @var null|string
16+
*/
17+
protected static $kind = 'Scale';
18+
19+
/**
20+
* The original scalable resource for this scale.
21+
*
22+
* @var \RenokiCo\PhpK8s\Kinds\K8sResource
23+
*/
24+
protected $resource;
25+
26+
/**
27+
* Wether the resource has a namespace.
28+
*
29+
* @var bool
30+
*/
31+
protected static $namespaceable = true;
32+
33+
/**
34+
* The default version for the resource.
35+
*
36+
* @var string
37+
*/
38+
protected static $stableVersion = 'autoscaling/v1';
39+
40+
/**
41+
* Get the path, prefixed by '/', that points to the resources list.
42+
*
43+
* @return string
44+
*/
45+
public function allResourcesPath(): string
46+
{
47+
return '';
48+
}
49+
50+
/**
51+
* Get the path, prefixed by '/', that points to the specific resource.
52+
*
53+
* @return string
54+
*/
55+
public function resourcePath(): string
56+
{
57+
return $this->resource->resourceScalePath();
58+
}
59+
60+
/**
61+
* Set the desired replicas for the scale.
62+
*
63+
* @param int $replicas
64+
* @return $this
65+
*/
66+
public function setReplicas(int $replicas)
67+
{
68+
return $this->setSpec('replicas', $replicas);
69+
}
70+
71+
/**
72+
* Get the defined replicas for the scale.
73+
*
74+
* @return int
75+
*/
76+
public function getReplicas(): int
77+
{
78+
return $this->getSpec('replicas', 0);
79+
}
80+
81+
/**
82+
* Set the original scalable resource for this scale.
83+
*
84+
* @param \RenokiCo\PhpK8s\Kinds\K8sResource $resource
85+
* @return $this
86+
*/
87+
public function setScalableResource(K8sResource $resource)
88+
{
89+
$this->resource = $resource;
90+
91+
return $this;
92+
}
93+
}

src/Kinds/K8sStatefulSet.php

+14-2
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@
44

55
use RenokiCo\PhpK8s\Contracts\InteractsWithK8sCluster;
66
use RenokiCo\PhpK8s\Contracts\Podable;
7+
use RenokiCo\PhpK8s\Contracts\Scalable;
78
use RenokiCo\PhpK8s\Contracts\Watchable;
9+
use RenokiCo\PhpK8s\Traits\CanScale;
810
use RenokiCo\PhpK8s\Traits\HasAnnotations;
911
use RenokiCo\PhpK8s\Traits\HasLabels;
1012
use RenokiCo\PhpK8s\Traits\HasPods;
1113
use RenokiCo\PhpK8s\Traits\HasSelector;
1214
use RenokiCo\PhpK8s\Traits\HasSpec;
1315
use RenokiCo\PhpK8s\Traits\HasTemplate;
1416

15-
class K8sStatefulSet extends K8sResource implements InteractsWithK8sCluster, Podable, Watchable
17+
class K8sStatefulSet extends K8sResource implements InteractsWithK8sCluster, Podable, Scalable, Watchable
1618
{
17-
use HasAnnotations, HasLabels, HasPods, HasSelector, HasSpec, HasTemplate;
19+
use CanScale, HasAnnotations, HasLabels, HasPods, HasSelector, HasSpec, HasTemplate;
1820

1921
/**
2022
* The resource Kind parameter.
@@ -159,6 +161,16 @@ public function resourceWatchPath(): string
159161
return "/apis/{$this->getApiVersion()}/watch/namespaces/{$this->getNamespace()}/statefulsets/{$this->getIdentifier()}";
160162
}
161163

164+
/**
165+
* Get the path, prefixed by '/', that points to the resource scale.
166+
*
167+
* @return string
168+
*/
169+
public function resourceScalePath(): string
170+
{
171+
return "/apis/{$this->getApiVersion()}/namespaces/{$this->getNamespace()}/statefulsets/{$this->getIdentifier()}/scale";
172+
}
173+
162174
/**
163175
* Get the selector for the pods that are owned by this resource.
164176
*

src/Traits/CanScale.php

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace RenokiCo\PhpK8s\Traits;
4+
5+
trait CanScale
6+
{
7+
/**
8+
* Scale the current resource to a specific number of replicas.
9+
*
10+
* @param int $replicas
11+
* @return \RenokiCo\PhpK8s\Kinds\K8sScale
12+
*/
13+
public function scale(int $replicas)
14+
{
15+
$scaler = $this->scaler();
16+
17+
$scaler->setReplicas($replicas)->update();
18+
19+
return $scaler;
20+
}
21+
}

src/Traits/HasPods.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function getPods(array $query = ['pretty' => 1])
2222
->all(array_merge(['labelSelector' => $labelSelector], $query));
2323
}
2424

25-
/**
25+
/**
2626
* Check if all scheduled pods are running.
2727
*
2828
* @return bool

tests/DeploymentTest.php

+19
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public function test_deployment_api_interaction()
7373
$this->runUpdateTests();
7474
$this->runWatchAllTests();
7575
$this->runWatchTests();
76+
$this->runScalingTests();
7677
$this->runDeletionTests();
7778
}
7879

@@ -236,4 +237,22 @@ public function runWatchTests()
236237

237238
$this->assertTrue($watch);
238239
}
240+
241+
public function runScalingTests()
242+
{
243+
$dep = $this->cluster->getDeploymentByName('mysql');
244+
245+
$scaler = $dep->scale(2);
246+
247+
while ($dep->getReadyReplicasCount() < 2 || $scaler->getReplicas() < 2) {
248+
dump("Awaiting for deployment {$dep->getName()} to scale to 2 replicas...");
249+
$scaler->refresh();
250+
$dep->refresh();
251+
sleep(1);
252+
}
253+
254+
$this->assertEquals(2, $dep->getReadyReplicasCount());
255+
$this->assertEquals(2, $scaler->getReplicas());
256+
$this->assertCount(2, $dep->getPods());
257+
}
239258
}

tests/StatefulSetTest.php

+19
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public function test_stateful_set_api_interaction()
110110
$this->runUpdateTests();
111111
$this->runWatchAllTests();
112112
$this->runWatchTests();
113+
$this->runScalingTests();
113114
$this->runDeletionTests();
114115
}
115116

@@ -294,4 +295,22 @@ public function runWatchTests()
294295

295296
$this->assertTrue($watch);
296297
}
298+
299+
public function runScalingTests()
300+
{
301+
$sts = $this->cluster->getStatefulSetByName('mysql');
302+
303+
$scaler = $sts->scale(2);
304+
305+
while ($sts->getReadyReplicasCount() < 2 || $scaler->getReplicas() < 2) {
306+
dump("Awaiting for statefulset {$sts->getName()} to scale to 2 replicas...");
307+
$scaler->refresh();
308+
$sts->refresh();
309+
sleep(1);
310+
}
311+
312+
$this->assertEquals(2, $sts->getReadyReplicasCount());
313+
$this->assertEquals(2, $scaler->getReplicas());
314+
$this->assertCount(2, $sts->getPods());
315+
}
297316
}

0 commit comments

Comments
 (0)