risingwave_object_store/object/opendal_engine/
s3.rs1use std::sync::Arc;
16use std::time::Duration;
17
18use opendal::layers::LoggingLayer;
19use opendal::services::S3;
20use opendal::{HttpTransporter, OperationContext, Operator};
21use opendal_http_transport_reqwest::ReqwestTransport;
22use risingwave_common::config::ObjectStoreConfig;
23use thiserror_ext::AsReport as _;
24
25use super::{MediaType, OpendalObjectStore, new_operator};
26use crate::object::object_metrics::ObjectStoreMetrics;
27use crate::object::{ObjectError, ObjectResult};
28
29impl OpendalObjectStore {
30 pub fn new_s3_engine(
32 bucket: String,
33 config: Arc<ObjectStoreConfig>,
34 metrics: Arc<ObjectStoreMetrics>,
35 ) -> ObjectResult<Self> {
36 let mut builder = S3::default().bucket(&bucket);
38 if let Ok(endpoint_url) = std::env::var("RW_S3_ENDPOINT") {
40 builder = builder.endpoint(&endpoint_url);
41 }
42
43 if std::env::var("RW_IS_FORCE_PATH_STYLE").is_err() {
44 builder = builder.enable_virtual_host_style();
45 }
46
47 let http_client = Self::new_http_client(&config)?;
48 let transport = HttpTransporter::new(ReqwestTransport::new(http_client));
49
50 let op = new_operator(
51 &config,
52 Operator::new(builder)?
53 .with_context(OperationContext::new().with_http_transport(transport))
54 .layer(LoggingLayer::default()),
55 );
56
57 Ok(Self {
58 op,
59 media_type: MediaType::S3,
60 config,
61 metrics,
62 })
63 }
64
65 pub fn new_minio_engine(
67 server: &str,
68 config: Arc<ObjectStoreConfig>,
69 metrics: Arc<ObjectStoreMetrics>,
70 ) -> ObjectResult<Self> {
71 let server = server.strip_prefix("minio://").unwrap();
72 let (access_key_id, rest) = server.split_once(':').unwrap();
73 let (secret_access_key, mut rest) = rest.split_once('@').unwrap();
74
75 let endpoint_prefix = if let Some(rest_stripped) = rest.strip_prefix("https://") {
76 rest = rest_stripped;
77 "https://"
78 } else if let Some(rest_stripped) = rest.strip_prefix("http://") {
79 rest = rest_stripped;
80 "http://"
81 } else {
82 "http://"
83 };
84 let (address, bucket) = rest.split_once('/').unwrap();
85 let builder = S3::default()
86 .bucket(bucket)
87 .region("custom")
88 .access_key_id(access_key_id)
89 .secret_access_key(secret_access_key)
90 .endpoint(&format!("{}{}", endpoint_prefix, address))
91 .disable_config_load();
92
93 let http_client = Self::new_http_client(&config)?;
94 let transport = HttpTransporter::new(ReqwestTransport::new(http_client));
95
96 let op = new_operator(
97 &config,
98 Operator::new(builder)?
99 .with_context(OperationContext::new().with_http_transport(transport))
100 .layer(LoggingLayer::default()),
101 );
102
103 Ok(Self {
104 op,
105 media_type: MediaType::Minio,
106 config,
107 metrics,
108 })
109 }
110
111 pub fn new_http_client(config: &ObjectStoreConfig) -> ObjectResult<reqwest::Client> {
112 let mut client_builder = reqwest::ClientBuilder::new();
113
114 if let Some(keepalive_ms) = config.s3.keepalive_ms.as_ref() {
115 client_builder = client_builder.tcp_keepalive(Duration::from_millis(*keepalive_ms));
116 }
117
118 if let Some(nodelay) = config.s3.nodelay.as_ref() {
119 client_builder = client_builder.tcp_nodelay(*nodelay);
120 }
121 client_builder.build().map_err(|e| {
122 ObjectError::internal(format!("failed to build HTTP client: {}", e.as_report()))
123 })
124 }
125}