Add documentation for persistent storage grpc API
[AGL/documentation.git] / docs / 06_Component_Documentation / 12_AGL_Persistent_Storage_API.md
1 # Persistent Storage API for the Automotive Grade Linux demo
2
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).
11
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'.
19
20 The AGL Persistent Storage API is constructed using a layered architecture:
21
22 - Controller layer: translates proto calls to service calls.
23 - Service layer: communicates with the controller and facade layers, implements
24   the business logic
25 - Facade layer: implements RocksDB.
26
27 ## API Specification
28
29 **Namespaces**
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.
31
32 - `DestroyDB() -> StandardResponse(success: boolean, message: string)`
33
34   - Consumer wants to destroy the entire database.
35
36     ```text
37     DestroyDB() -> //destroys entire database.
38     ```
39
40 - `Write(key: string, value: string, namespace: string) -> StandardResponse(success: boolean, message: string)`
41
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*.
46
47     ```text
48     Write('Vehicle.Infotainment.Radio.CurrentStation':'wdr 4') -> Response
49
50     Write('Vehicle.Infotainment':'yes') -> Response
51
52     Write('test':'1') -> Response
53
54     Write('':'test') -> Error
55
56     Write(key: 'Private.Info', value: 'test', namespace: 'AppName') -> Response
57     ```
58
59 - `Read(key: string, namespace: string) -> ReadResponse(success: boolean, message: string, value: string)`
60
61   - Consumer wants to read *value* of existing *key* in a given *namespace* (default is ""), e.g.
62     'Vehicle.Infotainment.Radio.CurrentStation':
63
64     ```text
65     Read('Vehicle.Infotainment.Radio.CurrentStation') -> 'wdr 4'
66
67     Read('Vehicle.doesNotExist') -> ERROR
68
69     Read(key: 'Private.Info', namespace: 'AppName') -> 'test'
70     ```
71
72 - `Delete(key: string, namespace: string) -> StandardResponse(success: boolean, message: string)`
73
74   - Consumer wants to delete an existing *key* + *value* from a given *namespace* (default is ""), e.g.
75     'Vehicle.Infotainment.Radio.CurrentStation':
76
77     ```text
78     Delete('Vehicle.Infotainment.Radio.CurrentStation') -> Response
79
80     Delete('Vehicle.doesNotExist') -> ERROR
81
82     Delete(key: 'Private.Info', namespace: 'AppName') -> Response
83     ```
84
85 - `Search(key: string, namespace: string) -> ListResponse(success: boolean, message: string, keys: repeated string)`
86
87   - Consumer wants to list all keys that contain *key* in a given *namespace* (default is ""), e.g. 'Radio'
88
89     ```text
90     Search('Radio') -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Communication.Radio.Volume')
91
92     Search('Info') -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature')
93
94     Search('nt.Rad') -> ('Vehicle.Infotainment.Radio.CurrentStation')
95
96     Search('') -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature', 'Vehicle.Communication.Radio.Volume')
97
98     Search(key: '', namespace: 'AppName') -> ('Private.Info')
99     ```
100
101 - `DeleteNodes(key: string, namespace: string) -> StandardResponse(success: boolean, message: string)`
102
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.
107
108     ```text
109     DeleteNodes('Vehicle.Infotainment') -> Response //deletes ('Vehicle.Infotainment', 'Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature')
110
111     DeleteNodes('Vehicle') -> Response //deletes ('Vehicle.Infotainment', 'Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature', 'Vehicle.Communication.Radio.Volume')
112
113     DeleteNodes('') -> ERROR
114
115     DeleteNodes('DoesNotExist') -> ERROR
116
117     DeleteNodes('Vehic') -> ERROR
118
119     DeleteNodes(key: 'Private', namespace: 'AppName') -> Response //deletes ('Private.Info')
120     ```
121
122 - `ListNodes(node: string, layers: optional int, namespace: string) -> ListResponse(boolean, message, repeated string keys)`
123
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'
126
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.
131
132     ```text
133     ListNodes('Vehicle.Infotainment', 1) -> ('Vehicle.Infotainment.Radio', 'Vehicle.Infotainment.HVAC')
134
135     ListNodes('Vehicle.Infotainment', 2) -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature')
136
137     ListNodes('Vehicle', 0) -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature', 'Vehicle.Communication.Radio.Volume', 'Vehicle.Infotainment')
138
139     ListNodes('', 0) -> ('Vehicle.Infotainment.Radio.CurrentStation', 'Vehicle.Infotainment.Radio.Volume', 'Vehicle.Infotainment.HVAC.OutdoorTemperature', 'Vehicle.Communication.Radio.Volume', 'Vehicle.Infotainment', 'test')
140
141     ListNodes('Vehicle.Infotainment') -> ('Vehicle.Infotainment.Radio', 'Vehicle.Infotainment.HVAC')
142
143     ListNodes('', 1) -> ('Vehicle', 'test')
144
145     ListNodes('Vehicle.Infotainment.Radio.Volume', 1) -> ()
146
147     ListNodes('Vehicle', -1) -> ERROR
148
149     ListNodes('Vehicle.DoesNotExist', 1) -> ERROR
150
151     ListNodes(key: 'Private', namespace: 'AppName') -> ('Private.Info')
152
153     For empty data base:
154     ListNodes('', 1) -> ()
155     ```
156
157 ## Example Tree
158
159 Note: nodes marked by \* are keys (and therefore have a value)
160
161 **Namespace: ""**
162 - Vehicle
163   - Infotainment \*
164     - Radio
165       - CurrentStation \*
166       - Volume \*
167     - HVAC
168       - OutdoorTemperature \*
169   - Communication
170     - Radio
171       - Volume \*
172 - test \*
173
174 **Namespace: "AppName"**
175 - Private
176   - Info \*
177
178 ## Setup instructions
179
180 1. Install rust
181
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`
186    
187 3. Build application
188
189    ```bash
190    cargo build
191    ```
192
193 4. Run tests
194
195    ```bash
196    cargo test
197    ```
198
199 5. Start server
200
201    ```bash
202    cargo run --release --bin server
203    ```
204
205 ## rpc Usage
206
207 Insomnia usage for manual testing is describd in
208 https://konghq.com/blog/engineering/building-grpc-apis-with-rust
209
210 ```text
211 DestroyDB: {}
212
213 Write: { "key": "foo", "value": "foobar", "namespace": "bar" }
214
215 Read: { "key": "foo", "namespace": "bar" }
216
217 Delete: { "key": "foo", "namespace": "bar" }
218
219 Search: { "key": "foo", "namespace": "bar" }
220
221 DeleteNodes: { "key": "foo", "namespace": "bar" }
222
223 ListNodes: { "key": "foo", "layers": 1, "namespace": "bar" }
224
225 ```