Skip to content

Commit ab4b05c

Browse files
authored
Merge pull request #185 from minrk/kv-abstract
reduce requirements of KV store implementations and custom methods
2 parents 7e16095 + daee508 commit ab4b05c

File tree

13 files changed

+662
-926
lines changed

13 files changed

+662
-926
lines changed

docs/source/details.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Implementation details
2+
3+
## Traefik API
4+
5+
traefik-proxy uses the [Traefik API](https://doc.traefik.io/traefik/operations/api/) to monitor routes and configurations.
6+
7+
Because of **security** concerns, in traefik-proxy implementation, traefik api endpoint isn't exposed on the public http endpoint. Instead, it runs on a dedicated **authenticated endpoint** that's on localhost by default.
8+
9+
The port on which traefik-proxy's api will run, as well as the username and password used for authenticating, can be passed to the proxy through `jupyterhub_config.py`, e.g.:
10+
11+
```
12+
c.TraefikFileProviderProxy.traefik_api_url = "http://127.0.0.1:8099"
13+
c.TraefikFileProviderProxy.traefik_api_password = "admin"
14+
c.TraefikFileProviderProxy.traefik_api_username = "admin"
15+
```
16+
17+
Check out TraefikProxy's [API Reference](TraefikProxy) for more configuration options.
18+
19+
## Class structure
20+
21+
A JupyterHub Proxy implementation must implement these methods:
22+
23+
- `start` / `stop` (starting and stopping the proxy)
24+
- `add_route`
25+
- `delete_route`
26+
- `get_all_routes`
27+
- `get_route` (a default implementation is provided by the base class, based on get_all_routes)
28+
29+
Additionally, for traefik we need to set up the separate "static config" (API access and where routes will be stored) and "dynamic config" (the routing table itself, which changes as servers start and stop).
30+
Where and how dynamic_config is stored is the ~only difference between TraefikProxy subclasses.
31+
32+
Setting up traefik configuration is in these methods:
33+
34+
- `_setup_traefik_static_config` - must be extended by subclasses to add any provider-specific configuration to `self.static_config`
35+
- `_setup_traefik_dynamic_config` - usually not modified
36+
37+
TraefikProxy is organized into three levels:
38+
39+
First, is [](TraefikProxy). This class is responsible for everything traefik-specific.
40+
It implements talking to the traefik API, and computing _what_ dynamic configuration is needed for each step.
41+
This base class provides implementations of `start`, `stop`, `add_route`, `delete_route`, and `get_all_routes`.
42+
43+
TraefikProxy subclasses must implement _how_ dynamic config is stored, and set the appropriate static config to tell traefik how to load the dynamic config.
44+
Specifically, the generic methods:
45+
46+
- `_setup_traefik_static_config` should extend `self.static_config` to configure the traefik [configuration discovery provider](https://doc.traefik.io/traefik/providers/overview/)
47+
- `_apply_dynamic_config` stores a given dynamic config dictionary in the appropriate config store
48+
- `_delete_dynamic_config` removes keys from the dynamic config store
49+
- `_get_jupyterhub_dynamic_config` reads the whole jupyterhub part (not read by traefik itself)
50+
51+
We have two classes at this level:
52+
53+
- TraefikFileProviderProxy, which stores dynamic config in a toml or yaml file, and
54+
- TKvProxy - another base class, which implements the above, based on a generic notion of a key-value store
55+
56+
TKvProxy is an adapter layer, implementing all of the above methods, based on a few basic actions on key-value stores:
57+
58+
- `_kv_atomic_set` should take a _flat dictionary_ of key _paths_ and string values, and store them.
59+
- `_kv_atomic_delete` should delete a number of keys in a single transaction
60+
- `_kv_get_tree` should recursively read everything in the key-value store under a prefix (returning the _flattened_ dictionary)
61+
62+
TKvProxy is responsible for translating between key-value-friendly "flat" dictionaries and the 'true' nested dictionary format of the configuration (i.e. the nested dictionary `{"a": {"b": 5}}` will be flattened to `{"a/b": "5"}`).
63+
64+
Finally, we have our specific key-value store implementations: [](TraefikEtcdProxy) and [](TraefikConsulProxy).
65+
These classes only need to implement:
66+
67+
1. configuration necessary to connect to the key-value provider
68+
2. `_setup_traefik_static_config` to tell traefik how to talk to the same key-value provider
69+
3. the above three `_kv_` methods for reading, writing, and deleting keys
70+
71+
## Testing jupyterhub-traefik-proxy
72+
73+
You can then run the all the test suite from the _traefik-proxy_ directory with:
74+
75+
```
76+
$ pytest -v ./tests
77+
```
78+
79+
Or you can run a specific test with:
80+
81+
```
82+
$ pytest -v ./tests/<test-file-name>
83+
```

docs/source/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ consul
4444
4545
api/index
4646
changelog
47+
details
4748
```
4849

4950
## Indices and tables

docs/source/install.md

Lines changed: 0 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -96,119 +96,9 @@ c.JupyterHub.proxy_class = "traefik_file"
9696
```
9797
c.JupyterHub.proxy_class = "traefik_etcd"
9898
# will configure JupyterHub to run with TraefikEtcdProxy
99-
10099
```
101100
102101
```
103102
c.JupyterHub.proxy_class = "traefik_consul"
104103
# will configure JupyterHub to run with TraefikConsulProxy
105-
106-
```
107-
108-
## Implementation details
109-
110-
1. **Traefik API**
111-
112-
traefik-proxy uses the [Traefik API](https://doc.traefik.io/traefik/operations/api/) to monitor routes and configurations.
113-
114-
Because of **security** concerns, in traefik-proxy implementation, traefik api endpoint isn't exposed on the public http endpoint. Instead, it runs on a dedicated **authenticated endpoint** that's on localhost by default.
115-
116-
The port on which traefik-proxy's api will run, as well as the username and password used for authenticating, can be passed to the proxy through `jupyterhub_config.py`, e.g.:
117-
118-
```
119-
c.TraefikFileProviderProxy.traefik_api_url = "http://127.0.0.1:8099"
120-
c.TraefikFileProviderProxy.traefik_api_password = "admin"
121-
c.TraefikFileProviderProxy.traefik_api_username = "admin"
122-
```
123-
124-
Check out TraefikProxy's [API Reference](TraefikProxy) for more configuration options.
125-
126-
2. **TKvProxy class**
127-
128-
TKvProxy is a JupyterHub Proxy implementation that uses traefik and a key-value store.
129-
**TraefikEtcdProxy** and **TraefikConsulProxy** are proxy implementations that sublass `TKvProxy`.
130-
Other custom proxies that wish to implementat a JupyterHub Trafik KV store Proxy can sublass `TKvProxy`.
131-
**TKvProxy** implements JupyterHub's Proxy public API and there is no need to override these public methods.
132-
The methods that **must be implemented** by the proxies that sublass `TKvProxy` are:
133-
134-
- **_\_define_kv_specific_static_config()_**
135-
- Define the traefik static configuration that configures
136-
traefik's communication with the key-value store.
137-
- Will be called during startup if should_start is True.
138-
- Subclasses must define this method if the proxy is to be started by the Hub.
139-
- In order to be picked up by the proxy, the static configuration
140-
must be stored into `proxy.static_config` dict under the `kv_name` key.
141-
- **_\_kv_atomic_add_route_parts(jupyterhub_routespec, target, data, route_keys, rule)_**
142-
- Add the key-value pairs associated with a route within a key-value store transaction.
143-
- Will be called during add_route.
144-
- When retrieving or deleting a route, the parts of a route are expected to have the following structure:
145-
```
146-
[ key: jupyterhub_routespec , value: target ]
147-
[ key: target , value: data ]
148-
[ key: route_keys.backend_url_path , value: target ]
149-
[ key: route_keys.frontend_rule_path , value: rule ]
150-
[ key: route_keys.frontend_backend_path, value: route_keys.backend_alias]
151-
[ key: route_keys.backend_weight_path , value: w(int) ]
152-
# where w is the weight of the backend to be used during load balancing)
153-
```
154-
- Returns:
155-
- result (tuple):
156-
- The transaction status (int, 0: failure, positive: success)
157-
- The transaction response(str)
158-
- **_\_kv_atomic_delete_route_parts(jupyterhub_routespec, route_keys)_**
159-
- Delete the key-value pairs associated with a route, within a key-value store transaction (if the route exists).
160-
- Will be called during delete_route.
161-
- The keys associated with a route are:
162-
- jupyterhub_routespec
163-
- target
164-
- route_keys.backend_url_path
165-
- route_keys.frontend_rule_path
166-
- route_keys.frontend_backend_path
167-
- route_keys.backend_weight_path
168-
- Returns:
169-
- result (tuple):
170-
- The transaction status (int, 0: failure, positive: success)
171-
- The transaction response (str)
172-
- **_\_kv_get_target(jupyterhub_routespec)_**
173-
- Retrive the target from the key-value store.
174-
- The target is the value associated with `jupyterhub_routespec` key.
175-
- Returns:
176-
- The full URL associated with this route (str)
177-
- **_\_kv_get_data(target)_**
178-
- Retrive the data associated with the `target` from the key-value store.
179-
- Returns:
180-
- A JSONable dict that holds extra info about the route (dict)
181-
- **_\_kv_get_route_parts(kv_entry)_**
182-
- Retrive all the parts that make up a route (i.e. routespec, target, data) from the key-value store given a `kv_entry`.
183-
- A `kv_entry` is a key-value store entry where the key starts with `proxy.jupyterhub_prefix`. It is expected that only the routespecs
184-
will be prefixed with `proxy.jupyterhub_prefix` when added to the kv store.
185-
- Returns:
186-
- routespec: The normalized route specification passed in to add_route ([host]/path/)
187-
- target: The target host for this route (proto://host)
188-
- data: The arbitrary data dict that was passed in by JupyterHub when adding this route.
189-
- **_\_kv_get_jupyterhub_prefixed_entries()_**
190-
- Retrive from the kv store all the key-value pairs where the key starts with `proxy.jupyterhub_prefix`.
191-
- It is expected that only the routespecs will be prefixed with `proxy.jupyterhub_prefix` when added to the kv store.
192-
- Returns:
193-
- routes: A list of key-value store entries where the keys start with `proxy.jupyterhub_prefix`.
194-
195-
## Testing jupyterhub-traefik-proxy
196-
197-
There are some tests that use _etcdctl_ command line client for etcd.
198-
Make sure to set environment variable ETCDCTL_API=3 before running the tests, so that the v3 API to be used, e.g.:
199-
200-
```
201-
$ export ETCDCTL_API=3
202-
```
203-
204-
You can then run the all the test suite from the _traefik-proxy_ directory with:
205-
206-
```
207-
$ pytest -v ./tests
208-
```
209-
210-
Or you can run a specific test with:
211-
212-
```
213-
$ pytest -v ./tests/<test-file-name>
214104
```

0 commit comments

Comments
 (0)