Skip to content

dev-service #172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions examples/scion/S06-dev-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
### Proposal: Remote Development Service

The DevelopmentService prepares a node for [Remote Development with VSCode](https://code.visualstudio.com/docs/remote/remote-overview).
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.
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.
With the DevService this paradigm can be adapted to meed the need of developing i.e. P2P software to maturity in a distributed environment.

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)
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.
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.

### Concerns:
- 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,
to allow as least nodes as possible on it (ideally /30 for only: the router-node, docker-host , broadcast and network address )
This is to inhibit 'short-circuiting' the simulated network topology (i.e. any crosstalk past the intended network topo among nodes)
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.

[^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

### TODO:
- 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)
- 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
- 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
- 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')

92 changes: 92 additions & 0 deletions examples/scion/S06-dev-service/dev-service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python3

from seedemu.compiler import Docker,Graphviz
from seedemu.core import Emulator, Binding, Filter
from seedemu.layers import ScionBase, ScionRouting, ScionIsd, Scion
from seedemu.layers.Scion import LinkType as ScLinkType
from seedemu.services import ContainerDevelopmentService

# Initialize
emu = Emulator()
base = ScionBase()
routing = ScionRouting()
scion_isd = ScionIsd()
scion = Scion()
devsvc = ContainerDevelopmentService()

# SCION ISDs
base.createIsolationDomain(1)

# Internet Exchange
base.createInternetExchange(100)

# AS-150
as150 = base.createAutonomousSystem(150)
scion_isd.addIsdAs(1, 150, is_core=True)
as150.createNetwork('net0')
as150.createControlService('cs1').joinNetwork('net0')
as150_router = as150.createRouter('br0')
as150_router.joinNetwork('net0').joinNetwork('ix100')
as150_router.crossConnect(153, 'br0', '10.50.0.2/29')

# Create a host running the bandwidth test server
as150.createHost('node150_0').joinNetwork('net0', address='10.150.0.30')

devsvc.install('dev_peer150_0').addVSCodeExtension('golang.Go').checkoutRepo('https://github.com/scionproto/scion','/home/root/repos/scion','master')

emu.addBinding(Binding('dev_peer150_0', filter=Filter(nodeName='node150_0', asn=150)))


# AS-151
as151 = base.createAutonomousSystem(151)
scion_isd.addIsdAs(1, 151, is_core=True)
as151.createNetwork('net0')
as151.createControlService('cs1').joinNetwork('net0')
as151.createRouter('br0').joinNetwork('net0').joinNetwork('ix100')

as151.createHost('node151_0').joinNetwork('net0', address='10.151.0.30')
devsvc.install('dev_peer151_0').addVSCodeExtension('golang.Go').checkoutRepo('https://github.com/scionproto/scion','/home/root/repos/scion','master')

emu.addBinding(Binding('dev_peer151_0', filter=Filter(nodeName='node151_0', asn=151,allowBound=True)))

# AS-152
as152 = base.createAutonomousSystem(152)
scion_isd.addIsdAs(1, 152, is_core=True)
as152.createNetwork('net0')
as152.createControlService('cs1').joinNetwork('net0')
as152.createRouter('br0').joinNetwork('net0').joinNetwork('ix100')

as152.createHost('node152_0').joinNetwork('net0', address='10.152.0.30')
emu.addBinding(Binding('peer152_0', filter=Filter(nodeName='node152_0', asn=152)))

# AS-153
as153 = base.createAutonomousSystem(153)
scion_isd.addIsdAs(1, 153, is_core=False)
scion_isd.setCertIssuer((1, 153), issuer=150)
as153.createNetwork('net0')
as153.createControlService('cs1').joinNetwork('net0')
as153_router = as153.createRouter('br0')
as153_router.joinNetwork('net0')
as153_router.crossConnect(150, 'br0', '10.50.0.3/29')

as153.createHost('node153_0').joinNetwork('net0', address='10.153.0.30')
emu.addBinding(Binding('peer153_0', filter=Filter(nodeName='node153_0', asn=153)))

# Inter-AS routing
scion.addIxLink(100, (1, 150), (1, 151), ScLinkType.Core)
scion.addIxLink(100, (1, 151), (1, 152), ScLinkType.Core)
scion.addIxLink(100, (1, 152), (1, 150), ScLinkType.Core)
scion.addXcLink((1, 150), (1, 153), ScLinkType.Transit)

# Rendering
emu.addLayer(base)
emu.addLayer(routing)
emu.addLayer(scion_isd)
emu.addLayer(scion)
emu.addLayer(devsvc)

emu.render()

