Skip to content

Commit f83258f

Browse files
committed
Add python/core support for configuring client name during connection setup.
1 parent 60ac1c7 commit f83258f

File tree

10 files changed

+102
-21
lines changed

10 files changed

+102
-21
lines changed

examples/python/client_example.py

100644100755
+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ async def test_standalone_client(host: str = "localhost", port: int = 6379):
4040
# Check `RedisClientConfiguration/ClusterClientConfiguration` for additional options.
4141
config = BaseClientConfiguration(
4242
addresses=addresses,
43+
client_name = 'test_standalone_client'
4344
# use_tls=True
4445
)
4546
client = await RedisClient.create(config)
@@ -57,6 +58,7 @@ async def test_cluster_client(host: str = "localhost", port: int = 6379):
5758
# Check `RedisClientConfiguration/ClusterClientConfiguration` for additional options.
5859
config = BaseClientConfiguration(
5960
addresses=addresses,
61+
client_name = 'test_cluster_client'
6062
# use_tls=True
6163
)
6264
client = await RedisClusterClient.create(config)

glide-core/src/client/mod.rs

+18-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::connection_request::{
2-
AuthenticationInfo, ConnectionRequest, NodeAddress, ProtocolVersion, ReadFrom, TlsMode,
2+
ConnectionRequest, NodeAddress, ProtocolVersion, ReadFrom, TlsMode,
33
};
44
use futures::FutureExt;
55
use logger_core::log_info;
@@ -44,22 +44,21 @@ pub fn convert_to_redis_protocol(protocol: ProtocolVersion) -> redis::ProtocolVe
4444
}
4545

