1 # Persistent Storage API for the Automotive Grade Linux demo
3 The [AGL Persistent Storage API](https://github.com/LSchwiedrzik/agl-persistent-storage-api)
4 is a grpc API for [AGL](https://www.automotivelinux.org/)
5 that serves as persistent storage API for the demo. The API is written
6 in Rust and makes use of [tonic](https://crates.io/crates/tonic-build) for grpc
7 functionality as well as [RocksDB](https://rocksdb.org/) as a database backend,
8 using [rust-rocksdb](https://crates.io/crates/rust-rocksdb). Use cases include
9 retaining settings over a system shutdown (e.g. audio, HVAC, profile data, Wifi
10 settings, radio presets, metric vs imperial units).
12 The most important hardware consideration for this project is that the AGL demo
13 runs on embedded hardware with flash storage, so we want to minimize number of
14 write operations. This impacts the choice of database; we have chosen to work
15 with RocksDB as it is well-suited for embedded computing and tunable with
16 respect to write amplification. In principle the API is flexible with
17 respect to database used (pluggable backends), but only RocksDB is implemented.
18 This API is part of the AGL demo as of release 'Royal Ricefish'.
20 The AGL Persistent Storage API is constructed using a layered architecture:
22 - Controller layer: translates proto calls to service calls.
23 - Service layer: communicates with the controller and facade layers, implements
25 - Facade layer: implements RocksDB.
30 The rpcs described below interact with keys belonging to specific namespaces. This feature enables applications to maintain private namespaces within the same database. Not specifying a namespace when calling the API will result in the default namespace "" being used. Alternatively, a specific namespace (e.g. "AppName") can be chosen. With the exception of DestroyDB, which acts on the entire database, all rpcs can only interact with one namespace at a time.
32 - `DestroyDB() -> StandardResponse(success: boolean, message: string)`
34 - Consumer wants to destroy the entire database.
37 DestroyDB() -> //destroys entire database.
40 - `Write(key: string, value: string, namespace: string) -> StandardResponse(success: boolean, message: string)`
42 - Consumer wants to save *key* + *value* to a given *namespace* (default is ""), (e.g.
43 'Vehicle.Infotainment.Radio.CurrentStation':'hr5').
44 - This overwrites existing *value* under *key*.
45 - An empty string cannot be used as a *key*.
48 Write('Vehicle.Infotainment.Radio.CurrentStation':'wdr 4') -> Response
50 Write('Vehicle.Infotainment':'yes') -> Response
52 Write('test':'1') -> Response
54 Write('':'test') -> Error
56 Write(key: 'Private.Info', value: 'test', namespace: 'AppName') -> Response
59 - `Read(key: string, namespace: string) -> ReadResponse(success: boolean, message: string, value: string)`
61 - Consumer wants to read *value* of existing *key* in a given *namespace* (default is ""), e.g.
62 'Vehicle.Infotainment.Radio.CurrentStation':
65 Read('Vehicle.Infotainment.Radio.CurrentStation') -> 'wdr 4'
67 Read('Vehicle.doesNotExist') -> ERROR
69 Read(key: 'Private.Info', namespace: 'AppName') -> 'test'
72 - `Delete(key: string, namespace: string) -> StandardResponse(success: boolean, message: string)`
74 - Consumer wants to delete an existing *key* + *value* from a given *namespace* (default is ""), e.g.
75 'Vehicle.Infotainment.Radio.CurrentStation':
78 Delete('Vehicle.Infotainment.Radio.CurrentStation') -> Response
80 Delete('Vehicle.doesNotExist') -> ERROR
82 Delete(key: 'Private.Info', namespace: 'AppName') -> Response
85 - `Search(key: string, namespace: string) -> ListResponse(success: boolean, message: string, keys: repeated string)`
87 - Consumer wants to list all keys that contain *key* in a given *namespace* (default is ""), e.g. 'Radio'
90 Search('Radio') -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Communication.Radio.Volume')
92 Search('Info') -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature')
94 Search('nt.Rad') -> ('Vehicle.Infotainment.Radio.CurrentStation')
96 Search('') -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature', 'Vehicle.Communication.Radio.Volume')
98 Search(key: '', namespace: 'AppName') -> ('Private.Info')
101 - `DeleteNodes(key: string, namespace: string) -> StandardResponse(success: boolean, message: string)`
103 - Consumer wants to delete all keys located in the subtree with root *key*, within the given *namespace* (default is ""), e.g.
104 'Vehicle.Infotainment'
105 - `key = ''` returns `ERROR`
106 - This rpc assumes that keys follow a VSS-like tree structure.
109 DeleteNodes('Vehicle.Infotainment') -> Response //deletes ('Vehicle.Infotainment', 'Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature')
111 DeleteNodes('Vehicle') -> Response //deletes ('Vehicle.Infotainment', 'Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature', 'Vehicle.Communication.Radio.Volume')
113 DeleteNodes('') -> ERROR
115 DeleteNodes('DoesNotExist') -> ERROR
117 DeleteNodes('Vehic') -> ERROR
119 DeleteNodes(key: 'Private', namespace: 'AppName') -> Response //deletes ('Private.Info')
122 - `ListNodes(node: string, layers: optional int, namespace: string) -> ListResponse(boolean, message, repeated string keys)`
124 - Consumer wants to list all nodes located in the subtree with root *node* exactly *layers*
125 layers deep, within the given *namespace* (default is "") , e.g. 'Vehicle.Infotainment'
127 - `layers = 0` lists all keys that start in *node* any number of *layers* deep
128 - `layers` default value is 1
129 - `node = ''` returns top-level root node(s)
130 - This rpc assumes that keys follow a VSS-like tree structure.
133 ListNodes('Vehicle.Infotainment', 1) -> ('Vehicle.Infotainment.Radio', 'Vehicle.Infotainment.HVAC')
135 ListNodes('Vehicle.Infotainment', 2) -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature')
137 ListNodes('Vehicle', 0) -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature', 'Vehicle.Communication.Radio.Volume', 'Vehicle.Infotainment')
139 ListNodes('', 0) -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature', 'Vehicle.Communication.Radio.Volume', 'Vehicle.Infotainment', 'test')
141 ListNodes('Vehicle.Infotainment') -> ('Vehicle.Infotainment.Radio', 'Vehicle.Infotainment.HVAC')
143 ListNodes('', 1) -> ('Vehicle', 'test')
145 ListNodes('Vehicle.Infotainment.Radio.Volume', 1) -> ()
147 ListNodes('Vehicle', -1) -> ERROR
149 ListNodes('Vehicle.DoesNotExist', 1) -> ERROR
151 ListNodes(key: 'Private', namespace: 'AppName') -> ('Private.Info')
154 ListNodes('', 1) -> ()
159 Note: nodes marked by \* are keys (and therefore have a value)
168 - OutdoorTemperature \*
174 **Namespace: "AppName"**
178 ## Setup instructions
182 2. Download or install protobuf (e.g. from
183 [here](https://github.com/protocolbuffers/protobuf/releases)) and set the
184 `PROTOC` environment variable:
185 `echo -n "export PROTOC=/path/to/protoc.exe" >> ~/.bashrc`
202 cargo run --release --bin server
207 Insomnia usage for manual testing is describd in
208 https://konghq.com/blog/engineering/building-grpc-apis-with-rust
213 Write: { "key": "foo", "value": "foobar", "namespace": "bar" }
215 Read: { "key": "foo", "namespace": "bar" }
217 Delete: { "key": "foo", "namespace": "bar" }
219 Search: { "key": "foo", "namespace": "bar" }
221 DeleteNodes: { "key": "foo", "namespace": "bar" }
223 ListNodes: { "key": "foo", "layers": 1, "namespace": "bar" }