Add 2023 R2 gRPC transport (RemoteGrpc) reusing native byte payloads
Stands up HistorianTransport.RemoteGrpc end-to-end for the read path, built on the recovered 2023 R2 gRPC contract (gRPC-Web/HTTP-1.1, port 32565, gzip). The opaque protobuf `bytes` fields carry the SAME native binary payloads as the 2020 WCF/MDAS path, so the proven serializers and parsers are reused unchanged. - Grpc/Protos/*.proto: 6 protoc-validated contracts recovered from embedded FileDescriptors (authoritative, not guessed). - Grpc/HistorianGrpcChannelFactory: GrpcWebHandler/HTTP-1.1 channel, ResolvePort/ResolveAddress, optional TLS + gzip. - Grpc/HistorianGrpcReadOrchestrator: mirrors the WCF read chain over gRPC; auth uses HistoryService.ExchangeKey (the gRPC ValCl op). - Wcf/HistorianNativeHandshake: transport-agnostic Open2 request builder + SSPI/Negotiate token loop + response decode, shared by WCF and gRPC. - Op map (2020 -> gRPC): ValCl->ExchangeKey, Open2->OpenConnection, StartQuery2->StartQuery, GetNextQueryResultBuffer2->GetNextQueryResultBuffer. - HistorianClientOptions: DefaultGrpcPort=32565, GrpcUseTls. - csproj: Google.Protobuf, Grpc.Net.Client(.Web), Grpc.Tools codegen. Not yet live-verified against a 2023 R2 server: ExchangeKey is the first thing to revisit if a live server rejects the handshake; the inner byte payloads are the proven 2020 protocol. Gated live test via HISTORIAN_GRPC_HOST. 188 unit tests green; build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
using AVEVA.Historian.Client.Models;
|
||||
|
||||
namespace AVEVA.Historian.Client.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Live integration tests for the 2023 R2 RemoteGrpc transport. Gated on a dedicated
|
||||
/// <c>HISTORIAN_GRPC_HOST</c> env var (plus <c>HISTORIAN_TEST_TAG</c>) so they skip cleanly until
|
||||
/// a 2023 R2 Historian is available. Optional:
|
||||
/// HISTORIAN_GRPC_PORT (default 32565), HISTORIAN_GRPC_TLS (true/false),
|
||||
/// HISTORIAN_USER / HISTORIAN_PASSWORD (explicit creds; otherwise IntegratedSecurity),
|
||||
/// HISTORIAN_GRPC_DNSID (server certificate name when connecting by IP over TLS).
|
||||
/// </summary>
|
||||
public sealed class HistorianGrpcIntegrationTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ReadRawAsync_OverGrpc_ReturnsAtLeastOneRow()
|
||||
{
|
||||
string? host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST");
|
||||
string? testTag = Environment.GetEnvironmentVariable("HISTORIAN_TEST_TAG");
|
||||
if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(testTag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HistorianClient client = new(BuildOptions(host));
|
||||
|
||||
DateTime endUtc = DateTime.UtcNow;
|
||||
DateTime startUtc = endUtc - TimeSpan.FromDays(7);
|
||||
|
||||
List<HistorianSample> samples = [];
|
||||
await foreach (HistorianSample sample in client.ReadRawAsync(testTag, startUtc, endUtc, maxValues: 8, CancellationToken.None))
|
||||
{
|
||||
samples.Add(sample);
|
||||
}
|
||||
|
||||
Assert.NotEmpty(samples);
|
||||
Assert.All(samples, s => Assert.Equal(testTag, s.TagName));
|
||||
}
|
||||
|
||||
private static HistorianClientOptions BuildOptions(string host)
|
||||
{
|
||||
string? user = Environment.GetEnvironmentVariable("HISTORIAN_USER");
|
||||
string? password = Environment.GetEnvironmentVariable("HISTORIAN_PASSWORD");
|
||||
bool explicitCreds = !string.IsNullOrEmpty(user);
|
||||
int port = int.TryParse(Environment.GetEnvironmentVariable("HISTORIAN_GRPC_PORT"), out int parsed)
|
||||
? parsed
|
||||
: HistorianClientOptions.DefaultGrpcPort;
|
||||
bool tls = string.Equals(Environment.GetEnvironmentVariable("HISTORIAN_GRPC_TLS"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return new HistorianClientOptions
|
||||
{
|
||||
Host = host,
|
||||
Port = port,
|
||||
Transport = HistorianTransport.RemoteGrpc,
|
||||
GrpcUseTls = tls,
|
||||
AllowUntrustedServerCertificate = tls,
|
||||
ServerDnsIdentity = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_DNSID"),
|
||||
IntegratedSecurity = !explicitCreds,
|
||||
UserName = user ?? string.Empty,
|
||||
Password = password ?? string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user