├── .codecov.yml ├── .github ├── CODEOWNERS └── workflows │ ├── add-untriaged.yml │ ├── add_backport_label.yml │ ├── backport.yml │ ├── build.yml │ ├── github-merit-badger.yml │ ├── publish-snapshots.yml │ ├── release-drafter.yml │ └── wrapper.yml ├── .gitignore ├── .whitesource ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CREATE_YOUR_FIRST_EXTENSION.md ├── DESIGN.md ├── DEVELOPER_GUIDE.md ├── Docs ├── CERTIFICATE_GENERATION.md ├── ExtensionRestActions.svg ├── Extensions.png ├── NamedXContent.svg ├── RemoteActionExecution.svg └── plugins.png ├── LICENSE ├── MAINTAINERS.md ├── NOTICE ├── PLUGIN_MIGRATION.md ├── README.md ├── RELEASING.md ├── SECURITY.md ├── THIRD-PARTY-NOTICES ├── _config.yml ├── _includes ├── head_custom.html └── sidenav.html ├── _layouts └── default.html ├── assets ├── css │ └── style.scss └── img │ ├── favicon.ico │ └── logo.png ├── build.gradle ├── config ├── certs │ └── cert-gen.sh └── checkstyle │ ├── checkstyle.xml │ └── checkstyle_suppressions.xml ├── formatter ├── formatterConfig.xml └── license-header.txt ├── gradle.properties ├── gradle ├── formatting.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jenkins └── release.jenkinsFile ├── settings.gradle └── src ├── main ├── java │ └── org │ │ └── opensearch │ │ └── sdk │ │ ├── ActionListener.java │ │ ├── BaseExtension.java │ │ ├── Extension.java │ │ ├── ExtensionSettings.java │ │ ├── ExtensionsRunner.java │ │ ├── NettyTransport.java │ │ ├── SDKClient.java │ │ ├── SDKClusterService.java │ │ ├── SDKNamedWriteableRegistry.java │ │ ├── SDKNamedXContentRegistry.java │ │ ├── SDKTransportService.java │ │ ├── action │ │ ├── RemoteExtensionAction.java │ │ ├── RemoteExtensionActionRequest.java │ │ ├── RemoteExtensionTransportAction.java │ │ └── SDKActionModule.java │ │ ├── api │ │ ├── ActionExtension.java │ │ ├── AnalysisExtension.java │ │ ├── CircuitBreakerExtension.java │ │ ├── EngineExtension.java │ │ ├── ExtensibleExtension.java │ │ ├── IndexStoreExtension.java │ │ ├── IngestExtension.java │ │ ├── MapperExtension.java │ │ ├── PersistentTaskExtension.java │ │ ├── ReloadableExtension.java │ │ ├── RepositoryExtension.java │ │ ├── ScriptExtension.java │ │ ├── SearchExtension.java │ │ └── SystemIndexExtension.java │ │ ├── handlers │ │ ├── AcknowledgedResponseHandler.java │ │ ├── ClusterSettingsResponseHandler.java │ │ ├── ClusterStateResponseHandler.java │ │ ├── EnvironmentSettingsResponseHandler.java │ │ ├── ExtensionActionRequestHandler.java │ │ ├── ExtensionActionResponseHandler.java │ │ ├── ExtensionDependencyResponseHandler.java │ │ ├── ExtensionsIndicesModuleNameRequestHandler.java │ │ ├── ExtensionsIndicesModuleRequestHandler.java │ │ ├── ExtensionsInitRequestHandler.java │ │ ├── ExtensionsRestRequestHandler.java │ │ ├── OpensearchRequestHandler.java │ │ └── UpdateSettingsRequestHandler.java │ │ ├── rest │ │ ├── BaseExtensionRestHandler.java │ │ ├── ExtensionRestHandler.java │ │ ├── ExtensionRestPathRegistry.java │ │ ├── ReplacedRouteHandler.java │ │ ├── SDKHttpRequest.java │ │ ├── SDKMethodHandlers.java │ │ └── SDKRestRequest.java │ │ ├── sample │ │ └── helloworld │ │ │ ├── ExampleCustomSettingConfig.java │ │ │ ├── HelloWorldExtension.java │ │ │ ├── hello.json │ │ │ ├── rest │ │ │ ├── RestHelloAction.java │ │ │ └── RestRemoteHelloAction.java │ │ │ ├── spec │ │ │ ├── openapi.json │ │ │ └── openapi.yaml │ │ │ └── transport │ │ │ ├── SampleAction.java │ │ │ ├── SampleRequest.java │ │ │ ├── SampleResponse.java │ │ │ └── SampleTransportAction.java │ │ └── ssl │ │ ├── DefaultSslKeyStore.java │ │ ├── SSLConfigConstants.java │ │ ├── SSLConnectionTestResult.java │ │ ├── SSLNettyTransport.java │ │ ├── SecureSSLSettings.java │ │ ├── SslKeyStore.java │ │ └── util │ │ ├── CertFileProps.java │ │ ├── CertFromFile.java │ │ ├── CertFromKeystore.java │ │ ├── CertFromTruststore.java │ │ ├── ExceptionUtils.java │ │ ├── KeystoreProps.java │ │ ├── PemKeyReader.java │ │ ├── SSLCertificateHelper.java │ │ └── Utils.java └── resources │ ├── log4j2-sdk.xml │ └── sample │ └── helloworld-settings.yml └── test ├── java └── org │ └── opensearch │ └── sdk │ ├── ExtensionsRunnerForTest.java │ ├── TestBaseExtension.java │ ├── TestExtensionInterfaces.java │ ├── TestExtensionSettings.java │ ├── TestExtensionsRunner.java │ ├── TestNetty4Transport.java │ ├── TestSDKClient.java │ ├── TestSDKClusterService.java │ ├── TestSDKNamedWriteableRegistry.java │ ├── TestSDKNamedXContentRegistry.java │ ├── TestSDKTransportService.java │ ├── TestThreadPool.java │ ├── action │ ├── TestProxyActionRequest.java │ └── TestSDKActionModule.java │ ├── rest │ ├── TestBaseExtensionRestHandler.java │ ├── TestExtensionRestHandler.java │ ├── TestExtensionRestPathRegistry.java │ ├── TestNamedRouteHandler.java │ └── TestSDKRestRequest.java │ └── sample │ └── helloworld │ ├── TestHelloWorldExtension.java │ ├── rest │ ├── TestHelloWorldIT.java │ └── TestRestHelloAction.java │ └── transport │ └── TestSampleAction.java └── resources ├── bad-extension.yml ├── extension.yml └── hello-world-extension.yml /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: "70...90" 5 | status: 6 | project: 7 | default: 8 | target: auto # the required coverage value 9 | threshold: 0.2% # the leniency in hitting the target 10 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This should match the owning team set up in https://github.com/orgs/opensearch-project/teams 2 | * @joshpalis @owaiskazi19 @ryanbogan @saratvemulapalli @dbwiddis 3 | -------------------------------------------------------------------------------- /.github/workflows/add-untriaged.yml: -------------------------------------------------------------------------------- 1 | name: Apply 'untriaged' label during issue lifecycle 2 | 3 | on: 4 | issues: 5 | types: [opened, reopened, transferred] 6 | 7 | jobs: 8 | apply-label: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/github-script@v7 12 | with: 13 | script: | 14 | github.rest.issues.addLabels({ 15 | issue_number: context.issue.number, 16 | owner: context.repo.owner, 17 | repo: context.repo.repo, 18 | labels: ['untriaged'] 19 | }) 20 | -------------------------------------------------------------------------------- /.github/workflows/add_backport_label.yml: -------------------------------------------------------------------------------- 1 | name: Add Backport Label 2 | on: 3 | pull_request_target: 4 | branches: main 5 | types: opened 6 | 7 | jobs: 8 | add_labels: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions-ecosystem/action-add-labels@v1 13 | with: 14 | labels: backport 1.x 15 | -------------------------------------------------------------------------------- /.github/workflows/backport.yml: -------------------------------------------------------------------------------- 1 | name: Backport 2 | on: 3 | pull_request_target: 4 | types: 5 | - closed 6 | - labeled 7 | 8 | jobs: 9 | backport: 10 | name: Backport 11 | runs-on: ubuntu-latest 12 | # Only react to merged PRs for security reasons. 13 | # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. 14 | if: > 15 | github.event.pull_request.merged 16 | && ( 17 | github.event.action == 'closed' 18 | || ( 19 | github.event.action == 'labeled' 20 | && contains(github.event.label.name, 'backport') 21 | ) 22 | ) 23 | permissions: 24 | contents: write 25 | pull-requests: write 26 | steps: 27 | - name: GitHub App token 28 | id: github_app_token 29 | uses: tibdex/github-app-token@v2 30 | with: 31 | app_id: ${{ secrets.APP_ID }} 32 | private_key: ${{ secrets.APP_PRIVATE_KEY }} 33 | installation_id: 22958780 34 | 35 | - name: Backport 36 | uses: VachaShah/backport@v2.2.0 37 | with: 38 | github_token: ${{ steps.github_app_token.outputs.token }} 39 | head_template: backport/backport-<%= number %>-to-<%= base %> 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Gradle Build 2 | on: 3 | push: 4 | branches-ignore: 5 | - 'backport/**' 6 | - 'whitesource-remediate/**' 7 | pull_request: 8 | types: [opened, synchronize, reopened] 9 | 10 | jobs: 11 | spotless: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Spotless Check 16 | run: ./gradlew spotlessCheck 17 | javadoc: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Javadoc CheckStyle 22 | run: ./gradlew checkstyleMain 23 | - name: Javadoc Check 24 | run: ./gradlew javadoc 25 | check: 26 | needs: [spotless, javadoc] 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Set up JDK 21 31 | uses: actions/setup-java@v4 32 | with: 33 | java-version: 21 34 | distribution: temurin 35 | - name: Run Gradle Check 36 | run: | 37 | ./gradlew check 38 | - name: Upload Coverage Report 39 | uses: codecov/codecov-action@v4 40 | with: 41 | file: ./build/reports/jacoco/test/jacocoTestReport.xml 42 | publish: 43 | needs: [check] 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v4 47 | - name: Publish to Maven Local 48 | run: ./gradlew publishToMavenLocal 49 | -------------------------------------------------------------------------------- /.github/workflows/github-merit-badger.yml: -------------------------------------------------------------------------------- 1 | name: github-merit-badger 2 | on: 3 | pull_request_target: 4 | types: 5 | - opened 6 | 7 | jobs: 8 | call-action: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | pull-requests: write 12 | steps: 13 | - uses: aws-github-ops/github-merit-badger@v0.0.98 14 | id: merit-badger 15 | with: 16 | github-token: ${{ secrets.GITHUB_TOKEN }} 17 | badges: '[first-time-contributor,repeat-contributor,valued-contributor,seasoned-contributor,all-star-contributor,distinguished-contributor]' 18 | thresholds: '[0,3,6,10,20,30]' 19 | badge-type: 'achievement' 20 | ignore-usernames: '[opensearch-ci-bot, dependabot, opensearch-trigger-bot, mend-for-github-com]' 21 | -------------------------------------------------------------------------------- /.github/workflows/publish-snapshots.yml: -------------------------------------------------------------------------------- 1 | name: Publish snapshots to maven 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | build-and-publish-snapshots: 11 | if: github.repository == 'opensearch-project/opensearch-sdk-java' 12 | runs-on: ubuntu-latest 13 | permissions: 14 | id-token: write 15 | contents: write 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | - name: Set up JDK 21 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: 21 23 | distribution: temurin 24 | - name: Configure AWS credentials 25 | uses: aws-actions/configure-aws-credentials@v4 26 | with: 27 | role-to-assume: ${{ secrets.PUBLISH_SNAPSHOTS_ROLE }} 28 | aws-region: us-east-1 29 | - name: publish snapshots to maven 30 | run: | 31 | export SONATYPE_USERNAME=$(aws secretsmanager get-secret-value --secret-id maven-snapshots-username --query SecretString --output text) 32 | export SONATYPE_PASSWORD=$(aws secretsmanager get-secret-value --secret-id maven-snapshots-password --query SecretString --output text) 33 | echo "::add-mask::$SONATYPE_USERNAME" 34 | echo "::add-mask::$SONATYPE_PASSWORD" 35 | ./gradlew publishMavenJavaPublicationToSnapshotsRepository 36 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release drafter 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | draft-a-release: 10 | name: Draft a release 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | issues: write 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | - id: get_data 19 | run: | 20 | echo "approvers=$(cat .github/CODEOWNERS | grep @ | tr -d '* ' | sed 's/@/,/g' | sed 's/,//1')" >> $GITHUB_OUTPUT 21 | echo "version=$(./gradlew getVersion -Dbuild.snapshot=false -Dbuild -q | grep version= | cut -d'=' -f2)" >> $GITHUB_OUTPUT 22 | - uses: trstringer/manual-approval@v1 23 | with: 24 | secret: ${{ github.TOKEN }} 25 | approvers: ${{ steps.get_data.outputs.approvers }} 26 | minimum-approvals: 2 27 | issue-title: 'Release opensearch-sdk-java : ${{ steps.get_data.outputs.version }}' 28 | issue-body: "Please approve or deny the release of opensearch-sdk-java. **VERSION**: ${{ steps.get_data.outputs.version }} **TAG**: ${{ github.ref_name }} **COMMIT**: ${{ github.sha }}" 29 | exclude-workflow-initiator-as-approver: true 30 | - name: Set up JDK 11 31 | uses: actions/setup-java@v4 32 | with: 33 | java-version: '11' 34 | distribution: 'temurin' 35 | cache: 'gradle' 36 | - name: Build with Gradle 37 | run: | 38 | ./gradlew --no-daemon -Dbuild.snapshot=false publishNebulaPublicationToLocalRepoRepository && tar -C build -cvf artifacts.tar.gz localRepo 39 | - name: Draft a release 40 | uses: softprops/action-gh-release@v2 41 | with: 42 | draft: true 43 | generate_release_notes: true 44 | files: | 45 | artifacts.tar.gz 46 | -------------------------------------------------------------------------------- /.github/workflows/wrapper.yml: -------------------------------------------------------------------------------- 1 | name: Validate Gradle Wrapper 2 | on: 3 | push: 4 | branches-ignore: 5 | - 'backport/**' 6 | - 'whitesource-remediate/**' 7 | pull_request: 8 | types: [opened, synchronize, reopened] 9 | 10 | jobs: 11 | validate: 12 | name: Validate 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: gradle/wrapper-validation-action@v3 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # intellij files 3 | .idea/ 4 | *.iml 5 | *.ipr 6 | *.iws 7 | *.log 8 | build-idea/ 9 | out/ 10 | 11 | # eclipse files 12 | .classpath 13 | .project 14 | 15 | # gradle stuff 16 | .gradle/ 17 | build/ 18 | 19 | bin/ 20 | 21 | # vscode stuff 22 | .vscode/ 23 | 24 | # osx stuff 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "configMode": "AUTO", 4 | "configExternalURL": "", 5 | "projectToken": "", 6 | "baseBranches": [] 7 | }, 8 | "checkRunSettings": { 9 | "vulnerableCheckRunConclusionLevel": "failure", 10 | "displayMode": "diff", 11 | "useMendCheckNames": true 12 | }, 13 | "issueSettings": { 14 | "minSeverityLevel": "LOW", 15 | "issueType": "DEPENDENCY" 16 | }, 17 | "remediateSettings": { 18 | "enableRenovate": true, 19 | "extends": [ 20 | "config:base", 21 | ":gitSignOff", 22 | "github>whitesource/merge-confidence:beta" 23 | ], 24 | "workflowRules": { 25 | "enabled": true 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | 6 | This code of conduct applies to all spaces provided by the OpenSource project including in code, documentation, issue trackers, mailing lists, chat channels, wikis, blogs, social media and any other communication channels used by the project. 7 | 8 | 9 | **Our open source communities endeavor to:** 10 | 11 | * Be Inclusive: We are committed to being a community where everyone can join and contribute. This means using inclusive and welcoming language. 12 | * Be Welcoming: We are committed to maintaining a safe space for everyone to be able to contribute. 13 | * Be Respectful: We are committed to encouraging differing viewpoints, accepting constructive criticism and work collaboratively towards decisions that help the project grow. Disrespectful and unacceptable behavior will not be tolerated. 14 | * Be Collaborative: We are committed to supporting what is best for our community and users. When we build anything for the benefit of the project, we should document the work we do and communicate to others on how this affects their work. 15 | 16 | 17 | **Our Responsibility. As contributors, members, or bystanders we each individually have the responsibility to behave professionally and respectfully at all times. Disrespectful and unacceptable behaviors include, but are not limited to:** 18 | 19 | * The use of violent threats, abusive, discriminatory, or derogatory language; 20 | * Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, race, political or religious affiliation; 21 | * Posting of sexually explicit or violent content; 22 | * The use of sexualized language and unwelcome sexual attention or advances; 23 | * Public or private harassment of any kind; 24 | * Publishing private information, such as physical or electronic address, without permission; 25 | * Other conduct which could reasonably be considered inappropriate in a professional setting; 26 | * Advocating for or encouraging any of the above behaviors. 27 | * Enforcement and Reporting Code of Conduct Issues: 28 | 29 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported. [Contact us](mailto:opensource-codeofconduct@amazon.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. 30 | -------------------------------------------------------------------------------- /Docs/Extensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/opensearch-sdk-java/654a735d0ae17862e147eb7c1831d666f1fcb802/Docs/Extensions.png -------------------------------------------------------------------------------- /Docs/plugins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/opensearch-sdk-java/654a735d0ae17862e147eb7c1831d666f1fcb802/Docs/plugins.png -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This document contains a list of maintainers in this repo. See [opensearch-project/.github/RESPONSIBILITIES.md](https://github.com/opensearch-project/.github/blob/main/RESPONSIBILITIES.md#maintainer-responsibilities) that explains what the role of maintainer means, what maintainers do in this and other repos, and how they should be doing it. If you're interested in contributing, and becoming a maintainer, see [CONTRIBUTING](CONTRIBUTING.md). 4 | 5 | ## Current Maintainers 6 | 7 | | Maintainer | GitHub ID | Affiliation | 8 | | ----------------- | ------------------------------------------------------- | ----------- | 9 | | Josh Palis | [joshpalis](https://github.com/joshpalis) | Amazon | 10 | | Owais Kazi | [owaiskazi19](https://github.com/owaiskazi19) | Amazon | 11 | | Ryan Bogan | [ryanbogan](https://github.com/ryanbogan) | Amazon | 12 | | Sarat Vemulapalli | [saratvemulapalli](https://github.com/saratvemulapalli) | Amazon | 13 | | Dan Widdis | [dbwiddis](https://github.com/dbwiddis) | Amazon | 14 | 15 | ## Emeritus Maintainers 16 | 17 | | Maintainer | GitHub ID | Affiliation | 18 | | ----------------- | ------------------------------------------------------- | ----------- | 19 | | Charlotte Henkle | [CEHENKLE](https://github.com/CEHENKLE) | Amazon | 20 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | OpenSearch (https://opensearch.org/) 4 | Copyright OpenSearch Contributors 5 | 6 | This product includes software developed by 7 | Elasticsearch (http://www.elastic.co). 8 | Copyright 2009-2018 Elasticsearch 9 | 10 | This product includes software developed by The Apache Software 11 | Foundation (http://www.apache.org/). 12 | 13 | This product includes software developed by 14 | Joda.org (http://www.joda.org/). 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![codecov](https://codecov.io/gh/opensearch-project/opensearch-sdk-java/branch/main/graph/badge.svg)](https://codecov.io/gh/opensearch-project/opensearch-sdk-java) 2 | [![GHA gradle check](https://github.com/opensearch-project/opensearch-sdk-java/actions/workflows/build.yml/badge.svg)](https://github.com/opensearch-project/opensearch-sdk-java/actions/workflows/build.yml) 3 | [![GHA validate pull request](https://github.com/opensearch-project/opensearch-sdk-java/actions/workflows/wrapper.yml/badge.svg)](https://github.com/opensearch-project/opensearch-sdk-java/actions/workflows/wrapper.yml) 4 | 5 | # OpenSearch SDK for Java 6 | 7 | * [OpenSearch SDK for Java](#opensearch-sdk-for-java) 8 | * [Introduction](#introduction) 9 | * [Design](#design) 10 | * [Creating an extension](#creating-an-extension) 11 | * [Developer Guide](#developer-guide) 12 | * [Plugin migration](#plugin-migration) 13 | * [Contributing](#contributing) 14 | * [Maintainers](#maintainers) 15 | * [Code of Conduct](#code-of-conduct) 16 | 17 | To view extension documentation as a webpage, go to [https://opensearch-project.github.io/opensearch-sdk-java/](https://opensearch-project.github.io/opensearch-sdk-java/). 18 | 19 | ## Introduction 20 | With OpenSearch plugins, you can extend and enhance various core features. However, the current plugin architecture may fatally impact clusters in the event of failure. To ensure that plugins run safely without impacting the system, our goal is to isolate plugin interactions with OpenSearch. The OpenSearch SDK for Java modularizes the [extension points](https://opensearch.org/blog/technical-post/2021/12/plugins-intro/) onto which plugins hook. 21 | 22 | For more information about extensibility, see [this GitHub issue](https://github.com/opensearch-project/OpenSearch/issues/1422). 23 | 24 | ## Design 25 | For an overview of extension architecture and information about how extensions work, see [DESIGN](DESIGN.md). 26 | 27 | ## Creating an extension 28 | For information about developing an extension, see [CREATE_YOUR_FIRST_EXTENSION](CREATE_YOUR_FIRST_EXTENSION.md). 29 | 30 | ## Developer Guide 31 | For instructions on building, testing, and running an extension, see the [DEVELOPER_GUIDE](DEVELOPER_GUIDE.md). 32 | 33 | ## Plugin migration 34 | For tips on migrating an existing plugin to an extension, see [PLUGIN_MIGRATION](PLUGIN_MIGRATION.md). 35 | 36 | ## Contributing 37 | For information about contributing, see [CONTRIBUTING](CONTRIBUTING.md). 38 | 39 | ## Maintainers 40 | For points of contact, see [MAINTAINERS](MAINTAINERS.md). 41 | 42 | ## Code of Conduct 43 | 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. 44 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | - [Overview](#overview) 2 | - [Branching](#branching) 3 | - [Release Branching](#release-branching) 4 | - [Feature Branches](#feature-branches) 5 | - [Releasing](#releasing) 6 | - [Snapshots](#snapshot-builds) 7 | 8 | ## Overview 9 | 10 | This document explains the release strategy for artifacts in this organization. 11 | 12 | ## Branching 13 | 14 | ### Release Branching 15 | 16 | Given the current major release of 1.0, projects in this organization maintain the following active branches. 17 | 18 | * **main**: The next _major_ release. This is the branch where all merges take place and code moves fast. 19 | * **1.x**: The next _minor_ release. Once a change is merged into `main`, decide whether to backport it to `1.x`. 20 | 21 | Label PRs with the next major version label (e.g. `2.0.0`) and merge changes into `main`. Label PRs that you believe need to be backported as `1.x`. Backport PRs by checking out the versioned branch, cherry-pick changes and open a PR against each target backport branch. 22 | 23 | ### Feature Branches 24 | 25 | Do not create branches in the upstream repo, use your fork, for the exception to long-lasting feature branches that require active collaboration from multiple developers. Name feature branches `feature/`. Once the work is merged to `main`, please make sure to delete the feature branch. 26 | 27 | ## Releasing 28 | 29 | The release process is standard across repositories in this org and is run by a release manager volunteering from amongst [maintainers](MAINTAINERS.md). 30 | 31 | 1. Create a tag, e.g. 1.0.0, and push it to this GitHub repository. 32 | 2. The [release-drafter.yml](.github/workflows/release-drafter.yml) will be automatically kicked off and a draft release will be created. 33 | 3. This draft release triggers the [jenkins release workflow](https://build.ci.opensearch.org/job/opensearch-sdk-java-release) as a result of which the sdk is released on [maven central](https://search.maven.org/search?q=org.opensearch.sdk). Please note that the release workflow is triggered only if created release is in draft state. 34 | 4. Once the above release workflow is successful, the drafted release on GitHub is published automatically. 35 | 5. Increment "version" in [build.gradle](https://github.com/opensearch-project/opensearch-sdk-java/blob/main/build.gradle#L79) to the next iteration, e.g. v1.0.1. 36 | 37 | ## Snapshot Builds 38 | The [snapshots builds](https://aws.oss.sonatype.org/content/repositories/snapshots/org/opensearch/sdk/opensearch-sdk-java/) are published to sonatype using [publish-snapshots.yml](./.github/workflows/publish-snapshots.yml) workflow. Each `push` event to the main branch triggers this workflow. 39 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: OpenSearch SDK for Java 2 | description: Extension Documentation 3 | theme: jekyll-theme-minimal 4 | logo: /assets/img/logo.png 5 | include: CONTRIBUTING.md 6 | 7 | sidenav: 8 | - name: Design 9 | link: DESIGN.md 10 | - name: Developer Guide 11 | link: DEVELOPER_GUIDE.md 12 | - name: Create your first extension 13 | link: CREATE_YOUR_FIRST_EXTENSION.md 14 | - name: Plugin migration 15 | link: PLUGIN_MIGRATION.md 16 | -------------------------------------------------------------------------------- /_includes/head_custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /_includes/sidenav.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% seo %} 9 | 10 | 13 | {% include head_custom.html %} 14 | 15 | 16 |
17 |
18 |
19 |