# Compilation
emu.compile(Docker(), './output')
emu.compile(Graphviz(), './output/graphs')
61 changes: 45 additions & 16 deletions seedemu/compiler/Docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,23 @@
}; done
'''

DockerCompilerFileTemplates['dockerbridge'] ="""\
{name}:
ipam:
config:
- subnet: {snet}
"""


DockerCompilerFileTemplates['compose'] = """\
version: "3.4"
services:
{dummies}
{services}
networks:
{networks}
volumes:
{named_volumes}
"""

DockerCompilerFileTemplates['compose_dummy'] = """\
Expand Down Expand Up @@ -136,6 +146,8 @@
{networks}{ports}{volumes}
labels:
{labelList}
environment:
{environment}
"""

DockerCompilerFileTemplates['compose_label_meta'] = """\
Expand Down Expand Up @@ -163,7 +175,7 @@
"""

DockerCompilerFileTemplates['compose_storage'] = """\
- {nodePath}
- {volume_name}:{nodePath}
"""

DockerCompilerFileTemplates['compose_service_network'] = """\
Expand Down Expand Up @@ -322,6 +334,8 @@ class Docker(Compiler):

__basesystem_dockerimage_mapping: dict

__named_volumes: List[str]

def __init__(
self,
platform:Platform = Platform.AMD64,
Expand Down Expand Up @@ -389,9 +403,10 @@ def __init__(
self.__image_per_node_list = {}

self.__platform = platform
self.__named_volumes = []

self.__basesystem_dockerimage_mapping = BASESYSTEM_DOCKERIMAGE_MAPPING_PER_PLATFORM[self.__platform]

for name, image in self.__basesystem_dockerimage_mapping.items():
priority = 0
if name == BaseSystem.DEFAULT:
Expand Down Expand Up @@ -853,6 +868,7 @@ def _compileNode(self, node: Node) -> str:
netId = real_netname,
address = address
)
node_nets += '\n'.join(node.getCustomNets())

_ports = node.getPorts()
ports = ''
Expand Down Expand Up @@ -882,16 +898,31 @@ def _compileNode(self, node: Node) -> str:
nodePath = nodePath
)

for path in storages:
for path,vname in storages:
self.__named_volumes.append(vname)
lst += DockerCompilerFileTemplates['compose_storage'].format(
nodePath = path
nodePath = path,
volume_name= "{}".format(vname)
)

volumes = DockerCompilerFileTemplates['compose_volumes'].format(
volumeList = lst
)

name = self.__naming_scheme.format(
asn = node.getAsn(),
role = self._nodeRoleToString(node.getRole()),
name = node.getName(),
displayName = node.getDisplayName() if node.getDisplayName() != None else node.getName(),
primaryIp = node.getInterfaces()[0].getAddress()
)

name = sub(r'[^a-zA-Z0-9_.-]', '_', name)

dockerfile = DockerCompilerFileTemplates['dockerfile']

dockerfile += 'ENV CONTAINER_NAME {}\n'.format(name)

mkdir(real_nodename)
chdir(real_nodename)

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

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

start_commands = ''
Expand Down Expand Up @@ -945,20 +977,10 @@ def _compileNode(self, node: Node) -> str:
dockerfile += self._importFile(cpath, hpath)

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

print(dockerfile, file=open('Dockerfile', 'w'))
chdir('..')

name = self.__naming_scheme.format(
asn = node.getAsn(),
role = self._nodeRoleToString(node.getRole()),
name = node.getName(),
displayName = node.getDisplayName() if node.getDisplayName() != None else node.getName(),
primaryIp = node.getInterfaces()[0].getAddress()
)

name = sub(r'[^a-zA-Z0-9_.-]', '_', name)

return DockerCompilerFileTemplates['compose_service'].format(
nodeId = real_nodename,
nodeName = name,
Expand All @@ -967,7 +989,8 @@ def _compileNode(self, node: Node) -> str:
# privileged = 'true' if node.isPrivileged() else 'false',
ports = ports,
labelList = self._getNodeMeta(node),
volumes = volumes
volumes = volumes,
environment= " - CONTAINER_NAME={}\n ".format(name) + '\n '.join(node.getCustomEnv())
)

def _compileNet(self, net: Network) -> str:
Expand Down Expand Up @@ -1088,9 +1111,15 @@ def _doCompile(self, emulator: Emulator):
dirName = image.getDirName()
)

bridges = registry.getByType('global','dockerbridge')
for b in bridges:
id = b.getAttribute('id')
self.__networks += DockerCompilerFileTemplates['dockerbridge'].format(name = b.getAttribute('name'), snet= str( b.getSubnet() ) )

self._log('creating docker-compose.yml...'.format(scope, name))
print(DockerCompilerFileTemplates['compose'].format(
services = self.__services,
networks = self.__networks,
named_volumes= '\n'.join( map(lambda name: " {}:".format(name) ,self.__named_volumes )),
dummies = local_images + self._makeDummies()
), file=open('docker-compose.yml', 'w'))
36 changes: 36 additions & 0 deletions seedemu/core/Network.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,42 @@
from .Visualization import Vertex
from typing import Dict, Tuple, List


class DockerBridge(Registrable):

# TODO: maybe add an ASn scope to bridges to simplify retrieval from registry
__base:IPv4Network= IPv4Network('172.29.0.0/16')
# the last one will be 172.29.255.248/29
# i doubt that anyone needs this many containers

"""
@brief currently only used as a means to connect simulation-node router containers
(and through it any nodes on its 'local network') to the docker hosts internet connection

the subnetmask for this 'micro' network is deliberately kept as big as possible,
to allow as least nodes as possible on it (ideally /30 for only: the router-node, docker-host , broadcast and network address )
This is to inhibit 'short-circuiting' the simulated network topology (i.e. crosstalk past the intended network topo among nodes)
"""
def __init__(self,n: str,id: int ):

super().__init__()
self.__base = IPv4Network('172.29.0.0/16')
super().doRegister('undefined', 'undefined', n)
super().setAttribute('name', n)
super().setAttribute('id',id)

def getSubnet(self):
i =0
bid = super().getAttribute('id')
gen = self.__base.subnets(new_prefix=29)
# docker complains, when /30 is used here, but why ?! 4x addresses in the net are enough
p: IPv4Network = IPv4Network('172.29.0.0/29')
while i <= bid:
p = next(gen)
i+=1
return p


class Network(Printable, Registrable, Vertex):
"""!
@brief The network class.
Expand Down
Loading