Skip to content

Commit 45638fe

Browse files
committed
dev-service
1 parent 548b1ae commit 45638fe

File tree

10 files changed

+521
-17
lines changed

10 files changed

+521
-17
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
### Proposal: Remote Development Service
2+
3+
The DevelopmentService prepares a node for [Remote Development with VSCode](https://code.visualstudio.com/docs/remote/remote-overview).
4+
It installs the VSCode Remote Server on the Node during the docker image build, which allows to ssh into the running container later i.e. to debug software.
5+
As of now the primary anticipated use of the seed-emulator has been the deployment of fully developed applications into an emulated szenario i.e. to observe its behaviour.
6+
With the DevService this paradigm can be adapted to meed the need of developing i.e. P2P software to maturity in a distributed environment.
7+
8+
The DevService allows for each individual node that it is installed on, to specify one or more Git repositories that shall be checked out (along with the desired filesystem path and branch) as well as the VSCode Extensions required for the projects software stack (i.e. golang.Go for the SCION implementation which is written in Go)
9+
The DevService takes care to install the entirety of build and analysis tools that are needed for a given programming language at docker image build time (for Go this being i.e. the compiler, language server gopls, debugger delve) so that no annoying time delay arises once the emulator is running and you want to attach to a container.
10+
Any specified Git repositories are checked out on its own separate docker volume, for the changes to persist between runs of the simulator in case one forgets to push.
11+
12+
### Concerns:
13+
- software development requires a 'real internet' connection of the container be it to git push/pull or fetch project dependencies for a build (i.e. go get, cargo build etc. ) Currently this is achieved by adding the nodes default gateway ( router node ) in the simulation to a docker-bridge network which it shares only with one other node, the docker-host that acts as a default gateway for the router. The subnetmask for these 'micro' networks is deliberately kept as big as possible,
14+
to allow as least nodes as possible on it (ideally /30 for only: the router-node, docker-host , broadcast and network address )
15+
This is to inhibit 'short-circuiting' the simulated network topology (i.e. any crosstalk past the intended network topo among nodes)
16+
I tried an alternative approach of configuring these 'service-networks' as IPv6 [^1] to avoid confusion with the simulated IPv4 network and it worked just fine, but was nonetheless rendered impractical as github.com doesnt seem to support IPv6.
17+
18+
[^1]: requires some tinkering with the /etc/docker/daemon.json , setting the 'inet6' option in the /etc/resolv.conf so that any domain names resolve to AAAA records and setting a default ip -6 route
19+
20+
### TODO:
21+
- set up a script to install DROP rules in the POSTROUTING Chain of the docker hosts iptables to prevent crosstalk via the service network(s)
22+
- maybe its a better idea to have several subclasses of the server for different software stacks i.e. rust / Go /python etc. and leave only the basics i.e git in the service
23+
- I have to look into the VSCode CLI again !! Time delays when first attaching to a container ought to be kept small, and thus as much as possible should be done at image build time
24+
- I need to rework the way the PATH and other environment variables are reliably set in the containers ( it is annoying to always have to type i.e. 'export PATH=$PATH:/usr/local/go/bin')
25+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python3
2+
3+
from seedemu.compiler import Docker,Graphviz
4+
from seedemu.core import Emulator, Binding, Filter
5+
from seedemu.layers import ScionBase, ScionRouting, ScionIsd, Scion
6+
from seedemu.layers.Scion import LinkType as ScLinkType
7+
from seedemu.services import ContainerDevelopmentService
8+
9+
# Initialize
10+
emu = Emulator()
11+
base = ScionBase()
12+
routing = ScionRouting()
13+
scion_isd = ScionIsd()
14+
scion = Scion()
15+
devsvc = ContainerDevelopmentService()
16+
17+
# SCION ISDs
18+
base.createIsolationDomain(1)
19+
20+
# Internet Exchange
21+
base.createInternetExchange(100)
22+
23+
# AS-150
24+
as150 = base.createAutonomousSystem(150)
25+
scion_isd.addIsdAs(1, 150, is_core=True)
26+
as150.createNetwork('net0')
27+
as150.createControlService('cs1').joinNetwork('net0')
28+
as150_router = as150.createRouter('br0')
29+
as150_router.joinNetwork('net0').joinNetwork('ix100')
30+
as150_router.crossConnect(153, 'br0', '10.50.0.2/29')
31+
32+
# Create a host running the bandwidth test server
33+
as150.createHost('node150_0').joinNetwork('net0', address='10.150.0.30')
34+
35+
devsvc.install('dev_peer150_0').addVSCodeExtension('golang.Go').checkoutRepo('https://github.com/scionproto/scion','/home/root/repos/scion','master')
36+
37+
emu.addBinding(Binding('dev_peer150_0', filter=Filter(nodeName='node150_0', asn=150)))
38+
39+
40+
# AS-151
41+
as151 = base.createAutonomousSystem(151)
42+
scion_isd.addIsdAs(1, 151, is_core=True)
43+
as151.createNetwork('net0')
44+
as151.createControlService('cs1').joinNetwork('net0')
45+
as151.createRouter('br0').joinNetwork('net0').joinNetwork('ix100')
46+
47+
as151.createHost('node151_0').joinNetwork('net0', address='10.151.0.30')
48+
devsvc.install('dev_peer151_0').addVSCodeExtension('golang.Go').checkoutRepo('https://github.com/scionproto/scion','/home/root/repos/scion','master')
49+
50+
emu.addBinding(Binding('dev_peer151_0', filter=Filter(nodeName='node151_0', asn=151,allowBound=True)))
51+
52+
# AS-152
53+
as152 = base.createAutonomousSystem(152)
54+
scion_isd.addIsdAs(1, 152, is_core=True)
55+
as152.createNetwork('net0')
56+
as152.createControlService('cs1').joinNetwork('net0')
57+
as152.createRouter('br0').joinNetwork('net0').joinNetwork('ix100')
58+
59+
as152.createHost('node152_0').joinNetwork('net0', address='10.152.0.30')
60+
emu.addBinding(Binding('peer152_0', filter=Filter(nodeName='node152_0', asn=152)))
61+
62+
# AS-153
63+
as153 = base.createAutonomousSystem(153)
64+
scion_isd.addIsdAs(1, 153, is_core=False)
65+
scion_isd.setCertIssuer((1, 153), issuer=150)
66+
as153.createNetwork('net0')
67+
as153.createControlService('cs1').joinNetwork('net0')
68+
as153_router = as153.createRouter('br0')
69+
as153_router.joinNetwork('net0')
70+
as153_router.crossConnect(150, 'br0', '10.50.0.3/29')
71+
72+
as153.createHost('node153_0').joinNetwork('net0', address='10.153.0.30')
73+
emu.addBinding(Binding('peer153_0', filter=Filter(nodeName='node153_0', asn=153)))
74+
75+
# Inter-AS routing
76+
scion.addIxLink(100, (1, 150), (1, 151), ScLinkType.Core)
77+
scion.addIxLink(100, (1, 151), (1, 152), ScLinkType.Core)
78+
scion.addIxLink(100, (1, 152), (1, 150), ScLinkType.Core)
79+
scion.addXcLink((1, 150), (1, 153), ScLinkType.Transit)
80+
81+
# Rendering
82+
emu.addLayer(base)
83+
emu.addLayer(routing)
84+
emu.addLayer(scion_isd)
85+
emu.addLayer(scion)
86+
emu.addLayer(devsvc)
87+
88+
emu.render()
89+
90+
# Compilation
91+
emu.compile(Docker(), './output')
92+
emu.compile(Graphviz(), './output/graphs')

seedemu/compiler/Docker.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@
9696
}; done
9797
'''
9898

99+
DockerCompilerFileTemplates['dockerbridge'] ="""\
100+
{name}:
101+
ipam:
102+
config:
103+
- subnet: {snet}
104+
"""
105+
106+
99107
DockerCompilerFileTemplates['compose'] = """\
100108
version: "3.4"
101109
services:
@@ -136,6 +144,8 @@
136144
{networks}{ports}{volumes}
137145
labels:
138146
{labelList}
147+
environment:
148+
{environment}
139149
"""
140150

141151
DockerCompilerFileTemplates['compose_label_meta'] = """\
@@ -853,6 +863,7 @@ def _compileNode(self, node: Node) -> str:
853863
netId = real_netname,
854864
address = address
855865
)
866+
node_nets += '\n'.join(node.getCustomNets())
856867

857868
_ports = node.getPorts()
858869
ports = ''
@@ -891,7 +902,20 @@ def _compileNode(self, node: Node) -> str:
891902
volumeList = lst
892903
)
893904

905+
name = self.__naming_scheme.format(
906+
asn = node.getAsn(),
907+
role = self._nodeRoleToString(node.getRole()),
908+
name = node.getName(),
909+
displayName = node.getDisplayName() if node.getDisplayName() != None else node.getName(),
910+
primaryIp = node.getInterfaces()[0].getAddress()
911+
)
912+
913+
name = sub(r'[^a-zA-Z0-9_.-]', '_', name)
914+
894915
dockerfile = DockerCompilerFileTemplates['dockerfile']
916+
917+
dockerfile += 'ENV CONTAINER_NAME {}\n'.format(name)
918+
895919
mkdir(real_nodename)
896920
chdir(real_nodename)
897921

@@ -912,6 +936,7 @@ def _compileNode(self, node: Node) -> str:
912936
dockerfile = 'FROM {}\n'.format(md5(image.getName().encode('utf-8')).hexdigest()) + dockerfile
913937
self._used_images.add(image.getName())
914938

939+
for cmd in node.getDockerCommands(): dockerfile += '{}\n'.format(cmd)
915940
for cmd in node.getBuildCommands(): dockerfile += 'RUN {}\n'.format(cmd)
916941

917942
start_commands = ''
@@ -945,20 +970,10 @@ def _compileNode(self, node: Node) -> str:
945970
dockerfile += self._importFile(cpath, hpath)
946971

947972
dockerfile += 'CMD ["/start.sh"]\n'
948-
print(dockerfile, file=open('Dockerfile', 'w'))
949973

974+
print(dockerfile, file=open('Dockerfile', 'w'))
950975
chdir('..')
951976

952-
name = self.__naming_scheme.format(
953-
asn = node.getAsn(),
954-
role = self._nodeRoleToString(node.getRole()),
955-
name = node.getName(),
956-
displayName = node.getDisplayName() if node.getDisplayName() != None else node.getName(),
957-
primaryIp = node.getInterfaces()[0].getAddress()
958-
)
959-
960-
name = sub(r'[^a-zA-Z0-9_.-]', '_', name)
961-
962977
return DockerCompilerFileTemplates['compose_service'].format(
963978
nodeId = real_nodename,
964979
nodeName = name,
@@ -967,7 +982,8 @@ def _compileNode(self, node: Node) -> str:
967982
# privileged = 'true' if node.isPrivileged() else 'false',
968983
ports = ports,
969984
labelList = self._getNodeMeta(node),
970-
volumes = volumes
985+
volumes = volumes,
986+
environment= " - CONTAINER_NAME={}\n ".format(name) + '\n '.join(node.getCustomEnv())
971987
)
972988

973989
def _compileNet(self, net: Network) -> str:
@@ -1088,6 +1104,11 @@ def _doCompile(self, emulator: Emulator):
10881104
dirName = image.getDirName()
10891105
)
10901106

1107+
bridges = registry.getByType('global','dockerbridge')
1108+
for b in bridges:
1109+
id = b.getAttribute('id')
1110+
self.__networks += DockerCompilerFileTemplates['dockerbridge'].format(name = b.getAttribute('name'), snet= str( b.getSubnet() ) )
1111+
10911112
self._log('creating docker-compose.yml...'.format(scope, name))
10921113
print(DockerCompilerFileTemplates['compose'].format(
10931114
services = self.__services,

seedemu/core/Network.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,42 @@
88
from .Visualization import Vertex
99
from typing import Dict, Tuple, List
1010

11+
12+
class DockerBridge(Registrable):
13+
14+
# TODO: maybe add an ASn scope to bridges to simplify retrieval from registry
15+
__base:IPv4Network= IPv4Network('172.29.0.0/16')
16+
# the last one will be 172.29.255.248/29
17+
# i doubt that anyone needs this many containers
18+
19+
"""
20+
@brief currently only used as a means to connect simulation-node router containers
21+
(and through it any nodes on its 'local network') to the docker hosts internet connection
22+
23+
the subnetmask for this 'micro' network is deliberately kept as big as possible,
24+
to allow as least nodes as possible on it (ideally /30 for only: the router-node, docker-host , broadcast and network address )
25+
This is to inhibit 'short-circuiting' the simulated network topology (i.e. crosstalk past the intended network topo among nodes)
26+
"""
27+
def __init__(self,n: str,id: int ):
28+
29+
super().__init__()
30+
self.__base = IPv4Network('172.29.0.0/16')
31+
super().doRegister('undefined', 'undefined', n)
32+
super().setAttribute('name', n)
33+
super().setAttribute('id',id)
34+
35+
def getSubnet(self):
36+
i =0
37+
bid = super().getAttribute('id')
38+
gen = self.__base.subnets(new_prefix=29)
39+
# docker complains, when /30 is used here, but why ?! 4x addresses in the net are enough
40+
p: IPv4Network = IPv4Network('172.29.0.0/29')
41+
while i <= bid:
42+
p = next(gen)
43+
i+=1
44+
return p
45+
46+
1147
class Network(Printable, Registrable, Vertex):
1248
"""!
1349
@brief The network class.

0 commit comments

Comments
 (0)