Skip to content

Commit 96f40c9

Browse files
authored
Merge pull request #285 from amdfxlucas/seed-contrib07
option system framework
2 parents 6d82cc6 + 7c7d3a5 commit 96f40c9

30 files changed

+2431
-176
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
**/output/*
2+
**/.scion_build_output/*
23
**/eth-states*/*
34
**/test_log*/*
45
**/__pycache__/*

docs/designs/design.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,34 @@ In the configure stage, layers should register the data that other layers might
127127

128128
The configure stage is especially useful if a layer wants to make changes to another layer but still requires the other layer to have configured the emulator first. Currently, this is used in the `ReverseDomainName` and `CymruIpOrigin` service, both of which create a new zone in the `DomainName` service. They do so in the configure stage, as `DomainName` compiles the `Zone` data structure to zone files in the render stage, and additional zone added after the render stage won't be included in the final output.
129129

130+
#### Customizing layers and Entities (option system)
131+
132+
SEED has a rich option system which allows for fine grained overrides of global default settings.
133+
134+
Its workings are basically as follows:
135+
136+
137+
Options can be anything from boolean flags to numerical configuration parameters and are best thought of as just a strong type for key-value pairs (where the value is mutable, but the key isn't). Moreover each option carries with it the notion of supported modes. That is, whether it can be changed only at compile/build-time (because its value gets baked into the container image i.e. as a config file entry ) and any reconfiguration requires a re-compile and image rebuild or whether its existence extends into the runtime (i.e. as a container ENV variable ) and its reconfiguration requires a mere container restart. Some options i.e. Feature Flags might require the installation of additional software on the target node(the `Customizable`) for their implementation and thus will be strickly limited to build-time.
138+
139+
140+
Layers inherit `DynamicConfigurable` and can thus return a set of supported options (`getAvailableOptions()`).
141+
Configurables are the only place to retrieve options, as they cannot just be constructed by the users arbitrarily (i.e. Layers are only considering their own set of options with known predefined keys/names and ignore any unknowns i.e. from other Layers ).
142+
143+
The counterpart of configurables are `Customizables`, objects that can be configured by options. The API of Customizables allows the setting and querying of options at a certain `Scope`(which can i.e. be Global, on AS level, by NodeType like 'All-Routers', or even specific to an individual Node ). If unspecified the scope for both option setters and getters will default to the scope that is most specific (narrow) to the respective Customizable (i.e. AS or Node). This distinctive Scope value can be obtained from any Customizable through the identically named getter function( i.e. for an AutonomousSystem AS150 `scope()` will return `Scope(ScopeTier.AS, as_id=150, node_type=ScopeType.ANY)` ). As a rule of thumb, `Scope`s are technical and users shouldn't have to deal with them during 'daily buisiness' because all methods provide sensible defaults. But do not shy away though.
144+
145+
Some Customizables are aggregates and own other Customizables (i.e. ASes are a collection of Nodes) to whom they can inherit their options (parent to child) with the `handDown()` method.
146+
147+
The base class impl of `DynamicConfigurable` calls the `prepare()` hook just before its configuration (`configure` call) in which its sets all its available options on all the Nodes in the emulation as global defaults. If a node already has more specific overrides for an option ( i.e. because it inherited its local ASes settings that deviate from the global default or the user explicitly changed the option on this particular node) this will be a NoOp (more specific settings preceede). The Base layer (which is configured very first) makes sure that all options which might have been overridden on AS level by the user are inherited down to the resident nodes of that AS.
148+
149+
The resulting state of option settings after the `prepare` call will be the basis for node configuration in the subsequent `configure` step.
150+
As a result of this it should be avoided to create new nodes during configuration(they'd sidestep/slip customization).
151+
152+
###### Options Guidelines and Best Practices
153+
154+
If you ever catch yourself adding configuration getters/setters to your layer (i.e. `setLoglevel()`) what you should be doing instead (rather than bloating the interface of your class) is to just add an option `LOGLEVEL` and pass sensible default values to the constructor of your layer.
155+
156+
A good way to provide access to your layer's options is i.e. a factory method which returns an error, when asked for options with an unknown key.
157+
130158
## Graphing
131159

132160
Serval classes of the emulator offer graphing options to convert the topologies to graphs. These classes are:

examples/scion/S02_scion_bgp_mixed/README.md

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
# SCION and BGP Coexistence
22

33
SCION can operate independently from but alongside BGP. This example demonstrates a more complex topology with both SCION and BGP routing.
4+
Moreover it serves as a demo of how to use the option system.
45

56
## Step 1: Create layers
67

78
```python
89
from seedemu.compiler import Docker, Graphviz
9-
from seedemu.core import Emulator
10+
from seedemu.core import Emulator, OptionMode, Scope, ScopeTier, ScopeType, OptionRegistry
1011
from seedemu.layers import (
11-
ScionBase, ScionRouting, ScionIsd, Scion, Ospf, Ibgp, Ebgp, PeerRelationship)
12+
ScionBase, ScionRouting, ScionIsd, Scion, Ospf, Ibgp, Ebgp, PeerRelationship,
13+
SetupSpecification, CheckoutSpecification)
1214
from seedemu.layers.Scion import LinkType as ScLinkType
13-
15+
# Initialize
1416
emu = Emulator()
1517
base = ScionBase()
16-
routing = ScionRouting()
18+
# change global defaults here .. .
19+
loglvl = OptionRegistry().scion_loglevel('error', mode=OptionMode.RUN_TIME)
20+
21+
spec = SetupSpecification.LOCAL_BUILD(
22+
CheckoutSpecification(
23+
mode = "build",
24+
git_repo_url = "https://github.com/scionproto/scion.git",
25+
checkout = "v0.12.0" # could be tag, branch or commit-hash
26+
))
27+
opt_spec = OptionRegistry().scion_setup_spec(spec)
28+
routing = ScionRouting(loglevel=loglvl, setup_spec=opt_spec)
29+
1730
ospf = Ospf()
1831
scion_isd = ScionIsd()
1932
scion = Scion()
@@ -22,6 +35,9 @@ ebgp = Ebgp()
2235
```
2336

2437
In addition to the SCION layers we instantiate the `Ibgp` and `Ebgp` layers for BGP as well as `Ospf` for AS-internal routing.
38+
Note that the ScionRouting layer accepts global default values for options as constructor parameters.
39+
We use it here to decrease the loglevel from 'debug' to 'error' for all SCION distributables in the whole emulation if not overriden elsewhere. Also we change the mode for `LOGLEVEL` from `BUILD_TIME`(default) to `RUN_TIME` in the same statement.
40+
Lastly we specify (as a global default for all nodes) that we want to use a local build of the `v0.12.0` SCION stack, rather than the 'official' `.deb` packages (`SetupSpecification.PACKAGES`). The SetupSpec is just an ordinary option and be overriden for ASes or individual nodes just like any other.
2541

2642
## Step 2: Create isolation domains and internet exchanges
2743

@@ -40,7 +56,8 @@ We have two ISDs and four internet exchanges this time.
4056

4157
## Step 3: Create autonomous systems
4258

43-
The topology we create contains the two core ASes 150 and 160. AS 150 is in ISD 1 and AS 160 is in ISD 2. ISD 1 has three additional non-core ASes. In ISD 2 we have one non-core AS.
59+
The topology we create contains the two core ASes 150 and 160. AS 150 is in ISD 1 and AS 160 is in ISD 2.
60+
ISD 1 has three additional non-core ASes. In ISD 2 we have one non-core AS.
4461

4562
### Step 3.1: Core AS of ISD 1
4663

@@ -65,6 +82,69 @@ as150_br3.joinNetwork('net3').joinNetwork('net0').joinNetwork('ix103')
6582

6683
AS 150 contains four internal network connected in a ring topology by the four border routers br0, br1, br2, and br3. There are two control services cs1 and cs2. We will use br0 to connect to the core of ISD 2 (i.e., AS 160) and the other border routers to connect to customer/child ASes 151, 152, and 153.
6784

85+
#### Step 3.1.1: Option Settings for ISD-AS 1-150
86+
```python
87+
88+
# override global default for AS150
89+
as150.setOption(OptionRegistry().scion_loglevel('info', OptionMode.RUN_TIME))
90+
as150.setOption(OptionRegistry().scion_disable_bfd(mode = OptionMode.RUN_TIME),
91+
Scope(ScopeTier.AS,
92+
as_id=as150.getAsn(),
93+
node_type=ScopeType.BRDNODE))
94+
95+
# override AS settings for individual nodes
96+
as150_br0.setOption(OptionRegistry().scion_loglevel('debug', OptionMode.RUN_TIME))
97+
as150_br1.setOption(OptionRegistry().scion_serve_metrics('true', OptionMode.RUN_TIME))
98+
as150_br1.addPortForwarding(30442, 30442)
99+
```
100+
This section overrides some global defaults:
101+
- DISABLE_BFD option is changed from default BUILD_TIME to RUNTIME_MODE (the value is left at the default: 'true') on all SCION border routers of AS150
102+
- LOGLEVEL is increased from global default 'error' to 'debug' only for node '150_br0' and the mode set to RUN_TIME
103+
- SERVE_METRICS option is enabled on node '150_br1'.
104+
This node will serve collected Prometheus metrics of the SCION border router distributable on port 30442.
105+
If this behaviour is no longer desired it can turned of at RUN_TIME again, without re-compile and container rebuild
106+
107+
As a result the following `.env` file will be generated next to the `docker-compose.yml` containing all instantiated `RUN_TIME` options:
108+
```
109+
LOGLEVEL_150_BR0=debug
110+
DISABLE_BFD_150_BRDNODE=true
111+
SERVE_METRICS_150_BR1=true
112+
LOGLEVEL_150=info
113+
LOGLEVEL=error
114+
```
115+
These variables are referenced from the `docker-compose.yml` file:
116+
117+
```
118+
brdnode_150_br0:
119+
...
120+
environment:
121+
- LOGLEVEL=${LOGLEVEL_150_BR0}
122+
- DISABLE_BFD=${DISABLE_BFD_150_BRDNODE}
123+
124+
brdnode_150_br1:
125+
...
126+
environment:
127+
- LOGLEVEL=${LOGLEVEL_150}
128+
- SERVE_METRICS=${SERVE_METRICS_150_BR1}
129+
- DISABLE_BFD=${DISABLE_BFD_150_BRDNODE}
130+
131+
brdnode_150_br2|3:
132+
...
133+
environment:
134+
- LOGLEVEL=${LOGLEVEL_150}
135+
- DISABLE_BFD=${DISABLE_BFD_150_BRDNODE}
136+
csnode_150_cs1:
137+
...
138+
environment:
139+
- LOGLEVEL=${LOGLEVEL_150}
140+
141+
brdnode_151_br0:
142+
...
143+
environment:
144+
- LOGLEVEL=${LOGLEVEL}
145+
```
146+
Note that each service has in its `environment:` section (only) the runtime variables that apply to it (that is: match its ASN, NodeType or NodeIdentity).
147+
68148
### Step 3.2 Non-Core ASes of ISD 1
69149

70150
```python

examples/scion/S02_scion_bgp_mixed/scion_bgp_mixed.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
#!/usr/bin/env python3
22

33
from seedemu.compiler import Docker, Graphviz
4-
from seedemu.core import Emulator
4+
from seedemu.core import Emulator, OptionMode, Scope, ScopeTier, ScopeType, OptionRegistry
55
from seedemu.layers import (
6-
ScionBase, ScionRouting, ScionIsd, Scion, Ospf, Ibgp, Ebgp, PeerRelationship)
6+
ScionBase, ScionRouting, ScionIsd, Scion, Ospf, Ibgp, Ebgp, PeerRelationship,
7+
SetupSpecification, CheckoutSpecification)
78
from seedemu.layers.Scion import LinkType as ScLinkType
8-
99
# Initialize
1010
emu = Emulator()
1111
base = ScionBase()
12-
routing = ScionRouting()
12+
# change global defaults here .. .
13+
loglvl = OptionRegistry().scion_loglevel('error', mode=OptionMode.RUN_TIME)
14+
15+
spec = SetupSpecification.LOCAL_BUILD(
16+
CheckoutSpecification(
17+
mode = "build",
18+
git_repo_url = "https://github.com/scionproto/scion.git",
19+
checkout = "v0.12.0" # could be tag, branch or commit-hash
20+
))
21+
opt_spec = OptionRegistry().scion_setup_spec(spec)
22+
routing = ScionRouting(loglevel=loglvl, setup_spec=opt_spec)
23+
1324
ospf = Ospf()
1425
scion_isd = ScionIsd()
1526
scion = Scion()
@@ -47,6 +58,18 @@
4758
as150_br2.joinNetwork('net2').joinNetwork('net3').joinNetwork('ix102')
4859
as150_br3.joinNetwork('net3').joinNetwork('net0').joinNetwork('ix103')
4960

61+
# override global default for AS150
62+
as150.setOption(OptionRegistry().scion_loglevel('info', OptionMode.RUN_TIME))
63+
as150.setOption(OptionRegistry().scion_disable_bfd(mode = OptionMode.RUN_TIME),
64+
Scope(ScopeTier.AS,
65+
as_id=as150.getAsn(),
66+
node_type=ScopeType.BRDNODE))
67+
68+
# override AS settings for individual nodes
69+
as150_br0.setOption(OptionRegistry().scion_loglevel('debug', OptionMode.RUN_TIME))
70+
as150_br1.setOption(OptionRegistry().scion_serve_metrics('true', OptionMode.RUN_TIME))
71+
as150_br1.addPortForwarding(30442, 30442)
72+
5073
# Non-core ASes in ISD 1
5174
asn_ix = {
5275
151: 101,

seedemu/compiler/DistributedDocker.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Distributed Docker Compiler is not maintained
1+
# Distributed Docker Compiler is not maintained
22

33
from .Docker import Docker, DockerCompilerFileTemplates
44
from seedemu.core import Emulator, ScopedRegistry, Node, Network
@@ -35,9 +35,9 @@ class DistributedDocker(Docker):
3535
DistributedDocker is one of the compiler driver. It compiles the lab to
3636
docker containers. This compiler will generate one set of containers with
3737
their docker-compose.yml for each AS, enable you to run the emulator
38-
distributed.
38+
distributed.
3939
40-
This works by making every IX network overlay network.
40+
This works by making every IX network overlay network.
4141
"""
4242

4343
def __init__(self, namingScheme: str = "as{asn}{role}-{name}-{primaryIp}"):
@@ -119,7 +119,8 @@ def _doCompile(self, emulator: Emulator):
119119

120120
self._used_images = set()
121121

122-
print('COMPOSE_PROJECT_NAME=sim_{}'.format(scope), file=open('.env', 'w'))
122+
if scope != 'ix':
123+
self.generateEnvFile(scope, '')
123124

124125
chdir('..')
125126

0 commit comments

Comments
 (0)