Skip to content

Commit 300272c

Browse files
committed
rofl-appd: Add upsert and delete metadata endpoint
1 parent 3e00a16 commit 300272c

4 files changed

Lines changed: 151 additions & 10 deletions

File tree

docs/rofl/features/appd.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,38 @@ and will trigger a registration refresh if the metadata has changed.
234234
**Note:** Metadata is validated against runtime-configured limits for the
235235
number of pairs, key size, and value size.
236236

237+
#### Upsert Metadata
238+
239+
Insert or update the given metadata key-value pairs. Unlike `Set`, this leaves
240+
existing keys that are not present in the request untouched. It will trigger a
241+
registration refresh if the metadata has changed.
242+
243+
**Endpoint:** `/rofl/v1/metadata` (`PUT`)
244+
245+
**Example request:**
246+
247+
```json
248+
{
249+
"version": "1.0.1"
250+
}
251+
```
252+
253+
**Note:** The resulting metadata is validated against runtime-configured limits
254+
for the number of pairs, key size, and value size.
255+
256+
#### Delete Metadata
257+
258+
Delete the given metadata keys. Keys that do not exist are ignored. It will
259+
trigger a registration refresh if the metadata has changed.
260+
261+
**Endpoint:** `/rofl/v1/metadata` (`DELETE`)
262+
263+
**Example request:**
264+
265+
```json
266+
["version", "key_fingerprint"]
267+
```
268+
237269
### Query
238270

239271
Runs arbitrary query method defined in the [Oasis Runtime SDK] module and

rofl-appd/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ where
4343

4444
let server = server.manage(cfg.metadata).mount(
4545
"/rofl/v1/metadata",
46-
routes![routes::metadata::set, routes::metadata::get],
46+
routes![
47+
routes::metadata::set,
48+
routes::metadata::upsert,
49+
routes::metadata::delete,
50+
routes::metadata::get
51+
],
4752
);
4853

4954
let server = server.mount("/rofl/v1/query", routes![routes::query::query]);

rofl-appd/src/routes/metadata.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
use std::{collections::BTreeMap, sync::Arc};
1+
use std::{
2+
collections::{BTreeMap, BTreeSet},
3+
sync::Arc,
4+
};
25

3-
use rocket::{http::Status, serde::json::Json, State};
6+
use rocket::{http::Status, serde::json::Json, suppress, State};
47

58
use crate::services::metadata::MetadataService;
69

@@ -16,6 +19,36 @@ pub async fn set(
1619
.map_err(|err| (Status::BadRequest, err.to_string()))
1720
}
1821

22+
/// Upsert metadata endpoint.
23+
///
24+
/// Inserts or updates the given key-value pairs, leaving other keys untouched.
25+
#[rocket::put("/", data = "<body>")]
26+
pub async fn upsert(
27+
body: Json<BTreeMap<String, String>>,
28+
metadata: &State<Arc<dyn MetadataService>>,
29+
) -> Result<(), (Status, String)> {
30+
metadata
31+
.upsert(body.into_inner())
32+
.await
33+
.map_err(|err| (Status::BadRequest, err.to_string()))
34+
}
35+
36+
/// Delete metadata endpoint.
37+
///
38+
/// Removes the given keys, ignoring those that do not exist.
39+
// Rocket complains, if DELETE requires body. Suppress it.
40+
#[suppress(dubious_payload)]
41+
#[rocket::delete("/", data = "<body>")]
42+
pub async fn delete(
43+
body: Json<BTreeSet<String>>,
44+
metadata: &State<Arc<dyn MetadataService>>,
45+
) -> Result<(), (Status, String)> {
46+
metadata
47+
.delete(body.into_inner())
48+
.await
49+
.map_err(|err| (Status::BadRequest, err.to_string()))
50+
}
51+
1952
/// Get metadata endpoint.
2053
#[rocket::get("/")]
2154
pub async fn get(

rofl-appd/src/services/metadata.rs

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::BTreeMap;
1+
use std::collections::{BTreeMap, BTreeSet};
22

33
use rocket::async_trait;
44
use tokio::sync::RwLock;
@@ -24,6 +24,18 @@ pub trait MetadataService: Send + Sync {
2424
/// refresh if the metadata has changed.
2525
async fn set(&self, metadata: BTreeMap<String, String>) -> Result<(), Error>;
2626

27+
/// Insert or update the given metadata key-value pairs.
28+
///
29+
/// Existing keys not present in `metadata` are left untouched. Will trigger a
30+
/// registration refresh if the metadata has changed.
31+
async fn upsert(&self, metadata: BTreeMap<String, String>) -> Result<(), Error>;
32+
33+
/// Delete the given metadata keys.
34+
///
35+
/// Keys that do not exist are ignored. Will trigger a registration refresh if the
36+
/// metadata has changed.
37+
async fn delete(&self, keys: BTreeSet<String>) -> Result<(), Error>;
38+
2739
/// Get all user-set metadata key-value pairs.
2840
async fn get(&self) -> Result<BTreeMap<String, String>, Error>;
2941
}
@@ -67,19 +79,16 @@ impl<A: App> OasisMetadataService<A> {
6779
limits,
6880
})
6981
}
70-
}
7182

