Skip to content

Commit 7b1ae6f

Browse files
committed
chore: Align repo standards
1 parent 7e95140 commit 7b1ae6f

File tree

11 files changed

+350
-239
lines changed

11 files changed

+350
-239
lines changed

docs/modules/qdrant.md

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
# Qdrant
22

3-
[Qdrant](https://qdrant.tech/) is an open source vector database designed for scalable and efficient similarity search
4-
and nearest neighbor retrieval. It provides both RESTful and gRPC APIs, making it easy to integrate with various
5-
applications, including search, recommendation, AI, and machine learning systems.
3+
[Qdrant](https://qdrant.tech/) is an open source vector database designed for scalable and efficient similarity search and nearest neighbor retrieval. It provides both RESTful and gRPC APIs, making it easy to integrate with various applications, including search, recommendation, AI, and machine learning systems.
64

75
Add the following dependency to your project file:
86

97
```shell title="NuGet"
108
dotnet add package Testcontainers.Qdrant
119
```
1210

13-
You can start a Qdrant container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method.
11+
You can start an Qdrant container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method.
1412

1513
=== "Usage Example"
1614
```csharp
17-
--8<-- "tests/Testcontainers.Qdrant.Tests/QdrantContainerTest.cs:UseQdrantContainer"
15+
--8<-- "tests/Testcontainers.Qdrant.Tests/QdrantDefaultContainerTest.cs:UseQdrantContainer"
1816
```
1917

2018
The test example uses the following NuGet dependencies:
@@ -28,23 +26,45 @@ To execute the tests, use the command `dotnet test` from a terminal.
2826

2927
--8<-- "docs/modules/_call_out_test_projects.txt"
3028

31-
## A Note To Developers
29+
## Configure API key
3230

33-
The Testcontainers module creates a container that listens to requests over **HTTP**. The official Qdrant client uses the gRPC APIs to communicate
34-
with Qdrant. **.NET Core** and **.NET** support the above example with no additional configuration. However, **.NET Framework** has limited supported for gRPC over HTTP/2, but it can be enabled by
31+
=== "Configure the API key"
32+
```csharp
33+
--8<-- "tests/Testcontainers.Qdrant.Tests/QdrantSecureContainerTest.cs:ConfigureQdrantContainerApiKey"
34+
```
3535

36-
- Configuring the Testcontainers module to use TLS and using **HTTPS** to communicate with the cluster
37-
- Configuring Server certificate validation
38-
- Reference `System.Net.Http.WinHttpHandler` 6.0.1 or later, and configuring `WinHttpHandler` as the inner handler for `GrpcChannelOptions` on the Qdrant client
36+
Make sure the underlying HTTP or gRPC client adds the API key to the HTTP header or gRPC metadata:
3937

40-
Refer to the [official Qdrant .NET SDK](https://github.com/qdrant/qdrant-dotnet) for further details.
38+
=== "Configure the Qdrant client"
39+
```csharp
40+
--8<-- "tests/Testcontainers.Qdrant.Tests/QdrantSecureContainerTest.cs:ConfigureQdrantClientCertificate-1"
41+
```
4142

42-
## Server certificate validation and API key
43+
## Configure TLS
4344

44-
The following example creates a self-signed certificate and configures the Testcontainers module to use TLS with the certificate and private key, along with an API key for authentication. The Qdrant client is configured to validate the TLS certificate using its thumbprint, and use the API key
45-
to authenticate.
45+
The following example generates a self-signed certificate and configures the Testcontainers module to use TLS with the certificate and private key:
4646

47-
=== "Usage Example"
47+
=== "Configure the TLS certificate"
4848
```csharp
49-
--8<-- "tests/Testcontainers.Qdrant.Tests/QdrantContainerApiKeyCertificateTest.cs:UseQdrantContainer"
49+
--8<-- "tests/Testcontainers.Qdrant.Tests/QdrantSecureContainerTest.cs:ConfigureQdrantClientCertificate"
5050
```
51+
52+
The Qdrant client is configured to validate the TLS certificate using its thumbprint:
53+
54+
=== "Configure the Qdrant client"
55+
```csharp
56+
--8<--
57+
"tests/Testcontainers.Qdrant.Tests/QdrantSecureContainerTest.cs:ConfigureQdrantClientCertificate-1"
58+
"tests/Testcontainers.Qdrant.Tests/QdrantSecureContainerTest.cs:ConfigureQdrantClientCertificate-2"
59+
--8<--
60+
```
61+
62+
## A Note To Developers
63+
64+
The Testcontainers module creates a container that listens to requests over **HTTP**. The official Qdrant client uses the gRPC APIs to communicate with Qdrant. **.NET Core** and **.NET** support the above example with no additional configuration. However, **.NET Framework** has limited supported for gRPC over HTTP/2, but it can be enabled by
65+
66+
1. Configuring the Testcontainers module to use TLS.
67+
1. Configuring server certificate validation.
68+
1. Reference [`System.Net.Http.WinHttpHandler`](https://www.nuget.org/packages/System.Net.Http.WinHttpHandler) version `6.0.1` or later, and configure `WinHttpHandler` as the handler for `GrpcChannelOptions` in the Qdrant client.
69+
70+
Refer to the [official Qdrant .NET SDK](https://github.com/qdrant/qdrant-dotnet) for more information.

src/Testcontainers.Qdrant/QdrantBuilder.cs

Lines changed: 85 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,85 +10,123 @@ public sealed class QdrantBuilder : ContainerBuilder<QdrantBuilder, QdrantContai
1010

1111
public const ushort QdrantGrpcPort = 6334;
1212

13-
public const string QdrantTlsCertFilePath = "/qdrant/tls/cert.pem";
13+
public const string CertificateFilePath = "/qdrant/tls/cert.pem";
1414

15-
public const string QdrantTlsKeyFilePath = "/qdrant/tls/key.pem";
15+
public const string CertificateKeyFilePath = "/qdrant/tls/key.pem";
1616

17-
public QdrantBuilder() : this(new QdrantConfiguration()) =>
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="QdrantBuilder" /> class.
19+
/// </summary>
20+
public QdrantBuilder()
21+
: this(new QdrantConfiguration())
22+
{
1823
DockerResourceConfiguration = Init().DockerResourceConfiguration;
24+
}
1925

20-
private QdrantBuilder(QdrantConfiguration dockerResourceConfiguration) : base(dockerResourceConfiguration) =>
21-
DockerResourceConfiguration = dockerResourceConfiguration;
26+
/// <summary>
27+
/// Initializes a new instance of the <see cref="QdrantBuilder" /> class.
28+
/// </summary>
29+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
30+
private QdrantBuilder(QdrantConfiguration resourceConfiguration)
31+
: base(resourceConfiguration)
32+
{
33+
DockerResourceConfiguration = resourceConfiguration;
34+
}
35+
36+
/// <inheritdoc />
37+
protected override QdrantConfiguration DockerResourceConfiguration { get; }
2238

2339
/// <summary>
24-
/// The API key used to secure the instance. A certificate and private key should also be
25-
/// provided to <see cref="WithCertificate"/> to enable Transport Layer Security (TLS).
40+
/// Sets the API key to secure the instance.
2641
/// </summary>
27-
/// <param name="apiKey">The API key</param>
28-
public QdrantBuilder WithApiKey(string apiKey) =>
29-
Merge(DockerResourceConfiguration, new QdrantConfiguration(apiKey: apiKey))
42+
/// <param name="apiKey">The API key.</param>
43+
public QdrantBuilder WithApiKey(string apiKey)
44+
{
45+
return Merge(DockerResourceConfiguration, new QdrantConfiguration(apiKey: apiKey))
3046
.WithEnvironment("QDRANT__SERVICE__API_KEY", apiKey);
47+
}
3148

3249
/// <summary>
33-
/// A certificate and private key to enable Transport Layer Security (TLS).
50+
/// Sets the public certificate and private key to enable TLS.
3451
/// </summary>
35-
/// <param name="certificate">A public certificate in PEM format</param>
36-
/// <param name="privateKey">A private key for the certificate in PEM format</param>
37-
public QdrantBuilder WithCertificate(string certificate, string privateKey)
52+
/// <param name="certificate">The public certificate in PEM format.</param>
53+
/// <param name="certificateKey">The private key associated with the certificate in PEM format.</param>
54+
public QdrantBuilder WithCertificate(string certificate, string certificateKey)
3855
{
39-
return Merge(DockerResourceConfiguration, new QdrantConfiguration(certificate: certificate, privateKey: privateKey))
56+
return Merge(DockerResourceConfiguration, new QdrantConfiguration(certificate: certificate, certificateKey: certificateKey))
4057
.WithEnvironment("QDRANT__SERVICE__ENABLE_TLS", "1")
41-
.WithResourceMapping(Encoding.UTF8.GetBytes(certificate), QdrantTlsCertFilePath)
42-
.WithEnvironment("QDRANT__TLS__CERT", QdrantTlsCertFilePath)
43-
.WithResourceMapping(Encoding.UTF8.GetBytes(privateKey), QdrantTlsKeyFilePath)
44-
.WithEnvironment("QDRANT__TLS__KEY", QdrantTlsKeyFilePath);
58+
.WithEnvironment("QDRANT__TLS__CERT", CertificateFilePath)
59+
.WithEnvironment("QDRANT__TLS__KEY", CertificateKeyFilePath)
60+
.WithResourceMapping(Encoding.UTF8.GetBytes(certificate), CertificateFilePath)
61+
.WithResourceMapping(Encoding.UTF8.GetBytes(certificateKey), CertificateKeyFilePath);
4562
}
4663

4764
/// <inheritdoc />
4865
public override QdrantContainer Build()
4966
{
5067
Validate();
5168

52-
var waitStrategy = Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request =>
53-
{
54-
var httpWaitStrategy = request.ForPort(QdrantHttpPort).ForPath("/readyz");
55-
56-
// allow any certificate defined to pass validation
57-
if (DockerResourceConfiguration.Certificate is not null)
58-
{
59-
httpWaitStrategy.UsingTls()
60-
.UsingHttpMessageHandler(new HttpClientHandler
61-
{
62-
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
63-
});
64-
}
65-
66-
return httpWaitStrategy;
67-
});
68-
69-
var qdrantBuilder = DockerResourceConfiguration.WaitStrategies.Count() > 1 ? this : WithWaitStrategy(waitStrategy);
69+
// By default, the base builder waits until the container is running. However, for Qdrant, a more advanced waiting strategy is necessary that requires access to the configured certificate.
70+
// If the user does not provide a custom waiting strategy, append the default Qdrant waiting strategy.
71+
var qdrantBuilder = DockerResourceConfiguration.WaitStrategies.Count() > 1 ? this : WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration)));
7072
return new QdrantContainer(qdrantBuilder.DockerResourceConfiguration);
7173
}
7274

7375
/// <inheritdoc />
74-
protected override QdrantBuilder Init() =>
75-
base.Init()
76+
protected override QdrantBuilder Init()
77+
{
78+
return base.Init()
7679
.WithImage(QdrantImage)
7780
.WithPortBinding(QdrantHttpPort, true)
7881
.WithPortBinding(QdrantGrpcPort, true);
82+
}
7983

8084
/// <inheritdoc />
81-
protected override QdrantBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration) =>
82-
Merge(DockerResourceConfiguration, new QdrantConfiguration(resourceConfiguration));
85+
protected override QdrantBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
86+
{
87+
return Merge(DockerResourceConfiguration, new QdrantConfiguration(resourceConfiguration));
88+
}
8389

