Skip to content

Commit a323b3b

Browse files
authored
Merge pull request #218 from demml/feat/grpc-tls
Feat/grpc tls
2 parents b82477a + ffca52c commit a323b3b

File tree

10 files changed

+473
-206
lines changed

10 files changed

+473
-206
lines changed

Cargo.lock

Lines changed: 307 additions & 163 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ default-members = [
99
]
1010

1111
[workspace.package]
12-
version = "0.18.0"
12+
version = "0.19.0"
1313
authors = [
1414
"Thorrester <support@demmlai.com>",
1515
"russellkemmit <support@demmlai.com>",
@@ -20,25 +20,25 @@ repository = "https://github.com/demml/scouter"
2020

2121

2222
[workspace.dependencies]
23-
scouter-auth = { path = "crates/scouter_auth", version = "0.18.0" }
24-
scouter-client = { path = "crates/scouter_client", version = "0.18.0" }
25-
scouter-dispatch = { path = "crates/scouter_dispatch", version = "0.18.0" }
26-
scouter-drift = { path = "crates/scouter_drift", version = "0.18.0", default-features = false }
27-
scouter-evaluate = { path = "crates/scouter_evaluate", version = "0.18.0" }
28-
scouter-events = { path = "crates/scouter_events", version = "0.18.0", default-features = false }
29-
scouter-http = { path = "crates/scouter_http", version = "0.18.0" }
30-
scouter-observability = { path = "crates/scouter_observability", version = "0.18.0" }
31-
scouter-profile = { path = "crates/scouter_profile", version = "0.18.0" }
32-
scouter-server = { path = "crates/scouter_server", version = "0.18.0" }
33-
scouter-semver = { path = "crates/scouter_semver", version = "0.18.0" }
34-
scouter-settings = { path = "crates/scouter_settings", version = "0.18.0" }
35-
scouter-dataframe = { path = "crates/scouter_dataframe", version = "0.18.0" }
36-
scouter-macro = { path = "crates/scouter_macro", version = "0.18.0" }
37-
scouter-state = { path = "crates/scouter_state", version = "0.18.0" }
38-
scouter-sql = { path = "crates/scouter_sql", version = "0.18.0" }
39-
scouter-tonic = { path = "crates/scouter_tonic", version = "0.18.0" }
40-
scouter-tracing = { path = "crates/scouter_tracing", version = "0.18.0" }
41-
scouter-types = { path = "crates/scouter_types", version = "0.18.0" }
23+
scouter-auth = { path = "crates/scouter_auth", version = "0.19.0" }
24+
scouter-client = { path = "crates/scouter_client", version = "0.19.0" }
25+
scouter-dispatch = { path = "crates/scouter_dispatch", version = "0.19.0" }
26+
scouter-drift = { path = "crates/scouter_drift", version = "0.19.0", default-features = false }
27+
scouter-evaluate = { path = "crates/scouter_evaluate", version = "0.19.0" }
28+
scouter-events = { path = "crates/scouter_events", version = "0.19.0", default-features = false }
29+
scouter-http = { path = "crates/scouter_http", version = "0.19.0" }
30+
scouter-observability = { path = "crates/scouter_observability", version = "0.19.0" }
31+
scouter-profile = { path = "crates/scouter_profile", version = "0.19.0" }
32+
scouter-server = { path = "crates/scouter_server", version = "0.19.0" }
33+
scouter-semver = { path = "crates/scouter_semver", version = "0.19.0" }
34+
scouter-settings = { path = "crates/scouter_settings", version = "0.19.0" }
35+
scouter-dataframe = { path = "crates/scouter_dataframe", version = "0.19.0" }
36+
scouter-macro = { path = "crates/scouter_macro", version = "0.19.0" }
37+
scouter-state = { path = "crates/scouter_state", version = "0.19.0" }
38+
scouter-sql = { path = "crates/scouter_sql", version = "0.19.0" }
39+
scouter-tonic = { path = "crates/scouter_tonic", version = "0.19.0" }
40+
scouter-tracing = { path = "crates/scouter_tracing", version = "0.19.0" }
41+
scouter-types = { path = "crates/scouter_types", version = "0.19.0" }
4242
scouter-mocks = { path = "crates/scouter_mocks" }
4343
test-utils = { path = "crates/test_utils" }
4444

crates/scouter_mocks/src/mock.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ impl ScouterTestServer {
198198
Some(format!("http://127.0.0.1:{grpc_port}")),
199199
Some("guest".to_string()),
200200
Some("guest".to_string()),
201+
None,
202+
None,
203+
None,
204+
None,
205+
None,
201206
);
202207

203208
// Use the health check method

crates/scouter_settings/src/grpc.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ pub struct GrpcConfig {
1515
#[pyo3(get, set)]
1616
pub password: String,
1717

18+
#[pyo3(get, set)]
19+
pub timeout_secs: Option<u64>,
20+
21+
#[pyo3(get, set)]
22+
pub connect_timeout_secs: Option<u64>,
23+
24+
#[pyo3(get, set)]
25+
pub keep_alive_interval_secs: Option<u64>,
26+
27+
#[pyo3(get, set)]
28+
pub keep_alive_timeout_secs: Option<u64>,
29+
30+
#[pyo3(get, set)]
31+
pub keep_alive_while_idle: Option<bool>,
32+
1833
#[pyo3(get)]
1934
pub transport_type: TransportType,
2035
}
@@ -26,11 +41,22 @@ impl GrpcConfig {
2641
server_uri=None,
2742
username=None,
2843
password=None,
44+
timeout_secs=None,
45+
connect_timeout_secs=None,
46+
keep_alive_interval_secs=None,
47+
keep_alive_timeout_secs=None,
48+
keep_alive_while_idle=None,
2949
))]
50+
#[allow(clippy::too_many_arguments)]
3051
pub fn new(
3152
server_uri: Option<String>,
3253
username: Option<String>,
3354
password: Option<String>,
55+
timeout_secs: Option<u64>,
56+
connect_timeout_secs: Option<u64>,
57+
keep_alive_interval_secs: Option<u64>,
58+
keep_alive_timeout_secs: Option<u64>,
59+
keep_alive_while_idle: Option<bool>,
3460
) -> Self {
3561
let server_uri = server_uri.unwrap_or_else(|| {
3662
std::env::var("SCOUTER_GRPC_URI")
@@ -49,6 +75,11 @@ impl GrpcConfig {
4975
server_uri,
5076
username,
5177
password,
78+
timeout_secs,
79+
connect_timeout_secs,
80+
keep_alive_interval_secs,
81+
keep_alive_timeout_secs,
82+
keep_alive_while_idle,
5283
transport_type: TransportType::Grpc,
5384
}
5485
}
@@ -60,6 +91,6 @@ impl GrpcConfig {
6091

6192
impl Default for GrpcConfig {
6293
fn default() -> Self {
63-
Self::new(None, None, None)
94+
Self::new(None, None, None, None, None, None, None, None)
6495
}
6596
}

crates/scouter_tonic/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ prost-types = { workspace = true }
2626

2727
[features]
2828
default = []
29-
client = ["tonic/channel"]
29+
client = ["tonic/channel", "tonic/tls-aws-lc", "tonic/tls-native-roots"]
3030
server = ["tonic/server"]
3131
all = ["client", "server"]
3232

crates/scouter_tonic/src/client.rs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{
55
};
66
use scouter_settings::grpc::GrpcConfig;
77
use std::sync::{Arc, RwLock};
8+
use std::time::Duration;
89
use tonic::metadata::MetadataValue;
910
use tonic::transport::Channel;
1011
use tonic::Request;
@@ -17,30 +18,60 @@ pub const AUTHORIZATION: &str = "authorization";
1718

1819
#[derive(Clone, Debug)]
1920
pub struct GrpcClient {
21+
channel: Channel,
2022
message_client: MessageServiceClient<Channel>,
2123
auth_client: AuthServiceClient<Channel>,
2224
auth_token: Arc<RwLock<String>>,
2325
config: GrpcConfig,
2426
}
2527

26-
impl GrpcClient {
27-
pub async fn new(config: GrpcConfig) -> Result<Self, ClientError> {
28-
let channel = Channel::from_shared(config.server_uri.clone())
29-
.map_err(|e| {
30-
error!("Failed to create gRPC channel: {}", e);
31-
ClientError::GrpcError(e.to_string())
32-
})?
28+
async fn build_channel(config: &GrpcConfig) -> Result<Channel, ClientError> {
29+
let mut endpoint = Channel::from_shared(config.server_uri.clone())
30+
.map_err(|e| ClientError::GrpcError(format!("Invalid URI: {}", e)))?;
31+
32+
if let Some(secs) = config.timeout_secs {
33+
endpoint = endpoint.timeout(Duration::from_secs(secs));
34+
}
35+
if let Some(secs) = config.connect_timeout_secs {
36+
endpoint = endpoint.connect_timeout(Duration::from_secs(secs));
37+
}
38+
if let Some(secs) = config.keep_alive_interval_secs {
39+
endpoint = endpoint.http2_keep_alive_interval(Duration::from_secs(secs));
40+
}
41+
if let Some(secs) = config.keep_alive_timeout_secs {
42+
endpoint = endpoint.keep_alive_timeout(Duration::from_secs(secs));
43+
}
44+
if let Some(enabled) = config.keep_alive_while_idle {
45+
endpoint = endpoint.keep_alive_while_idle(enabled);
46+
}
47+
48+
if config.server_uri.starts_with("https://") {
49+
endpoint
50+
.tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots())
51+
.map_err(|e| ClientError::GrpcError(format!("TLS config failed: {}", e)))?
52+
.connect()
53+
.await
54+
.map_err(|e| ClientError::GrpcError(format!("Failed to connect (TLS): {}", e)))
55+
} else {
56+
endpoint
3357
.connect()
3458
.await
35-
.map_err(|e| {
36-
error!("Failed to connect to gRPC server: {}", e);
37-
ClientError::GrpcError(e.to_string())
38-
})?;
59+
.map_err(|e| ClientError::GrpcError(format!("Failed to connect: {}", e)))
60+
}
61+
}
62+
63+
impl GrpcClient {
64+
pub async fn new(config: GrpcConfig) -> Result<Self, ClientError> {
65+
let channel = build_channel(&config).await.map_err(|e| {
66+
error!("Failed to connect to gRPC server: {}", e);
67+
e
68+
})?;
3969

4070
let message_client = MessageServiceClient::new(channel.clone());
41-
let auth_client = AuthServiceClient::new(channel);
71+
let auth_client = AuthServiceClient::new(channel.clone());
4272

4373
let mut grpc_client = Self {
74+
channel,
4475
message_client,
4576
auth_client,
4677
auth_token: Arc::new(RwLock::new(String::new())),
@@ -206,13 +237,7 @@ impl GrpcClient {
206237
}
207238

208239
pub async fn health_check(&self) -> Result<bool, ClientError> {
209-
let channel = Channel::from_shared(self.config.server_uri.clone())
210-
.map_err(|e| ClientError::GrpcError(format!("Invalid URI: {}", e)))?
211-
.connect()
212-
.await
213-
.map_err(|e| ClientError::GrpcError(format!("Connection failed: {}", e)))?;
214-
215-
let mut health_client = HealthClient::new(channel);
240+
let mut health_client = HealthClient::new(self.channel.clone());
216241

217242
// Check health of MessageService
218243
let request = HealthCheckRequest {

py-scouter/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ classifiers = [
77
"Programming Language :: Python :: Implementation :: PyPy",
88
]
99
license = "MIT"
10-
version = "0.18.0"
10+
version = "0.19.0"
1111
description = ""
1212
authors = [
1313
{name = "Thorrester", email = "<support@demmlai.com>"},

py-scouter/python/scouter/_scouter.pyi

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12876,18 +12876,29 @@ class GrpcConfig:
1287612876
server_uri: str
1287712877
username: str
1287812878
password: str
12879+
timeout_secs: Optional[int]
12880+
connect_timeout_secs: Optional[int]
12881+
keep_alive_interval_secs: Optional[int]
12882+
keep_alive_timeout_secs: Optional[int]
12883+
keep_alive_while_idle: Optional[bool]
1287912884

1288012885
def __init__(
1288112886
self,
1288212887
server_uri: Optional[str] = None,
1288312888
username: Optional[str] = None,
1288412889
password: Optional[str] = None,
12890+
timeout_secs: Optional[int] = None,
12891+
connect_timeout_secs: Optional[int] = None,
12892+
keep_alive_interval_secs: Optional[int] = None,
12893+
keep_alive_timeout_secs: Optional[int] = None,
12894+
keep_alive_while_idle: Optional[bool] = None,
1288512895
) -> None:
1288612896
"""gRPC configuration to use with the GrpcProducer.
1288712897

1288812898
Args:
1288912899
server_uri:
1289012900
URL of the gRPC server to publish messages to.
12901+
Use ``http://`` for plaintext or ``https://`` for TLS.
1289112902
If not provided, the value of the SCOUTER_GRPC_URI environment variable is used.
1289212903

1289312904
username:
@@ -12897,6 +12908,26 @@ class GrpcConfig:
1289712908
password:
1289812909
Password for basic authentication.
1289912910
If not provided, the value of the SCOUTER_PASSWORD environment variable is used.
12911+
12912+
timeout_secs:
12913+
Maximum time in seconds to wait for a response before the request is cancelled.
12914+
If not provided, requests will wait indefinitely.
12915+
12916+
connect_timeout_secs:
12917+
Maximum time in seconds to wait for the initial connection to be established.
12918+
If not provided, connection attempts will wait indefinitely.
12919+
12920+
keep_alive_interval_secs:
12921+
Interval in seconds between HTTP/2 keepalive pings sent to the server.
12922+
Recommended for long-lived connections behind load balancers or NAT.
12923+
12924+
keep_alive_timeout_secs:
12925+
Time in seconds to wait for a keepalive ping response before closing the connection.
12926+
Only applies when ``keep_alive_interval_secs`` is set.
12927+
12928+
keep_alive_while_idle:
12929+
If ``True``, keepalive pings are sent even when there are no active streams.
12930+
Only applies when ``keep_alive_interval_secs`` is set.
1290012931
"""
1290112932

1290212933
def __str__(self): ...

py-scouter/python/scouter/stubs/scouter.pyi

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,18 +552,29 @@ class GrpcConfig:
552552
server_uri: str
553553
username: str
554554
password: str
555+
timeout_secs: Optional[int]
556+
connect_timeout_secs: Optional[int]
557+
keep_alive_interval_secs: Optional[int]
558+
keep_alive_timeout_secs: Optional[int]
559+
keep_alive_while_idle: Optional[bool]
555560

556561
def __init__(
557562
self,
558563
server_uri: Optional[str] = None,
559564
username: Optional[str] = None,
560565
password: Optional[str] = None,
566+
timeout_secs: Optional[int] = None,
567+
connect_timeout_secs: Optional[int] = None,
568+
keep_alive_interval_secs: Optional[int] = None,
569+
keep_alive_timeout_secs: Optional[int] = None,
570+
keep_alive_while_idle: Optional[bool] = None,
561571
) -> None:
562572
"""gRPC configuration to use with the GrpcProducer.
563573
564574
Args:
565575
server_uri:
566576
URL of the gRPC server to publish messages to.
577+
Use ``http://`` for plaintext or ``https://`` for TLS.
567578
If not provided, the value of the SCOUTER_GRPC_URI environment variable is used.
568579
569580
username:
@@ -573,6 +584,26 @@ class GrpcConfig:
573584
password:
574585
Password for basic authentication.
575586
If not provided, the value of the SCOUTER_PASSWORD environment variable is used.
587+
588+
timeout_secs:
589+
Maximum time in seconds to wait for a response before the request is cancelled.
590+
If not provided, requests will wait indefinitely.
591+
592+
connect_timeout_secs:
593+
Maximum time in seconds to wait for the initial connection to be established.
594+
If not provided, connection attempts will wait indefinitely.
595+
596+
keep_alive_interval_secs:
597+
Interval in seconds between HTTP/2 keepalive pings sent to the server.
598+
Recommended for long-lived connections behind load balancers or NAT.
599+
600+
keep_alive_timeout_secs:
601+
Time in seconds to wait for a keepalive ping response before closing the connection.
602+
Only applies when ``keep_alive_interval_secs`` is set.
603+
604+
keep_alive_while_idle:
605+
If ``True``, keepalive pings are sent even when there are no active streams.
606+
Only applies when ``keep_alive_interval_secs`` is set.
576607
"""
577608

578609
def __str__(self): ...

py-scouter/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)