diff --git a/docs/kinds/Deployment.md b/docs/kinds/Deployment.md index 7e3d2a9b..e6122b6c 100644 --- a/docs/kinds/Deployment.md +++ b/docs/kinds/Deployment.md @@ -111,6 +111,24 @@ foreach ($dep->getPods() as $pod) { } ``` +### Scaling + +The Scaling API is available via a `K8sScale` resource: + +```php +$scaler = $dep->scaler(); + +$scaler->setReplicas(3)->update(); // autoscale the Deployment to 3 replicas +``` + +Shorthand, you can use `scale()` directly from the Deployment + +```php +$scaler = $dep->scale(3); + +$pods = $dep->getPods(); // Expecting 3 pods +``` + ### Deployment Status The Status API is available to be accessed for fresh instances: diff --git a/docs/kinds/StatefulSet.md b/docs/kinds/StatefulSet.md index 57fd7061..5ad3487b 100644 --- a/docs/kinds/StatefulSet.md +++ b/docs/kinds/StatefulSet.md @@ -127,6 +127,24 @@ foreach ($sts->getPods() as $pod) { } ``` +### Scaling + +The Scaling API is available via a `K8sScale` resource: + +```php +$scaler = $sts->scaler(); + +$scaler->setReplicas(3)->update(); // autoscale the StatefulSet to 3 replicas +``` + +Shorthand, you can use `scale()` directly from the StatefulSet + +```php +$scaler = $sts->scale(3); + +$pods = $sts->getPods(); // Expecting 3 pods +``` + ### StatefulSet Status The Status API is available to be accessed for fresh instances: diff --git a/src/Contracts/Scalable.php b/src/Contracts/Scalable.php new file mode 100644 index 00000000..4e8d960c --- /dev/null +++ b/src/Contracts/Scalable.php @@ -0,0 +1,13 @@ +getApiVersion()}/watch/namespaces/{$this->getNamespace()}/deployments/{$this->getIdentifier()}"; } + /** + * Get the path, prefixed by '/', that points to the resource scale. + * + * @return string + */ + public function resourceScalePath(): string + { + return "/apis/{$this->getApiVersion()}/namespaces/{$this->getNamespace()}/deployments/{$this->getIdentifier()}/scale"; + } + /** * Get the selector for the pods that are owned by this resource. * diff --git a/src/Kinds/K8sResource.php b/src/Kinds/K8sResource.php index 8b706296..70c51508 100644 --- a/src/Kinds/K8sResource.php +++ b/src/Kinds/K8sResource.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Jsonable; use RenokiCo\PhpK8s\Contracts\Loggable; +use RenokiCo\PhpK8s\Contracts\Scalable; use RenokiCo\PhpK8s\Contracts\Watchable; use RenokiCo\PhpK8s\Exceptions\KubernetesAPIException; use RenokiCo\PhpK8s\Exceptions\KubernetesWatchException; @@ -728,4 +729,31 @@ public function watchContainerLogsByName(string $name, string $container, Closur { return $this->whereName($name)->watchContainerLogs($container, $callback, $query); } + + /** + * Get a specific resource scaling data. + * + * @return \RenokiCo\PhpK8s\Kinds\K8sScale + */ + public function scaler(): K8sScale + { + if (! $this instanceof Scalable) { + throw new KubernetesScalingException( + 'The resource '.get_class($this).' does not support scaling.' + ); + } + + $scaler = $this->cluster + ->setResourceClass(K8sScale::class) + ->runOperation( + KubernetesCluster::GET_OP, + $this->resourceScalePath(), + $this->toJsonPayload(), + ['pretty' => 1] + ); + + $scaler->setScalableResource($this); + + return $scaler; + } } diff --git a/src/Kinds/K8sScale.php b/src/Kinds/K8sScale.php new file mode 100644 index 00000000..e12eb81e --- /dev/null +++ b/src/Kinds/K8sScale.php @@ -0,0 +1,93 @@ +resource->resourceScalePath(); + } + + /** + * Set the desired replicas for the scale. + * + * @param int $replicas + * @return $this + */ + public function setReplicas(int $replicas) + { + return $this->setSpec('replicas', $replicas); + } + + /** + * Get the defined replicas for the scale. + * + * @return int + */ + public function getReplicas(): int + { + return $this->getSpec('replicas', 0); + } + + /** + * Set the original scalable resource for this scale. + * + * @param \RenokiCo\PhpK8s\Kinds\K8sResource $resource + * @return $this + */ + public function setScalableResource(K8sResource $resource) + { + $this->resource = $resource; + + return $this; + } +} diff --git a/src/Kinds/K8sStatefulSet.php b/src/Kinds/K8sStatefulSet.php index 7171929d..56d72034 100644 --- a/src/Kinds/K8sStatefulSet.php +++ b/src/Kinds/K8sStatefulSet.php @@ -4,7 +4,9 @@ use RenokiCo\PhpK8s\Contracts\InteractsWithK8sCluster; use RenokiCo\PhpK8s\Contracts\Podable; +use RenokiCo\PhpK8s\Contracts\Scalable; use RenokiCo\PhpK8s\Contracts\Watchable; +use RenokiCo\PhpK8s\Traits\CanScale; use RenokiCo\PhpK8s\Traits\HasAnnotations; use RenokiCo\PhpK8s\Traits\HasLabels; use RenokiCo\PhpK8s\Traits\HasPods; @@ -12,9 +14,9 @@ use RenokiCo\PhpK8s\Traits\HasSpec; use RenokiCo\PhpK8s\Traits\HasTemplate; -class K8sStatefulSet extends K8sResource implements InteractsWithK8sCluster, Podable, Watchable +class K8sStatefulSet extends K8sResource implements InteractsWithK8sCluster, Podable, Scalable, Watchable { - use HasAnnotations, HasLabels, HasPods, HasSelector, HasSpec, HasTemplate; + use CanScale, HasAnnotations, HasLabels, HasPods, HasSelector, HasSpec, HasTemplate; /** * The resource Kind parameter. @@ -159,6 +161,16 @@ public function resourceWatchPath(): string return "/apis/{$this->getApiVersion()}/watch/namespaces/{$this->getNamespace()}/statefulsets/{$this->getIdentifier()}"; } + /** + * Get the path, prefixed by '/', that points to the resource scale. + * + * @return string + */ + public function resourceScalePath(): string + { + return "/apis/{$this->getApiVersion()}/namespaces/{$this->getNamespace()}/statefulsets/{$this->getIdentifier()}/scale"; + } + /** * Get the selector for the pods that are owned by this resource. * diff --git a/src/Traits/CanScale.php b/src/Traits/CanScale.php new file mode 100644 index 00000000..618877e9 --- /dev/null +++ b/src/Traits/CanScale.php @@ -0,0 +1,21 @@ +scaler(); + + $scaler->setReplicas($replicas)->update(); + + return $scaler; + } +} diff --git a/src/Traits/HasPods.php b/src/Traits/HasPods.php index 922ce1a2..77a0fec7 100644 --- a/src/Traits/HasPods.php +++ b/src/Traits/HasPods.php @@ -22,7 +22,7 @@ public function getPods(array $query = ['pretty' => 1]) ->all(array_merge(['labelSelector' => $labelSelector], $query)); } - /** + /** * Check if all scheduled pods are running. * * @return bool diff --git a/tests/DeploymentTest.php b/tests/DeploymentTest.php index ec3fcf54..cf94419d 100644 --- a/tests/DeploymentTest.php +++ b/tests/DeploymentTest.php @@ -73,6 +73,7 @@ public function test_deployment_api_interaction() $this->runUpdateTests(); $this->runWatchAllTests(); $this->runWatchTests(); + $this->runScalingTests(); $this->runDeletionTests(); } @@ -236,4 +237,22 @@ public function runWatchTests() $this->assertTrue($watch); } + + public function runScalingTests() + { + $dep = $this->cluster->getDeploymentByName('mysql'); + + $scaler = $dep->scale(2); + + while ($dep->getReadyReplicasCount() < 2 || $scaler->getReplicas() < 2) { + dump("Awaiting for deployment {$dep->getName()} to scale to 2 replicas..."); + $scaler->refresh(); + $dep->refresh(); + sleep(1); + } + + $this->assertEquals(2, $dep->getReadyReplicasCount()); + $this->assertEquals(2, $scaler->getReplicas()); + $this->assertCount(2, $dep->getPods()); + } } diff --git a/tests/StatefulSetTest.php b/tests/StatefulSetTest.php index ea5c5967..cdcf1201 100644 --- a/tests/StatefulSetTest.php +++ b/tests/StatefulSetTest.php @@ -110,6 +110,7 @@ public function test_stateful_set_api_interaction() $this->runUpdateTests(); $this->runWatchAllTests(); $this->runWatchTests(); + $this->runScalingTests(); $this->runDeletionTests(); } @@ -294,4 +295,22 @@ public function runWatchTests() $this->assertTrue($watch); } + + public function runScalingTests() + { + $sts = $this->cluster->getStatefulSetByName('mysql'); + + $scaler = $sts->scale(2); + + while ($sts->getReadyReplicasCount() < 2 || $scaler->getReplicas() < 2) { + dump("Awaiting for statefulset {$sts->getName()} to scale to 2 replicas..."); + $scaler->refresh(); + $sts->refresh(); + sleep(1); + } + + $this->assertEquals(2, $sts->getReadyReplicasCount()); + $this->assertEquals(2, $scaler->getReplicas()); + $this->assertCount(2, $sts->getPods()); + } }