{{ site.title | default: site.github.repository_name }}

20 |

{{ site.description | default: site.github.project_tagline }}

21 | 22 | {% if site.logo %} 23 | OpenSearch Logo 24 | {% endif %} 25 | 26 | {% include sidenav.html %} 27 | 28 |

opensearch-sdk-java repository

29 |

OpenSearch user documentation

30 |
31 |
32 |
33 | 34 | {{ content }} 35 | 36 |
37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /assets/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @import "{{ site.theme }}"; 5 | 6 | @media screen and (min-width: 1200px) { 7 | .wrapper { 8 | width:1200px; 9 | margin:0 auto; 10 | } 11 | 12 | section { 13 | width:900px; 14 | float:right; 15 | padding-bottom:50px; 16 | } 17 | } 18 | 19 | .link-wrapper { 20 | margin-top: 2.5rem; 21 | } 22 | 23 | ul.link { 24 | list-style-type: none; 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | ul.link li + li { 30 | margin-top: 1rem; 31 | } 32 | 33 | ul.link:last-child { 34 | margin-bottom: 2.5rem; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/opensearch-sdk-java/654a735d0ae17862e147eb7c1831d666f1fcb802/assets/img/favicon.ico -------------------------------------------------------------------------------- /assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/opensearch-sdk-java/654a735d0ae17862e147eb7c1831d666f1fcb802/assets/img/logo.png -------------------------------------------------------------------------------- /config/certs/cert-gen.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | openssl genrsa -out root-ca-key.pem 2048 4 | openssl req -new -x509 -sha256 -key root-ca-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=ROOT" -out root-ca.pem -days 730 5 | 6 | openssl genrsa -out extension-01-key-temp.pem 2048 7 | openssl pkcs8 -inform PEM -outform PEM -in extension-01-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out extension-01-key.pem 8 | openssl req -new -key extension-01-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=extension-01" -out extension-01.csr 9 | echo 'subjectAltName=DNS:extension-01' | tee -a extension-01.ext 10 | echo 'subjectAltName=IP:172.20.0.11' | tee -a extension-01.ext 11 | openssl x509 -req -in extension-01.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -out extension-01.pem -days 730 -extfile extension-01.ext 12 | 13 | rm extension-01-key-temp.pem 14 | rm extension-01.csr 15 | rm extension-01.ext 16 | rm root-ca.srl 17 | 18 | openssl genrsa -out admin-key-temp.pem 2048 19 | openssl pkcs8 -inform PEM -outform PEM -in admin-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out admin-key.pem 20 | openssl req -new -key admin-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=A" -out admin.csr 21 | openssl x509 -req -in admin.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -out admin.pem -days 730 22 | openssl genrsa -out os-node-01-key-temp.pem 2048 23 | openssl pkcs8 -inform PEM -outform PEM -in os-node-01-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out os-node-01-key.pem 24 | openssl req -new -key os-node-01-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=os-node-01" -out os-node-01.csr 25 | echo 'subjectAltName=DNS:os-node-01' | tee -a os-node-01.ext 26 | echo 'subjectAltName=IP:172.20.0.11' | tee -a os-node-01.ext 27 | openssl x509 -req -in os-node-01.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -out os-node-01.pem -days 730 -extfile os-node-01.ext 28 | 29 | rm admin-key-temp.pem 30 | rm admin.csr 31 | rm os-node-01-key-temp.pem 32 | rm os-node-01.csr 33 | rm os-node-01.ext 34 | rm root-ca.srl -------------------------------------------------------------------------------- /config/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /config/checkstyle/checkstyle_suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /formatter/license-header.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | # The OpenSearch Contributors require contributions made to 5 | # this file be licensed under the Apache-2.0 license or a 6 | # compatible open source license. 7 | # 8 | # Modifications Copyright OpenSearch Contributors. See 9 | # GitHub history for details. 10 | # 11 | 12 | # Enable build caching 13 | org.gradle.caching=true 14 | org.gradle.warning.mode=none 15 | org.gradle.parallel=true 16 | # Workaround for https://github.com/diffplug/spotless/issues/834 17 | org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Xss2m \ 18 | --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ 19 | --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ 20 | --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ 21 | --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ 22 | --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 23 | options.forkOptions.memoryMaximumSize=2g 24 | 25 | # Disable duplicate project id detection 26 | # See https://docs.gradle.org/current/userguide/upgrading_version_6.html#duplicate_project_names_may_cause_publication_to_fail 27 | systemProp.org.gradle.dependency.duplicate.project.detection=false 28 | 29 | # Enforce the build to fail on deprecated gradle api usage 30 | systemProp.org.gradle.warning.mode=fail 31 | 32 | # forcing to use TLS1.2 to avoid failure in vault 33 | # see https://github.com/hashicorp/vault/issues/8750#issuecomment-631236121 34 | systemProp.jdk.tls.client.protocols=TLSv1.2 35 | 36 | # jvm args for faster test execution by default 37 | systemProp.tests.jvm.argline=-XX:TieredStopAtLevel=1 -XX:ReservedCodeCacheSize=64m 38 | -------------------------------------------------------------------------------- /gradle/formatting.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | project.apply plugin: "com.diffplug.spotless" 3 | spotless { 4 | java { 5 | // Normally this isn't necessary, but we have Java sources in 6 | // non-standard places 7 | target '**/*.java' 8 | 9 | removeUnusedImports() 10 | importOrder( 11 | 'de.thetaphi', 12 | 'com.carrotsearch', 13 | 'com.fasterxml', 14 | 'com.avast', 15 | 'com.sun', 16 | 'com.maxmind|com.github|com.networknt|groovy|nebula', 17 | 'org.antlr', 18 | 'software.amazon', 19 | 'com.azure|com.microsoft|com.ibm|com.google|joptsimple|org.apache|org.bouncycastle|org.codehaus|org.opensearch|org.objectweb|org.joda|org.hamcrest|org.openjdk|org.gradle|org.junit', 20 | 'javax', 21 | 'java', 22 | '', 23 | '\\#java|\\#org.opensearch|\\#org.hamcrest|\\#' 24 | ) 25 | eclipse().configFile rootProject.file('formatter/formatterConfig.xml') 26 | trimTrailingWhitespace() 27 | endWithNewline(); 28 | 29 | // See DEVELOPER_GUIDE.md for details of when to enable this. 30 | if (System.getProperty('spotless.paddedcell') != null) { 31 | paddedCell() 32 | } 33 | } 34 | format 'misc', { 35 | target '*.md', '*.gradle', '**/*.json', '**/*.yaml', '**/*.yml', '**/*.svg' 36 | 37 | trimTrailingWhitespace() 38 | endWithNewline() 39 | } 40 | format("license", { 41 | licenseHeaderFile("${rootProject.file("formatter/license-header.txt")}", "package "); 42 | target("src/*/java/**/*.java") 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/opensearch-sdk-java/654a735d0ae17862e147eb7c1831d666f1fcb802/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /jenkins/release.jenkinsFile: -------------------------------------------------------------------------------- 1 | lib = library(identifier: 'jenkins@1.5.3', retriever: modernSCM([ 2 | $class: 'GitSCMSource', 3 | remote: 'https://github.com/opensearch-project/opensearch-build-libraries.git', 4 | ])) 5 | 6 | standardReleasePipelineWithGenericTrigger( 7 | overrideDockerImage: 'opensearchstaging/ci-runner:release-centos7-clients-v4', 8 | tokenIdCredential: 'jenkins-opensearch-sdk-java-generic-webhook-token', 9 | causeString: 'A tag was cut on opensearch-project/opensearch-sdk-java repository causing this workflow to run', 10 | downloadReleaseAsset: true, 11 | publishRelease: true) { 12 | publishToMaven( 13 | signingArtifactsPath: "$WORKSPACE/localRepo/", 14 | mavenArtifactsPath: "$WORKSPACE/localRepo/", 15 | autoPublish: true 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'opensearch-sdk-java' 2 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/ActionListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import org.opensearch.common.SuppressForbidden; 13 | import org.opensearch.common.unit.TimeValue; 14 | import org.opensearch.transport.ConnectionProfile; 15 | import org.opensearch.transport.TransportRequestOptions; 16 | 17 | import java.io.IOException; 18 | import java.io.UncheckedIOException; 19 | import java.net.InetAddress; 20 | import java.net.InetSocketAddress; 21 | import java.net.ServerSocket; 22 | import java.net.Socket; 23 | import java.net.UnknownHostException; 24 | 25 | /** 26 | * A listener for actions on the local port. 27 | */ 28 | public class ActionListener { 29 | 30 | /** 31 | * Get the local ephemeral port. 32 | * 33 | * @return The socket address for localhost. 34 | * @throws UnknownHostException if the local host name could not be resolved into an address. 35 | */ 36 | @SuppressForbidden(reason = "need local ephemeral port") 37 | private static InetSocketAddress createLocalEphemeralAddress() throws UnknownHostException { 38 | return new InetSocketAddress(InetAddress.getLocalHost(), 0); 39 | } 40 | 41 | /** 42 | * Run the action listener. 43 | * This is presently a placeholder; when it receives a byte on the listening port, it terminates. 44 | * Socket server to run extensions on the specified port provided in extensions.yml file after integrating SDK 45 | * 46 | * @param flag If true, waits for the other side to send a message. 47 | * @param timeout How long to wait, in milliseconds. If zero, infinite timeout. 48 | */ 49 | public void runActionListener(boolean flag, int timeout) { 50 | try (ServerSocket socket = new ServerSocket()) { 51 | 52 | // for testing considerations, otherwise zero which is interpreted as an infinite timeout 53 | socket.setSoTimeout(timeout); 54 | 55 | socket.bind(createLocalEphemeralAddress(), 1); 56 | socket.setReuseAddress(true); 57 | 58 | Thread t = new Thread() { 59 | @Override 60 | public void run() { 61 | try (Socket accept = socket.accept()) { 62 | if (flag) { // sometimes wait until the other side sends the message 63 | accept.getInputStream().read(); 64 | } 65 | } catch (IOException e) { 66 | throw new UncheckedIOException(e); 67 | } 68 | } 69 | }; 70 | t.start(); 71 | ConnectionProfile.Builder builder = new ConnectionProfile.Builder(); 72 | builder.addConnections( 73 | 1, 74 | TransportRequestOptions.Type.BULK, 75 | TransportRequestOptions.Type.PING, 76 | TransportRequestOptions.Type.RECOVERY, 77 | TransportRequestOptions.Type.REG, 78 | TransportRequestOptions.Type.STATE 79 | ); 80 | builder.setHandshakeTimeout(TimeValue.timeValueHours(1)); 81 | t.join(); 82 | 83 | } catch (IOException e) { 84 | e.printStackTrace(); 85 | } catch (InterruptedException e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/BaseExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import java.io.IOException; 13 | 14 | /** 15 | * An abstract class that simplifies extension initialization and provides an instance of the runner. 16 | */ 17 | public abstract class BaseExtension implements Extension { 18 | 19 | /** 20 | * The {@link ExtensionsRunner} instance running this extension 21 | */ 22 | private ExtensionsRunner extensionsRunner; 23 | 24 | /** 25 | * The extension settings include a name, host address, and port. 26 | */ 27 | private final ExtensionSettings settings; 28 | 29 | /** 30 | * Instantiate this extension, initializing the connection settings and REST actions. 31 | * @param path to extensions configuration. 32 | */ 33 | protected BaseExtension(String path) { 34 | try { 35 | this.settings = ExtensionSettings.readSettingsFromYaml(path); 36 | if (settings == null || settings.getHostAddress() == null || settings.getHostPort() == null) { 37 | throw new IOException("Failed to initialize Extension settings. No port bound."); 38 | } 39 | } catch (Exception e) { 40 | throw new IllegalArgumentException(e); 41 | } 42 | } 43 | 44 | /** 45 | * take an ExtensionSettings object and set it directly. 46 | * @param settings defined by the extension. 47 | */ 48 | protected BaseExtension(ExtensionSettings settings) { 49 | this.settings = settings; 50 | } 51 | 52 | @Override 53 | public ExtensionSettings getExtensionSettings() { 54 | return this.settings; 55 | } 56 | 57 | @Override 58 | public void setExtensionsRunner(ExtensionsRunner runner) { 59 | this.extensionsRunner = runner; 60 | } 61 | 62 | /** 63 | * Gets the {@link ExtensionsRunner} of this extension. 64 | * 65 | * @return the extension runner. 66 | */ 67 | public ExtensionsRunner extensionsRunner() { 68 | return this.extensionsRunner; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/Extension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import org.opensearch.common.settings.Setting; 13 | import org.opensearch.common.settings.Settings; 14 | import org.opensearch.core.common.io.stream.NamedWriteableRegistry; 15 | import org.opensearch.core.xcontent.NamedXContentRegistry; 16 | import org.opensearch.threadpool.ExecutorBuilder; 17 | 18 | import java.util.Collection; 19 | import java.util.Collections; 20 | import java.util.List; 21 | 22 | /** 23 | * This interface defines methods which an extension must provide. Extensions 24 | * will instantiate a class implementing this interface and pass it to the 25 | * {@link ExtensionsRunner} constructor to initialize. 26 | */ 27 | public interface Extension { 28 | 29 | /** 30 | * Set the instance of {@link ExtensionsRunner} for this extension. 31 | * 32 | * @param runner The ExtensionsRunner instance. 33 | */ 34 | public void setExtensionsRunner(ExtensionsRunner runner); 35 | 36 | /** 37 | * Gets the {@link ExtensionSettings} of this extension. 38 | * 39 | * @return the extension settings. 40 | */ 41 | ExtensionSettings getExtensionSettings(); 42 | 43 | /** 44 | * Gets an optional list of custom {@link Setting} for the extension to register with OpenSearch. 45 | * 46 | * @return a list of custom settings this extension uses. 47 | */ 48 | default List> getSettings() { 49 | return Collections.emptyList(); 50 | } 51 | 52 | /** 53 | * Gets an optional list of custom {@link NamedXContentRegistry.Entry} for the extension to combine with OpenSearch NamedWriteable. 54 | * 55 | * @return a list of custom NamedXConent this extension uses. 56 | */ 57 | default List getNamedXContent() { 58 | return Collections.emptyList(); 59 | } 60 | 61 | /** 62 | * Gets an optional list of custom {@link NamedWriteableRegistry.Entry} for the extension to combine with OpenSearch NamedXWriteable. 63 | * 64 | * @return a list of custom NamedWriteable this extension uses. 65 | */ 66 | default List getNamedWriteables() { 67 | return Collections.emptyList(); 68 | } 69 | 70 | /** 71 | * Returns components added by this extension. 72 | * 73 | * @param runner the ExtensionsRunner instance. Use getters from this object as required to instantiate components to return. 74 | * @return A collection of objects which will be bound to themselves for dependency injection. 75 | */ 76 | default Collection createComponents(ExtensionsRunner runner) { 77 | return Collections.emptyList(); 78 | } 79 | 80 | /** 81 | * Provides the list of this Extension's custom thread pools, empty if 82 | * none. 83 | * 84 | * @param settings the current settings 85 | * @return executors builders for this Extension's custom thread pools 86 | */ 87 | default List> getExecutorBuilders(Settings settings) { 88 | return Collections.emptyList(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/NettyTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import org.opensearch.Version; 13 | import org.opensearch.cluster.node.DiscoveryNode; 14 | import org.opensearch.common.network.NetworkService; 15 | import org.opensearch.common.settings.Settings; 16 | import org.opensearch.common.util.PageCacheRecycler; 17 | import org.opensearch.core.indices.breaker.CircuitBreakerService; 18 | import org.opensearch.core.indices.breaker.NoneCircuitBreakerService; 19 | import org.opensearch.sdk.ssl.DefaultSslKeyStore; 20 | import org.opensearch.sdk.ssl.SSLConfigConstants; 21 | import org.opensearch.sdk.ssl.SSLNettyTransport; 22 | import org.opensearch.sdk.ssl.SslKeyStore; 23 | import org.opensearch.telemetry.tracing.noop.NoopTracer; 24 | import org.opensearch.threadpool.ThreadPool; 25 | import org.opensearch.transport.SharedGroupFactory; 26 | import org.opensearch.transport.TransportInterceptor; 27 | import org.opensearch.transport.TransportService; 28 | import org.opensearch.transport.netty4.Netty4Transport; 29 | 30 | import java.nio.file.Path; 31 | import java.util.Collections; 32 | 33 | import static java.util.Collections.emptySet; 34 | import static org.opensearch.common.UUIDs.randomBase64UUID; 35 | 36 | /** 37 | * This class initializes a Netty4Transport object and control communication between the extension and OpenSearch. 38 | */ 39 | 40 | public class NettyTransport { 41 | private static final String NODE_NAME_SETTING = "node.name"; 42 | private final ExtensionsRunner extensionsRunner; 43 | private final TransportInterceptor NOOP_TRANSPORT_INTERCEPTOR = new TransportInterceptor() { 44 | }; 45 | 46 | /** 47 | * @param extensionsRunner Instantiate this object with a reference to the ExtensionsRunner. 48 | */ 49 | public NettyTransport(ExtensionsRunner extensionsRunner) { 50 | this.extensionsRunner = extensionsRunner; 51 | } 52 | 53 | /** 54 | * Initializes a Netty4Transport object. This object will be wrapped in a {@link TransportService} object. 55 | * 56 | * @param settings The transport settings to configure. 57 | * @param threadPool A thread pool to use. 58 | * @return The configured Netty4Transport object. 59 | */ 60 | public Netty4Transport getNetty4Transport(Settings settings, ThreadPool threadPool) { 61 | NetworkService networkService = new NetworkService(Collections.emptyList()); 62 | PageCacheRecycler pageCacheRecycler = new PageCacheRecycler(settings); 63 | 64 | final CircuitBreakerService circuitBreakerService = new NoneCircuitBreakerService(); 65 | 66 | boolean transportSSLEnabled = settings.getAsBoolean( 67 | SSLConfigConstants.SSL_TRANSPORT_ENABLED, 68 | SSLConfigConstants.SSL_TRANSPORT_ENABLED_DEFAULT 69 | ); 70 | 71 | Netty4Transport transport = new Netty4Transport( 72 | settings, 73 | Version.CURRENT, 74 | threadPool, 75 | networkService, 76 | pageCacheRecycler, 77 | extensionsRunner.getNamedWriteableRegistry().getRegistry(), 78 | circuitBreakerService, 79 | new SharedGroupFactory(settings), 80 | NoopTracer.INSTANCE 81 | ); 82 | 83 | if (transportSSLEnabled) { 84 | Path configPath = Path.of("").toAbsolutePath().resolve("config"); 85 | SslKeyStore sks = new DefaultSslKeyStore(settings, configPath); 86 | transport = new SSLNettyTransport( 87 | settings, 88 | Version.CURRENT, 89 | threadPool, 90 | networkService, 91 | pageCacheRecycler, 92 | extensionsRunner.getNamedWriteableRegistry().getRegistry(), 93 | circuitBreakerService, 94 | sks, 95 | new SharedGroupFactory(settings) 96 | ); 97 | } 98 | 99 | return transport; 100 | } 101 | 102 | /** 103 | * Initializes the TransportService object for this extension. This object will control communication between the extension and OpenSearch. 104 | * 105 | * @param settings The transport settings to configure. 106 | * @param threadPool The thread pool to use to start transport service. 107 | * @return The initialized TransportService object. 108 | */ 109 | public TransportService initializeExtensionTransportService(Settings settings, ThreadPool threadPool) { 110 | 111 | Netty4Transport transport = getNetty4Transport(settings, threadPool); 112 | 113 | // create transport service 114 | TransportService transportService = new TransportService( 115 | settings, 116 | transport, 117 | threadPool, 118 | NOOP_TRANSPORT_INTERCEPTOR, 119 | boundAddress -> DiscoveryNode.createLocal( 120 | Settings.builder().put(NODE_NAME_SETTING, settings.get(NODE_NAME_SETTING)).build(), 121 | boundAddress.publishAddress(), 122 | randomBase64UUID() 123 | ), 124 | null, 125 | emptySet(), 126 | NoopTracer.INSTANCE 127 | ); 128 | extensionsRunner.startTransportService(transportService); 129 | return transportService; 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/SDKNamedWriteableRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import org.opensearch.cluster.ClusterModule; 13 | import org.opensearch.common.network.NetworkModule; 14 | import org.opensearch.common.settings.Settings; 15 | import org.opensearch.core.common.io.stream.NamedWriteableRegistry; 16 | import org.opensearch.core.common.io.stream.NamedWriteableRegistry.Entry; 17 | import org.opensearch.indices.IndicesModule; 18 | import org.opensearch.search.SearchModule; 19 | 20 | import java.util.Collections; 21 | import java.util.List; 22 | import java.util.function.Function; 23 | import java.util.stream.Stream; 24 | 25 | import static java.util.stream.Collectors.toList; 26 | 27 | /** 28 | * Combines Extension NamedWriteable with core OpenSearch NamedWriteable 29 | */ 30 | public class SDKNamedWriteableRegistry { 31 | private NamedWriteableRegistry namedWriteableRegistry; 32 | 33 | /** 34 | * Creates and populates a NamedWriteableRegistry with the NamedWriteableRegistry entries for this extension and locally defined content. 35 | * 36 | * @param runner The ExtensionsRunner instance. 37 | */ 38 | public SDKNamedWriteableRegistry(ExtensionsRunner runner) { 39 | this.namedWriteableRegistry = createRegistry(runner.getEnvironmentSettings(), runner.getCustomNamedWriteables()); 40 | } 41 | 42 | /** 43 | * Updates the NamedWriteableRegistry with the NamedWriteableRegistry entries for this extension and locally defined content. 44 | *

45 | * Only necessary if environment settings have changed. 46 | * 47 | * @param runner The ExtensionsRunner instance. 48 | */ 49 | public void updateNamedWriteableRegistry(ExtensionsRunner runner) { 50 | this.namedWriteableRegistry = createRegistry(runner.getEnvironmentSettings(), runner.getCustomNamedWriteables()); 51 | } 52 | 53 | private NamedWriteableRegistry createRegistry(Settings settings, List extensionNamedWriteable) { 54 | Stream extensionContent = extensionNamedWriteable == null ? Stream.empty() : extensionNamedWriteable.stream(); 55 | return new NamedWriteableRegistry( 56 | Stream.of( 57 | extensionContent, 58 | NetworkModule.getNamedWriteables().stream(), 59 | new IndicesModule(Collections.emptyList()).getNamedWriteables().stream(), 60 | new SearchModule(settings, Collections.emptyList()).getNamedWriteables().stream(), 61 | ClusterModule.getNamedWriteables().stream() 62 | ).flatMap(Function.identity()).collect(toList()) 63 | ); 64 | } 65 | 66 | /** 67 | * Gets the NamedWriteableRegistry. 68 | * 69 | * @return The NamedWriteableRegistry. Includes both extension-defined Writeable and core OpenSearch Writeable. 70 | */ 71 | public NamedWriteableRegistry getRegistry() { 72 | return this.namedWriteableRegistry; 73 | } 74 | 75 | public void setNamedWriteableRegistry(NamedWriteableRegistry namedWriteableRegistry) { 76 | this.namedWriteableRegistry = namedWriteableRegistry; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/SDKNamedXContentRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import org.opensearch.cluster.ClusterModule; 13 | import org.opensearch.common.network.NetworkModule; 14 | import org.opensearch.common.settings.Settings; 15 | import org.opensearch.core.xcontent.NamedXContentRegistry; 16 | import org.opensearch.core.xcontent.NamedXContentRegistry.Entry; 17 | import org.opensearch.core.xcontent.XContentParser; 18 | import org.opensearch.indices.IndicesModule; 19 | import org.opensearch.search.SearchModule; 20 | 21 | import java.util.Collections; 22 | import java.util.List; 23 | import java.util.function.Function; 24 | import java.util.stream.Stream; 25 | 26 | import static java.util.stream.Collectors.toList; 27 | 28 | /** 29 | * Combines Extension NamedXContent with core OpenSearch NamedXContent 30 | */ 31 | public class SDKNamedXContentRegistry { 32 | /** 33 | * The empty {@link SDKNamedXContentRegistry} for use when you are sure that you aren't going to call 34 | * {@link XContentParser#namedObject(Class, String, Object)}. Be *very* careful with this singleton because a parser using it will fail 35 | * every call to {@linkplain XContentParser#namedObject(Class, String, Object)}. Every non-test usage really should be checked 36 | * thoroughly and marked with a comment about how it was checked. That way anyone that sees code that uses it knows that it is 37 | * potentially dangerous. 38 | */ 39 | public static final SDKNamedXContentRegistry EMPTY = new SDKNamedXContentRegistry(); 40 | 41 | private NamedXContentRegistry namedXContentRegistry; 42 | 43 | /** 44 | * Creates an empty registry. 45 | */ 46 | private SDKNamedXContentRegistry() { 47 | this.namedXContentRegistry = NamedXContentRegistry.EMPTY; 48 | } 49 | 50 | /** 51 | * Creates and populates a NamedXContentRegistry with the NamedXContentRegistry entries for this extension and locally defined content. 52 | * 53 | * @param runner The ExtensionsRunner instance. 54 | */ 55 | public SDKNamedXContentRegistry(ExtensionsRunner runner) { 56 | this.namedXContentRegistry = createRegistry(runner.getEnvironmentSettings(), runner.getCustomNamedXContent()); 57 | } 58 | 59 | /** 60 | * Updates the NamedXContentRegistry with the NamedXContentRegistry entries for this extension and locally defined content. 61 | *

62 | * Only necessary if environment settings have changed. 63 | * 64 | * @param runner The ExtensionsRunner instance. 65 | */ 66 | public void updateNamedXContentRegistry(ExtensionsRunner runner) { 67 | this.namedXContentRegistry = createRegistry(runner.getEnvironmentSettings(), runner.getCustomNamedXContent()); 68 | } 69 | 70 | private NamedXContentRegistry createRegistry(Settings settings, List extensionNamedXContent) { 71 | Stream extensionContent = extensionNamedXContent == null ? Stream.empty() : extensionNamedXContent.stream(); 72 | return new NamedXContentRegistry( 73 | Stream.of( 74 | extensionContent, 75 | NetworkModule.getNamedXContents().stream(), 76 | IndicesModule.getNamedXContents().stream(), 77 | new SearchModule(settings, Collections.emptyList()).getNamedXContents().stream(), 78 | ClusterModule.getNamedXWriteables().stream() 79 | ).flatMap(Function.identity()).collect(toList()) 80 | ); 81 | } 82 | 83 | /** 84 | * Gets the NamedXContentRegistry. 85 | * 86 | * @return The NamedXContentRegistry. Includes both extension-defined XContent and core OpenSearch XContent. 87 | */ 88 | public NamedXContentRegistry getRegistry() { 89 | return this.namedXContentRegistry; 90 | } 91 | 92 | /** 93 | * Sets the NamedXContentRegistry. Used primarily for tests. 94 | * 95 | * @param namedXContentRegistry The registry to set. 96 | */ 97 | public void setRegistry(NamedXContentRegistry namedXContentRegistry) { 98 | this.namedXContentRegistry = namedXContentRegistry; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/action/RemoteExtensionAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.action; 11 | 12 | import org.opensearch.action.ActionType; 13 | import org.opensearch.extensions.action.RemoteExtensionActionResponse; 14 | 15 | /** 16 | * The {@link ActionType} used as they key for the {@link RemoteExtensionTransportAction}. 17 | */ 18 | public class RemoteExtensionAction extends ActionType { 19 | 20 | /** 21 | * The name to look up this action with 22 | */ 23 | public static final String NAME = "internal:remote-extension-action"; 24 | /** 25 | * The singleton instance of this class 26 | */ 27 | public static final RemoteExtensionAction INSTANCE = new RemoteExtensionAction(); 28 | 29 | private RemoteExtensionAction() { 30 | super(NAME, RemoteExtensionActionResponse::new); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/action/RemoteExtensionTransportAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.action; 11 | 12 | import com.google.inject.Inject; 13 | import org.opensearch.action.support.ActionFilters; 14 | import org.opensearch.action.support.TransportAction; 15 | import org.opensearch.core.action.ActionListener; 16 | import org.opensearch.extensions.action.RemoteExtensionActionResponse; 17 | import org.opensearch.sdk.SDKTransportService; 18 | import org.opensearch.tasks.Task; 19 | import org.opensearch.tasks.TaskManager; 20 | 21 | /** 22 | * Sends a request to OpenSearch for a remote extension to execute an action. 23 | */ 24 | public class RemoteExtensionTransportAction extends TransportAction { 25 | 26 | private SDKTransportService sdkTransportService; 27 | 28 | /** 29 | * Instantiate this action 30 | * 31 | * @param actionName The action name 32 | * @param actionFilters Action filters 33 | * @param taskManager The task manager 34 | * @param sdkTransportService The SDK transport service 35 | */ 36 | @Inject 37 | protected RemoteExtensionTransportAction( 38 | String actionName, 39 | ActionFilters actionFilters, 40 | TaskManager taskManager, 41 | SDKTransportService sdkTransportService 42 | ) { 43 | super(actionName, actionFilters, taskManager); 44 | this.sdkTransportService = sdkTransportService; 45 | } 46 | 47 | @Override 48 | protected void doExecute(Task task, RemoteExtensionActionRequest request, ActionListener listener) { 49 | RemoteExtensionActionResponse response = sdkTransportService.sendRemoteExtensionActionRequest(request); 50 | if (response.getResponseBytes().length > 0) { 51 | listener.onResponse(response); 52 | } else { 53 | listener.onFailure(new RuntimeException("No response received from remote extension.")); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/action/SDKActionModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.action; 11 | 12 | import com.google.inject.AbstractModule; 13 | import com.google.inject.multibindings.MapBinder; 14 | import org.opensearch.action.ActionType; 15 | import org.opensearch.action.support.ActionFilters; 16 | import org.opensearch.action.support.TransportAction; 17 | import org.opensearch.common.NamedRegistry; 18 | import org.opensearch.sdk.Extension; 19 | import org.opensearch.sdk.api.ActionExtension; 20 | import org.opensearch.sdk.api.ActionExtension.ActionHandler; 21 | 22 | import java.util.Collections; 23 | import java.util.Map; 24 | import java.util.stream.Collectors; 25 | 26 | import static java.util.Collections.unmodifiableMap; 27 | 28 | /** 29 | * A module for injecting getActions classes into Guice. 30 | */ 31 | public class SDKActionModule extends AbstractModule { 32 | private final Map> actions; 33 | private final ActionFilters actionFilters; 34 | 35 | /** 36 | * Instantiate this module 37 | * 38 | * @param extension The extension 39 | */ 40 | public SDKActionModule(Extension extension) { 41 | this.actions = setupActions(extension); 42 | this.actionFilters = setupActionFilters(extension); 43 | } 44 | 45 | public Map> getActions() { 46 | return actions; 47 | } 48 | 49 | public ActionFilters getActionFilters() { 50 | return actionFilters; 51 | } 52 | 53 | private static Map> setupActions(Extension extension) { 54 | /** 55 | * Subclass of NamedRegistry permitting easier action registration 56 | */ 57 | class ActionRegistry extends NamedRegistry> { 58 | ActionRegistry() { 59 | super("action"); 60 | } 61 | 62 | /** 63 | * Register an action handler pairing an ActionType and TransportAction 64 | * 65 | * @param handler The ActionHandler to register 66 | */ 67 | public void register(ActionHandler handler) { 68 | register(handler.getAction().name(), handler); 69 | } 70 | } 71 | ActionRegistry actions = new ActionRegistry(); 72 | 73 | // Register SDK actions 74 | actions.register(new ActionHandler<>(RemoteExtensionAction.INSTANCE, RemoteExtensionTransportAction.class)); 75 | 76 | // Register actions from getActions extension point 77 | if (extension instanceof ActionExtension) { 78 | ((ActionExtension) extension).getActions().stream().forEach(actions::register); 79 | } 80 | return unmodifiableMap(actions.getRegistry()); 81 | } 82 | 83 | private static ActionFilters setupActionFilters(Extension extension) { 84 | return new ActionFilters( 85 | extension instanceof ActionExtension 86 | ? ((ActionExtension) extension).getActionFilters().stream().collect(Collectors.toSet()) 87 | : Collections.emptySet() 88 | ); 89 | } 90 | 91 | @Override 92 | protected void configure() { 93 | // Bind action filters 94 | bind(ActionFilters.class).toInstance(actionFilters); 95 | 96 | // bind ActionType -> transportAction Map used by Client 97 | @SuppressWarnings("rawtypes") 98 | MapBinder transportActionsBinder = MapBinder.newMapBinder( 99 | binder(), 100 | ActionType.class, 101 | TransportAction.class 102 | ); 103 | for (ActionHandler action : actions.values()) { 104 | // bind the action as eager singleton, so the map binder one will reuse it 105 | bind(action.getTransportAction()).asEagerSingleton(); 106 | transportActionsBinder.addBinding(action.getAction()).to(action.getTransportAction()).asEagerSingleton(); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/CircuitBreakerExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.opensearch.common.settings.Settings; 13 | import org.opensearch.core.common.breaker.CircuitBreaker; 14 | import org.opensearch.core.indices.breaker.CircuitBreakerService; 15 | import org.opensearch.indices.breaker.BreakerSettings; 16 | import org.opensearch.sdk.Extension; 17 | 18 | /** 19 | * An extension point for {@link Extension} implementations to add custom circuit breakers 20 | * 21 | * 22 | */ 23 | public interface CircuitBreakerExtension { 24 | 25 | /** 26 | * Each of the factory functions are passed to the configured {@link CircuitBreakerService}. 27 | * 28 | * The service then constructs a {@link CircuitBreaker} given the resulting {@link BreakerSettings}. 29 | * 30 | * Custom circuit breakers settings can be found in {@link BreakerSettings}. 31 | * See: 32 | * - limit (example: `breaker.foo.limit`) {@link BreakerSettings#CIRCUIT_BREAKER_LIMIT_SETTING} 33 | * - overhead (example: `breaker.foo.overhead`) {@link BreakerSettings#CIRCUIT_BREAKER_OVERHEAD_SETTING} 34 | * - type (example: `breaker.foo.type`) {@link BreakerSettings#CIRCUIT_BREAKER_TYPE} 35 | * 36 | * The `limit` and `overhead` settings will be dynamically updated in the circuit breaker service iff a {@link BreakerSettings} 37 | * object with the same name is provided at node startup. 38 | * @param settings The constructed {@link Settings} object 39 | * @return The constructed BreakerSetting object 40 | */ 41 | BreakerSettings getCircuitBreaker(Settings settings); 42 | 43 | /** 44 | * The passed {@link CircuitBreaker} object is the same one that was constructed by the {@link BreakerSettings} 45 | * provided by {@link CircuitBreakerExtension#getCircuitBreaker(Settings)}. 46 | * 47 | * This reference should never change throughout the lifetime of the node. 48 | * 49 | * @param circuitBreaker The constructed {@link CircuitBreaker} object from the {@link BreakerSettings} 50 | * provided by {@link CircuitBreakerExtension#getCircuitBreaker(Settings)} 51 | */ 52 | void setCircuitBreaker(CircuitBreaker circuitBreaker); 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/EngineExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.opensearch.index.IndexSettings; 13 | import org.opensearch.index.codec.CodecServiceFactory; 14 | import org.opensearch.index.engine.EngineFactory; 15 | import org.opensearch.index.seqno.RetentionLeases; 16 | import org.opensearch.index.translog.TranslogDeletionPolicy; 17 | import org.opensearch.index.translog.TranslogDeletionPolicyFactory; 18 | 19 | import java.util.Optional; 20 | import java.util.function.Supplier; 21 | 22 | /** 23 | * A Extension that provides alternative engine implementations. 24 | * 25 | */ 26 | public interface EngineExtension { 27 | 28 | /** 29 | * When an index is created this method is invoked for each engine Extension. Engine Extensions can inspect the index settings to determine 30 | * whether or not to provide an engine factory for the given index. A Extension that is not overriding the default engine should return 31 | * {@link Optional#empty()}. If multiple Extensions return an engine factory for a given index the index will not be created and an 32 | * {@link IllegalStateException} will be thrown during index creation. 33 | * 34 | * @param indexSettings the index settings to inspect 35 | * @return an optional engine factory 36 | */ 37 | default Optional getEngineFactory(IndexSettings indexSettings) { 38 | return Optional.empty(); 39 | } 40 | 41 | /** 42 | * EXPERT: 43 | * When an index is created this method is invoked for each engine Extension. Engine Extensions can inspect the index settings 44 | * to determine if a custom {@link CodecServiceFactory} should be provided for the given index. A Extension that is not overriding 45 | * the {@link CodecServiceFactory} through the Extension can ignore this method and the default Codec specified in the 46 | * {@link IndexSettings} will be used. 47 | * 48 | * @param indexSettings the index settings to inspect 49 | * @return an optional engine factory 50 | */ 51 | default Optional getCustomCodecServiceFactory(IndexSettings indexSettings) { 52 | return Optional.empty(); 53 | } 54 | 55 | /** 56 | * When an index is created this method is invoked for each engine Extension. Engine Extensions that need to provide a 57 | * custom {@link TranslogDeletionPolicy} can override this method to return a function that takes the {@link IndexSettings} 58 | * and a {@link Supplier} for {@link RetentionLeases} and returns a custom {@link TranslogDeletionPolicy}. 59 | * 60 | * Only one of the installed Engine Extensions can override this otherwise {@link IllegalStateException} will be thrown. 61 | * 62 | * @return a function that returns an instance of {@link TranslogDeletionPolicy} 63 | */ 64 | default Optional getCustomTranslogDeletionPolicyFactory() { 65 | return Optional.empty(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/ExtensibleExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.opensearch.sdk.Extension; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * An extension point for {@link Extension} implementations to be themselves extensible. 18 | * 19 | * This class provides a callback for extensible Extensions to be informed of other Extensions 20 | * which extend them. 21 | * 22 | */ 23 | 24 | public interface ExtensibleExtension { 25 | /** 26 | * Extension point for external Extensions to be extendable 27 | */ 28 | 29 | interface ExtensionLoader { 30 | /** 31 | * Load extensions of the type from all extending Extensions. The concrete extensions must have either a no-arg constructor 32 | * or a single-arg constructor accepting the specific Extension class. 33 | * @param extensionPointType the extension point type 34 | * @param extension point type 35 | * @return all implementing extensions. 36 | */ 37 | List loadExtensions(Class extensionPointType); 38 | } 39 | 40 | /** 41 | * Allow this Extension to load extensions from other Extensions. 42 | * 43 | * This method is called once only, after initializing this Extension and all Extensions extending this Extension. It is called before 44 | * any other methods on this Extension instance are called. 45 | * @param loader the loader to use to load extensions 46 | */ 47 | default void loadExtensions(ExtensionLoader loader) {} 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/IndexStoreExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.apache.lucene.store.Directory; 13 | import org.opensearch.cluster.node.DiscoveryNode; 14 | import org.opensearch.cluster.routing.ShardRouting; 15 | import org.opensearch.common.Nullable; 16 | import org.opensearch.index.IndexSettings; 17 | import org.opensearch.index.shard.ShardPath; 18 | import org.opensearch.indices.recovery.RecoveryState; 19 | 20 | import java.io.IOException; 21 | import java.util.Collections; 22 | import java.util.Map; 23 | 24 | /** 25 | * An extension that provides alternative directory implementations. 26 | * 27 | * 28 | */ 29 | public interface IndexStoreExtension { 30 | 31 | /** 32 | * An interface that describes how to create a new directory instance per shard. 33 | */ 34 | @FunctionalInterface 35 | interface DirectoryFactory { 36 | /** 37 | * Creates a new directory per shard. This method is called once per shard on shard creation. 38 | * @param indexSettings the shards index settings 39 | * @param shardPath the path the shard is using 40 | * @return a new lucene directory instance 41 | * @throws IOException if an IOException occurs while opening the directory 42 | */ 43 | Directory newDirectory(IndexSettings indexSettings, ShardPath shardPath) throws IOException; 44 | } 45 | 46 | /** 47 | * An interface that describes how to create a new remote directory instance per shard. 48 | */ 49 | @FunctionalInterface 50 | interface RemoteDirectoryFactory { 51 | /** 52 | * Creates a new remote directory per shard. This method is called once per shard on shard creation. 53 | * @param repositoryName repository name 54 | * @param indexSettings the shards index settings 55 | * @param shardPath the path the shard is using 56 | * @return a new RemoteDirectory instance 57 | * @throws IOException if an IOException occurs while opening the directory 58 | */ 59 | Directory newDirectory(String repositoryName, IndexSettings indexSettings, ShardPath shardPath) throws IOException; 60 | } 61 | 62 | /** 63 | * The {@link DirectoryFactory} mappings for this extension. When an index is created the store type setting 64 | * {@link org.opensearch.index.IndexModule#INDEX_STORE_TYPE_SETTING} on the index will be examined and either use the default or a 65 | * built-in type, or looked up among all the directory factories from {@link IndexStoreExtension} extensions. 66 | * 67 | * @return a map from store type to an directory factory 68 | */ 69 | Map getDirectoryFactories(); 70 | 71 | /** 72 | * An interface that allows to create a new {@link RecoveryState} per shard. 73 | */ 74 | @FunctionalInterface 75 | interface RecoveryStateFactory { 76 | /** 77 | * Creates a new {@link RecoveryState} per shard. This method is called once per shard on shard creation. 78 | * @param shardRouting immutably encapsulates information about shard. 79 | * @param targetNode the target node. 80 | * @param sourceNode the source node. 81 | * @return a new RecoveryState instance 82 | */ 83 | RecoveryState newRecoveryState(ShardRouting shardRouting, DiscoveryNode targetNode, @Nullable DiscoveryNode sourceNode); 84 | } 85 | 86 | /** 87 | * The {@link RecoveryStateFactory} mappings for this extension. When an index is created the recovery type setting 88 | * {@link org.opensearch.index.IndexModule#INDEX_RECOVERY_TYPE_SETTING} on the index will be examined and either use the default 89 | * or looked up among all the recovery state factories from {@link IndexStoreExtension} extensions. 90 | * 91 | * @return a map from recovery type to an recovery state factory 92 | */ 93 | default Map getRecoveryStateFactories() { 94 | return Collections.emptyMap(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/IngestExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.opensearch.ingest.Processor; 13 | import org.opensearch.sdk.Extension; 14 | 15 | import java.util.Collections; 16 | import java.util.Map; 17 | 18 | /** 19 | * An extension point for {@link Extension} implementations to add custom ingest processors 20 | */ 21 | 22 | public interface IngestExtension { 23 | 24 | /** 25 | * Returns additional ingest processor types added by this plugin. 26 | * 27 | * @param parameters instance of the Parameters class. 28 | * @return The key of the returned {@link Map} is the unique name for the processor which is 29 | * specified in pipeline configurations, and the value is a {@link Processor.Factory} 30 | * to create the processor from a given pipeline configuration. 31 | */ 32 | default Map getProcessors(Processor.Parameters parameters) { 33 | return Collections.emptyMap(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/MapperExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.opensearch.index.mapper.Mapper; 13 | import org.opensearch.index.mapper.MetadataFieldMapper; 14 | import org.opensearch.sdk.Extension; 15 | 16 | import java.util.Collections; 17 | import java.util.Map; 18 | import java.util.function.Function; 19 | import java.util.function.Predicate; 20 | 21 | /** 22 | * An extension point for {@link Extension} implementations to add custom mappers 23 | */ 24 | public interface MapperExtension { 25 | 26 | /** 27 | * Returns additional mapper implementations added by this extension. 28 | *

29 | * The key of the returned {@link Map} is the unique name for the mapper which will be used 30 | * as the mapping {@code type}, and the value is a {@link Mapper.TypeParser} to parse the 31 | * mapper settings into a {@link Mapper}. 32 | * @return a map of unique mapper names to their corresponding type parsers. 33 | */ 34 | default Map getMappers() { 35 | return Collections.emptyMap(); 36 | } 37 | 38 | /** 39 | * Returns additional metadata mapper implementations added by this extension. 40 | *

41 | * The key of the returned {@link Map} is the unique name for the metadata mapper, which 42 | * is used in the mapping json to configure the metadata mapper, and the value is a 43 | * {@link MetadataFieldMapper.TypeParser} to parse the mapper settings into a 44 | * {@link MetadataFieldMapper}. 45 | * @return a map of metadata mapper names to their corresponding type parsers. 46 | */ 47 | default Map getMetadataMappers() { 48 | return Collections.emptyMap(); 49 | } 50 | 51 | /** 52 | * Returns a function that filters the fields returned by get mappings, get index, get field mappings, and field capabilities API. 53 | * The returned function takes an index name as input and returns a predicate that specifies which fields should be returned by the API. 54 | * The predicate takes a field name as input and returns true to include the field or false to exclude it. 55 | * @return a function that filters the fields returned by certain APIs. 56 | */ 57 | default Function> getFieldFilter() { 58 | return NOOP_FIELD_FILTER; 59 | } 60 | 61 | /** 62 | * The default field predicate applied, which doesn't filter anything. That means that by default get mappings, get index 63 | * get field mappings and field capabilities API will return every field that's present in the mappings. 64 | */ 65 | Predicate NOOP_FIELD_PREDICATE = field -> true; 66 | 67 | /** 68 | * The default field filter applied, which doesn't filter anything. That means that by default get mappings, get index 69 | * get field mappings and field capabilities API will return every field that's present in the mappings. 70 | */ 71 | Function> NOOP_FIELD_FILTER = index -> NOOP_FIELD_PREDICATE; 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/PersistentTaskExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.opensearch.client.Client; 13 | import org.opensearch.cluster.metadata.IndexNameExpressionResolver; 14 | import org.opensearch.cluster.service.ClusterService; 15 | import org.opensearch.common.settings.SettingsModule; 16 | import org.opensearch.persistent.PersistentTasksExecutor; 17 | import org.opensearch.threadpool.ThreadPool; 18 | 19 | import java.util.Collections; 20 | import java.util.List; 21 | 22 | /** 23 | * Extension for registering persistent tasks executors. 24 | */ 25 | 26 | public interface PersistentTaskExtension { 27 | 28 | /** 29 | * Returns additional persistent tasks executors added by this extension. 30 | * @param clusterService the cluster service used to coordinate actions across the cluster. 31 | * @param threadPool the thread pool used to execute tasks. 32 | * @param client the client used to interact with the Elasticsearch cluster. 33 | * @param settingsModule the module containing Elasticsearch settings. 34 | * @param expressionResolver the resolver used to parse index name expressions. 35 | * @return an empty List. 36 | */ 37 | default List> getPersistentTasksExecutor( 38 | ClusterService clusterService, 39 | ThreadPool threadPool, 40 | Client client, 41 | SettingsModule settingsModule, 42 | IndexNameExpressionResolver expressionResolver 43 | ) { 44 | return Collections.emptyList(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/ReloadableExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.opensearch.common.settings.Settings; 13 | import org.opensearch.sdk.Extension; 14 | 15 | /** 16 | * An extension point for {@link Extension}s that can be reloaded. There is no 17 | * clear definition about what reloading a extension actually means. When a extension 18 | * is reloaded it might rebuild any internal members. Extensions usually implement 19 | * this interface in order to reread the values of {@code SecureSetting}s and 20 | * then rebuild any dependent internal members. 21 | */ 22 | public interface ReloadableExtension { 23 | 24 | /** 25 | * Called to trigger the rebuilt of the extension's internal members. The reload 26 | * operation is required to have been completed when the method returns. 27 | * Strictly speaking, the settings argument should not be accessed 28 | * outside of this method's call stack, as any values stored in the node's 29 | * keystore (see {@code SecureSetting}) will not otherwise be retrievable. The 30 | * setting values do not follow dynamic updates, i.e. the values are identical 31 | * to the ones during the initial extension loading, barring the keystore file on 32 | * disk changes. Any failure during the operation should be signaled by raising 33 | * an exception, but the extension should otherwise continue to function 34 | * unperturbed. 35 | * 36 | * @param settings 37 | * Settings used while reloading the extension. All values are 38 | * retrievable, including the values stored in the node's keystore. 39 | * The setting values are the initial ones, from when the node has be 40 | * started, i.e. they don't follow dynamic updates. 41 | * @throws Exception 42 | * if the operation failed. The extension should continue to operate as 43 | * if the offending call didn't happen. 44 | */ 45 | 46 | void reload(Settings settings) throws Exception; 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/RepositoryExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.opensearch.cluster.service.ClusterService; 13 | import org.opensearch.core.xcontent.NamedXContentRegistry; 14 | import org.opensearch.env.Environment; 15 | import org.opensearch.indices.recovery.RecoverySettings; 16 | import org.opensearch.repositories.Repository; 17 | import org.opensearch.sdk.Extension; 18 | 19 | import java.util.Collections; 20 | import java.util.Map; 21 | 22 | /** 23 | * An extension point for {@link Extension} implementations to add custom snapshot repositories. 24 | * 25 | */ 26 | 27 | public interface RepositoryExtension { 28 | 29 | /** 30 | * Returns repository types added by this extension. 31 | * 32 | * @param env The environment for the local node, which may be used for the local settings and path. repo 33 | * @param namedXContentRegistry register named objects. 34 | * @param clusterService service for operating cluster state. 35 | * @param recoverySettings settings related to cluster recovery. 36 | * 37 | * The key of the returned {@link Map} is the type name of the repository and 38 | * the value is a factory to construct the {@link Repository} interface. 39 | */ 40 | default Map getRepositories( 41 | Environment env, 42 | NamedXContentRegistry namedXContentRegistry, 43 | ClusterService clusterService, 44 | RecoverySettings recoverySettings 45 | ) { 46 | return Collections.emptyMap(); 47 | } 48 | 49 | /** 50 | * Returns internal repository types added by this extension. Internal repositories cannot be registered 51 | * through the external API. 52 | * 53 | * @param env The environment for the local node, which may be used for the local settings and path.repo 54 | * @param namedXContentRegistry register named objects. 55 | * @param clusterService service for operating cluster state. 56 | * @param recoverySettings settings related to cluster recovery. 57 | * 58 | * The key of the returned {@link Map} is the type name of the repository and 59 | * the value is a factory to construct the {@link Repository} interface. 60 | */ 61 | default Map getInternalRepositories( 62 | Environment env, 63 | NamedXContentRegistry namedXContentRegistry, 64 | ClusterService clusterService, 65 | RecoverySettings recoverySettings 66 | ) { 67 | return Collections.emptyMap(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/ScriptExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.opensearch.common.settings.Settings; 13 | import org.opensearch.script.ScriptContext; 14 | import org.opensearch.script.ScriptEngine; 15 | import org.opensearch.sdk.Extension; 16 | 17 | import java.util.Collection; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * An additional extension point for {@link Extension}s that extends OpenSearch's scripting functionality. 24 | */ 25 | public interface ScriptExtension { 26 | 27 | /** 28 | * Returns a {@link ScriptEngine} instance or null if this extension doesn't add a new script engine. 29 | * @param settings Node settings 30 | * @param contexts The contexts that {@link ScriptEngine#compile(String, String, ScriptContext, Map)} may be called with 31 | */ 32 | default ScriptEngine getScriptEngine(Settings settings, Collection> contexts) { 33 | return null; 34 | } 35 | 36 | /** 37 | * Return script contexts this extension wants to allow using. 38 | */ 39 | default List> getContexts() { 40 | return Collections.emptyList(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/api/SystemIndexExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.api; 11 | 12 | import org.opensearch.common.settings.Settings; 13 | import org.opensearch.indices.SystemIndexDescriptor; 14 | 15 | import java.util.Collection; 16 | import java.util.Collections; 17 | 18 | /** 19 | * Extension for defining system indices. Extends {@link ActionExtension} because system indices must be accessed via APIs 20 | * added by the extension that owns the system index, rather than standard APIs. 21 | * 22 | * 23 | */ 24 | public interface SystemIndexExtension extends ActionExtension { 25 | 26 | /** 27 | * Returns a {@link Collection} of {@link SystemIndexDescriptor}s that describe this plugin's system indices, including 28 | * name, mapping, and settings. 29 | * @param settings The node's settings 30 | * @return Descriptions of the system indices managed by this plugin. 31 | */ 32 | default Collection getSystemIndexDescriptors(Settings settings) { 33 | return Collections.emptyList(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/AcknowledgedResponseHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.opensearch.core.common.io.stream.StreamInput; 15 | import org.opensearch.extensions.AcknowledgedResponse; 16 | import org.opensearch.threadpool.ThreadPool; 17 | import org.opensearch.transport.TransportException; 18 | import org.opensearch.transport.TransportResponseHandler; 19 | 20 | import java.io.IOException; 21 | 22 | /** 23 | * This class handles the response {{@link org.opensearch.extensions.AcknowledgedResponse }} from OpenSearch to Extension. 24 | */ 25 | public class AcknowledgedResponseHandler implements TransportResponseHandler { 26 | private static final Logger logger = LogManager.getLogger(AcknowledgedResponseHandler.class); 27 | 28 | @Override 29 | public void handleResponse(AcknowledgedResponse response) { 30 | logger.info("received {}", response); 31 | } 32 | 33 | @Override 34 | public void handleException(TransportException exp) { 35 | logger.info("Extension Request failed", exp); 36 | } 37 | 38 | @Override 39 | public String executor() { 40 | return ThreadPool.Names.GENERIC; 41 | } 42 | 43 | @Override 44 | public AcknowledgedResponse read(StreamInput in) throws IOException { 45 | return new AcknowledgedResponse(in); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/ClusterSettingsResponseHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.opensearch.cluster.ClusterSettingsResponse; 15 | import org.opensearch.core.common.io.stream.StreamInput; 16 | import org.opensearch.threadpool.ThreadPool; 17 | import org.opensearch.transport.TransportException; 18 | import org.opensearch.transport.TransportResponseHandler; 19 | 20 | import java.io.IOException; 21 | 22 | /** 23 | * This class handles the response from OpenSearch to a {@link org.opensearch.sdk.SDKTransportService#sendClusterSettingsRequest()} call. 24 | */ 25 | public class ClusterSettingsResponseHandler implements TransportResponseHandler { 26 | private static final Logger logger = LogManager.getLogger(ClusterSettingsResponseHandler.class); 27 | 28 | @Override 29 | public void handleResponse(ClusterSettingsResponse response) { 30 | logger.info("received {}", response); 31 | } 32 | 33 | @Override 34 | public void handleException(TransportException exp) { 35 | logger.info("ClusterSettingRequest failed", exp); 36 | } 37 | 38 | @Override 39 | public String executor() { 40 | return ThreadPool.Names.GENERIC; 41 | } 42 | 43 | @Override 44 | public ClusterSettingsResponse read(StreamInput in) throws IOException { 45 | return new ClusterSettingsResponse(in); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/ClusterStateResponseHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.opensearch.action.admin.cluster.state.ClusterStateResponse; 15 | import org.opensearch.cluster.ClusterState; 16 | import org.opensearch.core.common.io.stream.StreamInput; 17 | import org.opensearch.extensions.ExtensionsManager; 18 | import org.opensearch.threadpool.ThreadPool; 19 | import org.opensearch.transport.TransportException; 20 | import org.opensearch.transport.TransportResponseHandler; 21 | 22 | import java.io.IOException; 23 | import java.util.concurrent.CompletableFuture; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | /** 27 | * This class handles the response from OpenSearch to a {@link org.opensearch.sdk.SDKTransportService#sendClusterStateRequest()} call. 28 | */ 29 | public class ClusterStateResponseHandler implements TransportResponseHandler { 30 | private static final Logger logger = LogManager.getLogger(ClusterStateResponseHandler.class); 31 | private final CompletableFuture inProgressFuture; 32 | private ClusterState clusterState; 33 | 34 | /** 35 | * Instantiates a new ClusterStateResponseHandler with a count down latch and an empty ClusterState object 36 | */ 37 | public ClusterStateResponseHandler() { 38 | this.inProgressFuture = new CompletableFuture<>(); 39 | this.clusterState = ClusterState.EMPTY_STATE; 40 | } 41 | 42 | @Override 43 | public void handleResponse(ClusterStateResponse response) { 44 | logger.info("received {}", response); 45 | 46 | // Set cluster state from response 47 | this.clusterState = response.getState(); 48 | inProgressFuture.complete(response); 49 | } 50 | 51 | @Override 52 | public void handleException(TransportException exp) { 53 | logger.info("ExtensionClusterStateRequest failed", exp); 54 | inProgressFuture.completeExceptionally(exp); 55 | } 56 | 57 | @Override 58 | public String executor() { 59 | return ThreadPool.Names.GENERIC; 60 | } 61 | 62 | @Override 63 | public ClusterStateResponse read(StreamInput in) throws IOException { 64 | return new ClusterStateResponse(in); 65 | } 66 | 67 | /** 68 | * Invokes await on the ClusterStateResponseHandler count down latch 69 | * @throws Exception 70 | * if the response times out 71 | */ 72 | public void awaitResponse() throws Exception { 73 | inProgressFuture.orTimeout(ExtensionsManager.EXTENSION_REQUEST_WAIT_TIMEOUT, TimeUnit.SECONDS).get(); 74 | } 75 | 76 | public ClusterState getClusterState() { 77 | return this.clusterState; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/EnvironmentSettingsResponseHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.opensearch.common.settings.Settings; 15 | import org.opensearch.core.common.io.stream.StreamInput; 16 | import org.opensearch.env.EnvironmentSettingsResponse; 17 | import org.opensearch.extensions.ExtensionsManager; 18 | import org.opensearch.threadpool.ThreadPool; 19 | import org.opensearch.transport.TransportException; 20 | import org.opensearch.transport.TransportResponseHandler; 21 | 22 | import java.io.IOException; 23 | import java.util.concurrent.CompletableFuture; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | /** 27 | * This class handles the response from OpenSearch to a {@link org.opensearch.sdk.SDKTransportService#sendEnvironmentSettingsRequest} call. 28 | */ 29 | public class EnvironmentSettingsResponseHandler implements TransportResponseHandler { 30 | 31 | private static final Logger logger = LogManager.getLogger(EnvironmentSettingsResponseHandler.class); 32 | private final CompletableFuture inProgressFuture; 33 | private Settings environmentSettings; 34 | 35 | /** 36 | * Instantiates a new EnvironmentSettingsResponseHandler with a count down latch and an empty Settings object 37 | */ 38 | public EnvironmentSettingsResponseHandler() { 39 | this.inProgressFuture = new CompletableFuture<>(); 40 | this.environmentSettings = Settings.EMPTY; 41 | } 42 | 43 | @Override 44 | public void handleResponse(EnvironmentSettingsResponse response) { 45 | logger.info("received {}", response); 46 | 47 | // Set environmentSettings from response 48 | this.environmentSettings = response.getEnvironmentSettings(); 49 | inProgressFuture.complete(response); 50 | } 51 | 52 | @Override 53 | public void handleException(TransportException exp) { 54 | logger.info("EnvironmentSettingsRequest failed", exp); 55 | inProgressFuture.completeExceptionally(exp); 56 | } 57 | 58 | @Override 59 | public String executor() { 60 | return ThreadPool.Names.GENERIC; 61 | } 62 | 63 | @Override 64 | public EnvironmentSettingsResponse read(StreamInput in) throws IOException { 65 | return new EnvironmentSettingsResponse(in); 66 | } 67 | 68 | /** 69 | * Invokes await on the EnvironmentSettingsResponseHandler count down latch 70 | * @throws Exception 71 | * if the response times out 72 | */ 73 | public void awaitResponse() throws Exception { 74 | inProgressFuture.orTimeout(ExtensionsManager.EXTENSION_REQUEST_WAIT_TIMEOUT, TimeUnit.SECONDS).get(); 75 | } 76 | 77 | public Settings getEnvironmentSettings() { 78 | return this.environmentSettings; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/ExtensionActionResponseHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.opensearch.core.common.io.stream.StreamInput; 15 | import org.opensearch.extensions.ExtensionsManager; 16 | import org.opensearch.extensions.action.RemoteExtensionActionResponse; 17 | import org.opensearch.sdk.SDKTransportService; 18 | import org.opensearch.threadpool.ThreadPool; 19 | import org.opensearch.transport.TransportException; 20 | import org.opensearch.transport.TransportResponseHandler; 21 | 22 | import java.io.IOException; 23 | import java.util.concurrent.CompletableFuture; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | /** 27 | * This class handles the response from OpenSearch to a {@link SDKTransportService#sendRemoteExtensionActionRequest} call. 28 | */ 29 | public class ExtensionActionResponseHandler implements TransportResponseHandler { 30 | 31 | private static final Logger logger = LogManager.getLogger(ExtensionActionResponseHandler.class); 32 | private final CompletableFuture inProgressFuture; 33 | private boolean success = false; 34 | private byte[] responseBytes = new byte[0]; 35 | 36 | /** 37 | * Instantiates a new ExtensionActionResponseHandler 38 | */ 39 | public ExtensionActionResponseHandler() { 40 | this.inProgressFuture = new CompletableFuture<>(); 41 | } 42 | 43 | @Override 44 | public void handleResponse(RemoteExtensionActionResponse response) { 45 | logger.info("Received response bytes: " + response.getResponseBytes().length + " bytes"); 46 | // Set ExtensionActionResponse from response 47 | this.success = response.isSuccess(); 48 | this.responseBytes = response.getResponseBytes(); 49 | inProgressFuture.complete(response); 50 | } 51 | 52 | @Override 53 | public void handleException(TransportException exp) { 54 | logger.error("ExtensionActionResponseRequest failed", exp); 55 | inProgressFuture.completeExceptionally(exp); 56 | } 57 | 58 | @Override 59 | public String executor() { 60 | return ThreadPool.Names.GENERIC; 61 | } 62 | 63 | @Override 64 | public RemoteExtensionActionResponse read(StreamInput in) throws IOException { 65 | return new RemoteExtensionActionResponse(in); 66 | } 67 | 68 | /** 69 | * Waits for the ExtensionActionResponseHandler future to complete 70 | * @throws Exception 71 | * if the response times out 72 | */ 73 | public void awaitResponse() throws Exception { 74 | inProgressFuture.orTimeout(ExtensionsManager.EXTENSION_REQUEST_WAIT_TIMEOUT, TimeUnit.SECONDS).get(); 75 | } 76 | 77 | public boolean isSuccess() { 78 | return success; 79 | } 80 | 81 | public byte[] getResponseBytes() { 82 | return this.responseBytes; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/ExtensionDependencyResponseHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.opensearch.core.common.io.stream.StreamInput; 15 | import org.opensearch.extensions.DiscoveryExtensionNode; 16 | import org.opensearch.extensions.ExtensionDependencyResponse; 17 | import org.opensearch.extensions.ExtensionsManager; 18 | import org.opensearch.threadpool.ThreadPool; 19 | import org.opensearch.transport.TransportException; 20 | import org.opensearch.transport.TransportResponseHandler; 21 | 22 | import java.io.IOException; 23 | import java.util.List; 24 | import java.util.concurrent.CompletableFuture; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | /** 28 | * This class handles the response from OpenSearch to a {@link org.opensearch.sdk.SDKTransportService#sendExtensionDependencyRequest} call. 29 | */ 30 | public class ExtensionDependencyResponseHandler implements TransportResponseHandler { 31 | private static final Logger logger = LogManager.getLogger(ExtensionDependencyResponseHandler.class); 32 | private final CompletableFuture inProgressFuture; 33 | private List extensions; 34 | 35 | /** 36 | * Instantiates a new ExtensionDependencyHandler with ConpletableFuture 37 | */ 38 | public ExtensionDependencyResponseHandler() { 39 | this.inProgressFuture = new CompletableFuture<>(); 40 | } 41 | 42 | @Override 43 | public void handleResponse(ExtensionDependencyResponse response) { 44 | 45 | // Set cluster state from response 46 | this.extensions = response.getExtensionDependency(); 47 | inProgressFuture.complete(response); 48 | } 49 | 50 | @Override 51 | public void handleException(TransportException exp) { 52 | logger.info("ExtensionDependencyResponseHandler failed", exp); 53 | inProgressFuture.completeExceptionally(exp); 54 | } 55 | 56 | @Override 57 | public String executor() { 58 | return ThreadPool.Names.GENERIC; 59 | } 60 | 61 | @Override 62 | public ExtensionDependencyResponse read(StreamInput in) throws IOException { 63 | return new ExtensionDependencyResponse(in); 64 | } 65 | 66 | /** 67 | * Invokes await on the ExtensionDependencyResponseHandler count down latch 68 | * @throws Exception 69 | * if the response times out, 70 | * if the response has been cancelled 71 | * if the response failed 72 | */ 73 | public void awaitResponse() throws Exception { 74 | inProgressFuture.orTimeout(ExtensionsManager.EXTENSION_REQUEST_WAIT_TIMEOUT, TimeUnit.SECONDS).get(); 75 | } 76 | 77 | /** 78 | * Get the dependency information form the Response 79 | * @return dependency information 80 | */ 81 | public List getExtensionDependencies() { 82 | return this.extensions; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/ExtensionsIndicesModuleNameRequestHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.opensearch.extensions.AcknowledgedResponse; 15 | import org.opensearch.index.IndicesModuleRequest; 16 | import org.opensearch.sdk.ExtensionsRunner; 17 | 18 | /** 19 | * This class handles the request from OpenSearch to a {@link ExtensionsRunner#startTransportService(TransportService transportService)} call. 20 | */ 21 | 22 | public class ExtensionsIndicesModuleNameRequestHandler { 23 | private static final Logger logger = LogManager.getLogger(ExtensionsIndicesModuleNameRequestHandler.class); 24 | 25 | /** 26 | * Handles a request for extension name from OpenSearch. The {@link ExtensionsInitRequestHandler} method must have been called first to initialize the extension. 27 | * 28 | * @param indicesModuleRequest The request to handle. 29 | * @return A response acknowledging the request. 30 | */ 31 | public AcknowledgedResponse handleIndicesModuleNameRequest(IndicesModuleRequest indicesModuleRequest) { 32 | // Works as beforeIndexRemoved 33 | logger.info("Registering Indices Module Name Request received from OpenSearch"); 34 | AcknowledgedResponse indicesModuleNameResponse = new AcknowledgedResponse(true); 35 | return indicesModuleNameResponse; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/ExtensionsIndicesModuleRequestHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.opensearch.index.IndicesModuleRequest; 15 | import org.opensearch.index.IndicesModuleResponse; 16 | import org.opensearch.sdk.ExtensionsRunner; 17 | import org.opensearch.transport.TransportService; 18 | 19 | /** 20 | * This class handles the request from OpenSearch to a {@link ExtensionsRunner#startTransportService(TransportService transportService)} call. 21 | */ 22 | 23 | public class ExtensionsIndicesModuleRequestHandler { 24 | private static final Logger logger = LogManager.getLogger(ExtensionsIndicesModuleRequestHandler.class); 25 | 26 | /** 27 | * Handles a request for extension point indices from OpenSearch. The {@link ExtensionsInitRequestHandler} class must have been called first to initialize the extension. 28 | * 29 | * @param indicesModuleRequest The request to handle. 30 | * @param transportService The transport service communicating with OpenSearch. 31 | * @return A response to OpenSearch with this extension's index and search listeners. 32 | */ 33 | public IndicesModuleResponse handleIndicesModuleRequest(IndicesModuleRequest indicesModuleRequest, TransportService transportService) { 34 | logger.info("Registering Indices Module Request received from OpenSearch"); 35 | IndicesModuleResponse indicesModuleResponse = new IndicesModuleResponse(true, true, true); 36 | return indicesModuleResponse; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/ExtensionsInitRequestHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.opensearch.common.settings.Settings; 15 | import org.opensearch.discovery.InitializeExtensionRequest; 16 | import org.opensearch.discovery.InitializeExtensionResponse; 17 | import org.opensearch.sdk.ExtensionsRunner; 18 | import org.opensearch.sdk.SDKTransportService; 19 | import org.opensearch.transport.TransportService; 20 | 21 | import static org.opensearch.sdk.ExtensionsRunner.NODE_NAME_SETTING; 22 | 23 | /** 24 | * This class handles the request from OpenSearch to a {@link ExtensionsRunner#startTransportService(TransportService transportService)} call. 25 | */ 26 | 27 | public class ExtensionsInitRequestHandler { 28 | private static final Logger logger = LogManager.getLogger(ExtensionsInitRequestHandler.class); 29 | 30 | // The default http port setting of OpenSearch 31 | private static final String DEFAULT_HTTP_PORT = "9200"; 32 | 33 | // The configured http port setting of opensearch.yml 34 | private static final String HTTP_PORT_SETTING = "http.port"; 35 | 36 | private final ExtensionsRunner extensionsRunner; 37 | 38 | /** 39 | * Instantiate this object with a reference to the ExtensionsRunner 40 | * 41 | * @param extensionsRunner the ExtensionsRunner instance 42 | */ 43 | public ExtensionsInitRequestHandler(ExtensionsRunner extensionsRunner) { 44 | this.extensionsRunner = extensionsRunner; 45 | } 46 | 47 | /** 48 | * Handles a extension request from OpenSearch. This is the first request for the transport communication and will initialize the extension and will be a part of OpenSearch bootstrap. 49 | * 50 | * @param extensionInitRequest The request to handle. 51 | * @return A response to OpenSearch validating that this is an extension. 52 | */ 53 | public InitializeExtensionResponse handleExtensionInitRequest(InitializeExtensionRequest extensionInitRequest) { 54 | logger.info("Registering Extension Request received from OpenSearch"); 55 | extensionsRunner.getThreadPool().getThreadContext().putHeader("extension_unique_id", extensionInitRequest.getExtension().getId()); 56 | SDKTransportService sdkTransportService = extensionsRunner.getSdkTransportService(); 57 | sdkTransportService.setOpensearchNode(extensionInitRequest.getSourceNode()); 58 | sdkTransportService.setUniqueId(extensionInitRequest.getExtension().getId()); 59 | // Successfully initialized. Send the response. 60 | try { 61 | return new InitializeExtensionResponse( 62 | extensionsRunner.getSettings().get(NODE_NAME_SETTING), 63 | extensionsRunner.getExtensionImplementedInterfaces() 64 | ); 65 | } finally { 66 | // After sending successful response to initialization, send the REST API and Settings 67 | extensionsRunner.setExtensionNode(extensionInitRequest.getExtension()); 68 | 69 | TransportService extensionTransportService = sdkTransportService.getTransportService(); 70 | extensionTransportService.connectToNodeAsExtension( 71 | extensionInitRequest.getSourceNode(), 72 | extensionInitRequest.getExtension().getId() 73 | ); 74 | sdkTransportService.sendRegisterRestActionsRequest(extensionsRunner.getExtensionRestPathRegistry()); 75 | sdkTransportService.sendRegisterCustomSettingsRequest(extensionsRunner.getCustomSettings()); 76 | sdkTransportService.sendRegisterTransportActionsRequest(extensionsRunner.getSdkActionModule().getActions()); 77 | // Get OpenSearch Settings and set values on ExtensionsRunner 78 | Settings settings = sdkTransportService.sendEnvironmentSettingsRequest(); 79 | extensionsRunner.setEnvironmentSettings(settings); 80 | extensionsRunner.updateNamedXContentRegistry(); 81 | extensionsRunner.updateSdkClusterService(); 82 | // Use OpenSearch Settings to update client REST Connections 83 | String openSearchNodeAddress = extensionInitRequest.getSourceNode().getAddress().getAddress(); 84 | String openSearchNodeHttpPort = settings.get(HTTP_PORT_SETTING) != null ? settings.get(HTTP_PORT_SETTING) : DEFAULT_HTTP_PORT; 85 | extensionsRunner.getSdkClient().updateOpenSearchNodeSettings(openSearchNodeAddress, openSearchNodeHttpPort); 86 | 87 | // Last step of initialization 88 | // TODO: make sure all the other sendX methods have completed 89 | // https://github.com/opensearch-project/opensearch-sdk-java/issues/17 90 | extensionsRunner.setInitialized(); 91 | 92 | // Trigger pending updates requiring completion of the above actions 93 | extensionsRunner.getSdkClusterService().getClusterSettings().sendPendingSettingsUpdateConsumers(); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/ExtensionsRestRequestHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.opensearch.core.common.bytes.BytesReference; 15 | import org.opensearch.extensions.rest.ExtensionRestRequest; 16 | import org.opensearch.extensions.rest.ExtensionRestResponse; 17 | import org.opensearch.extensions.rest.RestExecuteOnExtensionResponse; 18 | import org.opensearch.sdk.ExtensionsRunner; 19 | import org.opensearch.sdk.SDKNamedXContentRegistry; 20 | import org.opensearch.sdk.rest.ExtensionRestHandler; 21 | import org.opensearch.sdk.rest.ExtensionRestPathRegistry; 22 | import org.opensearch.sdk.rest.SDKHttpRequest; 23 | import org.opensearch.sdk.rest.SDKRestRequest; 24 | 25 | import static java.nio.charset.StandardCharsets.UTF_8; 26 | import static java.util.Collections.emptyList; 27 | import static java.util.Collections.emptyMap; 28 | import static org.opensearch.core.rest.RestStatus.NOT_FOUND; 29 | import static org.opensearch.rest.BytesRestResponse.TEXT_CONTENT_TYPE; 30 | 31 | /** 32 | * This class handles the request from OpenSearch to a {@link ExtensionsRunner#startTransportService(TransportService transportService)} call. 33 | */ 34 | 35 | public class ExtensionsRestRequestHandler { 36 | private static final Logger logger = LogManager.getLogger(ExtensionsRestRequestHandler.class); 37 | private final ExtensionRestPathRegistry extensionRestPathRegistry; 38 | private final SDKNamedXContentRegistry sdkNamedXContentRegistry; 39 | 40 | /** 41 | * Instantiate this class with an existing registry 42 | * 43 | * @param restPathRegistry The ExtensionsRunnerer's REST path registry 44 | * @param sdkNamedXContentRegistry The SDKNamedXContentRegistry wrapper 45 | */ 46 | public ExtensionsRestRequestHandler(ExtensionRestPathRegistry restPathRegistry, SDKNamedXContentRegistry sdkNamedXContentRegistry) { 47 | this.sdkNamedXContentRegistry = sdkNamedXContentRegistry; 48 | this.extensionRestPathRegistry = restPathRegistry; 49 | } 50 | 51 | /** 52 | * Handles a request from OpenSearch to execute a REST request on the extension. 53 | * 54 | * @param request The REST request to execute. 55 | * @return A response acknowledging the request. 56 | */ 57 | public RestExecuteOnExtensionResponse handleRestExecuteOnExtensionRequest(ExtensionRestRequest request) { 58 | 59 | ExtensionRestHandler restHandler = extensionRestPathRegistry.getHandler(request.method(), request.path()); 60 | if (restHandler == null) { 61 | return new RestExecuteOnExtensionResponse( 62 | NOT_FOUND, 63 | TEXT_CONTENT_TYPE, 64 | String.join(" ", "No handler for", request.method().name(), request.path()).getBytes(UTF_8), 65 | emptyMap(), 66 | emptyList(), 67 | false 68 | ); 69 | } 70 | 71 | SDKRestRequest sdkRestRequest = new SDKRestRequest( 72 | sdkNamedXContentRegistry.getRegistry(), 73 | request.params(), 74 | request.path(), 75 | request.headers(), 76 | new SDKHttpRequest(request), 77 | null 78 | ); 79 | 80 | // Get response from extension 81 | ExtensionRestResponse response = restHandler.handleRequest(sdkRestRequest); 82 | logger.info("Sending extension response to OpenSearch: " + response.status()); 83 | return new RestExecuteOnExtensionResponse( 84 | response.status(), 85 | response.contentType(), 86 | BytesReference.toBytes(response.content()), 87 | response.getHeaders(), 88 | response.getConsumedParams(), 89 | response.isContentConsumed() 90 | ); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/handlers/OpensearchRequestHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.handlers; 11 | 12 | import org.opensearch.core.transport.TransportResponse; 13 | import org.opensearch.extensions.OpenSearchRequest; 14 | import org.opensearch.sdk.ExtensionsRunner; 15 | 16 | /** 17 | * This class handles the request from OpenSearch to a {@link ExtensionsRunner#startTransportService(TransportService transportService)} call. 18 | */ 19 | 20 | public class OpensearchRequestHandler { 21 | 22 | /** 23 | * Handles a request from OpenSearch and invokes the extension point API corresponding with the request type 24 | * 25 | * @param request The request to handle. 26 | * @return A response to OpenSearch for the corresponding API 27 | * @throws Exception if the corresponding handler for the request is not present 28 | */ 29 | public TransportResponse handleOpenSearchRequest(OpenSearchRequest request) throws Exception { 30 | // Read enum 31 | switch (request.getRequestType()) { 32 | // Add additional request handlers here 33 | default: 34 | throw new IllegalArgumentException("Handler not present for the provided request"); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/rest/ExtensionRestHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.rest; 11 | 12 | import org.opensearch.extensions.rest.ExtensionRestResponse; 13 | import org.opensearch.rest.BaseRestHandler; 14 | import org.opensearch.rest.NamedRoute; 15 | import org.opensearch.rest.RestHandler; 16 | import org.opensearch.rest.RestHandler.DeprecatedRoute; 17 | import org.opensearch.rest.RestHandler.ReplacedRoute; 18 | import org.opensearch.rest.RestHandler.Route; 19 | import org.opensearch.rest.RestRequest; 20 | 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | /** 25 | * This interface defines methods which an extension REST handler (action) must provide. 26 | * It is the Extension counterpart to core OpenSearch {@link RestHandler}. 27 | */ 28 | @FunctionalInterface 29 | public interface ExtensionRestHandler { 30 | 31 | /** 32 | * Handles REST Requests forwarded from OpenSearch for a configured route on an extension. 33 | * Parameter contains components of the {@link RestRequest} received from a user. 34 | * This method corresponds to the {@link BaseRestHandler#prepareRequest} method. 35 | * 36 | * @param restRequest a REST request object for a request to be forwarded to extensions 37 | * @return An {@link ExtensionRestResponse} to the request. 38 | */ 39 | ExtensionRestResponse handleRequest(RestRequest restRequest); 40 | 41 | /** 42 | * A list of {@link Route}s that this ExtensionRestHandler is responsible for handling. 43 | * 44 | * @return The routes this handler will handle. 45 | */ 46 | default List routes() { 47 | return Collections.emptyList(); 48 | } 49 | 50 | /** 51 | * A list of routes handled by this RestHandler that are deprecated and do not have a direct replacement. 52 | * If changing the {@code path} or {@code method} of a route, use {@link #replacedRoutes()}. 53 | * 54 | * @return Deprecated routes this handler will handle. 55 | */ 56 | default List deprecatedRoutes() { 57 | return Collections.emptyList(); 58 | } 59 | 60 | /** 61 | * A list of routes handled by this RestHandler that have had their {@code path} and/or {@code method} changed. 62 | * The pre-existing {@code route} will be registered as deprecated alongside the updated {@code route}. 63 | * 64 | * @return Replaced routes this handler will handle. 65 | */ 66 | default List replacedRoutes() { 67 | return Collections.emptyList(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/rest/ReplacedRouteHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.rest; 11 | 12 | import org.opensearch.extensions.rest.ExtensionRestResponse; 13 | import org.opensearch.rest.RestHandler.ReplacedRoute; 14 | import org.opensearch.rest.RestHandler.Route; 15 | import org.opensearch.rest.RestRequest; 16 | import org.opensearch.rest.RestRequest.Method; 17 | import org.opensearch.rest.RestResponse; 18 | 19 | import java.util.function.Function; 20 | 21 | /** 22 | * A subclass of {@link ReplacedRoute} that includes a handler method for that route. 23 | */ 24 | public class ReplacedRouteHandler extends ReplacedRoute { 25 | 26 | private final Function responseHandler; 27 | 28 | /** 29 | * Handle replaced routes using new and deprecated methods and new and deprecated paths. 30 | * 31 | * @param method route method 32 | * @param path new route path 33 | * @param deprecatedMethod deprecated method 34 | * @param deprecatedPath deprecated path 35 | * @param handler The method which handles the REST method and path. 36 | */ 37 | public ReplacedRouteHandler( 38 | Method method, 39 | String path, 40 | Method deprecatedMethod, 41 | String deprecatedPath, 42 | Function handler 43 | ) { 44 | super(method, path, deprecatedMethod, deprecatedPath); 45 | this.responseHandler = handler; 46 | } 47 | 48 | /** 49 | * Handle replaced routes using route method, new and deprecated paths. 50 | * This constructor can be used when both new and deprecated paths use the same method. 51 | * 52 | * @param method route method 53 | * @param path new route path 54 | * @param deprecatedPath deprecated path 55 | * @param handler The method which handles the REST method and path. 56 | */ 57 | public ReplacedRouteHandler(Method method, String path, String deprecatedPath, Function handler) { 58 | this(method, path, method, deprecatedPath, handler); 59 | } 60 | 61 | /** 62 | * Handle replaced routes using route, new and deprecated prefixes. 63 | * 64 | * @param route route 65 | * @param prefix new route prefix 66 | * @param deprecatedPrefix deprecated prefix 67 | * @param handler The method which handles the REST method and path. 68 | */ 69 | public ReplacedRouteHandler(Route route, String prefix, String deprecatedPrefix, Function handler) { 70 | this(route.getMethod(), prefix + route.getPath(), deprecatedPrefix + route.getPath(), handler); 71 | } 72 | 73 | /** 74 | * Executes the handler for this route. 75 | * 76 | * @param request The request to handle 77 | * @return the {@link ExtensionRestResponse} result from the handler for this route. 78 | */ 79 | public ExtensionRestResponse handleRequest(RestRequest request) { 80 | return (ExtensionRestResponse) responseHandler.apply(request); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/rest/SDKHttpRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.rest; 11 | 12 | import org.opensearch.core.common.bytes.BytesReference; 13 | import org.opensearch.core.rest.RestStatus; 14 | import org.opensearch.extensions.rest.ExtensionRestRequest; 15 | import org.opensearch.http.HttpRequest; 16 | import org.opensearch.http.HttpResponse; 17 | import org.opensearch.rest.RestRequest; 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * This class helps to get instance of HttpRequest 24 | */ 25 | public class SDKHttpRequest implements HttpRequest { 26 | private final RestRequest.Method method; 27 | private final String uri; 28 | private final BytesReference content; 29 | private final Map> headers; 30 | private final HttpVersion httpVersion; 31 | 32 | /** 33 | * Instantiates this class with a copy of {@link ExtensionRestRequest} 34 | * 35 | * @param request The request 36 | */ 37 | public SDKHttpRequest(ExtensionRestRequest request) { 38 | this.method = request.method(); 39 | this.uri = request.uri(); 40 | this.content = request.content(); 41 | this.headers = request.headers(); 42 | this.httpVersion = request.protocolVersion(); 43 | } 44 | 45 | @Override 46 | public RestRequest.Method method() { 47 | return method; 48 | } 49 | 50 | @Override 51 | public String uri() { 52 | return uri; 53 | } 54 | 55 | @Override 56 | public BytesReference content() { 57 | return content; 58 | } 59 | 60 | @Override 61 | public Map> getHeaders() { 62 | return headers; 63 | } 64 | 65 | /** 66 | * Not implemented. Does nothing. 67 | * @return null 68 | */ 69 | @Override 70 | public List strictCookies() { 71 | return null; 72 | } 73 | 74 | @Override 75 | public HttpVersion protocolVersion() { 76 | return httpVersion; 77 | } 78 | 79 | /** 80 | * Not implemented. Does nothing. 81 | * @return null 82 | */ 83 | @Override 84 | public HttpRequest removeHeader(String s) { 85 | return null; 86 | } 87 | 88 | /** 89 | * Not implemented. Does nothing. 90 | * @param restStatus response status 91 | * @param bytesReference content 92 | * @return null 93 | */ 94 | @Override 95 | public HttpResponse createResponse(RestStatus restStatus, BytesReference bytesReference) { 96 | return null; 97 | } 98 | 99 | /** 100 | * Not implemented. Does nothing. 101 | * @return null 102 | */ 103 | @Override 104 | public Exception getInboundException() { 105 | return null; 106 | } 107 | 108 | /** 109 | * Not implemented. Does nothing. 110 | */ 111 | @Override 112 | public void release() { 113 | 114 | } 115 | 116 | /** 117 | * Not implemented. Does nothing. 118 | * @return null 119 | */ 120 | @Override 121 | public HttpRequest releaseAndCopy() { 122 | return null; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/rest/SDKMethodHandlers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.rest; 11 | 12 | import org.opensearch.common.Nullable; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | import static org.opensearch.rest.RestRequest.Method; 19 | 20 | /** 21 | * Encapsulate multiple handlers for the same path, allowing different handlers for different HTTP verbs. 22 | *

23 | * Used in SDK to provide identical path-matching functionality as OpenSearch, with Extension-based classes. 24 | */ 25 | final class SDKMethodHandlers { 26 | 27 | private final String path; 28 | private final Map methodHandlers; 29 | 30 | SDKMethodHandlers(String path, ExtensionRestHandler handler, Method... methods) { 31 | this.path = path; 32 | this.methodHandlers = new HashMap<>(methods.length); 33 | for (Method method : methods) { 34 | methodHandlers.put(method, handler); 35 | } 36 | } 37 | 38 | /** 39 | * Add a handler for an additional array of methods. Note that {@code SDKMethodHandlers} 40 | * does not allow replacing the handler for an already existing method. 41 | */ 42 | SDKMethodHandlers addMethods(ExtensionRestHandler handler, Method... methods) { 43 | for (Method method : methods) { 44 | ExtensionRestHandler existing = methodHandlers.putIfAbsent(method, handler); 45 | if (existing != null) { 46 | throw new IllegalArgumentException("Cannot replace existing handler for [" + path + "] for method: " + method); 47 | } 48 | } 49 | return this; 50 | } 51 | 52 | /** 53 | * Returns the handler for the given method or {@code null} if none exists. 54 | */ 55 | @Nullable 56 | ExtensionRestHandler getHandler(Method method) { 57 | return methodHandlers.get(method); 58 | } 59 | 60 | /** 61 | * Return a set of all valid HTTP methods for the particular path 62 | */ 63 | Set getValidMethods() { 64 | return methodHandlers.keySet(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/rest/SDKRestRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.rest; 11 | 12 | import org.opensearch.core.xcontent.NamedXContentRegistry; 13 | import org.opensearch.http.HttpChannel; 14 | import org.opensearch.http.HttpRequest; 15 | import org.opensearch.rest.RestRequest; 16 | 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | /** 21 | * This class helps to get instance of RestRequest 22 | */ 23 | public class SDKRestRequest extends RestRequest { 24 | /** 25 | * Instantiates this class with request's params 26 | * 27 | * @param xContentRegistry The request's content registry 28 | * @param params The request's params 29 | * @param path The request's path 30 | * @param headers The request's headers 31 | * @param httpRequest The request's httpRequest 32 | * @param httpChannel The request's http channel 33 | */ 34 | public SDKRestRequest( 35 | NamedXContentRegistry xContentRegistry, 36 | Map params, 37 | String path, 38 | Map> headers, 39 | HttpRequest httpRequest, 40 | HttpChannel httpChannel 41 | ) { 42 | super(xContentRegistry, params, path, headers, httpRequest, httpChannel); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/sample/helloworld/ExampleCustomSettingConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.sample.helloworld; 11 | 12 | import org.opensearch.common.settings.Setting; 13 | import org.opensearch.common.settings.Setting.Property; 14 | import org.opensearch.common.settings.Setting.RegexValidator; 15 | 16 | /** 17 | * {@link ExampleCustomSettingConfig} contains the custom settings value and their static declarations. 18 | */ 19 | public class ExampleCustomSettingConfig { 20 | private static final String FORBIDDEN_VALUE = "forbidden"; 21 | /** 22 | * A string setting. If the string setting matches the FORBIDDEN_REGEX string and parameter isMatching is true the validation will fail. 23 | */ 24 | public static final Setting VALIDATED_SETTING = Setting.simpleString( 25 | "custom.validate", 26 | new RegexValidator(FORBIDDEN_VALUE, false), 27 | Property.NodeScope, 28 | Property.Dynamic 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/sample/helloworld/HelloWorldExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.sample.helloworld; 11 | 12 | import org.opensearch.action.ActionRequest; 13 | import org.opensearch.common.settings.Setting; 14 | import org.opensearch.core.action.ActionResponse; 15 | import org.opensearch.sdk.BaseExtension; 16 | import org.opensearch.sdk.Extension; 17 | import org.opensearch.sdk.ExtensionSettings; 18 | import org.opensearch.sdk.ExtensionsRunner; 19 | import org.opensearch.sdk.api.ActionExtension; 20 | import org.opensearch.sdk.rest.ExtensionRestHandler; 21 | import org.opensearch.sdk.sample.helloworld.rest.RestHelloAction; 22 | import org.opensearch.sdk.sample.helloworld.rest.RestRemoteHelloAction; 23 | import org.opensearch.sdk.sample.helloworld.transport.SampleAction; 24 | import org.opensearch.sdk.sample.helloworld.transport.SampleTransportAction; 25 | 26 | import java.io.IOException; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | 30 | /** 31 | * Sample class to demonstrate how to use the OpenSearch SDK for Java to create 32 | * an extension. 33 | *

34 | * To create your own extension, implement the {@link #getExtensionSettings()} and {@link #getExtensionRestHandlers()} methods. 35 | * You may either create an {@link ExtensionSettings} object directly with the constructor, or read it from a YAML file on your class path. 36 | *

37 | * To execute, pass an instantiated object of this class to {@link ExtensionsRunner#run(Extension)}. 38 | */ 39 | public class HelloWorldExtension extends BaseExtension implements ActionExtension { 40 | 41 | /** 42 | * Optional classpath-relative path to a yml file containing extension settings. 43 | */ 44 | private static final String EXTENSION_SETTINGS_PATH = "/sample/helloworld-settings.yml"; 45 | 46 | /** 47 | * Instantiate this extension, initializing the connection settings and REST actions. 48 | * The Extension must provide its settings to the ExtensionsRunner. 49 | * These may be optionally read from a YAML file on the class path. 50 | * Or you may directly instantiate with the ExtensionSettings constructor. 51 | * 52 | */ 53 | public HelloWorldExtension() { 54 | super(EXTENSION_SETTINGS_PATH); 55 | } 56 | 57 | @Override 58 | public List getExtensionRestHandlers() { 59 | return List.of(new RestHelloAction(), new RestRemoteHelloAction(extensionsRunner())); 60 | } 61 | 62 | @Override 63 | public List> getActions() { 64 | return Arrays.asList(new ActionHandler<>(SampleAction.INSTANCE, SampleTransportAction.class)); 65 | } 66 | 67 | /** 68 | * A list of object that includes a single instance of Validator for Custom Setting 69 | */ 70 | public List> getSettings() { 71 | return Arrays.asList(ExampleCustomSettingConfig.VALIDATED_SETTING); 72 | } 73 | 74 | /** 75 | * Entry point to execute an extension. 76 | * 77 | * @param args Unused. 78 | * @throws IOException on a failure in the ExtensionsRunner 79 | */ 80 | public static void main(String[] args) throws IOException { 81 | // Execute this extension by instantiating it and passing to ExtensionsRunner 82 | ExtensionsRunner.run(new HelloWorldExtension()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/sample/helloworld/hello.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hello World", 3 | "uniqueId": "hello-world-java", 4 | "hostAddress": "127.0.0.1", 5 | "port": "4500", 6 | "version": "1.0", 7 | "opensearchVersion": "3.0.0", 8 | "minimumCompatibleVersion": "3.0.0", 9 | "dependencies": [ 10 | { 11 | "uniqueId": "test1", 12 | "version": "2.0.0" 13 | }, 14 | { 15 | "uniqueId": "test2", 16 | "version": "3.0.0" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/sample/helloworld/rest/RestRemoteHelloAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.sample.helloworld.rest; 11 | 12 | import org.opensearch.core.action.ActionListener; 13 | import org.opensearch.core.common.io.stream.StreamInput; 14 | import org.opensearch.extensions.ExtensionsManager; 15 | import org.opensearch.extensions.action.RemoteExtensionActionResponse; 16 | import org.opensearch.extensions.rest.ExtensionRestResponse; 17 | import org.opensearch.rest.NamedRoute; 18 | import org.opensearch.rest.RestRequest; 19 | import org.opensearch.rest.RestResponse; 20 | import org.opensearch.sdk.ExtensionsRunner; 21 | import org.opensearch.sdk.SDKClient; 22 | import org.opensearch.sdk.action.RemoteExtensionAction; 23 | import org.opensearch.sdk.action.RemoteExtensionActionRequest; 24 | import org.opensearch.sdk.rest.BaseExtensionRestHandler; 25 | import org.opensearch.sdk.sample.helloworld.transport.SampleAction; 26 | import org.opensearch.sdk.sample.helloworld.transport.SampleRequest; 27 | import org.opensearch.sdk.sample.helloworld.transport.SampleResponse; 28 | 29 | import java.util.Collections; 30 | import java.util.List; 31 | import java.util.concurrent.CompletableFuture; 32 | import java.util.concurrent.TimeUnit; 33 | import java.util.function.Function; 34 | 35 | import static org.opensearch.core.rest.RestStatus.OK; 36 | import static org.opensearch.rest.RestRequest.Method.GET; 37 | 38 | /** 39 | * Sample REST Handler demonstrating proxy actions to another extension 40 | */ 41 | public class RestRemoteHelloAction extends BaseExtensionRestHandler { 42 | 43 | private ExtensionsRunner extensionsRunner; 44 | 45 | /** 46 | * Instantiate this action 47 | * 48 | * @param runner The ExtensionsRunner instance 49 | */ 50 | public RestRemoteHelloAction(ExtensionsRunner runner) { 51 | this.extensionsRunner = runner; 52 | } 53 | 54 | @Override 55 | public List routes() { 56 | return List.of( 57 | 58 | new NamedRoute.Builder().method(GET) 59 | .path("/hello/{name}") 60 | .handler(handleRemoteGetRequest) 61 | .uniqueName(addRouteNamePrefix("remote_greet_with_name")) 62 | .legacyActionNames(Collections.emptySet()) 63 | .build() 64 | ); 65 | } 66 | 67 | private Function handleRemoteGetRequest = (request) -> { 68 | SDKClient client = extensionsRunner.getSdkClient(); 69 | 70 | String name = request.param("name"); 71 | // Create a request using class on remote 72 | // This class happens to be local for simplicity but is a class on the remote extension 73 | SampleRequest sampleRequest = new SampleRequest(name); 74 | 75 | // Serialize this request in a proxy action request 76 | // This requires that the remote extension has a corresponding transport action registered 77 | // This Action class happens to be local for simplicity but is a class on the remote extension 78 | RemoteExtensionActionRequest proxyActionRequest = new RemoteExtensionActionRequest(SampleAction.INSTANCE, sampleRequest); 79 | 80 | // TODO: We need async client.execute to hide these action listener details and return the future directly 81 | // https://github.com/opensearch-project/opensearch-sdk-java/issues/584 82 | CompletableFuture futureResponse = new CompletableFuture<>(); 83 | client.execute( 84 | RemoteExtensionAction.INSTANCE, 85 | proxyActionRequest, 86 | ActionListener.wrap(r -> futureResponse.complete(r), e -> futureResponse.completeExceptionally(e)) 87 | ); 88 | try { 89 | RemoteExtensionActionResponse response = futureResponse.orTimeout( 90 | ExtensionsManager.EXTENSION_REQUEST_WAIT_TIMEOUT, 91 | TimeUnit.SECONDS 92 | ).get(); 93 | if (!response.isSuccess()) { 94 | return new ExtensionRestResponse(request, OK, "Remote extension response failed: " + response.getResponseBytesAsString()); 95 | } 96 | // Parse out the expected response class from the bytes 97 | SampleResponse sampleResponse = new SampleResponse(StreamInput.wrap(response.getResponseBytes())); 98 | return new ExtensionRestResponse(request, OK, "Received greeting from remote extension: " + sampleResponse.getGreeting()); 99 | } catch (Exception e) { 100 | return exceptionalRequest(request, e); 101 | } 102 | }; 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/sample/helloworld/spec/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "title": "Hello World", 5 | "description": "This is a sample Hello World extension.", 6 | "license": { 7 | "name": "Apache 2.0", 8 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 9 | }, 10 | "version": "1.0.0-SNAPSHOT" 11 | }, 12 | "tags": [ 13 | { 14 | "name": "hello", 15 | "description": "Worldly Greetings" 16 | } 17 | ], 18 | "paths": { 19 | "/hello": { 20 | "get": { 21 | "tags": [ 22 | "hello" 23 | ], 24 | "summary": "Greet the world", 25 | "description": "Traditional greeting", 26 | "responses": { 27 | "200": { 28 | "description": "Successful operation", 29 | "content": { 30 | "text/plain; charset=utf-8": { 31 | "examples": { 32 | "Default Response": { 33 | "value": "Hello, World!" 34 | } 35 | } 36 | } 37 | } 38 | }, 39 | "400": { 40 | "description": "Syntax Error in URI" 41 | }, 42 | "404": { 43 | "description": "Improper REST action configuration" 44 | } 45 | } 46 | }, 47 | "post": { 48 | "tags": [ 49 | "hello" 50 | ], 51 | "summary": "Adds a descriptive world adjective to a list", 52 | "description": "Adds an adjective to a list from which a random element will be prepended to the world name", 53 | "operationId": "", 54 | "requestBody": { 55 | "description": "An adjective in plain text or JSON", 56 | "required": true, 57 | "content": { 58 | "application/json": { 59 | "schema": { 60 | "type": "object", 61 | "properties": { 62 | "adjective": { 63 | "type": "string", 64 | "example": "wonderful" 65 | } 66 | } 67 | } 68 | }, 69 | "text/plain": { 70 | "schema": { 71 | "type": "string", 72 | "example": "wonderful" 73 | } 74 | } 75 | } 76 | }, 77 | "responses": { 78 | "200": { 79 | "description": "Successful operation" 80 | }, 81 | "400": { 82 | "description": "Syntax Error in request" 83 | }, 84 | "404": { 85 | "description": "Improper REST action configuration" 86 | }, 87 | "406": { 88 | "description": "Content format not text or JSON" 89 | } 90 | } 91 | } 92 | }, 93 | "/hello/{name}": { 94 | "put": { 95 | "tags": [ 96 | "hello" 97 | ], 98 | "summary": "Rename the world", 99 | "description": "Update the world to a custom name", 100 | "parameters": [ 101 | { 102 | "name": "name", 103 | "in": "path", 104 | "description": "A new name for the world", 105 | "required": true, 106 | "schema": { 107 | "type": "string" 108 | } 109 | } 110 | ], 111 | "responses": { 112 | "200": { 113 | "description": "Successful operation", 114 | "content": { 115 | "text/plain; charset=utf-8": { 116 | "examples": { 117 | "Default Response": { 118 | "value": "Updated the world's name to OpenSearch" 119 | } 120 | } 121 | } 122 | } 123 | }, 124 | "400": { 125 | "description": "Syntax Error in URI" 126 | }, 127 | "404": { 128 | "description": "Improper REST action configuration" 129 | } 130 | } 131 | } 132 | }, 133 | "/goodbye": { 134 | "delete": { 135 | "tags": [ 136 | "hello" 137 | ], 138 | "summary": "Restores the world to default", 139 | "description": "Removes all adjectives and the custom world name", 140 | "operationId": "", 141 | "responses": { 142 | "200": { 143 | "description": "Successful operation" 144 | } 145 | } 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/sample/helloworld/spec/openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Hello World 4 | description: This is a sample Hello World extension. 5 | license: 6 | name: Apache 2.0 7 | url: http://www.apache.org/licenses/LICENSE-2.0.html 8 | version: 1.0.0-SNAPSHOT 9 | tags: 10 | - name: hello 11 | description: Worldly Greetings 12 | paths: 13 | /hello: 14 | get: 15 | tags: 16 | - hello 17 | summary: Greet the world 18 | description: Traditional greeting 19 | responses: 20 | '200': 21 | description: Successful operation 22 | content: 23 | text/plain; charset=utf-8: 24 | examples: 25 | Default Response: 26 | value: Hello, World! 27 | '400': 28 | description: Syntax Error in URI 29 | '404': 30 | description: Improper REST action configuration 31 | post: 32 | tags: 33 | - hello 34 | summary: Adds a descriptive world adjective to a list 35 | description: >- 36 | Adds an adjective to a list from which a random element will be 37 | prepended to the world name 38 | operationId: '' 39 | requestBody: 40 | description: An adjective in plain text or JSON 41 | required: true 42 | content: 43 | application/json: 44 | schema: 45 | type: object 46 | properties: 47 | adjective: 48 | type: string 49 | example: wonderful 50 | text/plain: 51 | schema: 52 | type: string 53 | example: wonderful 54 | responses: 55 | '200': 56 | description: Successful operation 57 | '400': 58 | description: Syntax Error in request 59 | '404': 60 | description: Improper REST action configuration 61 | '406': 62 | description: Content format not text or JSON 63 | delete: 64 | tags: 65 | - hello 66 | summary: Removes an adjective from the list 67 | description: >- 68 | Removes an adjective from the list from which a random element 69 | will be prepended to the world name 70 | operationId: '' 71 | responses: 72 | '200': 73 | description: Successful operation 74 | '304': 75 | description: Adjective not in the list, no action taken 76 | '400': 77 | description: Syntax Error in request 78 | '404': 79 | description: Improper REST action configuration 80 | '406': 81 | description: Content format not text or JSON 82 | /hello/{name}: 83 | put: 84 | tags: 85 | - hello 86 | summary: Rename the world 87 | description: Update the world to a custom name 88 | parameters: 89 | - name: name 90 | in: path 91 | description: A new name for the world 92 | required: true 93 | schema: 94 | type: string 95 | responses: 96 | '200': 97 | description: Successful operation 98 | content: 99 | text/plain; charset=utf-8: 100 | examples: 101 | Default Response: 102 | value: Updated the world's name to OpenSearch 103 | '400': 104 | description: Syntax Error in URI 105 | '404': 106 | description: Improper REST action configuration 107 | /goodbye: 108 | delete: 109 | tags: 110 | - hello 111 | summary: Restores the world to default 112 | description: >- 113 | Removes all adjectives and the custom world name 114 | operationId: '' 115 | responses: 116 | '200': 117 | description: Successful operation 118 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/sample/helloworld/transport/SampleAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.sample.helloworld.transport; 11 | 12 | import org.opensearch.action.ActionType; 13 | 14 | /** 15 | * A sample {@link ActionType} used as they key for the action map 16 | */ 17 | public class SampleAction extends ActionType { 18 | 19 | /** 20 | * The name to look up this action with 21 | */ 22 | public static final String NAME = "helloworld/sample"; 23 | /** 24 | * The singleton instance of this class 25 | */ 26 | public static final SampleAction INSTANCE = new SampleAction(); 27 | 28 | private SampleAction() { 29 | super(NAME, SampleResponse::new); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/sample/helloworld/transport/SampleRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.sample.helloworld.transport; 11 | 12 | import org.opensearch.action.ActionRequest; 13 | import org.opensearch.action.ActionRequestValidationException; 14 | import org.opensearch.core.common.io.stream.StreamInput; 15 | import org.opensearch.core.common.io.stream.StreamOutput; 16 | 17 | import java.io.IOException; 18 | 19 | /** 20 | * A sample request class to demonstrate extension actions 21 | */ 22 | public class SampleRequest extends ActionRequest { 23 | 24 | private final String name; 25 | 26 | /** 27 | * Instantiate this request 28 | * 29 | * @param name A name to pass to the action 30 | */ 31 | public SampleRequest(String name) { 32 | this.name = name; 33 | } 34 | 35 | /** 36 | * Instantiate this request from a byte stream 37 | * 38 | * @param in the byte stream 39 | * @throws IOException on failure reading the stream 40 | */ 41 | public SampleRequest(StreamInput in) throws IOException { 42 | this.name = in.readString(); 43 | } 44 | 45 | @Override 46 | public void writeTo(StreamOutput out) throws IOException { 47 | out.writeString(name); 48 | } 49 | 50 | @Override 51 | public ActionRequestValidationException validate() { 52 | return null; 53 | } 54 | 55 | public String getName() { 56 | return name; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/sample/helloworld/transport/SampleResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.sample.helloworld.transport; 11 | 12 | import org.opensearch.core.action.ActionResponse; 13 | import org.opensearch.core.common.io.stream.StreamInput; 14 | import org.opensearch.core.common.io.stream.StreamOutput; 15 | 16 | import java.io.IOException; 17 | 18 | /** 19 | * A sample response class to demonstrate extension actions 20 | */ 21 | public class SampleResponse extends ActionResponse { 22 | 23 | private final String greeting; 24 | 25 | /** 26 | * Instantiate this response 27 | * 28 | * @param greeting The greeting to return 29 | */ 30 | public SampleResponse(String greeting) { 31 | this.greeting = greeting; 32 | } 33 | 34 | /** 35 | * Instantiate this response from a byte stream 36 | * 37 | * @param in the byte stream 38 | * @throws IOException on failure reading the stream 39 | */ 40 | public SampleResponse(StreamInput in) throws IOException { 41 | this.greeting = in.readString(); 42 | } 43 | 44 | @Override 45 | public void writeTo(StreamOutput out) throws IOException { 46 | out.writeString(greeting); 47 | } 48 | 49 | public String getGreeting() { 50 | return greeting; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/sample/helloworld/transport/SampleTransportAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.sample.helloworld.transport; 11 | 12 | import com.google.inject.Inject; 13 | import org.opensearch.action.support.ActionFilters; 14 | import org.opensearch.action.support.TransportAction; 15 | import org.opensearch.core.action.ActionListener; 16 | import org.opensearch.tasks.Task; 17 | import org.opensearch.tasks.TaskManager; 18 | 19 | /** 20 | * A sample {@link TransportAction} used as they value for the action map 21 | */ 22 | public class SampleTransportAction extends TransportAction { 23 | 24 | /** 25 | * Instantiate this action 26 | * 27 | * @param actionName The action name 28 | * @param actionFilters Action filters 29 | * @param taskManager The task manager 30 | */ 31 | @Inject 32 | protected SampleTransportAction(String actionName, ActionFilters actionFilters, TaskManager taskManager) { 33 | super(actionName, actionFilters, taskManager); 34 | } 35 | 36 | @Override 37 | protected void doExecute(Task task, SampleRequest request, ActionListener listener) { 38 | // Fail if name is empty 39 | if (request.getName().isBlank()) { 40 | listener.onFailure(new IllegalArgumentException("The request name is blank.")); 41 | } else { 42 | listener.onResponse(new SampleResponse("Hello, " + request.getName())); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/ssl/SSLConnectionTestResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.ssl; 11 | 12 | /** 13 | * Return codes for SSLConnectionTestUtil.testConnection() 14 | */ 15 | public enum SSLConnectionTestResult { 16 | /** 17 | * OpenSearch Ping to the server failed. 18 | */ 19 | OPENSEARCH_PING_FAILED, 20 | /** 21 | * Server does not support SSL. 22 | */ 23 | SSL_NOT_AVAILABLE, 24 | /** 25 | * Server supports SSL. 26 | */ 27 | SSL_AVAILABLE 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/ssl/SslKeyStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.ssl; 11 | 12 | import javax.net.ssl.SSLEngine; 13 | import javax.net.ssl.SSLException; 14 | 15 | import java.security.cert.X509Certificate; 16 | 17 | /** 18 | * Interface for an SslKeyStore 19 | */ 20 | public interface SslKeyStore { 21 | 22 | /** 23 | * 24 | * @return SSLEngine 25 | * @throws SSLException 26 | */ 27 | public SSLEngine createServerTransportSSLEngine() throws SSLException; 28 | 29 | /** 30 | * 31 | * @param peerHost Peer Hostname 32 | * @param peerPort Peer Port 33 | * @return SSLEngine 34 | * @throws SSLException 35 | */ 36 | public SSLEngine createClientTransportSSLEngine(String peerHost, int peerPort) throws SSLException; 37 | 38 | /** 39 | * 40 | * @param cert Public Certificate 41 | * @return Returns the san from the certificate 42 | */ 43 | public String getSubjectAlternativeNames(X509Certificate cert); 44 | 45 | /** 46 | * Initialize SSL config 47 | */ 48 | public void initTransportSSLConfig(); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/ssl/util/CertFileProps.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.ssl.util; 11 | 12 | /** 13 | * File that contains properties of a certificate file 14 | */ 15 | public class CertFileProps { 16 | private final String pemCertFilePath; 17 | private final String pemKeyFilePath; 18 | private final String trustedCasFilePath; 19 | private final String pemKeyPassword; 20 | 21 | /** 22 | * 23 | * @param pemCertFilePath Path to the certificate file in the .pem format 24 | * @param pemKeyFilePath Path to the private key file in the .pem format 25 | * @param trustedCasFilePath Path to the trusted CA file 26 | * @param pemKeyPassword Password for the private key file 27 | */ 28 | public CertFileProps(String pemCertFilePath, String pemKeyFilePath, String trustedCasFilePath, String pemKeyPassword) { 29 | this.pemCertFilePath = pemCertFilePath; 30 | this.pemKeyFilePath = pemKeyFilePath; 31 | this.trustedCasFilePath = trustedCasFilePath; 32 | this.pemKeyPassword = pemKeyPassword; 33 | } 34 | 35 | /** 36 | * Returns the path to the certificate file in the .pem format. 37 | * 38 | * @return the path to the certificate file 39 | */ 40 | public String getPemCertFilePath() { 41 | return pemCertFilePath; 42 | } 43 | 44 | /** 45 | * Returns the path to the private key file in the .pem format. 46 | * 47 | * @return the path to the private key file 48 | */ 49 | public String getPemKeyFilePath() { 50 | return pemKeyFilePath; 51 | } 52 | 53 | /** 54 | * Returns the path to the trusted CA file. 55 | * 56 | * @return the path to the trusted CA file 57 | */ 58 | public String getTrustedCasFilePath() { 59 | return trustedCasFilePath; 60 | } 61 | 62 | /** 63 | * Returns the password for the private key file. 64 | * 65 | * @return the password for the private key file 66 | */ 67 | public String getPemKeyPassword() { 68 | return pemKeyPassword; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/ssl/util/CertFromFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.ssl.util; 11 | 12 | import java.io.File; 13 | import java.security.cert.X509Certificate; 14 | 15 | /** 16 | * Class with methods for reading in a certificate from a file 17 | */ 18 | public class CertFromFile { 19 | private final CertFileProps clientCertProps; 20 | private final CertFileProps serverCertProps; 21 | 22 | private final File serverPemCert; 23 | private final File serverPemKey; 24 | private final File serverTrustedCas; 25 | 26 | private final File clientPemCert; 27 | private final File clientPemKey; 28 | private final File clientTrustedCas; 29 | 30 | private final X509Certificate[] loadedCerts; 31 | 32 | /** 33 | * 34 | * @param clientCertProps Client certificate properties 35 | * @param serverCertProps Server certificate properties 36 | * @throws Exception 37 | */ 38 | public CertFromFile(CertFileProps clientCertProps, CertFileProps serverCertProps) throws Exception { 39 | this.serverCertProps = serverCertProps; 40 | this.serverPemCert = new File(serverCertProps.getPemCertFilePath()); 41 | this.serverPemKey = new File(serverCertProps.getPemKeyFilePath()); 42 | this.serverTrustedCas = nullOrFile(serverCertProps.getTrustedCasFilePath()); 43 | 44 | this.clientCertProps = clientCertProps; 45 | this.clientPemCert = new File(clientCertProps.getPemCertFilePath()); 46 | this.clientPemKey = new File(clientCertProps.getPemKeyFilePath()); 47 | this.clientTrustedCas = nullOrFile(clientCertProps.getTrustedCasFilePath()); 48 | 49 | loadedCerts = new X509Certificate[] { 50 | PemKeyReader.loadCertificateFromFile(clientCertProps.getPemCertFilePath()), 51 | PemKeyReader.loadCertificateFromFile(serverCertProps.getPemCertFilePath()) }; 52 | } 53 | 54 | /** 55 | * 56 | * @param certProps Certificate properties 57 | * @throws Exception 58 | */ 59 | public CertFromFile(CertFileProps certProps) throws Exception { 60 | this.serverCertProps = certProps; 61 | this.serverPemCert = new File(certProps.getPemCertFilePath()); 62 | this.serverPemKey = new File(certProps.getPemKeyFilePath()); 63 | this.serverTrustedCas = nullOrFile(certProps.getTrustedCasFilePath()); 64 | 65 | this.clientCertProps = serverCertProps; 66 | this.clientPemCert = serverPemCert; 67 | this.clientPemKey = serverPemKey; 68 | this.clientTrustedCas = serverTrustedCas; 69 | 70 | loadedCerts = new X509Certificate[] { PemKeyReader.loadCertificateFromFile(certProps.getPemCertFilePath()) }; 71 | } 72 | 73 | public X509Certificate[] getCerts() { 74 | return loadedCerts; 75 | } 76 | 77 | public File getServerPemKey() { 78 | return serverPemKey; 79 | } 80 | 81 | public File getServerPemCert() { 82 | return serverPemCert; 83 | } 84 | 85 | public File getServerTrustedCas() { 86 | return serverTrustedCas; 87 | } 88 | 89 | public String getServerPemKeyPassword() { 90 | return serverCertProps.getPemKeyPassword(); 91 | } 92 | 93 | public File getClientPemKey() { 94 | return clientPemKey; 95 | } 96 | 97 | public File getClientPemCert() { 98 | return clientPemCert; 99 | } 100 | 101 | public File getClientTrustedCas() { 102 | return clientTrustedCas; 103 | } 104 | 105 | public String getClientPemKeyPassword() { 106 | return clientCertProps.getPemKeyPassword(); 107 | } 108 | 109 | private File nullOrFile(String path) { 110 | if (path != null) { 111 | return new File(path); 112 | } 113 | return null; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/ssl/util/CertFromTruststore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.ssl.util; 11 | 12 | import org.opensearch.OpenSearchException; 13 | 14 | import java.io.IOException; 15 | import java.security.KeyStore; 16 | import java.security.KeyStoreException; 17 | import java.security.NoSuchAlgorithmException; 18 | import java.security.cert.CertificateException; 19 | import java.security.cert.X509Certificate; 20 | 21 | /** 22 | * Helper class with methods to read a certificate from a truststore 23 | */ 24 | public class CertFromTruststore { 25 | private final KeystoreProps keystoreProps; 26 | 27 | private final String serverTruststoreAlias; 28 | private final X509Certificate[] serverTrustedCerts; 29 | 30 | private final String clientTruststoreAlias; 31 | private final X509Certificate[] clientTrustedCerts; 32 | 33 | /** 34 | * Default constructor 35 | */ 36 | public CertFromTruststore() { 37 | keystoreProps = null; 38 | serverTruststoreAlias = null; 39 | serverTrustedCerts = null; 40 | clientTruststoreAlias = null; 41 | clientTrustedCerts = null; 42 | } 43 | 44 | /** 45 | * 46 | * @return An empty CertFromTruststore object 47 | */ 48 | public static CertFromTruststore Empty() { 49 | return new CertFromTruststore(); 50 | } 51 | 52 | /** 53 | * 54 | * @param keystoreProps Keystore Props 55 | * @param truststoreAlias Truststore Alias 56 | * @throws CertificateException 57 | * @throws NoSuchAlgorithmException 58 | * @throws KeyStoreException 59 | * @throws IOException 60 | */ 61 | public CertFromTruststore(KeystoreProps keystoreProps, String truststoreAlias) throws CertificateException, NoSuchAlgorithmException, 62 | KeyStoreException, IOException { 63 | this.keystoreProps = keystoreProps; 64 | final KeyStore ts = keystoreProps.loadKeystore(); 65 | 66 | serverTruststoreAlias = truststoreAlias; 67 | serverTrustedCerts = SSLCertificateHelper.exportRootCertificates(ts, truststoreAlias); 68 | 69 | clientTruststoreAlias = serverTruststoreAlias; 70 | clientTrustedCerts = serverTrustedCerts; 71 | 72 | validate(); 73 | } 74 | 75 | /** 76 | * 77 | * @param keystoreProps Keystore Props 78 | * @param serverTruststoreAlias Server Truststore Alias 79 | * @param clientTruststoreAlias Client Truststore Alias 80 | * @throws CertificateException 81 | * @throws NoSuchAlgorithmException 82 | * @throws KeyStoreException 83 | * @throws IOException 84 | */ 85 | public CertFromTruststore(KeystoreProps keystoreProps, String serverTruststoreAlias, String clientTruststoreAlias) 86 | throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { 87 | this.keystoreProps = keystoreProps; 88 | final KeyStore ts = this.keystoreProps.loadKeystore(); 89 | 90 | this.serverTruststoreAlias = serverTruststoreAlias; 91 | serverTrustedCerts = SSLCertificateHelper.exportRootCertificates(ts, this.serverTruststoreAlias); 92 | 93 | this.clientTruststoreAlias = clientTruststoreAlias; 94 | clientTrustedCerts = SSLCertificateHelper.exportRootCertificates(ts, this.clientTruststoreAlias); 95 | 96 | validate(); 97 | } 98 | 99 | private void validate() { 100 | if (serverTrustedCerts == null || serverTrustedCerts.length == 0) { 101 | throw new OpenSearchException("No truststore configured for server certs"); 102 | } 103 | 104 | if (clientTrustedCerts == null || clientTrustedCerts.length == 0) { 105 | throw new OpenSearchException("No truststore configured for client certs"); 106 | } 107 | } 108 | 109 | public X509Certificate[] getServerTrustedCerts() { 110 | return serverTrustedCerts; 111 | } 112 | 113 | public X509Certificate[] getClientTrustedCerts() { 114 | return clientTrustedCerts; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/ssl/util/ExceptionUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.ssl.util; 11 | 12 | /** 13 | * File with help methods for handling exceptions related to SSL 14 | */ 15 | public class ExceptionUtils { 16 | 17 | /** 18 | * 19 | * @param e Throwable 20 | * @param msg Message to find within message text 21 | * @return Returns the throwable if it contains the message text 22 | */ 23 | public static Throwable findMsg(final Throwable e, String msg) { 24 | 25 | if (e == null) { 26 | return null; 27 | } 28 | 29 | if (e.getMessage() != null && e.getMessage().contains(msg)) { 30 | return e; 31 | } 32 | 33 | final Throwable cause = e.getCause(); 34 | if (cause == null) { 35 | return null; 36 | } 37 | return findMsg(cause, msg); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/ssl/util/KeystoreProps.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.ssl.util; 11 | 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.IOException; 15 | import java.security.KeyStore; 16 | import java.security.KeyStoreException; 17 | import java.security.NoSuchAlgorithmException; 18 | import java.security.cert.CertificateException; 19 | 20 | /** 21 | * File that contains properties for a keystore 22 | */ 23 | public class KeystoreProps { 24 | private final String filePath; 25 | private final String type; 26 | private final char[] password; 27 | 28 | /** 29 | * 30 | * @param filePath Filepath to the keystore 31 | * @param type The type of keystore 32 | * @param password The password to the keystore 33 | */ 34 | public KeystoreProps(String filePath, String type, String password) { 35 | this.filePath = filePath; 36 | this.type = type; 37 | this.password = Utils.toCharArray(password); 38 | } 39 | 40 | public String getFilePath() { 41 | return filePath; 42 | } 43 | 44 | public String getType() { 45 | return type; 46 | } 47 | 48 | public char[] getPassword() { 49 | return password; 50 | } 51 | 52 | /** 53 | * 54 | * @return Returns the keystore give the keystore props 55 | * @throws KeyStoreException 56 | * @throws IOException 57 | * @throws CertificateException 58 | * @throws NoSuchAlgorithmException 59 | */ 60 | public KeyStore loadKeystore() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { 61 | final KeyStore ts = KeyStore.getInstance(type); 62 | ts.load(new FileInputStream(new File(filePath)), password); 63 | return ts; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/ssl/util/PemKeyReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.ssl.util; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | 15 | import java.io.FileInputStream; 16 | import java.security.cert.CertificateFactory; 17 | import java.security.cert.X509Certificate; 18 | 19 | /** 20 | * Class that reads a private key in .pem format 21 | */ 22 | public final class PemKeyReader { 23 | protected static final Logger log = LogManager.getLogger(PemKeyReader.class); 24 | 25 | /** 26 | * 27 | * @param file Path to certificate file 28 | * @return Returns an X509Certificate object 29 | * @throws Exception 30 | */ 31 | public static X509Certificate loadCertificateFromFile(String file) throws Exception { 32 | if (file == null) { 33 | return null; 34 | } 35 | 36 | CertificateFactory fact = CertificateFactory.getInstance("X.509"); 37 | try (FileInputStream is = new FileInputStream(file)) { 38 | return (X509Certificate) fact.generateCertificate(is); 39 | } 40 | } 41 | 42 | private PemKeyReader() {} 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/opensearch/sdk/ssl/util/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.ssl.util; 11 | 12 | /** 13 | * Convenience class that contains utility methods used for SSL 14 | */ 15 | public class Utils { 16 | 17 | /** 18 | * 19 | * @param first First nullable arg 20 | * @param more list of potentially nullable args 21 | * @return The first non-null arg 22 | * @param The type of arg 23 | */ 24 | public static T coalesce(T first, T... more) { 25 | if (first != null) { 26 | return first; 27 | } 28 | 29 | if (more == null || more.length == 0) { 30 | return null; 31 | } 32 | 33 | for (int i = 0; i < more.length; i++) { 34 | T t = more[i]; 35 | if (t != null) { 36 | return t; 37 | } 38 | } 39 | 40 | return null; 41 | } 42 | 43 | /** 44 | * 45 | * @param str String to convert 46 | * @return char[] representation of the input str 47 | */ 48 | public static char[] toCharArray(String str) { 49 | return (str == null || str.length() == 0) ? null : str.toCharArray(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/resources/log4j2-sdk.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/sample/helloworld-settings.yml: -------------------------------------------------------------------------------- 1 | extensionName: hello-world 2 | hostAddress: 127.0.0.1 3 | hostPort: 4500 4 | opensearchAddress: 127.0.0.1 5 | opensearchPort: 9200 6 | #ssl.transport.enabled: true 7 | #ssl.transport.pemcert_filepath: certs/extension-01.pem 8 | #ssl.transport.pemkey_filepath: certs/extension-01-key.pem 9 | #ssl.transport.pemtrustedcas_filepath: certs/root-ca.pem 10 | #ssl.transport.enforce_hostname_verification: false 11 | #path.home: 12 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/ExtensionsRunnerForTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import java.io.IOException; 13 | 14 | /** 15 | * An Extension Runner for testing using test settings. 16 | */ 17 | public class ExtensionsRunnerForTest extends ExtensionsRunner { 18 | 19 | public static final String NODE_NAME = "sample-extension"; 20 | public static final String NODE_HOST = "127.0.0.1"; 21 | public static final String NODE_PORT = "4532"; 22 | 23 | /** 24 | * Instantiates a new Extensions Runner using test settings. 25 | * 26 | * @throws IOException if the runner failed to read settings or API. 27 | */ 28 | public ExtensionsRunnerForTest() throws IOException { 29 | super(new BaseExtension(new ExtensionSettings(NODE_NAME, NODE_HOST, NODE_PORT, "127.0.0.1", "9200")) { 30 | }); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/TestBaseExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import org.opensearch.test.OpenSearchTestCase; 13 | import org.junit.jupiter.api.Test; 14 | 15 | import java.io.IOException; 16 | 17 | /* 18 | * Most of the code in BaseExtension is tested by HelloWorld extension tests. 19 | * This class tests code paths which are not tested. 20 | */ 21 | public class TestBaseExtension extends OpenSearchTestCase { 22 | 23 | private static final String UNPARSEABLE_EXTENSION_CONFIG = "/bad-extension.yml"; 24 | private static final String EXTENSION_DESCRIPTOR_FILEPATH = "src/test/resources" + UNPARSEABLE_EXTENSION_CONFIG; 25 | 26 | public static class TestExtension extends BaseExtension { 27 | 28 | public TestExtension(String path) { 29 | super(path); 30 | } 31 | } 32 | 33 | @Test 34 | public void testBaseExtensionWithNullPath() { 35 | // When a null path is passed, reading from YAML will fail. 36 | assertThrows(IllegalArgumentException.class, () -> { new TestExtension(null); }); 37 | } 38 | 39 | @Test 40 | public void testBaseExtensionWithBadConfig() { 41 | // When a bad extensions.yml config is passed, expect failing initialization. 42 | assertThrows(IllegalArgumentException.class, () -> { new TestExtension(EXTENSION_DESCRIPTOR_FILEPATH); }); 43 | } 44 | 45 | @Test 46 | public void testGetExtensionsRunner() throws IOException { 47 | ExtensionsRunnerForTest runner = new ExtensionsRunnerForTest(); 48 | assertEquals(runner, ((BaseExtension) runner.getExtension()).extensionsRunner()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/TestExtensionSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import org.opensearch.test.OpenSearchTestCase; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | 16 | import java.io.IOException; 17 | 18 | public class TestExtensionSettings extends OpenSearchTestCase { 19 | private static final String EXTENSION_DESCRIPTOR_CLASSPATH = "/extension.yml"; 20 | private ExtensionSettings extensionSettings; 21 | 22 | @Override 23 | @BeforeEach 24 | public void setUp() throws Exception { 25 | super.setUp(); 26 | extensionSettings = ExtensionSettings.readSettingsFromYaml(EXTENSION_DESCRIPTOR_CLASSPATH); 27 | } 28 | 29 | @Test 30 | public void testSettingsStrings() { 31 | assertEquals("sample-extension", extensionSettings.getExtensionName()); 32 | assertEquals("127.0.0.1", extensionSettings.getHostAddress()); 33 | assertEquals("4532", extensionSettings.getHostPort()); 34 | assertEquals("127.0.0.1", extensionSettings.getOpensearchAddress()); 35 | assertEquals("9200", extensionSettings.getOpensearchPort()); 36 | 37 | extensionSettings.setOpensearchAddress("localhost"); 38 | assertEquals("localhost", extensionSettings.getOpensearchAddress()); 39 | extensionSettings.setOpensearchPort("9300"); 40 | assertEquals("9300", extensionSettings.getOpensearchPort()); 41 | } 42 | 43 | @Test 44 | public void testConstructorWithArgs() { 45 | ExtensionSettings settings = new ExtensionSettings("foo", "bar", "baz", "os", "port"); 46 | assertEquals("foo", settings.getExtensionName()); 47 | assertEquals("bar", settings.getHostAddress()); 48 | assertEquals("baz", settings.getHostPort()); 49 | assertEquals("os", settings.getOpensearchAddress()); 50 | assertEquals("port", settings.getOpensearchPort()); 51 | } 52 | 53 | @Test 54 | public void testReadSettingsFromYaml() throws IOException { 55 | ExtensionSettings settings = ExtensionSettings.readSettingsFromYaml(EXTENSION_DESCRIPTOR_CLASSPATH); 56 | assertNotNull(settings); 57 | assertEquals(extensionSettings.getExtensionName(), settings.getExtensionName()); 58 | assertEquals(extensionSettings.getHostAddress(), settings.getHostAddress()); 59 | assertEquals(extensionSettings.getHostPort(), settings.getHostPort()); 60 | assertEquals(extensionSettings.getOpensearchAddress(), settings.getOpensearchAddress()); 61 | assertEquals(extensionSettings.getOpensearchPort(), settings.getOpensearchPort()); 62 | expectThrows(IOException.class, () -> ExtensionSettings.readSettingsFromYaml("this/path/does/not/exist")); 63 | expectThrows(IOException.class, () -> ExtensionSettings.readSettingsFromYaml(EXTENSION_DESCRIPTOR_CLASSPATH + "filedoesnotexist")); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/TestSDKNamedWriteableRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import org.opensearch.common.settings.Settings; 13 | import org.opensearch.core.common.io.stream.NamedWriteable; 14 | import org.opensearch.core.common.io.stream.NamedWriteableRegistry; 15 | import org.opensearch.core.common.io.stream.StreamInput; 16 | import org.opensearch.core.common.io.stream.StreamOutput; 17 | import org.opensearch.core.common.io.stream.Writeable; 18 | import org.opensearch.test.OpenSearchTestCase; 19 | import org.junit.jupiter.api.BeforeEach; 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.io.IOException; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | public class TestSDKNamedWriteableRegistry extends OpenSearchTestCase { 27 | private TestSDKNamedWriteableRegistry.ExampleRunnerForTest runner; 28 | 29 | private static class DummyNamedWriteable implements NamedWriteable { 30 | DummyNamedWriteable(StreamInput in) {} 31 | 32 | @Override 33 | public String getWriteableName() { 34 | return "test"; 35 | } 36 | 37 | @Override 38 | public void writeTo(StreamOutput out) throws IOException {} 39 | } 40 | 41 | private static class Example implements NamedWriteable { 42 | public static final String NAME = "Example"; 43 | public static final NamedWriteableRegistry.Entry WRITEABLE_REGISTRY = new NamedWriteableRegistry.Entry( 44 | NamedWriteable.class, 45 | NAME, 46 | DummyNamedWriteable::new 47 | ); 48 | 49 | private final String name; 50 | 51 | public Example(String name) { 52 | this.name = name; 53 | } 54 | 55 | @Override 56 | public String getWriteableName() { 57 | return name; 58 | } 59 | 60 | @Override 61 | public void writeTo(StreamOutput out) throws IOException {} 62 | } 63 | 64 | private static class ExampleRunnerForTest extends ExtensionsRunnerForTest { 65 | 66 | private List testNamedWriteables = Collections.emptyList(); 67 | private final SDKNamedWriteableRegistry sdkNamedWriteableRegistry = new SDKNamedWriteableRegistry(this); 68 | 69 | public ExampleRunnerForTest() throws IOException { 70 | super(); 71 | } 72 | 73 | @Override 74 | public Settings getEnvironmentSettings() { 75 | return Settings.EMPTY; 76 | } 77 | 78 | @Override 79 | public List getCustomNamedWriteables() { 80 | return this.testNamedWriteables; 81 | } 82 | 83 | @Override 84 | public void updateNamedWriteableRegistry() { 85 | this.testNamedWriteables = Collections.singletonList(Example.WRITEABLE_REGISTRY); 86 | this.sdkNamedWriteableRegistry.updateNamedWriteableRegistry(this); 87 | } 88 | } 89 | 90 | @Override 91 | @BeforeEach 92 | public void setUp() throws IOException { 93 | this.runner = new TestSDKNamedWriteableRegistry.ExampleRunnerForTest(); 94 | } 95 | 96 | @Test 97 | public void testDefaultNamedWriteableRegistry() throws IOException { 98 | NamedWriteableRegistry registry = runner.sdkNamedWriteableRegistry.getRegistry(); 99 | 100 | IllegalArgumentException ex = assertThrows( 101 | IllegalArgumentException.class, 102 | () -> registry.getReader(TestSDKNamedWriteableRegistry.Example.class, TestSDKNamedWriteableRegistry.Example.NAME) 103 | ); 104 | assertEquals("Unknown NamedWriteable category [" + TestSDKNamedWriteableRegistry.Example.class.getName() + "]", ex.getMessage()); 105 | } 106 | 107 | @Test 108 | public void testCustomNamedWriteableRegistry() throws IOException { 109 | // Update the runner before testing 110 | runner.updateNamedWriteableRegistry(); 111 | NamedWriteableRegistry registry = runner.sdkNamedWriteableRegistry.getRegistry(); 112 | 113 | Writeable.Reader reader = registry.getReader(NamedWriteable.class, Example.NAME); 114 | assertNotNull(reader.read(null)); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/TestSDKTransportService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import org.opensearch.Version; 13 | import org.opensearch.cluster.node.DiscoveryNode; 14 | import org.opensearch.common.settings.Settings; 15 | import org.opensearch.core.common.transport.TransportAddress; 16 | import org.opensearch.extensions.ExtensionsManager; 17 | import org.opensearch.extensions.action.RegisterTransportActionsRequest; 18 | import org.opensearch.sdk.action.RemoteExtensionAction; 19 | import org.opensearch.sdk.action.SDKActionModule; 20 | import org.opensearch.sdk.action.TestSDKActionModule; 21 | import org.opensearch.sdk.handlers.AcknowledgedResponseHandler; 22 | import org.opensearch.telemetry.tracing.noop.NoopTracer; 23 | import org.opensearch.test.OpenSearchTestCase; 24 | import org.opensearch.transport.Transport; 25 | import org.opensearch.transport.TransportService; 26 | import org.junit.jupiter.api.BeforeEach; 27 | import org.junit.jupiter.api.Test; 28 | 29 | import java.net.InetAddress; 30 | import java.util.Collections; 31 | 32 | import org.mockito.ArgumentCaptor; 33 | 34 | import static java.util.Collections.emptyMap; 35 | import static java.util.Collections.emptySet; 36 | import static org.mockito.ArgumentMatchers.any; 37 | import static org.mockito.ArgumentMatchers.eq; 38 | import static org.mockito.Mockito.mock; 39 | import static org.mockito.Mockito.spy; 40 | import static org.mockito.Mockito.times; 41 | import static org.mockito.Mockito.verify; 42 | 43 | public class TestSDKTransportService extends OpenSearchTestCase { 44 | 45 | private static final String TEST_UNIQUE_ID = "test-extension"; 46 | 47 | private TransportService transportService; 48 | private DiscoveryNode opensearchNode; 49 | private SDKActionModule sdkActionModule; 50 | private SDKTransportService sdkTransportService; 51 | 52 | @Override 53 | @BeforeEach 54 | public void setUp() throws Exception { 55 | super.setUp(); 56 | this.transportService = spy( 57 | new TransportService( 58 | Settings.EMPTY, 59 | mock(Transport.class), 60 | null, 61 | TransportService.NOOP_TRANSPORT_INTERCEPTOR, 62 | x -> null, 63 | null, 64 | Collections.emptySet(), 65 | NoopTracer.INSTANCE 66 | ) 67 | ); 68 | this.opensearchNode = new DiscoveryNode( 69 | "test_node", 70 | new TransportAddress(InetAddress.getByName("localhost"), 9876), 71 | emptyMap(), 72 | emptySet(), 73 | Version.CURRENT 74 | ); 75 | sdkActionModule = new SDKActionModule(new TestSDKActionModule.TestActionExtension()); 76 | 77 | sdkTransportService = new SDKTransportService(); 78 | sdkTransportService.setTransportService(transportService); 79 | sdkTransportService.setOpensearchNode(opensearchNode); 80 | sdkTransportService.setUniqueId(TEST_UNIQUE_ID); 81 | } 82 | 83 | @Test 84 | public void testRegisterTransportAction() { 85 | ArgumentCaptor registerTransportActionsRequestCaptor = ArgumentCaptor.forClass( 86 | RegisterTransportActionsRequest.class 87 | ); 88 | 89 | sdkTransportService.sendRegisterTransportActionsRequest(sdkActionModule.getActions()); 90 | verify(transportService, times(1)).sendRequest( 91 | any(), 92 | eq(ExtensionsManager.REQUEST_EXTENSION_REGISTER_TRANSPORT_ACTIONS), 93 | registerTransportActionsRequestCaptor.capture(), 94 | any(AcknowledgedResponseHandler.class) 95 | ); 96 | assertEquals(TEST_UNIQUE_ID, registerTransportActionsRequestCaptor.getValue().getUniqueId()); 97 | // Should contain the TestAction, but since it's mocked the name may change 98 | assertTrue( 99 | registerTransportActionsRequestCaptor.getValue() 100 | .getTransportActions() 101 | .stream() 102 | .anyMatch(s -> s.startsWith("org.opensearch.action.ActionType$MockitoMock$")) 103 | ); 104 | // Internal action should be filtered out 105 | assertFalse(registerTransportActionsRequestCaptor.getValue().getTransportActions().contains(RemoteExtensionAction.class.getName())); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/TestThreadPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk; 11 | 12 | import org.opensearch.common.settings.Settings; 13 | import org.opensearch.common.util.concurrent.OpenSearchExecutors; 14 | import org.opensearch.node.Node; 15 | import org.opensearch.threadpool.ExecutorBuilder; 16 | import org.opensearch.threadpool.ThreadPool; 17 | 18 | import java.util.concurrent.CountDownLatch; 19 | import java.util.concurrent.ExecutorService; 20 | import java.util.concurrent.ThreadFactory; 21 | import java.util.concurrent.ThreadPoolExecutor; 22 | 23 | public class TestThreadPool extends ThreadPool { 24 | private final CountDownLatch blockingLatch; 25 | private volatile boolean returnRejectingExecutor; 26 | private volatile ThreadPoolExecutor rejectingExecutor; 27 | 28 | public TestThreadPool(String name, ExecutorBuilder... customBuilders) { 29 | this(name, Settings.EMPTY, customBuilders); 30 | } 31 | 32 | public TestThreadPool(String name, Settings settings, ExecutorBuilder... customBuilders) { 33 | super(Settings.builder().put(Node.NODE_NAME_SETTING.getKey(), name).put(settings).build(), customBuilders); 34 | this.blockingLatch = new CountDownLatch(1); 35 | this.returnRejectingExecutor = false; 36 | } 37 | 38 | public ExecutorService executor(String name) { 39 | return (ExecutorService) (this.returnRejectingExecutor ? this.rejectingExecutor : super.executor(name)); 40 | } 41 | 42 | public void startForcingRejections() { 43 | if (this.rejectingExecutor == null) { 44 | this.createRejectingExecutor(); 45 | } 46 | 47 | this.returnRejectingExecutor = true; 48 | } 49 | 50 | public void stopForcingRejections() { 51 | this.returnRejectingExecutor = false; 52 | } 53 | 54 | public void shutdown() { 55 | this.blockingLatch.countDown(); 56 | if (this.rejectingExecutor != null) { 57 | this.rejectingExecutor.shutdown(); 58 | } 59 | 60 | super.shutdown(); 61 | } 62 | 63 | public void shutdownNow() { 64 | this.blockingLatch.countDown(); 65 | if (this.rejectingExecutor != null) { 66 | this.rejectingExecutor.shutdownNow(); 67 | } 68 | 69 | super.shutdownNow(); 70 | } 71 | 72 | private synchronized void createRejectingExecutor() { 73 | if (this.rejectingExecutor == null) { 74 | ThreadFactory factory = OpenSearchExecutors.daemonThreadFactory("reject_thread"); 75 | this.rejectingExecutor = OpenSearchExecutors.newFixed("rejecting", 1, 0, factory, this.getThreadContext()); 76 | CountDownLatch startedLatch = new CountDownLatch(1); 77 | this.rejectingExecutor.execute(() -> { 78 | try { 79 | startedLatch.countDown(); 80 | this.blockingLatch.await(); 81 | } catch (InterruptedException var3) { 82 | throw new RuntimeException(var3); 83 | } 84 | }); 85 | 86 | try { 87 | startedLatch.await(); 88 | } catch (InterruptedException var4) { 89 | throw new RuntimeException(var4); 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/action/TestSDKActionModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.action; 11 | 12 | import org.opensearch.action.ActionRequest; 13 | import org.opensearch.action.ActionType; 14 | import org.opensearch.core.action.ActionResponse; 15 | import org.opensearch.sdk.BaseExtension; 16 | import org.opensearch.sdk.ExtensionSettings; 17 | import org.opensearch.sdk.api.ActionExtension; 18 | import org.opensearch.test.OpenSearchTestCase; 19 | import org.junit.jupiter.api.BeforeEach; 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | import static org.mockito.Mockito.mock; 26 | import static org.mockito.Mockito.when; 27 | 28 | public class TestSDKActionModule extends OpenSearchTestCase { 29 | 30 | public static final String TEST_ACTION_NAME = "testAction"; 31 | 32 | private SDKActionModule sdkActionModule; 33 | 34 | public static class TestActionExtension extends BaseExtension implements ActionExtension { 35 | public TestActionExtension() { 36 | super(mock(ExtensionSettings.class)); 37 | } 38 | 39 | @Override 40 | public List> getActions() { 41 | @SuppressWarnings("unchecked") 42 | ActionType testAction = mock(ActionType.class); 43 | when(testAction.name()).thenReturn(TEST_ACTION_NAME); 44 | 45 | return Arrays.asList(new ActionHandler(testAction, null)); 46 | } 47 | } 48 | 49 | @Override 50 | @BeforeEach 51 | public void setUp() throws Exception { 52 | super.setUp(); 53 | sdkActionModule = new SDKActionModule(new TestActionExtension()); 54 | } 55 | 56 | @Test 57 | public void testGetActions() { 58 | assertEquals(2, sdkActionModule.getActions().size()); 59 | assertTrue(sdkActionModule.getActions().containsKey(RemoteExtensionAction.NAME)); 60 | assertTrue(sdkActionModule.getActions().containsKey(TEST_ACTION_NAME)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/rest/TestExtensionRestHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.rest; 11 | 12 | import org.opensearch.extensions.rest.ExtensionRestResponse; 13 | import org.opensearch.rest.RestRequest; 14 | import org.opensearch.test.OpenSearchTestCase; 15 | import org.junit.jupiter.api.Test; 16 | 17 | public class TestExtensionRestHandler extends OpenSearchTestCase { 18 | private static class NoOpExtensionRestHandler implements ExtensionRestHandler { 19 | 20 | @Override 21 | public ExtensionRestResponse handleRequest(RestRequest request) { 22 | return null; 23 | } 24 | } 25 | 26 | @Test 27 | public void testHandlerDefaultRoutes() { 28 | NoOpExtensionRestHandler handler = new NoOpExtensionRestHandler(); 29 | assertTrue(handler.routes().isEmpty()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/rest/TestNamedRouteHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.rest; 11 | 12 | import org.opensearch.core.rest.RestStatus; 13 | import org.opensearch.extensions.rest.ExtensionRestResponse; 14 | import org.opensearch.rest.NamedRoute; 15 | import org.opensearch.test.OpenSearchTestCase; 16 | 17 | import java.util.Collections; 18 | 19 | import static org.opensearch.rest.RestRequest.Method.GET; 20 | 21 | public class TestNamedRouteHandler extends OpenSearchTestCase { 22 | public void testUnnamedRouteHandler() { 23 | assertThrows( 24 | IllegalArgumentException.class, 25 | () -> new NamedRoute.Builder().method(GET) 26 | .path("/foo/bar") 27 | .handler(req -> new ExtensionRestResponse(req, RestStatus.OK, "content")) 28 | .uniqueName("") 29 | .legacyActionNames(Collections.emptySet()) 30 | .build() 31 | ); 32 | } 33 | 34 | public void testNamedRouteHandler() { 35 | NamedRoute nr = new NamedRoute.Builder().method(GET) 36 | .path("/foo/bar") 37 | .handler(req -> new ExtensionRestResponse(req, RestStatus.OK, "content")) 38 | .uniqueName("") 39 | .legacyActionNames(Collections.emptySet()) 40 | .build(); 41 | 42 | assertEquals("foo", nr.name()); 43 | assertNotNull(nr.handler()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/rest/TestSDKRestRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.rest; 11 | 12 | import org.opensearch.common.xcontent.XContentType; 13 | import org.opensearch.core.common.bytes.BytesArray; 14 | import org.opensearch.core.common.bytes.BytesReference; 15 | import org.opensearch.extensions.rest.ExtensionRestRequest; 16 | import org.opensearch.http.HttpRequest; 17 | import org.opensearch.rest.RestRequest; 18 | import org.opensearch.rest.RestRequest.Method; 19 | import org.opensearch.test.OpenSearchTestCase; 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.io.IOException; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | import static java.util.Map.entry; 28 | 29 | public class TestSDKRestRequest extends OpenSearchTestCase { 30 | @Test 31 | public void testSDKRestRequestMethods() throws IOException { 32 | RestRequest.Method expectedMethod = Method.GET; 33 | String expectedUri = "foobar?foo=bar&baz=42"; 34 | String expectedPath = "foo"; 35 | Map expectedParams = Map.ofEntries(entry("foo", "bar"), entry("baz", "42")); 36 | Map> expectedHeaders = Map.ofEntries( 37 | entry("Content-Type", Arrays.asList("application/json")), 38 | entry("foo", Arrays.asList("hello", "world")) 39 | ); 40 | XContentType exptectedXContentType = XContentType.JSON; 41 | BytesReference expectedContent = new BytesArray("{\"foo\":\"bar\"}"); 42 | 43 | RestRequest sdkRestRequest = createTestRestRequest( 44 | expectedMethod, 45 | expectedUri, 46 | expectedPath, 47 | expectedParams, 48 | expectedHeaders, 49 | exptectedXContentType, 50 | expectedContent, 51 | "", 52 | null 53 | ); 54 | assertEquals(expectedMethod, sdkRestRequest.method()); 55 | assertEquals(expectedUri, sdkRestRequest.uri()); 56 | assertEquals(expectedPath, sdkRestRequest.path()); 57 | assertEquals(expectedParams, sdkRestRequest.params()); 58 | assertEquals(expectedHeaders, sdkRestRequest.getHeaders()); 59 | assertEquals(exptectedXContentType, sdkRestRequest.getMediaType()); 60 | assertEquals(expectedContent, sdkRestRequest.content()); 61 | 62 | Map source = sdkRestRequest.contentParser().mapStrings(); 63 | assertEquals("bar", source.get("foo")); 64 | } 65 | 66 | public static RestRequest createTestRestRequest( 67 | final Method method, 68 | final String uri, 69 | final String path, 70 | final Map params, 71 | final Map> headers, 72 | final XContentType xContentType, 73 | final BytesReference content, 74 | final String principalIdentifier, 75 | final HttpRequest.HttpVersion httpVersion 76 | ) { 77 | // xContentType is not used. It will be parsed from headers 78 | ExtensionRestRequest request = new ExtensionRestRequest( 79 | method, 80 | uri, 81 | path, 82 | params, 83 | headers, 84 | xContentType, 85 | content, 86 | principalIdentifier, 87 | httpVersion 88 | ); 89 | return new SDKRestRequest(null, request.params(), request.path(), request.headers(), new SDKHttpRequest(request), null); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/sample/helloworld/rest/TestHelloWorldIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.sample.helloworld.rest; 11 | 12 | import org.apache.hc.core5.http.ContentType; 13 | import org.apache.hc.core5.http.HttpEntity; 14 | import org.apache.hc.core5.http.io.entity.StringEntity; 15 | import org.apache.logging.log4j.LogManager; 16 | import org.apache.logging.log4j.Logger; 17 | import org.opensearch.client.Request; 18 | import org.opensearch.client.Response; 19 | import org.opensearch.client.RestClient; 20 | import org.opensearch.core.rest.RestStatus; 21 | import org.opensearch.test.rest.OpenSearchRestTestCase; 22 | import org.junit.Test; 23 | 24 | import java.io.IOException; 25 | import java.util.Map; 26 | import java.util.concurrent.Callable; 27 | 28 | public class TestHelloWorldIT extends OpenSearchRestTestCase { 29 | private static final Logger logger = LogManager.getLogger(TestHelloWorldIT.class); 30 | 31 | public static final String EXTENSION_INIT_URI = "/_extensions/initialize/"; 32 | public static final String HELLO_WORLD_EXTENSION_BASE_URI = "/_extensions/_hello-world"; 33 | public static final String HELLO_BASE_URI = HELLO_WORLD_EXTENSION_BASE_URI + "/hello"; 34 | public static final String HELLO_NAME_URI = HELLO_BASE_URI + "/%s"; 35 | public static final String GOODBYE_URI = HELLO_WORLD_EXTENSION_BASE_URI + "/goodbye"; 36 | 37 | // TODO : Move extension initialization to setUp method prior to adding addtional tests 38 | 39 | @Test 40 | public void testInitializeExtension() throws Exception { 41 | // Send initialization request 42 | String helloWorldInitRequestBody = "{\"name\":\"hello-world\"" 43 | + ",\"uniqueId\":\"hello-world\"" 44 | + ",\"hostAddress\":\"127.0.0.1\"" 45 | + ",\"port\":\"4500\"" 46 | + ",\"version\":\"1.0\"" 47 | + ",\"opensearchVersion\":\"3.0.0\"" 48 | + ",\"minimumCompatibleVersion\":\"3.0.0\"}"; 49 | Response response = makeRequest(client(), "POST", EXTENSION_INIT_URI, null, toHttpEntity(helloWorldInitRequestBody)); 50 | 51 | assertEquals(RestStatus.ACCEPTED, restStatus(response)); 52 | Map responseMap = entityAsMap(response); 53 | String initializationResponse = (String) responseMap.get("success"); 54 | assertEquals("A request to initialize an extension has been sent.", initializationResponse); 55 | } 56 | 57 | /** 58 | * Retrieves the REST status from a response 59 | * 60 | * @param response the REST response 61 | * @return the RestStatus of the response 62 | * 63 | */ 64 | private static RestStatus restStatus(Response response) { 65 | return RestStatus.fromCode(response.getStatusLine().getStatusCode()); 66 | } 67 | 68 | /** 69 | * Converts a JSON string into an HttpEntity 70 | * 71 | * @param jsonString The JSON string 72 | * @return the HttpEntity 73 | * 74 | */ 75 | private static HttpEntity toHttpEntity(String jsonString) throws IOException { 76 | return new StringEntity(jsonString, ContentType.APPLICATION_JSON); 77 | } 78 | 79 | /** 80 | * Helper method to send a REST Request 81 | * 82 | * @param client The REST client 83 | * @param method The request method 84 | * @param endpoint The REST endpoint 85 | * @param params The request parameters 86 | * @param entity The request body 87 | * @return the REST response 88 | * 89 | */ 90 | private static Response makeRequest(RestClient client, String method, String endpoint, Map params, HttpEntity entity) 91 | throws IOException { 92 | 93 | // Create request 94 | Request request = new Request(method, endpoint); 95 | if (params != null) { 96 | params.entrySet().forEach(it -> request.addParameter(it.getKey(), it.getValue())); 97 | } 98 | if (entity != null) { 99 | request.setEntity(entity); 100 | } 101 | return client.performRequest(request); 102 | } 103 | 104 | /** 105 | * Invokes the callable method and asserts that the expected exception is thrown with the given exception message 106 | * 107 | * @param clazz The exception class 108 | * @param message The exception message 109 | * @param callable The callable request method 110 | * 111 | */ 112 | private static void assertFailWith(Class clazz, String message, Callable callable) throws Exception { 113 | try { 114 | callable.call(); 115 | } catch (Throwable e) { 116 | if (e.getClass() != clazz) { 117 | throw e; 118 | } 119 | if (message != null && !e.getMessage().contains(message)) { 120 | throw e; 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/org/opensearch/sdk/sample/helloworld/transport/TestSampleAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * The OpenSearch Contributors require contributions made to 6 | * this file be licensed under the Apache-2.0 license or a 7 | * compatible open source license. 8 | */ 9 | 10 | package org.opensearch.sdk.sample.helloworld.transport; 11 | 12 | import org.opensearch.action.support.ActionFilters; 13 | import org.opensearch.common.io.stream.BytesStreamOutput; 14 | import org.opensearch.core.action.ActionListener; 15 | import org.opensearch.core.common.bytes.BytesReference; 16 | import org.opensearch.core.common.io.stream.BytesStreamInput; 17 | import org.opensearch.test.OpenSearchTestCase; 18 | import org.junit.jupiter.api.Test; 19 | 20 | import java.io.IOException; 21 | import java.util.Collections; 22 | import java.util.concurrent.CompletableFuture; 23 | import java.util.concurrent.ExecutionException; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | public class TestSampleAction extends OpenSearchTestCase { 27 | 28 | @Test 29 | public void testSampleAction() { 30 | SampleAction action = SampleAction.INSTANCE; 31 | assertEquals(SampleAction.NAME, action.name()); 32 | } 33 | 34 | @Test 35 | public void testSampleRequest() throws IOException { 36 | String name = "test"; 37 | SampleRequest request = new SampleRequest(name); 38 | assertEquals(name, request.getName()); 39 | 40 | try (BytesStreamOutput out = new BytesStreamOutput()) { 41 | request.writeTo(out); 42 | out.flush(); 43 | try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { 44 | request = new SampleRequest(in); 45 | assertEquals(name, request.getName()); 46 | } 47 | } 48 | } 49 | 50 | @Test 51 | public void testSampleResponse() throws IOException { 52 | String greeting = "test"; 53 | SampleResponse response = new SampleResponse(greeting); 54 | assertEquals(greeting, response.getGreeting()); 55 | 56 | try (BytesStreamOutput out = new BytesStreamOutput()) { 57 | response.writeTo(out); 58 | out.flush(); 59 | try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { 60 | response = new SampleResponse(in); 61 | assertEquals(greeting, response.getGreeting()); 62 | } 63 | } 64 | } 65 | 66 | @Test 67 | public void testSampleTransportAction() throws Exception { 68 | String expectedName = "world"; 69 | String expectedGreeting = "Hello, " + expectedName; 70 | 71 | SampleRequest request = new SampleRequest(expectedName); 72 | CompletableFuture responseFuture = new CompletableFuture<>(); 73 | ActionListener listener = new ActionListener() { 74 | 75 | @Override 76 | public void onResponse(SampleResponse response) { 77 | responseFuture.complete(response); 78 | } 79 | 80 | @Override 81 | public void onFailure(Exception e) { 82 | responseFuture.completeExceptionally(e); 83 | } 84 | }; 85 | 86 | // test successful response 87 | SampleTransportAction action = new SampleTransportAction(null, new ActionFilters(Collections.emptySet()), null); 88 | action.doExecute(null, request, listener); 89 | SampleResponse response = responseFuture.get(1, TimeUnit.SECONDS); 90 | assertEquals(expectedGreeting, response.getGreeting()); 91 | } 92 | 93 | @Test 94 | public void testExceptionalSampleTransportAction() throws Exception { 95 | String expectedName = ""; 96 | 97 | SampleRequest request = new SampleRequest(expectedName); 98 | CompletableFuture responseFuture = new CompletableFuture<>(); 99 | ActionListener listener = new ActionListener() { 100 | 101 | @Override 102 | public void onResponse(SampleResponse response) { 103 | responseFuture.complete(response); 104 | } 105 | 106 | @Override 107 | public void onFailure(Exception e) { 108 | responseFuture.completeExceptionally(e); 109 | } 110 | }; 111 | 112 | SampleTransportAction action = new SampleTransportAction(null, new ActionFilters(Collections.emptySet()), null); 113 | action.doExecute(null, request, listener); 114 | ExecutionException ex = assertThrows(ExecutionException.class, () -> responseFuture.get(1, TimeUnit.SECONDS)); 115 | Throwable cause = ex.getCause(); 116 | assertTrue(cause instanceof IllegalArgumentException); 117 | assertEquals("The request name is blank.", cause.getMessage()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/test/resources/bad-extension.yml: -------------------------------------------------------------------------------- 1 | extensionName: bad-extension 2 | -------------------------------------------------------------------------------- /src/test/resources/extension.yml: -------------------------------------------------------------------------------- 1 | extensionName: sample-extension 2 | hostAddress: 127.0.0.1 3 | hostPort: 4532 4 | opensearchAddress: 127.0.0.1 5 | opensearchPort: 9200 6 | -------------------------------------------------------------------------------- /src/test/resources/hello-world-extension.yml: -------------------------------------------------------------------------------- 1 | name: hello-world 2 | uniqueId: opensearch-sdk-java-1 3 | hostAddress: '127.0.0.1' 4 | port: '4500' 5 | version: '1.0' 6 | opensearchVersion: '3.0.0' 7 | minimumCompatibleVersion: '3.0.0' 8 | --------------------------------------------------------------------------------