Skip to content

Commit a15cee8

Browse files
author
Harsh Dev Pathak
committed
feat: Added CI for in_memory_testing
1 parent a609e81 commit a15cee8

File tree

4 files changed

+138
-35
lines changed

4 files changed

+138
-35
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: In-Memory VSS Server CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
8+
concurrency:
9+
group: ${{ github.workflow }}-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
test-in-memory:
14+
runs-on: ubuntu-latest
15+
timeout-minutes: 5
16+
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
21+
- name: Create in-memory config
22+
run: |
23+
mkdir -p rust/server
24+
cat > rust/server/vss-server-config.toml <<EOF
25+
[server_config]
26+
host = "127.0.0.1"
27+
port = 8080
28+
store_type = "in_memory"
29+
EOF
30+
31+
- name: Build server
32+
working-directory: rust
33+
run: cargo build --release --bin server
34+
35+
- name: Start VSS server
36+
working-directory: rust
37+
run: |
38+
./target/release/server vss-server-config.toml > server.log 2>&1 &
39+
echo "Server PID: $!"
40+
41+
- name: Wait for server
42+
run: |
43+
for i in {1..15}; do
44+
if curl -s http://127.0.0.1:8080 > /dev/null; then
45+
echo "Server is up!"
46+
exit 0
47+
fi
48+
sleep 1
49+
done
50+
echo "Server failed:"
51+
cat rust/server.log
52+
exit 1
53+
54+
- name: HTTP Smoke Test
55+
run: |
56+
# PUT: store="test", key="k1", value="kv1"
57+
curl -f \
58+
-H "Authorization: Bearer test_user" \
59+
--data-binary @<(echo "0A04746573741A150A026B3110FFFFFFFFFFFFFFFFFF011A046B317631" | xxd -r -p) \
60+
http://127.0.0.1:8080/vss/putObjects
61+
62+
# GET: store="test", key="k1"
63+
RESPONSE=$(curl -f \
64+
-H "Authorization: Bearer test_user" \
65+
--data-binary @<(echo "0A047465737412026B31" | xxd -r -p) \
66+
http://127.0.0.1:8080/vss/getObject)
67+
68+
- name: Run unit tests
69+
working-directory: rust
70+
run: cargo test --package impls --lib -- --nocapture

rust/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ cargo build --release
2525
cargo run -- server/vss-server-config.toml
2626
```
2727

28-
**Note:** For testing purposes you can edit `vss-server-config.toml` to use `store_type` as in-memory instead of PostgreSQL: `store_type = "in_memory"`
28+
**Note:** For testing purposes, you can pass `--in-memory` to use in-memory instead of PostgreSQL
29+
```
30+
cargo run -- server/vss-server-config.toml --in-memory
31+
```
2932
4. VSS endpoint should be reachable at `http://localhost:8080/vss`.
3033

3134
### Configuration

rust/impls/src/in_memory_store.rs

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -288,61 +288,82 @@ impl KvStore for InMemoryBackendImpl {
288288
&self, user_token: String, request: ListKeyVersionsRequest,
289289
) -> Result<ListKeyVersionsResponse, VssError> {
290290
let store_id = request.store_id;
291-
let key_prefix = request.key_prefix.unwrap_or("".to_string());
292-
let page_token = request.page_token.unwrap_or("".to_string());
291+
let key_prefix = request.key_prefix.unwrap_or_default();
292+
let page_token = request.page_token.unwrap_or_default();
293293
let page_size = request.page_size.unwrap_or(i32::MAX);
294294
let limit = std::cmp::min(page_size, LIST_KEY_VERSIONS_MAX_PAGE_SIZE) as usize;
295295

296+
// Global version only on first page
296297
let mut global_version = None;
297298
if page_token.is_empty() {
298-
let get_global_version_request = GetObjectRequest {
299+
let get_global = GetObjectRequest {
299300
store_id: store_id.clone(),
300301
key: GLOBAL_VERSION_KEY.to_string(),
301302
};
302-
let get_response = self.get(user_token.clone(), get_global_version_request).await?;
303-
global_version = Some(get_response.value.unwrap().version);
303+
let resp = self.get(user_token.clone(), get_global).await?;
304+
global_version = resp.value.map(|kv| kv.version);
304305
}
305306

306-
let key_versions: Vec<KeyValue> = {
307-
let guard = self.store.lock().unwrap();
308-
let mut key_versions: Vec<KeyValue> = guard
309-
.iter()
310-
.filter(|(k, _)| {
311-
let parts: Vec<&str> = k.split('#').collect();
312-
if parts.len() < 3 {
313-
return false;
307+
let guard = self.store.lock().unwrap();
308+
309+
let mut latest_per_key: std::collections::HashMap<String, KeyValue> =
310+
std::collections::HashMap::new();
311+
for (k, r) in guard.iter() {
312+
let parts: Vec<&str> = k.split('#').collect();
313+
if parts.len() != 3
314+
|| parts[0] != user_token.as_str()
315+
|| parts[1] != store_id.as_str()
316+
|| parts[2] == GLOBAL_VERSION_KEY
317+
|| !parts[2].starts_with(&key_prefix)
318+
{
319+
continue;
320+
}
321+
let key = parts[2].to_string();
322+
let candidate = KeyValue { key: key.clone(), value: Bytes::new(), version: r.version };
323+
latest_per_key
324+
.entry(key)
325+
.and_modify(|e| {
326+
if r.version > e.version {
327+
*e = candidate.clone()
314328
}
315-
parts[0] == user_token.as_str()
316-
&& parts[1] == store_id.as_str()
317-
&& parts[2].starts_with(&key_prefix)
318-
&& parts[2] > page_token.as_str()
319-
&& parts[2] != GLOBAL_VERSION_KEY
320-
})
321-
.map(|(_, record)| KeyValue {
322-
key: record.key.clone(),
323-
value: Bytes::new(),
324-
version: record.version,
325329
})
326-
.collect();
330+
.or_insert(candidate);
331+
}
327332

328-
key_versions.sort_by(|a, b| a.key.cmp(&b.key));
329-
key_versions.into_iter().take(limit).collect()
330-
};
333+
let mut keys: Vec<String> = latest_per_key.keys().cloned().collect();
334+
keys.sort();
331335

332-
let next_page_token = if key_versions.len() == limit {
333-
key_versions.last().map(|kv| kv.key.clone())
336+
let start_idx = if page_token.is_empty() {
337+
0
334338
} else {
335-
None
339+
match keys.iter().position(|k| k == &page_token) {
340+
Some(i) => i + 1,
341+
None => {
342+
return Ok(ListKeyVersionsResponse {
343+
key_versions: vec![],
344+
next_page_token: None,
345+
global_version,
346+
});
347+
},
348+
}
336349
};
337350

338-
Ok(ListKeyVersionsResponse { key_versions, next_page_token, global_version })
351+
let page_keys: Vec<String> = keys.into_iter().skip(start_idx).take(limit).collect();
352+
let page: Vec<KeyValue> =
353+
page_keys.iter().filter_map(|k| latest_per_key.get(k).cloned()).collect();
354+
355+
let next_page_token =
356+
if page.is_empty() { None } else { page.last().map(|kv| kv.key.clone()) };
357+
358+
Ok(ListKeyVersionsResponse { key_versions: page, next_page_token, global_version })
339359
}
340360
}
341361

342362
#[cfg(test)]
343363
mod tests {
344364
use super::*;
345365
use api::define_kv_store_tests;
366+
use api::types::{GetObjectRequest, KeyValue, PutObjectRequest};
346367
use bytes::Bytes;
347368
use tokio::test;
348369

rust/server/src/main.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,28 @@ pub(crate) mod vss_service;
2929

3030
fn main() {
3131
let args: Vec<String> = std::env::args().collect();
32-
if args.len() != 2 {
33-
eprintln!("Usage: {} <config-file-path>", args[0]);
32+
if args.len() < 2 {
33+
eprintln!("Usage: {} <config-file-path> [--in-memory]", args[0]);
3434
std::process::exit(1);
3535
}
3636

37-
let config = match util::config::load_config(&args[1]) {
37+
let config_path = &args[1];
38+
let use_in_memory = args.contains(&"--in-memory".to_string());
39+
40+
let mut config = match util::config::load_config(config_path) {
3841
Ok(cfg) => cfg,
3942
Err(e) => {
4043
eprintln!("Failed to load configuration: {}", e);
4144
std::process::exit(1);
4245
},
4346
};
4447

48+
// Override the `store_type` if --in-memory flag passed
49+
if use_in_memory {
50+
println!("Overriding backend type: using in-memory backend (via --in-memory flag)");
51+
config.server_config.store_type = "in_memory".to_string();
52+
}
53+
4554
let addr: SocketAddr =
4655
match format!("{}:{}", config.server_config.host, config.server_config.port).parse() {
4756
Ok(addr) => addr,

0 commit comments

Comments
 (0)