From a21712acc860fac08131ac224cd45637ced0ea20 Mon Sep 17 00:00:00 2001 From: dblock Date: Mon, 5 Aug 2024 14:45:02 -0400 Subject: [PATCH 1/4] Added samples. Signed-off-by: dblock --- CHANGELOG.md | 1 + DEVELOPER_GUIDE.md | 8 +++ README.md | 25 ++++----- samples/README.md | 15 ++++++ samples/composer.json | 18 +++++++ samples/index.php | 23 +++++++++ .../Endpoints/MlNamespaceIntegrationTest.php | 6 +-- tests/Utility.php | 51 +++---------------- 8 files changed, 88 insertions(+), 59 deletions(-) create mode 100644 samples/README.md create mode 100644 samples/composer.json create mode 100644 samples/index.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c290244..393be22a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added - Generate endpoints from OpenSearch API Specification ([#194](https://github.com/opensearch-project/opensearch-php/pull/194)) - Added workflow for automated API update using OpenSearch API specification ([#209](https://github.com/opensearch-project/opensearch-php/pull/209)) +- Added samples ([#218](https://github.com/opensearch-project/opensearch-php/pull/218)) ### Changed ### Deprecated ### Removed diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 6f615d3e..11f396c4 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -36,6 +36,14 @@ The integration tests are using by default following address `https://admin:admi To run the integration tests, you can use `composer run integration` +```bash +export OPENSEARCH_PASSWORD=myStrongPassword123! +export OPENSEARCH_URL=https://admin:$OPENSEARCH_PASSWORD@localhost:9200 + +composer run integration +``` + + ### Static analyse and code style checker The project uses PhpStan for static analyse and php-cs-fixer for code style checker. You can use both tools with following codes diff --git a/README.md b/README.md index 62c2c163..5c151062 100644 --- a/README.md +++ b/README.md @@ -10,30 +10,31 @@ ## Welcome! -**opensearch-php** is [a community-driven, open source fork](https://aws.amazon.com/blogs/opensource/introducing-opensearch/) of elasticsearch-php licensed under the [Apache v2.0 License](https://github.com/opensearch-project/opensearch-php/blob/main/LICENSE). For more information, see [opensearch.org](https://opensearch.org/). +**opensearch-php** is [a community-driven, open source fork](https://aws.amazon.com/blogs/opensource/introducing-opensearch/) of elasticsearch-php licensed under the [Apache v2.0 License](LICENSE). For more information, see [opensearch.org](https://opensearch.org/). ## Project Resources * [Project Website](https://opensearch.org/) -* [User Guide And Sample Code](https://github.com/opensearch-project/opensearch-php/blob/main/USER_GUIDE.md) -* [Developer Guide](https://github.com/opensearch-project/opensearch-php/blob/main/DEVELOPER_GUIDE.md) +* [User Guide](USER_GUIDE.md) +* [Samples](samples) +* [Developer Guide](DEVELOPER_GUIDE.md) * [Downloads](https://opensearch.org/downloads.html). * [Documentation](https://opensearch.org/docs/latest/) * Need help? Try [Forums](https://discuss.opendistrocommunity.dev/) * [Project Principles](https://opensearch.org/#principles) -* [Contributing to OpenSearch](https://github.com/opensearch-project/opensearch-php/blob/main/CONTRIBUTING.md) -* [Maintainer Responsibilities](https://github.com/opensearch-project/opensearch-php/blob/main/MAINTAINERS.md) -* [Release Management](https://github.com/opensearch-project/opensearch-php/blob/main/RELEASING.md) -* [Admin Responsibilities](https://github.com/opensearch-project/opensearch-php/blob/main/ADMINS.md) -* [Security](https://github.com/opensearch-project/opensearch-php/blob/main/SECURITY.md) +* [Contributing to OpenSearch](CONTRIBUTING.md) +* [Maintainer Responsibilities](MAINTAINERS.md) +* [Release Management](RELEASING.md) +* [Admin Responsibilities](ADMINS.md) +* [Security](SECURITY.md) ## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://github.com/opensearch-project/opensearch-php/blob/main/CODE_OF_CONDUCT.md). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq), or contact [opensource-codeofconduct@amazon.com](mailto:opensource-codeofconduct@amazon.com) with any additional questions or comments. +This project has adopted the [Amazon Open Source Code of Conduct](CODE_OF_CONDUCT.md). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq), or contact [opensource-codeofconduct@amazon.com](mailto:opensource-codeofconduct@amazon.com) with any additional questions or comments. ## Sample code -See [Sample Code](https://github.com/opensearch-project/opensearch-php/blob/main/USER_GUIDE.md). +See [Sample Code](USER_GUIDE.md). ## Compatibility with OpenSearch @@ -41,8 +42,8 @@ See [Compatibility](COMPATIBILITY.md). ## License -This project is licensed under the [Apache v2.0 License](https://github.com/opensearch-project/opensearch-php/blob/main/LICENSE). +This project is licensed under the [Apache v2.0 License](LICENSE). ## Copyright -Copyright OpenSearch Contributors. See [NOTICE](https://github.com/opensearch-project/opensearch-php/blob/main/NOTICE) for details. \ No newline at end of file +Copyright OpenSearch Contributors. See [NOTICE](NOTICE) for details. \ No newline at end of file diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 00000000..5c2fd6c9 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,15 @@ +### OpenSearch PHP Client Samples + +Start an OpenSearch instance. + +``` +docker run -d -p 9200:9200 -p 9600:9600 -e "discovery.type=single-node" -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=myStrongPassword123!" opensearchproject/opensearch:latest +``` + +Run sample. + +``` +export OPENSEARCH_PASSWORD=myStrongPassword123! +composer install +composer run index +``` diff --git a/samples/composer.json b/samples/composer.json new file mode 100644 index 00000000..90aa9137 --- /dev/null +++ b/samples/composer.json @@ -0,0 +1,18 @@ +{ + "name": "opensearch-php/samples", + "description": "OpenSearch PHP client samples.", + "type": "project", + "authors": [], + "scripts": { + "index": ["php index.php"] + }, + "repositories": [ + { + "type": "path", + "url": "../" + } + ], + "require": { + "opensearch-project/opensearch-php": "dev-main" + } + } \ No newline at end of file diff --git a/samples/index.php b/samples/index.php new file mode 100644 index 00000000..570acaeb --- /dev/null +++ b/samples/index.php @@ -0,0 +1,23 @@ + [ + 'https://localhost:9200' + ], + 'BasicAuthentication' => ['admin', getenv('OPENSEARCH_PASSWORD')], + 'Retries' => 2, + 'SSLVerification' => false +]); + +$info = $client->info(); + +echo "{$info['version']['distribution']}: {$info['version']['number']}\n"; + +?> diff --git a/tests/Endpoints/MlNamespaceIntegrationTest.php b/tests/Endpoints/MlNamespaceIntegrationTest.php index 61962d88..6c10335a 100644 --- a/tests/Endpoints/MlNamespaceIntegrationTest.php +++ b/tests/Endpoints/MlNamespaceIntegrationTest.php @@ -30,7 +30,7 @@ public function testRegisterModelGroup() { $client = Utility::getClient(); - if (!Utility::isOpenSearchVersionAtLeast($client, '2.8.0') || !Utility::isOpenSearchVersionAtmost($client, '2.x')) { + if (!Utility::isOpenSearchVersionAtLeast($client, '2.8.0')) { $this->markTestSkipped('Ml plugin tests require OpenSearch >= 2.8.0'); } @@ -47,7 +47,7 @@ public function testgetModels() { $client = Utility::getClient(); - if (!Utility::isOpenSearchVersionAtLeast($client, '2.12.0') || !Utility::isOpenSearchVersionAtmost($client, '2.x')) { + if (!Utility::isOpenSearchVersionAtLeast($client, '2.12.0')) { $this->markTestSkipped('Ml plugin tests require OpenSearch >= 2.12.0'); } @@ -59,7 +59,7 @@ public function testsearchModels() { $client = Utility::getClient(); - if (!Utility::isOpenSearchVersionAtLeast($client, '2.12.0') || !Utility::isOpenSearchVersionAtmost($client, '2.x')) { + if (!Utility::isOpenSearchVersionAtLeast($client, '2.12.0')) { $this->markTestSkipped('Ml plugin tests require OpenSearch >= 2.12.0'); } diff --git a/tests/Utility.php b/tests/Utility.php index 8775829a..7ca46e2d 100644 --- a/tests/Utility.php +++ b/tests/Utility.php @@ -79,35 +79,7 @@ public static function isOpenSearchVersionAtLeast(Client $client, string $versio return false; } $versionNumber = $versionInfo['number']; - return version_compare($versionNumber, $version) >= 0; - } - - /** - * Check if cluster is OpenSearch and version is less than the specified version. - */ - public static function isOpenSearchVersionAtMost(Client $client, string $version): bool - { - $versionInfo = self::getVersion($client); - $distribution = $versionInfo['distribution'] ?? null; - if ($distribution !== 'opensearch') { - return false; - } - $versionNumber = $versionInfo['number']; - return version_compare($versionNumber, $version, '<'); - } - - /** - * Check if cluster is Elasticsearch and version is greater than or equal to specified version. - */ - public static function isElasticSearchVersionAtLeast(Client $client, string $version): bool - { - $versionInfo = self::getVersion($client); - $distribution = $versionInfo['distribution'] ?? null; - if ($distribution === 'opensearch') { - return false; - } - $versionNumber = $versionInfo['number']; - return version_compare($versionNumber, $version) >= 0; + return version_compare($versionNumber, $version, '>='); } private static function getVersion(Client $client): array @@ -137,10 +109,7 @@ public static function cleanUpCluster(Client $client): void */ private static function wipeCluster(Client $client): void { - if (self::isElasticSearchVersionAtLeast($client, '7.4.0')) { - self::deleteAllSLMPolicies($client); - } - + self::deleteAllSLMPolicies($client); self::wipeSnapshots($client); self::wipeDataStreams($client); self::wipeAllIndices($client); @@ -229,12 +198,10 @@ private static function deleteAllSLMPolicies(Client $client): void private static function wipeDataStreams(Client $client): void { try { - if (self::isElasticSearchVersionAtLeast($client, '7.9.0')) { - $client->indices()->deleteDataStream([ - 'name' => '*', - 'expand_wildcards' => 'all' - ]); - } + $client->indices()->deleteDataStream([ + 'name' => '*', + 'expand_wildcards' => 'all' + ]); } catch (OpenSearchException $e) { // We hit a version of ES that doesn't understand expand_wildcards, try again without it try { @@ -255,14 +222,10 @@ private static function wipeDataStreams(Client $client): void */ private static function wipeAllIndices(Client $client): void { - $expand = 'open,closed'; - if (self::isElasticSearchVersionAtLeast($client, '7.7.0')) { - $expand .= ',hidden'; - } try { $client->indices()->delete([ 'index' => '*,-.ds-ilm-history-*', - 'expand_wildcards' => $expand + 'expand_wildcards' => 'open,closed,hidden' ]); } catch (Exception $e) { if ($e->getCode() != '404') { From ce29577ca286627b7a2ceb152e2bfd00d3536209 Mon Sep 17 00:00:00 2001 From: dblock Date: Mon, 5 Aug 2024 16:37:35 -0400 Subject: [PATCH 2/4] Added integration-min to skip running tests w/plugins. Signed-off-by: dblock --- .github/workflows/test.yml | 8 +++++--- .github/workflows/test_unreleased.yml | 4 ++-- DEVELOPER_GUIDE.md | 2 +- composer.json | 3 +++ tests/ClientIntegrationTest.php | 1 + .../SniffingConnectionPoolIntegrationTest.php | 1 + .../StaticConnectionPoolIntegrationTest.php | 1 + tests/Endpoints/CreateIntegrationTest.php | 1 + tests/Endpoints/CreatePointInTimeIntegrationTest.php | 1 + tests/Endpoints/DeletePointInTimeIntegrationTest.php | 1 + 10 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 831aba37..860cb57a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -132,7 +132,7 @@ jobs: composer run unit integration-test-elasticsearch: - name: Tntegration Test (Elasticsearch) + name: Integration Test (Elasticsearch) runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -175,7 +175,7 @@ jobs: OPENSEARCH_URL: 'http://localhost:9200' integration-test-opensearch: - name: Tntegration Test (OpenSearch) + name: Integration Test (OpenSearch) runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -234,7 +234,7 @@ jobs: OPENSEARCH_URL: 'http://localhost:9200' integration-test-opensearch-strong-password: - name: Tntegration Test (OpenSearch w/Strong Password) + name: Integration Test (OpenSearch w/Strong Password) runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -244,6 +244,8 @@ jobs: search-server-image: - opensearchproject/opensearch:2.12.0 - opensearchproject/opensearch:2.13.0 + - opensearchproject/opensearch:2.14.0 + - opensearchproject/opensearch:2.15.0 services: search-server: image: ${{ matrix.search-server-image }} diff --git a/.github/workflows/test_unreleased.yml b/.github/workflows/test_unreleased.yml index ac03cedb..c502bf0d 100644 --- a/.github/workflows/test_unreleased.yml +++ b/.github/workflows/test_unreleased.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - opensearch_ref: [ '1.x', '2.x', '2.0', 'main' ] + opensearch_ref: [ '1.x', '2.x', 'main' ] steps: - name: Checkout PHP Client uses: actions/checkout@v2 @@ -73,6 +73,6 @@ jobs: - name: Integration tests run: | - composer run integration + composer run integration-min env: OPENSEARCH_URL: 'http://localhost:9200' diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 11f396c4..5c71551e 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -34,7 +34,7 @@ If you don't have a running server, you can start one with Docker using `docker The integration tests are using by default following address `https://admin:admin@localhost:9200`. This can be changed by setting the environment variable `OPENSEARCH_URL` to a different url. -To run the integration tests, you can use `composer run integration` +To run the integration tests, you can use `composer run integration-min` for just OpenSearch or `composer run integration` for OpenSearch and its plugins. ```bash export OPENSEARCH_PASSWORD=myStrongPassword123! diff --git a/composer.json b/composer.json index 6b5af53b..dd4520ae 100644 --- a/composer.json +++ b/composer.json @@ -61,6 +61,9 @@ "integration": [ "phpunit --group Integration" ], + "integration-min": [ + "phpunit --group Integration-Min" + ], "phpunit": [ "phpunit" ], diff --git a/tests/ClientIntegrationTest.php b/tests/ClientIntegrationTest.php index 5b491217..c52d9971 100644 --- a/tests/ClientIntegrationTest.php +++ b/tests/ClientIntegrationTest.php @@ -33,6 +33,7 @@ * * @subpackage Tests * @group Integration + * @group Integration-Min */ class ClientIntegrationTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/ConnectionPool/SniffingConnectionPoolIntegrationTest.php b/tests/ConnectionPool/SniffingConnectionPoolIntegrationTest.php index 0373a4e8..e73b1794 100644 --- a/tests/ConnectionPool/SniffingConnectionPoolIntegrationTest.php +++ b/tests/ConnectionPool/SniffingConnectionPoolIntegrationTest.php @@ -32,6 +32,7 @@ * * @subpackage Tests/SniffingConnectionPoolTest * @group Integration + * @group Integration-Min */ class SniffingConnectionPoolIntegrationTest extends TestCase { diff --git a/tests/ConnectionPool/StaticConnectionPoolIntegrationTest.php b/tests/ConnectionPool/StaticConnectionPoolIntegrationTest.php index 6bfc6524..0f3462c3 100644 --- a/tests/ConnectionPool/StaticConnectionPoolIntegrationTest.php +++ b/tests/ConnectionPool/StaticConnectionPoolIntegrationTest.php @@ -29,6 +29,7 @@ * * @subpackage Tests/StaticConnectionPoolTest * @group Integration + * @group Integration-Min */ class StaticConnectionPoolIntegrationTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/Endpoints/CreateIntegrationTest.php b/tests/Endpoints/CreateIntegrationTest.php index f3ef4eb6..55e5e3b0 100644 --- a/tests/Endpoints/CreateIntegrationTest.php +++ b/tests/Endpoints/CreateIntegrationTest.php @@ -28,6 +28,7 @@ * * @subpackage Tests\Endpoints * @group Integration + * @group Integration-Min */ class CreateIntegrationTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/Endpoints/CreatePointInTimeIntegrationTest.php b/tests/Endpoints/CreatePointInTimeIntegrationTest.php index c27f76b5..7e3033e9 100644 --- a/tests/Endpoints/CreatePointInTimeIntegrationTest.php +++ b/tests/Endpoints/CreatePointInTimeIntegrationTest.php @@ -29,6 +29,7 @@ * * @subpackage Tests\Endpoints * @group Integration + * @group Integration-Min */ class CreatePointInTimeIntegrationTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/Endpoints/DeletePointInTimeIntegrationTest.php b/tests/Endpoints/DeletePointInTimeIntegrationTest.php index ad9b2833..d1a7d22e 100644 --- a/tests/Endpoints/DeletePointInTimeIntegrationTest.php +++ b/tests/Endpoints/DeletePointInTimeIntegrationTest.php @@ -29,6 +29,7 @@ * * @subpackage Tests\Endpoints * @group Integration + * @group Integration-Min */ class DeletePointInTimeIntegrationTest extends \PHPUnit\Framework\TestCase { From 1fae7e526a3badf979e506a8e101bbee8a746fb7 Mon Sep 17 00:00:00 2001 From: dblock Date: Sun, 4 Aug 2024 08:50:07 -0400 Subject: [PATCH 3/4] Correct version comparisons for ML tests. Signed-off-by: dblock --- DEVELOPER_GUIDE.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 5c71551e..3c2b13cd 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -44,6 +44,14 @@ composer run integration ``` +```bash +export OPENSEARCH_PASSWORD=myStrongPassword123! +export OPENSEARCH_URL=https://admin:$OPENSEARCH_PASSWORD@localhost:9200 + +composer run integration +``` + + ### Static analyse and code style checker The project uses PhpStan for static analyse and php-cs-fixer for code style checker. You can use both tools with following codes From 787aa5dc730c7bfa672138de838201d49900c632 Mon Sep 17 00:00:00 2001 From: dblock Date: Sun, 4 Aug 2024 17:19:32 -0400 Subject: [PATCH 4/4] WIP: Upgrading guzzle. Signed-off-by: dblock --- composer.json | 7 +- src/OpenSearch/ClientBuilder.php | 85 +++---- src/OpenSearch/Connections/Connection.php | 209 +++++++++--------- src/OpenSearch/Handlers/SigV4Handler.php | 2 +- .../Namespaces/BooleanRequestWrapper.php | 1 - src/OpenSearch/Transport.php | 12 +- tests/ClientTest.php | 2 - tests/Handlers/SigV4HandlerTest.php | 1 - tests/TransportTest.php | 2 - 9 files changed, 161 insertions(+), 160 deletions(-) diff --git a/composer.json b/composer.json index dd4520ae..85529f54 100644 --- a/composer.json +++ b/composer.json @@ -14,9 +14,12 @@ ], "require": { "php": "^7.3 || ^8.0", - "ext-json": ">=1.3.7", "ext-curl": "*", - "ezimuel/ringphp": "^1.1.2", + "ext-json": ">=1.3.7", + "guzzlehttp/guzzle": "^7.9", + "guzzlehttp/promises": "^2.0", + "psr/http-client": "^1.0", + "psr/http-message": "^1.1 || ^2.0", "psr/log": "^1|^2|^3", "symfony/yaml": "*" }, diff --git a/src/OpenSearch/ClientBuilder.php b/src/OpenSearch/ClientBuilder.php index 20c6b7af..b0e28f13 100644 --- a/src/OpenSearch/ClientBuilder.php +++ b/src/OpenSearch/ClientBuilder.php @@ -38,12 +38,14 @@ use OpenSearch\Namespaces\NamespaceBuilderInterface; use OpenSearch\Serializers\SerializerInterface; use OpenSearch\Serializers\SmartSerializer; -use GuzzleHttp\Ring\Client\CurlHandler; -use GuzzleHttp\Ring\Client\CurlMultiHandler; -use GuzzleHttp\Ring\Client\Middleware; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use ReflectionClass; +use GuzzleHttp\Handler\CurlHandler; +use GuzzleHttp\Handler\CurlMultiHandler; +use GuzzleHttp\Handler\Proxy; +use Psr\Http\Message\RequestInterface; +use GuzzleHttp\Utils; class ClientBuilder { @@ -239,30 +241,30 @@ public static function fromConfig(array $config, bool $quiet = false): Client return $builder->build(); } - /** - * Get the default handler - * - * @param array $multiParams - * @param array $singleParams - * @throws \RuntimeException - */ - public static function defaultHandler(array $multiParams = [], array $singleParams = []): callable - { - $future = null; - if (extension_loaded('curl')) { - $config = array_merge([ 'mh' => curl_multi_init() ], $multiParams); - if (function_exists('curl_reset')) { - $default = new CurlHandler($singleParams); - $future = new CurlMultiHandler($config); - } else { - $default = new CurlMultiHandler($config); - } - } else { - throw new \RuntimeException('OpenSearch-PHP requires cURL, or a custom HTTP handler.'); - } - - return $future ? Middleware::wrapFuture($default, $future) : $default; - } + // /** + // * Get the default handler + // * + // * @param array $multiParams + // * @param array $singleParams + // * @throws \RuntimeException + // */ + // public static function defaultHandler(array $multiParams = [], array $singleParams = []): callable + // { + // $future = null; + // if (extension_loaded('curl')) { + // $config = array_merge([ 'mh' => curl_multi_init() ], $multiParams); + // if (function_exists('curl_reset')) { + // $default = new CurlHandler($singleParams); + // $future = new CurlMultiHandler($config); + // } else { + // $default = new CurlMultiHandler($config); + // } + // } else { + // throw new \RuntimeException('OpenSearch-PHP requires cURL, or a custom HTTP handler.'); + // } + + // return $future ? Proxy::wrapSync($default, $future) : $default; + // } /** * Get the multi handler for async (CurlMultiHandler) @@ -583,7 +585,7 @@ public function build(): Client $this->buildLoggers(); if (is_null($this->handler)) { - $this->handler = ClientBuilder::defaultHandler(); + $this->handler = Utils::chooseHandler(); // ClientBuilder::defaultHandler(); } if (!is_null($this->sigV4CredentialProvider)) { @@ -610,18 +612,21 @@ public function build(): Client } if (!is_null($sslOptions)) { - $sslHandler = function (callable $handler, array $sslOptions) { - return function (array $request) use ($handler, $sslOptions) { - // Add our custom headers - foreach ($sslOptions as $key => $value) { - $request['client'][$key] = $value; - } - - // Send the request using the handler and return the response. - return $handler($request); - }; - }; - $this->handler = $sslHandler($this->handler, $sslOptions); + // $sslHandler = function (callable $handler, array $sslOptions) { + // return function (RequestInterface $request, array $options) use ($handler, $sslOptions) { + // // Add our custom headers + // foreach ($sslOptions as $key => $value) { + // $request['client'][$key] = $value; + // } + + // // Send the request using the handler and return the response. + // return $handler($request); + // }; + // }; + // $this->handler = $sslHandler($this->handler, $sslOptions); + foreach ($sslOptions as $key => $value) { + $this->connectionParams['client'][$key] = $value; + } } if (is_null($this->serializer)) { diff --git a/src/OpenSearch/Connections/Connection.php b/src/OpenSearch/Connections/Connection.php index 3a67e930..e82b794a 100644 --- a/src/OpenSearch/Connections/Connection.php +++ b/src/OpenSearch/Connections/Connection.php @@ -42,10 +42,9 @@ use OpenSearch\Serializers\SerializerInterface; use OpenSearch\Transport; use Exception; -use GuzzleHttp\Ring\Core; -use GuzzleHttp\Ring\Exception\ConnectException; -use GuzzleHttp\Ring\Exception\RingException; use Psr\Log\LoggerInterface; +use GuzzleHttp\Psr7\Request; +use Psr\Http\Message\RequestInterface; class Connection implements ConnectionInterface { @@ -211,31 +210,34 @@ public function performRequest(string $method, string $uri, ?array $params = [], $host = $this->host; if (isset($this->connectionParams['client']['port_in_header']) && $this->connectionParams['client']['port_in_header']) { - $host .= ':' . $this->port; + $host .= ':' . $this->port; } - $request = [ + $request_options = [ 'http_method' => $method, 'scheme' => $this->transportSchema, + 'address' => $this->transportSchema . '://' . $host . ':' . $this->port, 'uri' => $this->getURI($uri, $params), 'body' => $body, 'headers' => array_merge( [ - 'Host' => [$host] + 'Host' => [$host] ], $headers ) ]; - $request = array_replace_recursive($request, $this->connectionParams, $options); + $request_options = array_replace_recursive($request_options, $this->connectionParams, $options); - // RingPHP does not like if client is empty - if (empty($request['client'])) { - unset($request['client']); - } + $request = new Request( + $request_options['http_method'], + $request_options['address'] . $request_options['uri'], + $request_options['headers'], + $request_options['body'] + ); $handler = $this->handler; - $future = $handler($request, $this, $transport, $options); + $future = $handler($request, $this, $transport, $request_options['client']); return $future; } @@ -252,91 +254,88 @@ public function getLastRequestInfo(): array private function wrapHandler(callable $handler): callable { - return function (array $request, Connection $connection, Transport $transport = null, $options) use ($handler) { + return function (RequestInterface $request, Connection $connection, Transport $transport = null, $options) use ($handler) { $this->lastRequest = []; $this->lastRequest['request'] = $request; - // Send the request using the wrapped handler. - $response = Core::proxy( - $handler($request), - function ($response) use ($connection, $transport, $request, $options) { + $response = $handler($request, $options)->then( + function ($response) { $this->lastRequest['response'] = $response; - if (isset($response['error']) === true) { - if ($response['error'] instanceof ConnectException || $response['error'] instanceof RingException) { - $this->log->warning("Curl exception encountered."); - - $exception = $this->getCurlRetryException($request, $response); - - $this->logRequestFail($request, $response, $exception); - - $node = $connection->getHost(); - $this->log->warning("Marking node $node dead."); - $connection->markDead(); - - // If the transport has not been set, we are inside a Ping or Sniff, - // so we don't want to retrigger retries anyway. - // - // TODO this could be handled better, but we are limited because connectionpools do not - // have access to Transport. Architecturally, all of this needs to be refactored - if (isset($transport) === true) { - $transport->connectionPool->scheduleCheck(); - - $neverRetry = isset($request['client']['never_retry']) ? $request['client']['never_retry'] : false; - $shouldRetry = $transport->shouldRetry($request); - $shouldRetryText = ($shouldRetry) ? 'true' : 'false'; - - $this->log->warning("Retries left? $shouldRetryText"); - if ($shouldRetry && !$neverRetry) { - return $transport->performRequest( - $request['http_method'], - $request['uri'], - [], - $request['body'], - $options - ); - } - } + // $connection->markAlive(); - $this->log->warning("Out of retries, throwing exception from $node"); - // Only throw if we run out of retries - throw $exception; - } else { - // Something went seriously wrong, bail - $exception = new TransportException($response['error']->getMessage()); - $this->logRequestFail($request, $response, $exception); - throw $exception; - } - } else { - $connection->markAlive(); + if (isset($response->getHeaders()['Warning'])) { + $this->logWarning($request, $response); + } - if (isset($response['headers']['Warning'])) { - $this->logWarning($request, $response); - } - if (isset($response['body']) === true) { - $response['body'] = stream_get_contents($response['body']); - $this->lastRequest['response']['body'] = $response['body']; + if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) { + $ignore = $request['client']['ignore'] ?? []; + // Skip 404 if succeeded true in the body (e.g. clear_scroll) + $body = $response->getBody()->getContents() ?? ''; + if (strpos($body, '"succeeded":true') !== false) { + $ignore[] = 404; } + $this->process4xxError($request, $response, $ignore); + } elseif ($response->getStatusCode() >= 500) { + $ignore = $request['client']['ignore'] ?? []; + $this->process5xxError($request, $response, $ignore); + } - if ($response['status'] >= 400 && $response['status'] < 500) { - $ignore = $request['client']['ignore'] ?? []; - // Skip 404 if succeeded true in the body (e.g. clear_scroll) - $body = $response['body'] ?? ''; - if (strpos($body, '"succeeded":true') !== false) { - $ignore[] = 404; + // return isset($request['client']['verbose']) && $request['client']['verbose'] === true ? $response : $response['body']; + + // No error, deserialize + $responseBody = $this->serializer->deserialize( + $response->getBody()->getContents(), + $response->getHeaders() // ['transfer_stats'] + ); + + // $this->logRequestSuccess($request, $response); + return $responseBody; + }, function ($error) { + if ($error instanceof ConnectException) { + $this->log->warning("Curl exception encountered."); + + $exception = $this->getCurlRetryException($request, $response); + + // $this->logRequestFail($request, $response, $exception); + + $node = $connection->getHost(); + $this->log->warning("Marking node $node dead."); + $connection->markDead(); + + // If the transport has not been set, we are inside a Ping or Sniff, + // so we don't want to retrigger retries anyway. + // + // TODO this could be handled better, but we are limited because connectionpools do not + // have access to Transport. Architecturally, all of this needs to be refactored + if (isset($transport) === true) { + $transport->connectionPool->scheduleCheck(); + + $neverRetry = isset($request['client']['never_retry']) ? $request['client']['never_retry'] : false; + $shouldRetry = $transport->shouldRetry($request); + $shouldRetryText = ($shouldRetry) ? 'true' : 'false'; + + $this->log->warning("Retries left? $shouldRetryText"); + if ($shouldRetry && !$neverRetry) { + return $transport->performRequest( + $request['http_method'], + $request['uri'], + [], + $request['body'], + $options + ); } - $this->process4xxError($request, $response, $ignore); - } elseif ($response['status'] >= 500) { - $ignore = $request['client']['ignore'] ?? []; - $this->process5xxError($request, $response, $ignore); } - // No error, deserialize - $response['body'] = $this->serializer->deserialize($response['body'], $response['transfer_stats']); + $this->log->warning("Out of retries, throwing exception from $node"); + // Only throw if we run out of retries + throw $exception; + } else { + // Something went seriously wrong, bail + $exception = new TransportException($error->getMessage()); + // $this->logRequestFail($request, $response, $exception); + throw $exception; } - $this->logRequestSuccess($request, $response); - - return isset($request['client']['verbose']) && $request['client']['verbose'] === true ? $response : $response['body']; } ); @@ -375,7 +374,7 @@ public function getHeaders(): array return $this->headers; } - public function logWarning(array $request, array $response): void + public function logWarning(RequestInterface $request, ResponseInterface $response): void { $this->log->warning('Deprecation', $response['headers']['Warning']); } @@ -383,11 +382,11 @@ public function logWarning(array $request, array $response): void /** * Log a successful request * - * @param array $request - * @param array $response + * @param RequestInterface $request + * @param ResponseInterface $response * @return void */ - public function logRequestSuccess(array $request, array $response): void + public function logRequestSuccess(RequestInterface $request, ResponseInterface $response): void { $port = $request['client']['curl'][CURLOPT_PORT] ?? $response['transfer_stats']['primary_port'] ?? ''; $uri = $this->addPortInUrl($response['effective_url'], (int) $port); @@ -400,7 +399,7 @@ public function logRequestSuccess(array $request, array $response): void 'uri' => $uri, 'port' => $port, 'headers' => $request['headers'], - 'HTTP code' => $response['status'], + 'HTTP code' => $response->getStatusCode(), 'duration' => $response['transfer_stats']['total_time'], ) ); @@ -416,7 +415,7 @@ public function logRequestSuccess(array $request, array $response): void 'method' => $request['http_method'], 'uri' => $uri, 'port' => $port, - 'HTTP code' => $response['status'], + 'HTTP code' => $response->getStatusCode(), 'duration' => $response['transfer_stats']['total_time'], ) ); @@ -431,7 +430,7 @@ public function logRequestSuccess(array $request, array $response): void * * @return void */ - public function logRequestFail(array $request, array $response, \Throwable $exception): void + public function logRequestFail(RequestInterface $request, ResponseInterface $response, \Throwable $exception): void { $port = $request['client']['curl'][CURLOPT_PORT] ?? $response['transfer_stats']['primary_port'] ?? ''; $uri = $this->addPortInUrl($response['effective_url'], (int) $port); @@ -444,7 +443,7 @@ public function logRequestFail(array $request, array $response, \Throwable $exce 'uri' => $uri, 'port' => $port, 'headers' => $request['headers'], - 'HTTP code' => $response['status'], + 'HTTP code' => $response->getStatusCode(), 'duration' => $response['transfer_stats']['total_time'], 'error' => $exception->getMessage(), ) @@ -461,7 +460,7 @@ public function logRequestFail(array $request, array $response, \Throwable $exce 'method' => $request['http_method'], 'uri' => $uri, 'port' => $port, - 'HTTP code' => $response['status'], + 'HTTP code' => $response->getStatusCode(), 'duration' => $response['transfer_stats']['total_time'], ) ); @@ -485,7 +484,7 @@ public function ping(): bool return false; } - if ($response['status'] === 200) { + if ($response->getStatusCode() === 200) { $this->markAlive(); return true; @@ -563,7 +562,7 @@ public function getPort() return $this->port; } - protected function getCurlRetryException(array $request, array $response): OpenSearchException + protected function getCurlRetryException(RequestInterface $request, ResponseInterface $response): OpenSearchException { $exception = null; $message = $response['error']->getMessage(); @@ -634,16 +633,16 @@ private function buildCurlCommand(string $method, string $url, ?string $body): s /** * @throws OpenSearchException */ - private function process4xxError(array $request, array $response, array $ignore): void + private function process4xxError(RequestInterface $request, ResponseInterface $response, array $ignore): void { - $statusCode = $response['status']; + $statusCode = $response->getStatusCode(); /** * @var \Exception $exception */ $exception = $this->tryDeserialize400Error($response); - if (array_search($response['status'], $ignore) !== false) { + if (array_search($response->getStatusCode(), $ignore) !== false) { return; } @@ -672,9 +671,9 @@ private function process4xxError(array $request, array $response, array $ignore) /** * @throws OpenSearchException */ - private function process5xxError(array $request, array $response, array $ignore): void + private function process5xxError(RequestInterface $request, ResponseInterface $response, array $ignore): void { - $statusCode = (int) $response['status']; + $statusCode = (int) $response->getStatusCode(); $responseBody = $response['body']; /** @@ -742,7 +741,7 @@ private function tryDeserializeError(array $response, string $errorClass): OpenS // <2.0 "i just blew up" nonstructured exception // $error is an array but we don't know the format, reuse the response body instead // added json_encode to convert into a string - return new $errorClass(json_encode($response['body']), (int) $response['status']); + return new $errorClass(json_encode($response['body']), (int) $response->getStatusCode()); } // 2.0 structured exceptions @@ -752,19 +751,19 @@ private function tryDeserializeError(array $response, string $errorClass): OpenS $cause = $info['reason']; $type = $info['type']; // added json_encode to convert into a string - $original = new $errorClass(json_encode($response['body']), $response['status']); + $original = new $errorClass(json_encode($response['body']), $response->getStatusCode()); - return new $errorClass("$type: $cause", (int) $response['status'], $original); + return new $errorClass("$type: $cause", (int) $response->getStatusCode(), $original); } // <2.0 semi-structured exceptions // added json_encode to convert into a string - $original = new $errorClass(json_encode($response['body']), $response['status']); + $original = new $errorClass(json_encode($response['body']), $response->getStatusCode()); $errorEncoded = $error['error']; if (is_array($errorEncoded)) { $errorEncoded = json_encode($errorEncoded); } - return new $errorClass($errorEncoded, (int) $response['status'], $original); + return new $errorClass($errorEncoded, (int) $response->getStatusCode(), $original); } // if responseBody is not string, we convert it so it can be used as Exception message diff --git a/src/OpenSearch/Handlers/SigV4Handler.php b/src/OpenSearch/Handlers/SigV4Handler.php index 41bc8b8c..330aab24 100644 --- a/src/OpenSearch/Handlers/SigV4Handler.php +++ b/src/OpenSearch/Handlers/SigV4Handler.php @@ -46,7 +46,7 @@ public function __construct( ?: CredentialProvider::defaultProvider(); } - public function __invoke(array $request) + public function __invoke(RequestInterface $request) { $creds = call_user_func($this->credentialProvider)->wait(); diff --git a/src/OpenSearch/Namespaces/BooleanRequestWrapper.php b/src/OpenSearch/Namespaces/BooleanRequestWrapper.php index 9c29f656..245777ba 100644 --- a/src/OpenSearch/Namespaces/BooleanRequestWrapper.php +++ b/src/OpenSearch/Namespaces/BooleanRequestWrapper.php @@ -25,7 +25,6 @@ use OpenSearch\Common\Exceptions\RoutingMissingException; use OpenSearch\Endpoints\AbstractEndpoint; use OpenSearch\Transport; -use GuzzleHttp\Ring\Future\FutureArrayInterface; abstract class BooleanRequestWrapper { diff --git a/src/OpenSearch/Transport.php b/src/OpenSearch/Transport.php index 8655ce5e..bb7a8749 100644 --- a/src/OpenSearch/Transport.php +++ b/src/OpenSearch/Transport.php @@ -25,8 +25,8 @@ use OpenSearch\ConnectionPool\AbstractConnectionPool; use OpenSearch\Connections\Connection; use OpenSearch\Connections\ConnectionInterface; -use GuzzleHttp\Ring\Future\FutureArrayInterface; use Psr\Log\LoggerInterface; +use GuzzleHttp\Promise\Promise; class Transport { @@ -96,7 +96,7 @@ public function getConnection(): ConnectionInterface * * @throws Common\Exceptions\NoNodesAvailableException|\Exception */ - public function performRequest(string $method, string $uri, array $params = [], $body = null, array $options = []): FutureArrayInterface + public function performRequest(string $method, string $uri, array $params = [], $body = null, array $options = []): Promise { try { $connection = $this->getConnection(); @@ -118,7 +118,7 @@ public function performRequest(string $method, string $uri, array $params = [], $this ); - $future->promise()->then( + $future->then( //onSuccess function ($response) { $this->retryAttempts = 0; @@ -144,19 +144,19 @@ function ($response) { * * @return callable|array */ - public function resultOrFuture(FutureArrayInterface $result, array $options = []) + public function resultOrFuture(Promise $result, array $options = []) { $response = null; $async = isset($options['client']['future']) ? $options['client']['future'] : null; if (is_null($async) || $async === false) { do { $result = $result->wait(); - } while ($result instanceof FutureArrayInterface); + } while ($result instanceof Promise); } return $result; } - public function shouldRetry(array $request): bool + public function shouldRetry(RequestInterface $request): bool { if ($this->retryAttempts < $this->retries) { $this->retryAttempts += 1; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 43470607..845e0f61 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -21,8 +21,6 @@ namespace OpenSearch\Tests; -use GuzzleHttp\Ring\Client\MockHandler; -use GuzzleHttp\Ring\Future\FutureArray; use Mockery as m; use OpenSearch; use OpenSearch\Client; diff --git a/tests/Handlers/SigV4HandlerTest.php b/tests/Handlers/SigV4HandlerTest.php index 862043b2..4cc821b8 100644 --- a/tests/Handlers/SigV4HandlerTest.php +++ b/tests/Handlers/SigV4HandlerTest.php @@ -6,7 +6,6 @@ use Aws\Credentials\CredentialProvider; use Aws\Credentials\Credentials; -use GuzzleHttp\Ring\Future\CompletedFutureArray; use OpenSearch\ClientBuilder; use OpenSearch\Handlers\SigV4Handler; use PHPUnit\Framework\TestCase; diff --git a/tests/TransportTest.php b/tests/TransportTest.php index 96469261..613fbac1 100644 --- a/tests/TransportTest.php +++ b/tests/TransportTest.php @@ -26,8 +26,6 @@ use OpenSearch\Connections\Connection; use OpenSearch\Serializers\SerializerInterface; use OpenSearch\Transport; -use GuzzleHttp\Ring\Future\FutureArray; -use GuzzleHttp\Ring\Future\FutureArrayInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface;