From 965977d84ba8e90a47df34926f2a5088fe087787 Mon Sep 17 00:00:00 2001 From: butschster Date: Tue, 10 Oct 2023 11:22:30 +0400 Subject: [PATCH 1/3] Dynamic Workers Scaling via RPC see #1728 --- src/WorkerPool.php | 39 ++++++++++++ tests/Unit/EnvironmentTest.php | 59 +++++++++++++++++++ tests/Unit/PayloadFactoryTest.php | 90 ++++++++++++++++++++++++++++ tests/Unit/PayloadTest.php | 47 +++++++++++++++ tests/Unit/VersionTest.php | 98 +++++++++++++++++++++++++++++++ tests/Unit/WorkerPoolTest.php | 44 ++++++++++++++ 6 files changed, 377 insertions(+) create mode 100644 src/WorkerPool.php create mode 100644 tests/Unit/EnvironmentTest.php create mode 100644 tests/Unit/PayloadFactoryTest.php create mode 100644 tests/Unit/PayloadTest.php create mode 100644 tests/Unit/VersionTest.php create mode 100644 tests/Unit/WorkerPoolTest.php diff --git a/src/WorkerPool.php b/src/WorkerPool.php new file mode 100644 index 0000000..fda88e2 --- /dev/null +++ b/src/WorkerPool.php @@ -0,0 +1,39 @@ +rpc = $rpc->withCodec(new JsonCodec()); + } + + /** + * Add worker to the pool. + * + * @param non-empty-string $plugin + */ + public function addWorker(string $plugin): void + { + $this->rpc->call('informer.AddWorker', $plugin); + } + + /** + * Remove worker from the pool. + * + * @param non-empty-string $plugin + */ + public function removeWorker(string $plugin): void + { + $this->rpc->call('informer.RemoveWorker', $plugin); + } +} \ No newline at end of file diff --git a/tests/Unit/EnvironmentTest.php b/tests/Unit/EnvironmentTest.php new file mode 100644 index 0000000..93040cf --- /dev/null +++ b/tests/Unit/EnvironmentTest.php @@ -0,0 +1,59 @@ +assertEquals('', $env->getMode()); + } + + public function testGetModeWithValue(): void + { + $env = new Environment(['RR_MODE' => 'mode_value']); + $this->assertEquals('mode_value', $env->getMode()); + } + + public function testGetRelayAddressWithDefault(): void + { + $env = new Environment(); + $this->assertEquals('pipes', $env->getRelayAddress()); + } + + public function testGetRelayAddressWithValue(): void + { + $env = new Environment(['RR_RELAY' => 'relay_value']); + $this->assertEquals('relay_value', $env->getRelayAddress()); + } + + public function testGetRPCAddressWithDefault(): void + { + $env = new Environment(); + $this->assertEquals('tcp://127.0.0.1:6001', $env->getRPCAddress()); + } + + public function testGetRPCAddressWithValue(): void + { + $env = new Environment(['RR_RPC' => 'rpc_value']); + $this->assertEquals('rpc_value', $env->getRPCAddress()); + } + + public function testFromGlobals(): void + { + $_ENV['RR_MODE'] = 'global_mode'; + $_SERVER['RR_RELAY'] = 'global_relay'; + + $env = Environment::fromGlobals(); + + $this->assertEquals('global_mode', $env->getMode()); + $this->assertEquals('global_relay', $env->getRelayAddress()); + $this->assertEquals('tcp://127.0.0.1:6001', $env->getRPCAddress()); + } +} \ No newline at end of file diff --git a/tests/Unit/PayloadFactoryTest.php b/tests/Unit/PayloadFactoryTest.php new file mode 100644 index 0000000..9339b92 --- /dev/null +++ b/tests/Unit/PayloadFactoryTest.php @@ -0,0 +1,90 @@ +byte10 = Frame::BYTE10_STOP; + $payload = PayloadFactory::fromFrame($frame); + + $this->assertInstanceOf(StreamStop::class, $payload); + } + + public function testFromFrameWithPongFlag(): void + { + $frame = new Frame("{}", []); + $frame->byte10 = Frame::BYTE10_PONG; + $payload = PayloadFactory::fromFrame($frame); + + $this->assertInstanceOf(Pong::class, $payload); + } + + public function testFromFrameWithoutSpecificFlags(): void + { + $frame = new Frame("test", [0]); + $payload = PayloadFactory::fromFrame($frame); + + $this->assertNotNull($payload); + $this->assertSame("test", $payload->body); + $this->assertSame("", $payload->header); + } + + public function testMakeControlWithWorkerStop(): void + { + $json = \json_encode(['stop' => true]); + $frame = new Frame($json); + $frame->setFlag(Frame::CONTROL); + + $payload = PayloadFactory::fromFrame($frame); + $this->assertInstanceOf(WorkerStop::class, $payload); + } + + public function testMakeControlWithGetProcessId(): void + { + $json = \json_encode(['pid' => true]); + $frame = new Frame($json); + $frame->setFlag(Frame::CONTROL); + + $payload = PayloadFactory::fromFrame($frame); + $this->assertInstanceOf(GetProcessId::class, $payload); + } + + public function testFromFrameWithControlFlag(): void + { + $frame = new Frame(null, [], Frame::CONTROL); + + $this->expectException(RoadRunnerException::class); + $this->expectExceptionMessage('Invalid task header, JSON payload is expected: Syntax error'); + PayloadFactory::fromFrame($frame); + } + + public function testMakeControlWithException(): void + { + $this->expectException(RoadRunnerException::class); + $this->expectExceptionMessage('Invalid task header, undefined control package'); + $json = json_encode([]); + $frame = new Frame($json); + $frame->setFlag(Frame::CONTROL); + + PayloadFactory::fromFrame($frame); + } + + public function testMakePayload(): void + { + $this->markTestIncomplete('Not implemented yet.'); + } +} \ No newline at end of file diff --git a/tests/Unit/PayloadTest.php b/tests/Unit/PayloadTest.php new file mode 100644 index 0000000..01fd27c --- /dev/null +++ b/tests/Unit/PayloadTest.php @@ -0,0 +1,47 @@ +assertEquals('body_content', $payload->body); + $this->assertEquals('header_content', $payload->header); + $this->assertFalse($payload->eos); + } + + public function testPayloadConstructionWithDefaultValues(): void + { + $payload = new Payload(null, null); + + $this->assertEquals('', $payload->body); + $this->assertEquals('', $payload->header); + $this->assertTrue($payload->eos); + } + + public function testPayloadConstructionWithPartialValues(): void + { + $payload = new Payload('body_content'); + + $this->assertEquals('body_content', $payload->body); + $this->assertEquals('', $payload->header); + $this->assertTrue($payload->eos); + } + + public function testPayloadConstructionWithEosFalse(): void + { + $payload = new Payload(null, null, false); + + $this->assertEquals('', $payload->body); + $this->assertEquals('', $payload->header); + $this->assertFalse($payload->eos); + } +} \ No newline at end of file diff --git a/tests/Unit/VersionTest.php b/tests/Unit/VersionTest.php new file mode 100644 index 0000000..d363ceb --- /dev/null +++ b/tests/Unit/VersionTest.php @@ -0,0 +1,98 @@ + [ + 'pretty_version' => 'v1.9.0', + ], + 'spiral/roadrunner-worker' => [ + 'pretty_version' => 'v1.8.0', + ], + ], + '1.9.0', + '1.*' + ]; + + + yield [ + [ + 'spiral/roadrunner' => [ + 'pretty_version' => '2.1.0', + ], + ], + '2.1.0', + '2.*' + ]; + + yield [ + [ + 'spiral/roadrunner-worker' => [ + 'pretty_version' => 'v1.8.0', + ], + 'spiral/roadrunner' => [ + 'pretty_version' => 'v1.9.0', + ], + ], + '1.9.0', + '1.*' + ]; + + yield [ + [ + 'spiral/roadrunner-worker' => [ + 'pretty_version' => 'v1.8.0', + ], + ], + '1.8.0', + '1.*' + ]; + + yield [ + [ + 'spiral/roadrunner-http' => [ + 'pretty_version' => 'v1.8.0', + ], + ], + Version::VERSION_FALLBACK, + '*' + ]; + + yield [ + [], + Version::VERSION_FALLBACK, + '*' + ]; + } + + protected function setUp(): void + { + parent::setUp(); + + $ref = new \ReflectionClass(InstalledVersions::class); + $ref->setStaticPropertyValue('canGetVendors', false); + } + + #[DataProvider('provideVersions')] + public function testGetVersion(array $versions, string $expectedVersion, string $expectedConstraint): void + { + InstalledVersions::reload([ + 'versions' => $versions, + ]); + + $this->assertSame($expectedVersion, Version::current()); + $this->assertSame($expectedConstraint, Version::constraint()); + } +} \ No newline at end of file diff --git a/tests/Unit/WorkerPoolTest.php b/tests/Unit/WorkerPoolTest.php new file mode 100644 index 0000000..ea72bf2 --- /dev/null +++ b/tests/Unit/WorkerPoolTest.php @@ -0,0 +1,44 @@ +rpc = $this->createMock(RPCInterface::class); + $this->rpc->expects($this->once())->method('withCodec')->with($this->isInstanceOf(JsonCodec::class))->willReturnSelf(); + + $this->workerPool = new WorkerPool($this->rpc); + } + + public function testAddWorker(): void + { + $this->rpc->expects($this->once())->method('call')->with('informer.AddWorker', 'test'); + + $this->workerPool->addWorker('test'); + } + + public function testRemoveWorker(): void + { + $this->rpc->expects($this->once())->method('call')->with('informer.RemoveWorker', 'test'); + + $this->workerPool->removeWorker('test'); + } +} \ No newline at end of file From 5ce4121aabb5d1c6ba1f3d89449f34e95901a922 Mon Sep 17 00:00:00 2001 From: Pavel Buchnev Date: Tue, 10 Oct 2023 13:14:54 +0400 Subject: [PATCH 2/3] Update src/WorkerPool.php Co-authored-by: Aleksei Gagarin --- src/WorkerPool.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WorkerPool.php b/src/WorkerPool.php index fda88e2..eb7b69a 100644 --- a/src/WorkerPool.php +++ b/src/WorkerPool.php @@ -36,4 +36,4 @@ public function removeWorker(string $plugin): void { $this->rpc->call('informer.RemoveWorker', $plugin); } -} \ No newline at end of file +} From b0c3ea1b253a253d03ad0ff967648559eb8f9632 Mon Sep 17 00:00:00 2001 From: butschster Date: Tue, 10 Oct 2023 13:52:27 +0400 Subject: [PATCH 3/3] Mark WorkerPool class as final --- src/WorkerPool.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WorkerPool.php b/src/WorkerPool.php index fda88e2..ec4b205 100644 --- a/src/WorkerPool.php +++ b/src/WorkerPool.php @@ -7,7 +7,7 @@ use Spiral\Goridge\RPC\Codec\JsonCodec; use Spiral\Goridge\RPC\RPCInterface; -class WorkerPool +final class WorkerPool { private readonly RPCInterface $rpc;