@@ -23,6 +23,7 @@ use super::cluster::{fetch_stats_from_ingestors, INTERNAL_STREAM_NAME};
23
23
use crate :: alerts:: Alerts ;
24
24
use crate :: handlers:: {
25
25
CUSTOM_PARTITION_KEY , STATIC_SCHEMA_FLAG , TIME_PARTITION_KEY , TIME_PARTITION_LIMIT_KEY ,
26
+ UPDATE_STREAM_KEY ,
26
27
} ;
27
28
use crate :: metadata:: STREAM_INFO ;
28
29
use crate :: metrics:: { EVENTS_INGESTED_DATE , EVENTS_INGESTED_SIZE_DATE , EVENTS_STORAGE_SIZE_DATE } ;
@@ -187,77 +188,166 @@ pub async fn get_alert(req: HttpRequest) -> Result<impl Responder, StreamError>
187
188
}
188
189
189
190
pub async fn put_stream ( req : HttpRequest , body : Bytes ) -> Result < impl Responder , StreamError > {
190
- let time_partition = if let Some ( ( _, time_partition_name) ) = req
191
- . headers ( )
192
- . iter ( )
193
- . find ( |& ( key, _) | key == TIME_PARTITION_KEY )
194
- {
195
- time_partition_name. to_str ( ) . unwrap ( )
196
- } else {
197
- ""
198
- } ;
191
+ let stream_name: String = req. match_info ( ) . get ( "logstream" ) . unwrap ( ) . parse ( ) . unwrap ( ) ;
192
+ let ( time_partition, time_partition_limit, custom_partition, static_schema_flag, update_stream) =
193
+ fetch_headers_from_put_stream_request ( & req) ;
194
+
195
+ if metadata:: STREAM_INFO . stream_exists ( & stream_name) && update_stream != "true" {
196
+ // Error if the log stream already exists
197
+ return Err ( StreamError :: Custom {
198
+ msg : format ! (
199
+ "Logstream {stream_name} already exists, please create a new log stream with unique name"
200
+ ) ,
201
+ status : StatusCode :: BAD_REQUEST ,
202
+ } ) ;
203
+ }
204
+
205
+ if !time_partition. is_empty ( ) && update_stream == "true" {
206
+ return Err ( StreamError :: Custom {
207
+ msg : "Altering the time partition of an existing stream is restricted." . to_string ( ) ,
208
+ status : StatusCode :: BAD_REQUEST ,
209
+ } ) ;
210
+ }
199
211
let mut time_partition_in_days: & str = "" ;
200
- if let Some ( ( _, time_partition_limit_name) ) = req
201
- . headers ( )
202
- . iter ( )
203
- . find ( |& ( key, _) | key == TIME_PARTITION_LIMIT_KEY )
204
- {
205
- let time_partition_limit = time_partition_limit_name. to_str ( ) . unwrap ( ) ;
206
- if !time_partition_limit. ends_with ( 'd' ) {
207
- return Err ( StreamError :: Custom {
208
- msg : "missing 'd' suffix for duration value" . to_string ( ) ,
209
- status : StatusCode :: BAD_REQUEST ,
210
- } ) ;
211
- }
212
- let days = & time_partition_limit[ 0 ..time_partition_limit. len ( ) - 1 ] ;
213
- if days. parse :: < NonZeroU32 > ( ) . is_err ( ) {
214
- return Err ( StreamError :: Custom {
215
- msg : "could not convert duration to an unsigned number" . to_string ( ) ,
216
- status : StatusCode :: BAD_REQUEST ,
217
- } ) ;
212
+ if !time_partition_limit. is_empty ( ) {
213
+ let time_partition_days = validate_time_partition_limit ( & time_partition_limit) ;
214
+ if let Err ( err) = time_partition_days {
215
+ return Err ( StreamError :: CreateStream ( err) ) ;
218
216
} else {
219
- time_partition_in_days = days;
217
+ time_partition_in_days = time_partition_days. unwrap ( ) ;
218
+ if update_stream == "true" {
219
+ if let Err ( err) = update_time_partition_limit_in_stream (
220
+ stream_name. clone ( ) ,
221
+ time_partition_in_days,
222
+ )
223
+ . await
224
+ {
225
+ return Err ( StreamError :: CreateStream ( err) ) ;
226
+ }
227
+ return Ok ( ( "Log stream updated" , StatusCode :: OK ) ) ;
228
+ }
220
229
}
221
230
}
222
- let static_schema_flag = if let Some ( ( _, static_schema_flag) ) = req
223
- . headers ( )
224
- . iter ( )
225
- . find ( |& ( key, _) | key == STATIC_SCHEMA_FLAG )
226
- {
227
- static_schema_flag. to_str ( ) . unwrap ( )
228
- } else {
229
- ""
230
- } ;
231
- let mut custom_partition: & str = "" ;
232
- if let Some ( ( _, custom_partition_key) ) = req
233
- . headers ( )
234
- . iter ( )
235
- . find ( |& ( key, _) | key == CUSTOM_PARTITION_KEY )
236
- {
237
- custom_partition = custom_partition_key. to_str ( ) . unwrap ( ) ;
238
- let custom_partition_list = custom_partition. split ( ',' ) . collect :: < Vec < & str > > ( ) ;
239
- if custom_partition_list. len ( ) > 3 {
240
- return Err ( StreamError :: Custom {
241
- msg : "maximum 3 custom partition keys are supported" . to_string ( ) ,
242
- status : StatusCode :: BAD_REQUEST ,
243
- } ) ;
231
+
232
+ if !static_schema_flag. is_empty ( ) && update_stream == "true" {
233
+ return Err ( StreamError :: Custom {
234
+ msg : "Altering the schema of an existing stream is restricted." . to_string ( ) ,
235
+ status : StatusCode :: BAD_REQUEST ,
236
+ } ) ;
237
+ }
238
+
239
+ if !custom_partition. is_empty ( ) {
240
+ if let Err ( err) = validate_custom_partition ( & custom_partition) {
241
+ return Err ( StreamError :: CreateStream ( err) ) ;
242
+ }
243
+ if update_stream == "true" {
244
+ if let Err ( err) =
245
+ update_custom_partition_in_stream ( stream_name. clone ( ) , & custom_partition) . await
246
+ {
247
+ return Err ( StreamError :: CreateStream ( err) ) ;
248
+ }
249
+ return Ok ( ( "Log stream updated" , StatusCode :: OK ) ) ;
244
250
}
245
251
}
246
252
247
- let stream_name: String = req. match_info ( ) . get ( "logstream" ) . unwrap ( ) . parse ( ) . unwrap ( ) ;
248
- let mut schema = Arc :: new ( Schema :: empty ( ) ) ;
249
- if metadata:: STREAM_INFO . stream_exists ( & stream_name) {
250
- // Error if the log stream already exists
251
- return Err ( StreamError :: Custom {
252
- msg : format ! (
253
- "logstream {stream_name} already exists, please create a new log stream with unique name"
254
- ) ,
253
+ let schema = validate_static_schema (
254
+ & body,
255
+ & stream_name,
256
+ & time_partition,
257
+ & custom_partition,
258
+ & static_schema_flag,
259
+ ) ;
260
+ if let Err ( err) = schema {
261
+ return Err ( StreamError :: CreateStream ( err) ) ;
262
+ }
263
+
264
+ create_stream (
265
+ stream_name,
266
+ & time_partition,
267
+ time_partition_in_days,
268
+ & custom_partition,
269
+ & static_schema_flag,
270
+ schema. unwrap ( ) ,
271
+ )
272
+ . await ?;
273
+
274
+ Ok ( ( "Log stream created" , StatusCode :: OK ) )
275
+ }
276
+
277
+ fn fetch_headers_from_put_stream_request (
278
+ req : & HttpRequest ,
279
+ ) -> ( String , String , String , String , String ) {
280
+ let mut time_partition = String :: default ( ) ;
281
+ let mut time_partition_limit = String :: default ( ) ;
282
+ let mut custom_partition = String :: default ( ) ;
283
+ let mut static_schema_flag = String :: default ( ) ;
284
+ let mut update_stream = String :: default ( ) ;
285
+ req. headers ( ) . iter ( ) . for_each ( |( key, value) | {
286
+ if key == TIME_PARTITION_KEY {
287
+ time_partition = value. to_str ( ) . unwrap ( ) . to_string ( ) ;
288
+ }
289
+ if key == TIME_PARTITION_LIMIT_KEY {
290
+ time_partition_limit = value. to_str ( ) . unwrap ( ) . to_string ( ) ;
291
+ }
292
+ if key == CUSTOM_PARTITION_KEY {
293
+ custom_partition = value. to_str ( ) . unwrap ( ) . to_string ( ) ;
294
+ }
295
+ if key == STATIC_SCHEMA_FLAG {
296
+ static_schema_flag = value. to_str ( ) . unwrap ( ) . to_string ( ) ;
297
+ }
298
+ if key == UPDATE_STREAM_KEY {
299
+ update_stream = value. to_str ( ) . unwrap ( ) . to_string ( ) ;
300
+ }
301
+ } ) ;
302
+
303
+ (
304
+ time_partition,
305
+ time_partition_limit,
306
+ custom_partition,
307
+ static_schema_flag,
308
+ update_stream,
309
+ )
310
+ }
311
+
312
+ fn validate_time_partition_limit ( time_partition_limit : & str ) -> Result < & str , CreateStreamError > {
313
+ if !time_partition_limit. ends_with ( 'd' ) {
314
+ return Err ( CreateStreamError :: Custom {
315
+ msg : "Missing 'd' suffix for duration value" . to_string ( ) ,
255
316
status : StatusCode :: BAD_REQUEST ,
256
317
} ) ;
257
318
}
319
+ let days = & time_partition_limit[ 0 ..time_partition_limit. len ( ) - 1 ] ;
320
+ if days. parse :: < NonZeroU32 > ( ) . is_err ( ) {
321
+ return Err ( CreateStreamError :: Custom {
322
+ msg : "Could not convert duration to an unsigned number" . to_string ( ) ,
323
+ status : StatusCode :: BAD_REQUEST ,
324
+ } ) ;
325
+ }
326
+
327
+ Ok ( days)
328
+ }
258
329
330
+ fn validate_custom_partition ( custom_partition : & str ) -> Result < ( ) , CreateStreamError > {
331
+ let custom_partition_list = custom_partition. split ( ',' ) . collect :: < Vec < & str > > ( ) ;
332
+ if custom_partition_list. len ( ) > 3 {
333
+ return Err ( CreateStreamError :: Custom {
334
+ msg : "Maximum 3 custom partition keys are supported" . to_string ( ) ,
335
+ status : StatusCode :: BAD_REQUEST ,
336
+ } ) ;
337
+ }
338
+ Ok ( ( ) )
339
+ }
340
+
341
+ fn validate_static_schema (
342
+ body : & Bytes ,
343
+ stream_name : & str ,
344
+ time_partition : & str ,
345
+ custom_partition : & str ,
346
+ static_schema_flag : & str ,
347
+ ) -> Result < Arc < Schema > , CreateStreamError > {
348
+ let mut schema = Arc :: new ( Schema :: empty ( ) ) ;
259
349
if !body. is_empty ( ) && static_schema_flag == "true" {
260
- let static_schema: StaticSchema = serde_json:: from_slice ( & body) ?;
350
+ let static_schema: StaticSchema = serde_json:: from_slice ( body) ?;
261
351
262
352
let parsed_schema = convert_static_schema_to_arrow_schema (
263
353
static_schema. clone ( ) ,
@@ -267,31 +357,21 @@ pub async fn put_stream(req: HttpRequest, body: Bytes) -> Result<impl Responder,
267
357
if let Ok ( parsed_schema) = parsed_schema {
268
358
schema = parsed_schema;
269
359
} else {
270
- return Err ( StreamError :: Custom {
271
- msg : format ! ( "unable to commit static schema, logstream {stream_name} not created" ) ,
360
+ return Err ( CreateStreamError :: Custom {
361
+ msg : format ! ( "Unable to commit static schema, logstream {stream_name} not created" ) ,
272
362
status : StatusCode :: BAD_REQUEST ,
273
363
} ) ;
274
364
}
275
365
} else if body. is_empty ( ) && static_schema_flag == "true" {
276
- return Err ( StreamError :: Custom {
366
+ return Err ( CreateStreamError :: Custom {
277
367
msg : format ! (
278
- "please provide schema in the request body for static schema logstream {stream_name}"
368
+ "Please provide schema in the request body for static schema logstream {stream_name}"
279
369
) ,
280
370
status : StatusCode :: BAD_REQUEST ,
281
371
} ) ;
282
372
}
283
373
284
- create_stream (
285
- stream_name,
286
- time_partition,
287
- time_partition_in_days,
288
- custom_partition,
289
- static_schema_flag,
290
- schema,
291
- )
292
- . await ?;
293
-
294
- Ok ( ( "log stream created" , StatusCode :: OK ) )
374
+ Ok ( schema)
295
375
}
296
376
297
377
pub async fn put_alert (
@@ -626,6 +706,56 @@ fn remove_id_from_alerts(value: &mut Value) {
626
706
}
627
707
}
628
708
709
+ pub async fn update_time_partition_limit_in_stream (
710
+ stream_name : String ,
711
+ time_partition_limit : & str ,
712
+ ) -> Result < ( ) , CreateStreamError > {
713
+ let storage = CONFIG . storage ( ) . get_object_store ( ) ;
714
+ if let Err ( err) = storage
715
+ . update_time_partition_limit_in_stream ( & stream_name, time_partition_limit)
716
+ . await
717
+ {
718
+ return Err ( CreateStreamError :: Storage { stream_name, err } ) ;
719
+ }
720
+
721
+ if metadata:: STREAM_INFO
722
+ . update_time_partition_limit ( & stream_name, time_partition_limit. to_string ( ) )
723
+ . is_err ( )
724
+ {
725
+ return Err ( CreateStreamError :: Custom {
726
+ msg : "failed to update time partition limit in metadata" . to_string ( ) ,
727
+ status : StatusCode :: EXPECTATION_FAILED ,
728
+ } ) ;
729
+ }
730
+
731
+ Ok ( ( ) )
732
+ }
733
+
734
+ pub async fn update_custom_partition_in_stream (
735
+ stream_name : String ,
736
+ custom_partition : & str ,
737
+ ) -> Result < ( ) , CreateStreamError > {
738
+ let storage = CONFIG . storage ( ) . get_object_store ( ) ;
739
+ if let Err ( err) = storage
740
+ . update_custom_partition_in_stream ( & stream_name, custom_partition)
741
+ . await
742
+ {
743
+ return Err ( CreateStreamError :: Storage { stream_name, err } ) ;
744
+ }
745
+
746
+ if metadata:: STREAM_INFO
747
+ . update_custom_partition ( & stream_name, custom_partition. to_string ( ) )
748
+ . is_err ( )
749
+ {
750
+ return Err ( CreateStreamError :: Custom {
751
+ msg : "failed to update custom partition in metadata" . to_string ( ) ,
752
+ status : StatusCode :: EXPECTATION_FAILED ,
753
+ } ) ;
754
+ }
755
+
756
+ Ok ( ( ) )
757
+ }
758
+
629
759
pub async fn create_stream (
630
760
stream_name : String ,
631
761
time_partition : & str ,
@@ -757,6 +887,10 @@ pub mod error {
757
887
stream_name : String ,
758
888
err : ObjectStorageError ,
759
889
} ,
890
+ #[ error( "{msg}" ) ]
891
+ Custom { msg : String , status : StatusCode } ,
892
+ #[ error( "Could not deserialize into JSON object, {0}" ) ]
893
+ SerdeError ( #[ from] serde_json:: Error ) ,
760
894
}
761
895
762
896
#[ derive( Debug , thiserror:: Error ) ]
@@ -809,6 +943,12 @@ pub mod error {
809
943
StreamError :: CreateStream ( CreateStreamError :: Storage { .. } ) => {
810
944
StatusCode :: INTERNAL_SERVER_ERROR
811
945
}
946
+ StreamError :: CreateStream ( CreateStreamError :: Custom { .. } ) => {
947
+ StatusCode :: INTERNAL_SERVER_ERROR
948
+ }
949
+ StreamError :: CreateStream ( CreateStreamError :: SerdeError ( _) ) => {
950
+ StatusCode :: BAD_REQUEST
951
+ }
812
952
StreamError :: CacheNotEnabled ( _) => StatusCode :: BAD_REQUEST ,
813
953
StreamError :: StreamNotFound ( _) => StatusCode :: NOT_FOUND ,
814
954
StreamError :: Custom { status, .. } => * status,
0 commit comments