8490
/// <inheritdoc />
85-
protected override QdrantBuilder Merge(QdrantConfiguration oldValue, QdrantConfiguration newValue) =>
86-
new(new QdrantConfiguration(oldValue, newValue));
91+
protected override QdrantBuilder Clone(IContainerConfiguration resourceConfiguration)
92+
{
93+
return Merge(DockerResourceConfiguration, new QdrantConfiguration(resourceConfiguration));
94+
}
8795

8896
/// <inheritdoc />
89-
protected override QdrantConfiguration DockerResourceConfiguration { get; }
97+
protected override QdrantBuilder Merge(QdrantConfiguration oldValue, QdrantConfiguration newValue)
98+
{
99+
return new QdrantBuilder(new QdrantConfiguration(oldValue, newValue));
100+
}
90101

91-
/// <inheritdoc />
92-
protected override QdrantBuilder Clone(IContainerConfiguration resourceConfiguration) =>
93-
Merge(DockerResourceConfiguration, new QdrantConfiguration(resourceConfiguration));
102+
/// <inheritdoc cref="IWaitUntil" />
103+
private sealed class WaitUntil : IWaitUntil
104+
{
105+
private readonly bool _tlsEnabled;
106+
107+
/// <summary>
108+
/// Initializes a new instance of the <see cref="WaitUntil" /> class.
109+
/// </summary>
110+
/// <param name="configuration">The container configuration.</param>
111+
public WaitUntil(QdrantConfiguration configuration)
112+
{
113+
_tlsEnabled = configuration.TlsEnabled;
114+
}
115+
116+
/// <inheritdoc />
117+
public async Task<bool> UntilAsync(IContainer container)
118+
{
119+
using var httpMessageHandler = new HttpClientHandler();
120+
httpMessageHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
121+
122+
var httpWaitStrategy = new HttpWaitStrategy()
123+
.UsingHttpMessageHandler(httpMessageHandler)
124+
.UsingTls(_tlsEnabled)
125+
.ForPort(QdrantHttpPort)
126+
.ForPath("/readyz");
127+
128+
return await httpWaitStrategy.UntilAsync(container)
129+
.ConfigureAwait(false);
130+
}
131+
}
94132
}