4646
pub(super) fn get_redis_connection_info(
47-
authentication_info: Option<Box<AuthenticationInfo>>,
48-
database_id: u32,
49-
protocol: ProtocolVersion,
47+
connection_request: &ConnectionRequest,
5048
) -> redis::RedisConnectionInfo {
51-
let protocol = convert_to_redis_protocol(protocol);
52-
match authentication_info {
49+
let protocol = convert_to_redis_protocol(connection_request.protocol.enum_value_or_default());
50+
match connection_request.authentication_info.0.as_ref() {
5351
Some(info) => redis::RedisConnectionInfo {
54-
db: database_id as i64,
52+
db: connection_request.database_id as i64,
5553
username: chars_to_string_option(&info.username),
5654
password: chars_to_string_option(&info.password),
5755
protocol,
58-
client_name: None,
56+
client_name: chars_to_string_option(&connection_request.client_name),
5957
},
6058
None => redis::RedisConnectionInfo {
61-
db: database_id as i64,
59+
db: connection_request.database_id as i64,
6260
protocol,
61+
client_name: chars_to_string_option(&connection_request.client_name),
6362
..Default::default()
6463
},
6564
}
@@ -271,9 +270,7 @@ async fn create_cluster_client(
271270
) -> RedisResult<redis::cluster_async::ClusterConnection> {
272271
// TODO - implement timeout for each connection attempt
273272
let tls_mode = request.tls_mode.enum_value_or_default();
274-
let protocol = request.protocol.enum_value_or_default();
275-
let redis_connection_info =
276-
get_redis_connection_info(request.authentication_info.0, 0, protocol);
273+
let redis_connection_info = get_redis_connection_info(&request);
277274
let initial_nodes: Vec<_> = request
278275
.addresses
279276
.into_iter()
@@ -286,7 +283,12 @@ async fn create_cluster_client(
286283
if read_from_replicas {
287284
builder = builder.read_from_replicas();
288285
}
289-
builder = builder.use_protocol(convert_to_redis_protocol(protocol));
286+
builder = builder.use_protocol(convert_to_redis_protocol(
287+
request.protocol.enum_value_or_default(),
288+
));
289+
if let Some(client_name) = redis_connection_info.client_name {
290+
builder = builder.client_name(client_name);
291+
}
290292
if tls_mode != TlsMode::NoTls {
291293
let tls = if tls_mode == TlsMode::SecureTls {
292294
redis::cluster::TlsMode::Secure
@@ -353,9 +355,11 @@ fn sanitized_request_string(request: &ConnectionRequest) -> String {
353355
}
354356
None => String::new(),
355357
};
358+
let protocol = request.protocol.enum_value_or_default();
359+
let client_name = chars_to_string_option(&request.client_name);
356360

357361
format!(
358-
"\naddresses: {addresses}\nTLS mode: {tls_mode:?}\ncluster mode: {cluster_mode}{request_timeout}\nRead from replica strategy: {rfr_strategy:?}{database_id}{connection_retry_strategy}",
362+
"\nAddresses: {addresses}\nTLS mode: {tls_mode:?}\nCluster mode: {cluster_mode}\nRequest timeout: {request_timeout}\nRead from replica strategy: {rfr_strategy:?}\nConnection retry strategy: {connection_retry_strategy}\nDatabase id: {database_id}\nProtocol: {protocol:?}\nClient name: {client_name:?}",
359363
)
360364
}
361365

glide-core/src/client/standalone_client.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,7 @@ impl StandaloneClient {
7171
return Err(StandaloneClientConnectionError::NoAddressesProvided);
7272
}
7373
let retry_strategy = RetryStrategy::new(&connection_request.connection_retry_strategy.0);
74-
let redis_connection_info = get_redis_connection_info(
75-
connection_request.authentication_info.0,
76-
connection_request.database_id,
77-
connection_request.protocol.enum_value_or_default(),
78-
);
74+
let redis_connection_info = get_redis_connection_info(&connection_request);
7975

8076
let tls_mode = connection_request.tls_mode.enum_value_or_default();
8177
let node_count = connection_request.addresses.len();

glide-core/src/protobuf/connection_request.proto

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ message ConnectionRequest {
4040
AuthenticationInfo authentication_info = 7;
4141
uint32 database_id = 8;
4242
ProtocolVersion protocol = 9;
43+
string client_name = 10;
4344
}
4445

4546
message ConnectionRetryStrategy {

glide-core/tests/test_client.rs

+47
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,53 @@ mod shared_client_tests {
274274
});
275275
}
276276

277+
#[rstest]
278+
#[timeout(SHORT_CLUSTER_TEST_TIMEOUT)]
279+
fn test_client_name_after_reconnection(#[values(false, true)] use_cluster: bool) {
280+
const CLIENT_NAME: &str = "TEST_CLIENT_NAME";
281+
let mut client_info_cmd = redis::Cmd::new();
282+
client_info_cmd.arg("CLIENT").arg("INFO");
283+
block_on_all(async move {
284+
let test_basics = setup_test_basics(
285+
use_cluster,
286+
TestConfiguration {
287+
shared_server: true,
288+
client_name: Some(CLIENT_NAME.to_string()),
289+
..Default::default()
290+
},
291+
)
292+
.await;
293+
let mut client = test_basics.client;
294+
let client_info: String = redis::from_redis_value(
295+
&client.send_command(&client_info_cmd, None).await.unwrap(),
296+
)
297+
.unwrap();
298+
assert!(client_info.contains(&format!("name={CLIENT_NAME}")));
299+
300+
kill_connection(&mut client).await;
301+
302+
let error = client.send_command(&client_info_cmd, None).await;
303+
// In Standalone mode the error is passed back to the client,
304+
// while in Cluster mode the request is retried with reconnect
305+
if !use_cluster {
306+
assert!(error.is_err(), "{error:?}",);
307+
let error = error.unwrap_err();
308+
assert!(
309+
error.is_connection_dropped() || error.is_timeout(),
310+
"{error:?}",
311+
);
312+
}
313+
let client_info: String = repeat_try_create(|| async {
314+
let mut client = client.clone();
315+
redis::from_redis_value(&client.send_command(&client_info_cmd, None).await.unwrap())
316+
.ok()
317+
})
318+
.await;
319+
320+
assert!(client_info.contains(&format!("name={CLIENT_NAME}")));
321+
});
322+
}
323+
277324
#[rstest]
278325
#[timeout(SHORT_CLUSTER_TEST_TIMEOUT)]
279326
fn test_request_transaction_and_convert_all_values(#[values(false, true)] use_cluster: bool) {

glide-core/tests/utilities/mod.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use redis::{
1212
};
1313
use socket2::{Domain, Socket, Type};
1414
use std::{
15-
env, fs, io, net::SocketAddr, net::TcpListener, path::PathBuf, process, sync::Mutex,
16-
time::Duration,
15+
env, fs, io, net::SocketAddr, net::TcpListener, ops::Deref, path::PathBuf, process,
16+
sync::Mutex, time::Duration,
1717
};
1818
use tempfile::TempDir;
1919

@@ -643,6 +643,11 @@ pub fn create_connection_request(
643643
configuration.connection_info.clone().unwrap_or_default(),
644644
&mut connection_request,
645645
);
646+
647+
if let Some(client_name) = &configuration.client_name {
648+
connection_request.client_name = client_name.deref().into();
649+
}
650+
646651
connection_request
647652
}
648653

@@ -656,6 +661,7 @@ pub struct TestConfiguration {
656661
pub shared_server: bool,
657662
pub read_from: Option<connection_request::ReadFrom>,
658663
pub database_id: u32,
664+
pub client_name: Option<String>,
659665
pub protocol: ProtocolVersion,
660666
}
661667

python/python/glide/config.py

+11
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def __init__(
8383
credentials: Optional[RedisCredentials] = None,
8484
read_from: ReadFrom = ReadFrom.PRIMARY,
8585
request_timeout: Optional[int] = None,
86+
client_name: Optional[str] = None,
8687
):
8788
"""
8889
Represents the configuration settings for a Redis client.
@@ -106,12 +107,14 @@ def __init__(
106107
request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to complete.
107108
This duration encompasses sending the request, awaiting for a response from the server, and any required reconnections or retries.
108109
If the specified timeout is exceeded for a pending request, it will result in a timeout error. If not set, a default value will be used.
110+
client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command during connection establishment.
109111
"""
110112
self.addresses = addresses or [NodeAddress()]
111113
self.use_tls = use_tls
112114
self.credentials = credentials
113115
self.read_from = read_from
114116
self.request_timeout = request_timeout
117+
self.client_name = client_name
115118

116119
def _create_a_protobuf_conn_request(
117120
self, cluster_mode: bool = False
@@ -139,6 +142,8 @@ def _create_a_protobuf_conn_request(
139142
if self.credentials.username:
140143
request.authentication_info.username = self.credentials.username
141144
request.authentication_info.password = self.credentials.password
145+
if self.client_name:
146+
request.client_name = self.client_name
142147
request.protocol = SentProtocolVersion.RESP2
143148

144149
return request
@@ -169,6 +174,7 @@ class RedisClientConfiguration(BaseClientConfiguration):
169174
connection failures.
170175
If not set, a default backoff strategy will be used.
171176
database_id (Optional[Int]): index of the logical database to connect to.
177+
client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command during connection establishment.
172178
"""
173179

174180
def __init__(
@@ -180,13 +186,15 @@ def __init__(
180186
request_timeout: Optional[int] = None,
181187
reconnect_strategy: Optional[BackoffStrategy] = None,
182188
database_id: Optional[int] = None,
189+
client_name: Optional[str] = None,
183190
):
184191
super().__init__(
185192
addresses=addresses,
186193
use_tls=use_tls,
187194
credentials=credentials,
188195
read_from=read_from,
189196
request_timeout=request_timeout,
197+
client_name=client_name,
190198
)
191199
self.reconnect_strategy = reconnect_strategy
192200
self.database_id = database_id
@@ -229,6 +237,7 @@ class ClusterClientConfiguration(BaseClientConfiguration):
229237
request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to complete.
230238
This duration encompasses sending the request, awaiting for a response from the server, and any required reconnections or retries.
231239
If the specified timeout is exceeded for a pending request, it will result in a timeout error. If not set, a default value will be used.
240+
client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command during connection establishment.
232241
233242
Notes:
234243
Currently, the reconnection strategy in cluster mode is not configurable, and exponential backoff
@@ -242,11 +251,13 @@ def __init__(
242251
credentials: Optional[RedisCredentials] = None,
243252
read_from: ReadFrom = ReadFrom.PRIMARY,
244253
request_timeout: Optional[int] = None,
254+
client_name: Optional[str] = None,
245255
):
246256
super().__init__(
247257
addresses=addresses,
248258
use_tls=use_tls,
249259
credentials=credentials,
250260
read_from=read_from,
251261
request_timeout=request_timeout,
262+
client_name=client_name,
252263
)

python/python/tests/conftest.py

+3
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ async def create_client(
107107
credentials: Optional[RedisCredentials] = None,
108108
database_id: int = 0,
109109
addresses: Optional[List[NodeAddress]] = None,
110+
client_name: Optional[str] = None,
110111
) -> Union[RedisClient, RedisClusterClient]:
111112
# Create async socket client
112113
use_tls = request.config.getoption("--tls")
@@ -118,6 +119,7 @@ async def create_client(
118119
addresses=seed_nodes if addresses is None else addresses,
119120
use_tls=use_tls,
120121
credentials=credentials,
122+
client_name=client_name,
121123
)
122124
return await RedisClusterClient.create(cluster_config)
123125
else:
@@ -129,5 +131,6 @@ async def create_client(
129131
use_tls=use_tls,
130132
credentials=credentials,
131133
database_id=database_id,
134+
client_name=client_name,
132135
)
133136
return await RedisClient.create(config)

python/python/tests/test_async_client.py

+8
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,14 @@ async def test_select_standalone_database_id(self, request):
239239
client_info = await redis_client.custom_command(["CLIENT", "INFO"])
240240
assert "db=4" in client_info
241241

242+
@pytest.mark.parametrize("cluster_mode", [True, False])
243+
async def test_client_name(self, request, cluster_mode):
244+
redis_client = await create_client(
245+
request, cluster_mode=cluster_mode, client_name="TEST_CLIENT_NAME"
246+
)
247+
client_info = await redis_client.custom_command(["CLIENT", "INFO"])
248+
assert "name=TEST_CLIENT_NAME" in client_info
249+
242250

243251
@pytest.mark.asyncio
244252
class TestCommands:

python/python/tests/test_config.py

+3
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@ def test_default_client_config():
1010
assert config.addresses[0].port == 6379
1111
assert config.read_from.value == ProtobufReadFrom.Primary
1212
assert config.use_tls is False
13+
assert config.client_name is None
1314

1415

1516
def test_convert_to_protobuf():
1617
config = BaseClientConfiguration(
1718
[NodeAddress("127.0.0.1")],
1819
use_tls=True,
1920
read_from=ReadFrom.PREFER_REPLICA,
21+
client_name="TEST_CLIENT_NAME",
2022
)
2123
request = config._create_a_protobuf_conn_request()
2224
assert isinstance(request, ConnectionRequest)
2325
assert request.addresses[0].host == "127.0.0.1"
2426
assert request.addresses[0].port == 6379
2527
assert request.tls_mode is TlsMode.SecureTls
2628
assert request.read_from == ProtobufReadFrom.PreferReplica
29+
assert request.client_name == "TEST_CLIENT_NAME"

0 commit comments

Comments
 (0)