Skip to content

Commit 33009bf

Browse files
authored
Add support for automatic stream creation (#211)
In case the user specifies x-p-stream in the header and the stream doesn't exist, server failed with stream doesn't exist error. But in automated environments like kubernetes, it is important to dynamically create the stream so that Parseable is able to ingest the log data quickly without too much user involvement.
1 parent 9c1c13d commit 33009bf

File tree

7 files changed

+84
-53
lines changed

7 files changed

+84
-53
lines changed

server/src/event.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ impl Event {
172172
let inferred_schema = self.infer_schema()?;
173173

174174
let event = self.get_reader(inferred_schema.clone());
175-
176175
let stream_schema = metadata::STREAM_INFO.schema(&self.stream_name)?;
177176

178177
if let Some(existing_schema) = stream_schema {
@@ -211,7 +210,7 @@ impl Event {
211210
// note for functions _schema_with_map and _set_schema_with_map,
212211
// these are to be called while holding a write lock specifically.
213212
// this guarantees two things
214-
// - no other metadata operation can happen inbetween
213+
// - no other metadata operation can happen in between
215214
// - map always have an entry for this stream
216215

217216
let stream_name = &self.stream_name;

server/src/handlers/event.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use self::error::{PostError, QueryError};
3232

3333
const PREFIX_TAGS: &str = "x-p-tag-";
3434
const PREFIX_META: &str = "x-p-meta-";
35-
const STREAM_NAME_HEADER_KEY: &str = "x-p-stream-name";
35+
const STREAM_NAME_HEADER_KEY: &str = "x-p-stream";
3636
const SEPARATOR: char = '^';
3737

3838
pub async fn query(_req: HttpRequest, json: web::Json<Value>) -> Result<HttpResponse, QueryError> {
@@ -49,6 +49,9 @@ pub async fn query(_req: HttpRequest, json: web::Json<Value>) -> Result<HttpResp
4949
.map_err(|e| e.into())
5050
}
5151

52+
// Handler for POST /api/v1/ingest
53+
// ingests events into the specified logstream in the header
54+
// if the logstream does not exist, it is created
5255
pub async fn ingest(
5356
req: HttpRequest,
5457
body: web::Json<serde_json::Value>,
@@ -58,21 +61,24 @@ pub async fn ingest(
5861
.iter()
5962
.find(|&(key, _)| key == STREAM_NAME_HEADER_KEY)
6063
{
61-
push_logs(stream_name.to_str().unwrap().to_owned(), req, body).await?;
62-
64+
let str_name = stream_name.to_str().unwrap().to_owned();
65+
super::logstream::create_stream_if_not_exists(str_name.clone()).await;
66+
push_logs(str_name, req, body).await?;
6367
Ok(HttpResponse::Ok().finish())
6468
} else {
6569
Err(PostError::Header(ParseHeaderError::MissingStreamName))
6670
}
6771
}
6872

73+
// Handler for POST /api/v1/logstream/{logstream}
74+
// only ingests events into the specified logstream
75+
// fails if the logstream does not exist
6976
pub async fn post_event(
7077
req: HttpRequest,
7178
body: web::Json<serde_json::Value>,
7279
) -> Result<HttpResponse, PostError> {
7380
let stream_name: String = req.match_info().get("logstream").unwrap().parse().unwrap();
7481
push_logs(stream_name, req, body).await?;
75-
7682
Ok(HttpResponse::Ok().finish())
7783
}
7884

server/src/handlers/logstream.rs

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -164,50 +164,9 @@ pub async fn get_alert(req: HttpRequest) -> HttpResponse {
164164
.to_http()
165165
}
166166

167-
pub async fn put(req: HttpRequest) -> HttpResponse {
167+
pub async fn put_stream(req: HttpRequest) -> HttpResponse {
168168
let stream_name: String = req.match_info().get("logstream").unwrap().parse().unwrap();
169-
170-
// fail to proceed if there is an error in log stream name validation
171-
if let Err(e) = validator::stream_name(&stream_name) {
172-
return response::ServerResponse {
173-
msg: format!("failed to create log stream due to err: {}", e),
174-
code: StatusCode::BAD_REQUEST,
175-
}
176-
.to_http();
177-
}
178-
179-
let s3 = S3::new();
180-
181-
// Proceed to create log stream if it doesn't exist
182-
if s3.get_schema(&stream_name).await.is_err() {
183-
// Fail if unable to create log stream on object store backend
184-
if let Err(e) = s3.create_stream(&stream_name).await {
185-
return response::ServerResponse {
186-
msg: format!(
187-
"failed to create log stream {} due to err: {}",
188-
stream_name, e
189-
),
190-
code: StatusCode::INTERNAL_SERVER_ERROR,
191-
}
192-
.to_http();
193-
}
194-
metadata::STREAM_INFO.add_stream(stream_name.to_string(), None, Alerts::default());
195-
return response::ServerResponse {
196-
msg: format!("created log stream {}", stream_name),
197-
code: StatusCode::OK,
198-
}
199-
.to_http();
200-
}
201-
202-
// Error if the log stream already exists
203-
response::ServerResponse {
204-
msg: format!(
205-
"log stream {} already exists, please create a new log stream with unique name",
206-
stream_name
207-
),
208-
code: StatusCode::BAD_REQUEST,
209-
}
210-
.to_http()
169+
create_stream_if_not_exists(stream_name).await
211170
}
212171

213172
pub async fn put_alert(req: HttpRequest, body: web::Json<serde_json::Value>) -> HttpResponse {
@@ -349,3 +308,47 @@ fn remove_id_from_alerts(value: &mut Value) {
349308
});
350309
}
351310
}
311+
312+
// Check if the stream exists and create a new stream if doesn't exist
313+
pub async fn create_stream_if_not_exists(stream_name: String) -> HttpResponse {
314+
if metadata::STREAM_INFO.stream_exists(stream_name.as_str()) {
315+
// Error if the log stream already exists
316+
response::ServerResponse {
317+
msg: format!(
318+
"log stream {} already exists, please create a new log stream with unique name",
319+
stream_name
320+
),
321+
code: StatusCode::BAD_REQUEST,
322+
}
323+
.to_http();
324+
}
325+
326+
// fail to proceed if invalid stream name
327+
if let Err(e) = validator::stream_name(&stream_name) {
328+
response::ServerResponse {
329+
msg: format!("failed to create log stream due to err: {}", e),
330+
code: StatusCode::BAD_REQUEST,
331+
}
332+
.to_http();
333+
}
334+
335+
// Proceed to create log stream if it doesn't exist
336+
let s3 = S3::new();
337+
if let Err(e) = s3.create_stream(&stream_name).await {
338+
// Fail if unable to create log stream on object store backend
339+
response::ServerResponse {
340+
msg: format!(
341+
"failed to create log stream {} due to err: {}",
342+
stream_name, e
343+
),
344+
code: StatusCode::INTERNAL_SERVER_ERROR,
345+
}
346+
.to_http();
347+
}
348+
metadata::STREAM_INFO.add_stream(stream_name.to_string(), None, Alerts::default());
349+
response::ServerResponse {
350+
msg: format!("created log stream {}", stream_name),
351+
code: StatusCode::OK,
352+
}
353+
.to_http()
354+
}

server/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
265265
// logstream API
266266
web::resource(logstream_path("{logstream}"))
267267
// PUT "/logstream/{logstream}" ==> Create log stream
268-
.route(web::put().to(handlers::logstream::put))
268+
.route(web::put().to(handlers::logstream::put_stream))
269269
// POST "/logstream/{logstream}" ==> Post logs to given log stream
270270
.route(web::post().to(handlers::event::post_event))
271271
// DELETE "/logstream/{logstream}" ==> Delete log stream

server/src/metadata.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ impl STREAM_INFO {
8080
})
8181
}
8282

83+
pub fn stream_exists(&self, stream_name: &str) -> bool {
84+
let map = self.read().expect(LOCK_EXPECT);
85+
map.contains_key(stream_name)
86+
}
87+
8388
pub fn schema(&self, stream_name: &str) -> Result<Option<Schema>, MetadataError> {
8489
let map = self.read().expect(LOCK_EXPECT);
8590
map.get(stream_name)
@@ -112,7 +117,7 @@ impl STREAM_INFO {
112117
}
113118

114119
pub async fn load(&self, storage: &impl ObjectStorage) -> Result<(), LoadError> {
115-
// When loading streams this funtion will assume list_streams only returns valid streams.
120+
// When loading streams this function will assume list_streams only returns valid streams.
116121
// a valid stream would have a .schema file.
117122
// .schema file could be empty in that case it will be treated as an uninitialized stream.
118123
// return error in case of an error from object storage itself.

server/src/stats.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
/*
2+
* Parseable Server (C) 2022 Parseable, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Affero General Public License as
6+
* published by the Free Software Foundation, either version 3 of the
7+
* License, or (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*
17+
*/
18+
119
use std::sync::atomic::{AtomicU64, Ordering};
220

321
use serde::{Deserialize, Serialize};

server/src/utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub fn merge(value: Value, fields: HashMap<String, String>) -> Value {
3535
for (k, v) in fields {
3636
match m.get_mut(&k) {
3737
Some(val) => {
38-
let mut final_val = String::new();
38+
let mut final_val = String::default();
3939
final_val.push_str(val.as_str().unwrap());
4040
final_val.push(',');
4141
final_val.push_str(&v);
@@ -103,7 +103,7 @@ pub mod header_parsing {
103103
SeperatorInKey(char),
104104
#[error("A value passed in header contains reserved char {0}")]
105105
SeperatorInValue(char),
106-
#[error("Stream name not found in header")]
106+
#[error("Stream name not found in header [x-p-stream]")]
107107
MissingStreamName,
108108
}
109109

0 commit comments

Comments
 (0)