opensearch-testcontainer 0.3.1

Testcontainer for OpenSearch
Documentation
# OpenSearch Testcontainer

A Rust testcontainer implementation for OpenSearch, enabling easy integration testing with OpenSearch instances in Docker containers.

## Overview

This crate provides a `testcontainers` implementation for OpenSearch, allowing you to spin up OpenSearch instances programmatically for integration tests. It's built on top of the [testcontainers](https://github.com/testcontainers/testcontainers-rs) library.

## Features

- 🐳 **Docker Integration** - Seamlessly runs OpenSearch in Docker containers
-**Fast Setup** - Quick container startup for testing
- 🔧 **Configurable** - Customizable cluster settings and environment variables  
- 🔒 **Security Ready** - Built-in authentication support
- 🎯 **Ready Conditions** - Waits for OpenSearch to be fully initialized
- 📦 **Multiple Versions** - Support for different OpenSearch versions

## Installation

Add this to your `Cargo.toml`:

```toml
[dev-dependencies]
opensearch-testcontainer = "0.1.0"
tokio = { version = "1.0", features = ["full"] }
```

## Quick Start

### Basic Usage

```rust
use opensearch_testcontainer::OpenSearch;
use testcontainers::runners::AsyncRunner;

#[tokio::test]
async fn test_opensearch_integration() {
    // Start OpenSearch container with default settings
    let opensearch = OpenSearch::default().start().await.unwrap();
    
    // Get the host port
    let host_port = opensearch.get_host_port_ipv4(9200).await.unwrap();
    let base_url = format!("https://127.0.0.1:{}", host_port);
    
    // Your test code here
    let client = reqwest::Client::builder()
        .danger_accept_invalid_certs(true)
        .build()
        .unwrap();
    
    let response = client
        .get(&base_url)
        .basic_auth("admin", Some("?qbr9:6Y7nk6"))
        .send()
        .await
        .unwrap();
    
    assert!(response.status().is_success());
}
```

### Custom Configuration

```rust
use opensearch_testcontainer::OpenSearch;
use testcontainers::runners::AsyncRunner;

#[tokio::test]
async fn test_custom_opensearch() {
    let opensearch = OpenSearch::default()
        .with_tag("2.11.0")  // Use specific version
        .with_cluster_name("test-cluster")
        .with_env_var("OPENSEARCH_JAVA_OPTS", "-Xms1g -Xmx1g")
        .start()
        .await
        .unwrap();
    
    // Container is ready for use
    let host_port = opensearch.get_host_port_ipv4(9200).await.unwrap();
    // ... your test code
}
```

## Configuration Options

### Builder Methods

The `OpenSearch` struct provides several builder methods for customization:

| Method | Description | Example |
|--------|-------------|---------|
| `with_name(name)` | Set custom Docker image name | `.with_name("my-registry/opensearch")` |
| `with_tag(tag)` | Set OpenSearch version | `.with_tag("2.11.0")` |
| `with_cluster_name(name)` | Set cluster name | `.with_cluster_name("test-cluster")` |
| `with_env_var(key, value)` | Add environment variable | `.with_env_var("OPENSEARCH_JAVA_OPTS", "-Xms512m")` |

### Default Configuration

The default OpenSearch container comes preconfigured with:

- **Image**: `opensearchproject/opensearch:3.1.0`
- **Discovery**: Single-node cluster
- **Authentication**: Username `admin`, password `?qbr9:6Y7nk6`
- **Ports**: 9200 (HTTP), 9300 (Transport), 9600 (Performance Analyzer)
- **Ready Conditions**: Waits for cluster state to be GREEN and ML configuration

### Authentication

```rust
let opensearch = OpenSearch::default();

// Get credentials
let username = opensearch.username(); // "admin"
let password = opensearch.password(); // "?qbr9:6Y7nk6"

// Use in HTTP client
let client = reqwest::Client::builder()
    .danger_accept_invalid_certs(true)
    .build()
    .unwrap();

let response = client
    .get("https://127.0.0.1:9200")
    .basic_auth(username, Some(password))
    .send()
    .await
    .unwrap();
```

## Common Patterns

### Testing with opensearch-client

```rust
use opensearch_client::{ClientBuilder, OpenSearch as OSClient};
use opensearch_testcontainer::OpenSearch;
use testcontainers::runners::AsyncRunner;

#[tokio::test]
async fn test_with_opensearch_client() {
    // Start container
    let container = OpenSearch::default().start().await.unwrap();
    let host_port = container.get_host_port_ipv4(9200).await.unwrap();
    
    // Create client
    let client = ClientBuilder::new()
        .host(&format!("https://127.0.0.1:{}", host_port))
        .basic_auth("admin", "?qbr9:6Y7nk6")
        .danger_accept_invalid_certs(true)
        .build()
        .unwrap();
    
    // Test operations
    let info = client.info().await.unwrap();
    assert_eq!(info.version.number, "3.1.0");
}
```