72-
#[async_trait]
73-
impl<A: App> MetadataService for OasisMetadataService<A> {
74-
async fn set(&self, metadata: BTreeMap<String, String>) -> Result<(), Error> {
75-
// Validate metadata against runtime limits.
83+
/// Validate the given metadata map against the configured runtime limits.
84+
fn validate(&self, metadata: &BTreeMap<String, String>) -> Result<(), Error> {
7685
let max_user_pairs =
7786
(self.limits.max_pairs as usize).saturating_sub(RESERVED_METADATA_SLOTS);
7887
if metadata.len() > max_user_pairs {
7988
return Err(Error::InvalidArgument);
8089
}
8190

82-
for (key, value) in &metadata {
91+
for (key, value) in metadata {
8392
// Account for namespace prefix when checking key size.
8493
let full_key_size = METADATA_NAMESPACE.len() + 1 + key.len();
8594
if full_key_size > self.limits.max_key_size as usize {
@@ -90,8 +99,70 @@ impl<A: App> MetadataService for OasisMetadataService<A> {
9099
}
91100
}
92101

102+
Ok(())
103+
}
104+
}
105+
106+
#[async_trait]
107+
impl<A: App> MetadataService for OasisMetadataService<A> {
108+
async fn set(&self, metadata: BTreeMap<String, String>) -> Result<(), Error> {
109+
// Validate metadata against runtime limits.
110+
self.validate(&metadata)?;
111+
93112
let mut map = self.metadata.write().await;
113+
if *map == metadata {
114+
return Ok(());
115+
}
94116
*map = metadata;
117+
drop(map);
118+
119+
// Refresh registration.
120+
self.env.refresh_registration().await?;
121+
122+
Ok(())
123+
}
124+
125+
async fn upsert(&self, metadata: BTreeMap<String, String>) -> Result<(), Error> {
126+
let mut map = self.metadata.write().await;
127+
128+
// Apply the upsert onto a copy and validate the resulting map against runtime
129+
// limits before committing.
130+
let mut updated = map.clone();
131+
let mut changed = false;
132+
for (key, value) in metadata {
133+
if updated.get(&key) != Some(&value) {
134+
changed = true;
135+
}
136+
updated.insert(key, value);
137+
}
138+
self.validate(&updated)?;
139+
140+
if !changed {
141+
return Ok(());
142+
}
143+
*map = updated;
144+
drop(map);
145+
146+
// Refresh registration.
147+
self.env.refresh_registration().await?;
148+
149+
Ok(())
150+
}
151+
152+
async fn delete(&self, keys: BTreeSet<String>) -> Result<(), Error> {
153+
let mut map = self.metadata.write().await;
154+
155+
let mut changed = false;
156+
for key in &keys {
157+
if map.remove(key).is_some() {
158+
changed = true;
159+
}
160+
}
161+
162+
if !changed {
163+
return Ok(());
164+
}
165+
drop(map);
95166

96167
// Refresh registration.
97168
self.env.refresh_registration().await?;

0 commit comments

Comments
 (0)