Plugins, Extensions & Cross-Repo Labs
OpenSearch is not one repository. The engine in opensearch-project/OpenSearch
is a deliberately small core; the product most people run is that core plus a
constellation of large plugins (security, k-NN, SQL/PPL, alerting,
anomaly-detection, ml-commons, index-management, observability) plus the
OpenSearch Dashboards UI plus the language clients plus Apache Lucene
underneath. Almost every interesting production bug, and a large fraction of the
open issues you will triage, lives at a boundary between these repos — not
inside any one of them.
This section builds the single most valuable cross-repo skill: given a symptom,
attribute it to the right repository and produce a minimal reproducer there.
It is the OpenSearch analog of the Hive-on-Tez labs in the sibling Tez
curriculum: there, the boundary was Hive's TezTask handing a DAG to Tez; here,
the canonical boundary is a Dashboards visualization handing a _search request
to core, and the second boundary is a plugin implementing a core extension point.
Filing a vector-search bug on core when it belongs in k-NN, or a rendering bug
on core when Dashboards built the wrong DSL, wastes the maintainer's time and
yours. These six labs make the attribution mechanical.
This builds directly on the Plugin Architecture deep dive
(read it first — these labs assume you know how PluginsService loads a plugin
and gives it an isolated classloader) and connects to
Search Execution,
Serialization and BWC, and
Stage 8 — Plugin and Extension Compatibility.
Why cross-repo skill matters
A single-repo contributor can read server/ end to end and still be useless on
the issues users actually file, because those issues span repos:
- A Dashboards dashboard renders the wrong number. Is the bug in the TypeScript that built the aggregation, in core's reduce, or in Lucene's doc-values?
- A
knnquery returns bad neighbors. Is the bug in thek-NNplugin's query builder, in core's search fan-out, or in the underlying Lucene/faiss/nmslibengine? - A request 403s. Is that the
securityplugin's action filter doing its job, a misconfigured role, or a core action that the filter mis-classifies?
None of these can be answered from one repository. The contributor who can say
"this is a k-NN bug, here is the 12-line curl repro, and here is why it is
not core" is worth ten who can only say "search is broken."
The repositories involved
| Repo | Language | Role | What it owns |
|---|---|---|---|
opensearch-project/OpenSearch | Java | Core engine | REST/transport/action layer, search & indexing, cluster state, the plugin SPI |
opensearch-project/security | Java | Plugin | AuthN/AuthZ, TLS, action filters, field/document-level security |
opensearch-project/k-NN | Java + C++ (JNI) | Plugin | knn_vector field type, knn query, faiss/nmslib/Lucene engines |
opensearch-project/sql | Java + Kotlin | Plugin | SQL and PPL query languages over _search |
opensearch-project/alerting | Kotlin | Plugin | Monitors, triggers, notifications |
opensearch-project/anomaly-detection | Java | Plugin | Real-time and historical anomaly detectors |
opensearch-project/ml-commons | Java | Plugin | Model serving, connectors, agents |
opensearch-project/index-management | Kotlin | Plugin | ISM policies, rollups, transforms |
opensearch-project/observability | Kotlin | Plugin | Saved objects for traces/metrics/logs |
opensearch-project/OpenSearch-Dashboards | TypeScript | UI | Visualizations, the search source builder, server-side proxy to core |
opensearch-project/opensearch-java | Java | Client | Typed Java client |
opensearch-project/opensearch-js | TypeScript | Client | Node client; what Dashboards' server uses |
apache/lucene | Java | Library | Inverted index, codecs, doc-values, Query/Scorer, HNSW vectors |
Note: The big functional plugins live in separate repos precisely because they are large subsystems with their own teams and release cadences. They build against the published
org.opensearch:opensearchartifacts, not against a checkout of core. That decoupling is exactly what makes cross-repo attribution a distinct skill — a core change can break a plugin with zero failing tests in the core repo. See Stage 8.
The labs
| Lab | Focus | The boundary it teaches |
|---|---|---|
| P1: Dashboards Query → OpenSearch Search | The canonical cross-repo trace | Dashboards (TS) builds DSL → opensearch-js → core RestSearchAction/TransportSearchAction |
| P2: Inspecting a Plugin's Extension Points | Mapping plugin code to the core SPI | k-NN/security ↔ MapperPlugin/SearchPlugin/ActionPlugin/EnginePlugin |
| P3: Debugging a Failed Cross-Plugin Request | Reading a failure across two stacks | core ↔ plugin transport/action boundary; TRACE logging on both sides |
| P4: Bug Attribution | Deciding which repo owns a symptom | core vs. plugin vs. Dashboards vs. Lucene — the decision flowchart |
| P5: Reproducing Integration Bugs | Minimal deterministic cross-repo repro | curl/REST-YAML on a distro, or OpenSearchIntegTestCase with nodePlugins() |
| P6: Writing Diagnostics | The highest-leverage cross-repo contribution | Actionable boundary errors, _cat/plugins, Profile/_tasks, version-mismatch messages |
The extension model in one screen
There are two ways code extends OpenSearch. You will work almost entirely with the first.
Plugins (the mainstream model). A plugin extends
org.opensearch.plugins.Plugin and implements one or more extension
interfaces, each of which the engine queries at startup. The relevant ones for
these labs:
| Interface | Method you'll grep for | Real plugin that uses it |
|---|---|---|
MapperPlugin | getMappers() | k-NN registers knn_vector |
SearchPlugin | getQueries(), getAggregations() | k-NN registers the knn query |
EnginePlugin | getEngineFactory() | k-NN for the native vector engine path |
ActionPlugin | getActions(), getRestHandlers(), getActionFilters() | security injects an action filter |
NetworkPlugin | getTransports(), getTransportInterceptors() | security wraps transport with TLS/auth |
Plugins run in-process with full access to internal APIs — powerful but
version-coupled. This is why a plugin built against opensearch 3.1.0 will not
load on a 3.0.0 node (see P5 and
P6).
Extensions (forward-looking). OpenSearch 2.10+ ships an experimental
Extensions SDK (opensearch-sdk-java) that runs extensions out of process
over a defined protocol, for fault isolation and looser version coupling. Treat
it as the strategic direction for some extension categories; in-process plugins
remain the fully-supported mainstream model, and these labs are entirely about
them.
How a plugin builds against core
This mechanic underlies every lab that touches an out-of-repo plugin
(P2, P3,
P5). A plugin's build.gradle applies the
opensearch.opensearchplugin Gradle plugin and depends on the core artifacts at
a pinned version:
// build.gradle of an out-of-repo plugin (e.g. k-NN), simplified
buildscript {
ext { opensearch_version = "3.1.0-SNAPSHOT" }
dependencies {
classpath "org.opensearch.gradle:build-tools:${opensearch_version}"
}
}
apply plugin: 'opensearch.opensearchplugin'
opensearchplugin {
name 'opensearch-knn'
classname 'org.opensearch.knn.plugin.KNNPlugin'
}
dependencies {
compileOnly "org.opensearch:opensearch:${opensearch_version}"
}
To test a local, unreleased core change against a plugin, you publish core to your local Maven cache and point the plugin at the same version:
# In your core checkout — publish the snapshot artifacts locally
cd ~/OpenSearch
./gradlew publishToMavenLocal # writes to ~/.m2/repository/org/opensearch/...
# In the plugin checkout — build against that exact snapshot
cd ~/k-NN
./gradlew assemble -Dopensearch.version=3.1.0-SNAPSHOT
ls build/distributions/ # opensearch-knn-3.1.0-SNAPSHOT.zip
The plugin produces a zip carrying its jars and a
plugin-descriptor.properties. You install it into a runnable distro with
bin/opensearch-plugin install, and the opensearch.version field in the
descriptor must match the node version exactly. That version lock is the source
of a whole class of cross-repo failures you'll learn to recognize.
# In a localDistro built from the same core version
bin/opensearch-plugin install file:///abs/path/opensearch-knn-3.1.0-SNAPSHOT.zip
bin/opensearch-plugin list
curl -s localhost:9200/_cat/plugins?v
Reading order
P1 and P2 are foundational — do them in order. P1 gives you the end-to-end Dashboards→core trace; P2 gives you the plugin↔SPI mapping. P3 and P4 are the debugging-and-attribution pair: P3 teaches you to read a cross-stack failure, P4 teaches you to attribute it. P5 and P6 are the contributor-facing skills — turning an attributed bug into a minimal repro (P5) and into a diagnostics patch that helps the next person (P6).
If you arrived here from the capstone or from Stage 8, P4 and P5 are the most directly relevant.
Validation for the section
You have absorbed this section when, given a freshly-failing query in a production OpenSearch + Dashboards + plugin deployment, you can:
- Within 10 minutes, name which repo owns the failure (core / a named plugin / Dashboards / Lucene) and say why.
- Within 30 minutes, locate the relevant code on both sides of the boundary.
- Within 1 hour, capture the actual
_searchrequest (viaprofile=true, slow log, or browser devtools) and the failing component's log. - Within a day, produce a minimal
curl/REST-YAML orOpenSearchIntegTestCasereproducer. - File the issue in the right repo with a repro that does not drag in the other repos unnecessarily.
That routine, executed crisply, is what gets cross-repo OpenSearch issues resolved. The labs build the muscle.