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 knn query returns bad neighbors. Is the bug in the k-NN plugin's query builder, in core's search fan-out, or in the underlying Lucene/faiss/nmslib engine?
  • A request 403s. Is that the security plugin'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

RepoLanguageRoleWhat it owns
opensearch-project/OpenSearchJavaCore engineREST/transport/action layer, search & indexing, cluster state, the plugin SPI
opensearch-project/securityJavaPluginAuthN/AuthZ, TLS, action filters, field/document-level security
opensearch-project/k-NNJava + C++ (JNI)Pluginknn_vector field type, knn query, faiss/nmslib/Lucene engines
opensearch-project/sqlJava + KotlinPluginSQL and PPL query languages over _search
opensearch-project/alertingKotlinPluginMonitors, triggers, notifications
opensearch-project/anomaly-detectionJavaPluginReal-time and historical anomaly detectors
opensearch-project/ml-commonsJavaPluginModel serving, connectors, agents
opensearch-project/index-managementKotlinPluginISM policies, rollups, transforms
opensearch-project/observabilityKotlinPluginSaved objects for traces/metrics/logs
opensearch-project/OpenSearch-DashboardsTypeScriptUIVisualizations, the search source builder, server-side proxy to core
opensearch-project/opensearch-javaJavaClientTyped Java client
opensearch-project/opensearch-jsTypeScriptClientNode client; what Dashboards' server uses
apache/luceneJavaLibraryInverted 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:opensearch artifacts, 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

LabFocusThe boundary it teaches
P1: Dashboards Query → OpenSearch SearchThe canonical cross-repo traceDashboards (TS) builds DSL → opensearch-js → core RestSearchAction/TransportSearchAction
P2: Inspecting a Plugin's Extension PointsMapping plugin code to the core SPIk-NN/securityMapperPlugin/SearchPlugin/ActionPlugin/EnginePlugin
P3: Debugging a Failed Cross-Plugin RequestReading a failure across two stackscore ↔ plugin transport/action boundary; TRACE logging on both sides
P4: Bug AttributionDeciding which repo owns a symptomcore vs. plugin vs. Dashboards vs. Lucene — the decision flowchart
P5: Reproducing Integration BugsMinimal deterministic cross-repo reprocurl/REST-YAML on a distro, or OpenSearchIntegTestCase with nodePlugins()
P6: Writing DiagnosticsThe highest-leverage cross-repo contributionActionable 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:

InterfaceMethod you'll grep forReal plugin that uses it
MapperPlugingetMappers()k-NN registers knn_vector
SearchPlugingetQueries(), getAggregations()k-NN registers the knn query
EnginePlugingetEngineFactory()k-NN for the native vector engine path
ActionPlugingetActions(), getRestHandlers(), getActionFilters()security injects an action filter
NetworkPlugingetTransports(), 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:

  1. Within 10 minutes, name which repo owns the failure (core / a named plugin / Dashboards / Lucene) and say why.
  2. Within 30 minutes, locate the relevant code on both sides of the boundary.
  3. Within 1 hour, capture the actual _search request (via profile=true, slow log, or browser devtools) and the failing component's log.
  4. Within a day, produce a minimal curl/REST-YAML or OpenSearchIntegTestCase reproducer.
  5. 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.