### Multiple Test Containers

```rust
use opensearch_testcontainer::OpenSearch;
use testcontainers::runners::AsyncRunner;

#[tokio::test]
async fn test_multiple_clusters() {
    // Start multiple containers for testing cluster interactions
    let cluster1 = OpenSearch::default()
        .with_cluster_name("cluster1")
        .with_env_var("node.name", "node1")
        .start()
        .await
        .unwrap();
    
    let cluster2 = OpenSearch::default()
        .with_cluster_name("cluster2")
        .with_env_var("node.name", "node2")
        .start()
        .await
        .unwrap();
    
    // Test cross-cluster scenarios
    // ...
}
```

### Resource Constraints

```rust
use opensearch_testcontainer::OpenSearch;
use testcontainers::runners::AsyncRunner;

#[tokio::test]
async fn test_resource_limited() {
    let opensearch = OpenSearch::default()
        .with_env_var("OPENSEARCH_JAVA_OPTS", "-Xms256m -Xmx256m")
        .with_env_var("bootstrap.memory_lock", "false")
        .start()
        .await
        .unwrap();
    
    // Test with limited resources
    // ...
}
```

## Advanced Usage

### Custom Wait Conditions

The container waits for specific log messages to ensure OpenSearch is ready:

- `[YELLOW] to [GREEN]` - Cluster state becomes healthy
- `ML configuration initialized successfully` - Machine learning is ready

### Environment Variables

Common OpenSearch environment variables you might want to set:

```rust
let opensearch = OpenSearch::default()
    .with_env_var("cluster.name", "test-cluster")
    .with_env_var("node.name", "test-node")
    .with_env_var("discovery.type", "single-node")
    .with_env_var("bootstrap.memory_lock", "true")
    .with_env_var("OPENSEARCH_JAVA_OPTS", "-Xms512m -Xmx512m")
    .with_env_var("DISABLE_SECURITY_PLUGIN", "true")  // Disable security
    .start()
    .await
    .unwrap();
```

### Version Testing

Test against multiple OpenSearch versions:

```rust
#[tokio::test]
async fn test_version_compatibility() {
    let versions = ["2.11.0", "2.12.0", "3.0.0", "3.1.0"];
    
    for version in versions {
        let container = OpenSearch::default()
            .with_tag(version)
            .start()
            .await
            .unwrap();
        
        // Test your code against this version
        let port = container.get_host_port_ipv4(9200).await.unwrap();
        // ... version-specific tests
    }
}
```

## Troubleshooting

### Common Issues

**Container fails to start**
```rust
// Check Docker is running and you have the opensearch image
// Pull manually if needed: docker pull opensearchproject/opensearch:3.1.0
```

**Connection refused errors**
```rust
// Ensure you're connecting to the correct port
let host_port = container.get_host_port_ipv4(9200).await.unwrap();
let url = format!("https://127.0.0.1:{}", host_port);  // Note: HTTPS, not HTTP
```

**SSL/TLS errors**
```rust
// OpenSearch uses self-signed certificates by default
let client = reqwest::Client::builder()
    .danger_accept_invalid_certs(true)  // Required for testing
    .build()
    .unwrap();
```

**Authentication errors**
```rust
// Use the correct default credentials
.basic_auth("admin", Some("?qbr9:6Y7nk6"))
```

### Debug Logging

Enable logging to see container startup:

```rust
// In your test
env_logger::init();

// Or set environment variable
// RUST_LOG=debug cargo test
```

## Performance Tips

1. **Reuse containers** - For multiple tests, consider using the same container
2. **Resource limits** - Set appropriate JVM heap sizes for faster startup
3. **Disable unnecessary features** - Turn off security or ML if not needed
4. **Parallel tests** - Use `#[tokio::test(flavor = "multi_thread")]` for parallel execution

## Examples

See the `tests` directory for more comprehensive examples:

- Basic connectivity testing
- Index creation and document operations  
- Search and aggregation testing
- Bulk operations testing
- Authentication scenarios

## Requirements

- Docker must be installed and running
- Rust 1.70+ (async/await support)
- Internet connection (to pull OpenSearch Docker images)

## Related Crates

- [`testcontainers`]https://crates.io/crates/testcontainers - Core testcontainers functionality
- [`opensearch-client`]https://crates.io/crates/opensearch-client - OpenSearch Rust client
- [`tokio`]https://crates.io/crates/tokio - Async runtime

## License

This project is licensed under the Apache 2.0 License - see the [LICENSE](../LICENSE) file for details.

## Contributing

Contributions are welcome! Please see the [Contributing Guide](../docs/contributing.md) for details on how to contribute to this project.