src/Testcontainers.Qdrant/QdrantConfiguration.cs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ public sealed class QdrantConfiguration : ContainerConfiguration
77
/// <summary>
88
/// Initializes a new instance of the <see cref="QdrantConfiguration" /> class.
99
/// </summary>
10-
public QdrantConfiguration(string apiKey = null, string certificate = null, string privateKey = null)
10+
/// <param name="apiKey">The API key.</param>
11+
/// <param name="certificate">The public certificate in PEM format.</param>
12+
/// <param name="certificateKey">The private key associated with the certificate in PEM format.</param>
13+
public QdrantConfiguration(
14+
string apiKey = null,
15+
string certificate = null,
16+
string certificateKey = null)
1117
{
1218
ApiKey = apiKey;
1319
Certificate = certificate;
14-
PrivateKey = privateKey;
20+
CertificateKey = certificateKey;
1521
}
1622

1723
/// <summary>
@@ -21,6 +27,7 @@ public QdrantConfiguration(string apiKey = null, string certificate = null, stri
2127
public QdrantConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
2228
: base(resourceConfiguration)
2329
{
30+
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
2431
}
2532

2633
/// <summary>
@@ -30,6 +37,7 @@ public QdrantConfiguration(IResourceConfiguration<CreateContainerParameters> res
3037
public QdrantConfiguration(IContainerConfiguration resourceConfiguration)
3138
: base(resourceConfiguration)
3239
{
40+
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
3341
}
3442

3543
/// <summary>
@@ -39,6 +47,7 @@ public QdrantConfiguration(IContainerConfiguration resourceConfiguration)
3947
public QdrantConfiguration(QdrantConfiguration resourceConfiguration)
4048
: this(new QdrantConfiguration(), resourceConfiguration)
4149
{
50+
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
4251
}
4352

4453
/// <summary>
@@ -51,21 +60,26 @@ public QdrantConfiguration(QdrantConfiguration oldValue, QdrantConfiguration new
5160
{
5261
ApiKey = BuildConfiguration.Combine(oldValue.ApiKey, newValue.ApiKey);
5362
Certificate = BuildConfiguration.Combine(oldValue.Certificate, newValue.Certificate);
54-
PrivateKey = BuildConfiguration.Combine(oldValue.PrivateKey, newValue.PrivateKey);
63+
CertificateKey = BuildConfiguration.Combine(oldValue.CertificateKey, newValue.CertificateKey);
5564
}
5665

5766
/// <summary>
58-
/// Gets the API key used to secure Qdrant.
67+
/// Gets a value indicating whether TLS is enabled or not.
68+
/// </summary>
69+
public bool TlsEnabled => Certificate != null;
70+
71+
/// <summary>
72+
/// Gets the API key that secures the instance.
5973
/// </summary>
6074
public string ApiKey { get; }
6175

6276
/// <summary>
63-
/// Gets the certificate used to configure Transport Layer Security. Certificate must be in PEM format.
77+
/// Gets the public certificate in PEM format.
6478
/// </summary>
6579
public string Certificate { get; }
6680

6781
/// <summary>
68-
/// Gets the private key used to configure Transport Layer Security. Private key must be in PEM format.
82+
/// Gets the private key associated with the certificate in PEM format.
6983
/// </summary>
70-
public string PrivateKey { get; }
84+
public string CertificateKey { get; }
7185
}

src/Testcontainers.Qdrant/QdrantContainer.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,32 @@ public sealed class QdrantContainer : DockerContainer
66
{
77
private readonly QdrantConfiguration _configuration;
88

9-
public QdrantContainer(QdrantConfiguration configuration) : base(configuration)
9+
/// <summary>
10+
/// Initializes a new instance of the <see cref="QdrantContainer" /> class.
11+
/// </summary>
12+
/// <param name="configuration">The container configuration.</param>
13+
public QdrantContainer(QdrantConfiguration configuration)
14+
: base(configuration)
1015
{
1116
_configuration = configuration;
1217
}
1318

1419
/// <summary>
15-
/// Gets the connection string for connecting to Qdrant REST APIs
20+
/// Gets the connection string for connecting to Qdrant REST APIs.
1621
/// </summary>
1722
public string GetHttpConnectionString()
1823
{
19-
var scheme = _configuration.Certificate != null ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
24+
var scheme = _configuration.TlsEnabled ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
2025
var endpoint = new UriBuilder(scheme, Hostname, GetMappedPublicPort(QdrantBuilder.QdrantHttpPort));
2126
return endpoint.ToString();
2227
}
2328

2429
/// <summary>
25-
/// Gets the connection string for connecting to Qdrant gRPC APIs
30+
/// Gets the connection string for connecting to Qdrant gRPC APIs.
2631
/// </summary>
2732
public string GetGrpcConnectionString()
2833
{
29-
var scheme = _configuration.Certificate != null ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
34+
var scheme = _configuration.TlsEnabled ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
3035
var endpoint = new UriBuilder(scheme, Hostname, GetMappedPublicPort(QdrantBuilder.QdrantGrpcPort));
3136
return endpoint.ToString();
3237
}

src/Testcontainers.Qdrant/Usings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
global using System.Linq;
33
global using System.Net.Http;
44
global using System.Text;
5+
global using System.Threading.Tasks;
56
global using Docker.DotNet.Models;
67
global using DotNet.Testcontainers.Builders;
78
global using DotNet.Testcontainers.Configurations;

0 commit comments

Comments
 (0)