Commit a5a1ff28 authored by Antoine Kaufmann's avatar Antoine Kaufmann
Browse files

tweak system spec a bit more with full system image

parent b34ddb29
......@@ -36,6 +36,7 @@ class System:
class Component(abc.ABC):
def __init__(self, s: System) -> None:
s.system = s
s.parameters = {}
s.add_component(self)
@abc.abstractmethod
......
......@@ -34,17 +34,24 @@ class Application(abc.ABC):
# Note AK: Maybe we can factor most of the duplicate calls with the host out
# into a separate module.
class LinuxApplication(abc.ABC):
class BaseLinuxApplication(abc.ABC):
def __init__(self, h: base.LinuxHost) -> None:
self.host = h
self.start_delay: float | None = None
self.end_delay: float | None = None
self.wait = True
@abc.abstractmethod
def run_cmds(self, env: expenv.ExpEnv) -> list[str]:
"""Commands to run on node."""
return self.app.run_cmds(self)
pass
def cleanup_cmds(self, env: expenv.ExpEnv) -> list[str]:
"""Commands to run to cleanup node."""
return []
if self.end_delay is None:
return []
else:
return [f'sleep {self.start_delay}']
def config_files(self, env: expenv.ExpEnv) -> dict[str, tp.IO]:
"""
......@@ -68,7 +75,10 @@ class LinuxApplication(abc.ABC):
def prepare_post_cp(self, env: expenv.ExpEnv) -> list[str]:
"""Commands to run to prepare node after checkpoint restore."""
return []
if self.end_delay is None:
return []
else:
return [f'sleep {self.end_delay}']
def strfile(self, s: str) -> io.BytesIO:
"""
......@@ -79,3 +89,48 @@ class LinuxApplication(abc.ABC):
simulated node.
"""
return io.BytesIO(bytes(s, encoding="UTF-8"))
class PingClient(BaseLinuxApplication):
def __init__(self, server_ip: str = '192.168.64.1') -> None:
super().__init__()
self.server_ip = server_ip
def run_cmds(self, env: expenv.ExpEnv) -> tp.List[str]:
return [f'ping {self.server_ip} -c 10']
class Sleep(BaseLinuxApplication):
def __init__(self, delay: float = 10) -> None:
super().__init__()
self.delay = delay
def run_cmds(self, env: expenv.ExpEnv) -> tp.List[str]:
return [f'sleep {self.delay}']
class NetperfServer(BaseLinuxApplication):
def __init__(self) -> None:
super().__init__()
def run_cmds(self, env: expenv.ExpEnv) -> tp.List[str]:
return ['netserver', 'sleep infinity']
class NetperfClient(BaseLinuxApplication):
def __init__(self, server_ip: str = '192.168.64.1') -> None:
super().__init__()
self.server_ip = server_ip
self.duration_tp = 10
self.duration_lat = 10
def run_cmds(self, env: expenv.ExpEnv) -> tp.List[str]:
return [
'netserver',
'sleep 0.5',
f'netperf -H {self.server_ip} -l {self.duration_tp}',
(
f'netperf -H {self.server_ip} -l {self.duration_lat} -t TCP_RR'
' -- -o mean_latency,p50_latency,p90_latency,p99_latency'
)
]
\ No newline at end of file
......@@ -23,6 +23,7 @@
import typing as tp
import io
from os import path
from simbricks.orchestration.experiment import experiment_environment as expenv
from simbricks.orchestration.system import base
from simbricks.orchestration.system import eth
from simbricks.orchestration.system import mem
......@@ -34,8 +35,8 @@ from simbricks.orchestration.system.host import app
class Host(base.Component):
def __init__(self, s: base.System):
super().__init__(s)
self.ifs: list[pcie.PCIeHostInterface] = []
self.applications: list[Application]
self.ifs: list[base.Interface] = []
self.applications: list[app.Application]
def interfaces(self) -> list[base.Interface]:
return self.pcie_ifs + self.eth_ifs + self.mem_ifs
......@@ -43,7 +44,7 @@ class Host(base.Component):
def add_if(self, i: base.Interface) -> None:
self.ifs.append(i)
def add_app(self, a: Application) -> None:
def add_app(self, a: app.Application) -> None:
self.applications.append(a)
......@@ -55,27 +56,42 @@ class FullSystemHost(Host):
self.cpu_freq = '3GHz'
self.disks: list[disk_images.DiskImage] = []
def add_disk(self, disk: DiskImage) -> None:
def add_disk(self, disk: disk_images.DiskImage) -> None:
self.disks.append(disk)
class LinuxHost(FullSystemHost):
class BaseLinuxHost(FullSystemHost):
def __init__(self, s: base.System) -> None:
super().__init__(s)
self.applications: list[LinuxApplication] = []
self.applications: list[app.BaseLinuxApplication] = []
self.load_modules = []
self.kcmd_append = ''
def add_app(self, a: LinuxApplication) -> None:
def add_app(self, a: app.BaseLinuxApplication) -> None:
self.applications.append(a)
def _concat_app_cmds(
self,
env: expenv.ExpEnv,
mapper: tp.Callable[[app.LinuxApplication, expenv.ExpEnv],
list[str]]
) -> list[str]:
"""
Generate command list from applications by applying `mapper` to each
application on this host and concatenating the commands.
"""
cmds = []
for app in self.applications:
cmds += mapper(app, env)
return cmds
def run_cmds(self, env: expenv.ExpEnv) -> list[str]:
"""Commands to run on node."""
return self.app.run_cmds(self)
return self._concat_app_cmds(env, app.LinuxApplication.run_cmds)
def cleanup_cmds(self, env: expenv.ExpEnv) -> list[str]:
"""Commands to run to cleanup node."""
return []
return self._concat_app_cmds(env, app.LinuxApplication.cleanup_cmds)
def config_files(self, env: expenv.ExpEnv) -> dict[str, tp.IO]:
"""
......@@ -92,17 +108,24 @@ class LinuxHost(FullSystemHost):
def prepare_pre_cp(self, env: expenv.ExpEnv) -> list[str]:
"""Commands to run to prepare node before checkpointing."""
return [
'set -x',
'export HOME=/root',
'export LANG=en_US',
'export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:' + \
'/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"'
]
self._concat_app_cmds(env, app.LinuxApplication.prepare_pre_cp)
def prepare_post_cp(self, env: expenv.ExpEnv) -> list[str]:
"""Commands to run to prepare node after checkpoint restore."""
return []
return self._concat_app_cmds(env, app.LinuxApplication.prepare_post_cp)
def _config_str(self, env: expenv.ExpEnv) -> str:
if env.create_cp:
sim = env.exp.get_simulator(self)
cp_cmd = self.checkpoint_commands()
else:
cp_cmd = []
es = self.prepare_pre_cp(env) + self.app.prepare_pre_cp(env) + \
cp_cmd + \
self.prepare_post_cp(env) + self.app.prepare_post_cp(env) + \
self.run_cmds() + self.cleanup_cmds()
return '\n'.join(es)
def strfile(self, s: str) -> io.BytesIO:
"""
......@@ -112,4 +135,71 @@ class LinuxHost(FullSystemHost):
Using this, you can create a file with the string as its content on the
simulated node.
"""
return io.BytesIO(bytes(s, encoding='UTF-8'))
\ No newline at end of file
return io.BytesIO(bytes(s, encoding='UTF-8'))
class LinuxHost(BaseLinuxHost):
def __init__(self, sys) -> None:
super().__init__(sys)
self.drivers: list[str] = []
def cleanup_cmds(self, env: expenv.ExpEnv) -> list[str]:
return super().cleanup_cmds(env) + ['poweroff -f']
def prepare_pre_cp(self, env: expenv.ExpEnv) -> list[str]:
"""Commands to run to prepare node before checkpointing."""
return [
'set -x',
'export HOME=/root',
'export LANG=en_US',
'export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:' + \
'/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"'
] + super().prepare_pre_cp(env)
def prepare_post_cp(self) -> tp.List[str]:
l = []
for d in self.drivers:
if d[0] == '/':
l.append(f'insmod {d}')
else:
l.append(f'modprobe {d}')
eth_i = 0
for i in self.interfaces():
# Get ifname parameter if set, otherwise default to ethX
if 'ifname' in i.parameters:
ifn = i.parameters['ifname']
elif isinstance(i, eth.EthSimpleNIC):
ifn = f'eth{eth_i}'
eth_i += 1
else:
continue
# Force MAC if requested
if 'force_mac_addr' in i.parameters:
l.append(f'ip link set dev {ifn} address '
f'{i.parameters['force_mac_addr']}')
# Bring interface up
l.append(f'ip link set dev {ifn} up')
# Add IP addresses if included
if 'ipv4_addrs' in i.parameters:
for a in i.parameters['ipv4_addrs']:
l.append(f'ip addr add {a} dev {ifn}')
return super().prepare_post_cp() + l
class I40ELinuxHost(LinuxHost):
def __init__(self, sys) -> None:
super().__init__(sys)
self.drivers.append('i40e')
class CorundumLinuxHost(LinuxHost):
def __init__(self, sys) -> None:
super().__init__(sys)
self.drivers.append('/tmp/guest/mqnic.ko')
def config_files(self, env: expenv.ExpEnv) -> tp.Dict[str, tp.IO]:
m = {'mqnic.ko': open('../images/mqnic/mqnic.ko', 'rb')}
return {**m, **super().config_files()}
\ No newline at end of file
......@@ -21,13 +21,15 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import abc
import io
import os.path
import tarfile
from simbricks.orchestration.system.host import base
from simbricks.orchestration.experiment import experiment_environment as expenv
class DiskImage(abc.ABC):
def __init__(self, h: host.Host) -> None:
def __init__(self, h: base.Host) -> None:
self.host = h
@abc.abstractmethod
......@@ -41,7 +43,7 @@ class DiskImage(abc.ABC):
# Disk image where user just provides a path
class ExternalDiskImage(DiskImage):
def __init__(self, h: host.FullSystemHost, path: str) -> None:
def __init__(self, h: base.FullSystemHost, path: str) -> None:
super().__init__(h)
self.path = path
self.formats = ["raw", "qcow2"]
......@@ -56,7 +58,7 @@ class ExternalDiskImage(DiskImage):
# Disk images shipped with simbricks
class DistroDiskImage(DiskImage):
def __init__(self, h: host.FullSystemHost, name: str) -> None:
def __init__(self, h: base.FullSystemHost, name: str) -> None:
super().__init__(h)
self.name = name
self.formats = ["raw", "qcow2"]
......@@ -78,22 +80,44 @@ class DistroDiskImage(DiskImage):
# Builds the Tar with the commands to run etc.
class LinuxConfigDiskImage(DiskImage):
def __init__(self, h: host.LinuxHost) -> None:
def __init__(self, h: base.LinuxHost) -> None:
super().__init__(h)
self.host: base.LinuxHost
def available_formats(self) -> list[str]:
return ["raw"]
async def prepare_image_path(self, env: expenv.ExpEnv, format: str) -> str:
# TODO: build tar from host path parameters and then return path
pass
path = env.dynamic_img_path(self, format)
with tarfile.open(path, 'w:') as tar:
# add main run script
cfg_i = tarfile.TarInfo('guest/run.sh')
cfg_i.mode = 0o777
cfg_f = self.host.strfile(self.host.config_str())
cfg_f.seek(0, io.SEEK_END)
cfg_i.size = cfg_f.tell()
cfg_f.seek(0, io.SEEK_SET)
tar.addfile(tarinfo=cfg_i, fileobj=cfg_f)
cfg_f.close()
# add additional config files
for (n, f) in self.host.config_files(env).items():
f_i = tarfile.TarInfo('guest/' + n)
f_i.mode = 0o777
f.seek(0, io.SEEK_END)
f_i.size = f.tell()
f.seek(0, io.SEEK_SET)
tar.addfile(tarinfo=f_i, fileobj=f)
f.close()
# This is an additional example: building disk images directly from python
# Could of course also have a version that generates the packer config from
# python
class PackerDiskImage(DiskImage):
def __init__(self, h: host.FullSystemHost, packer_config_path: str) -> None:
def __init__(self, h: base.FullSystemHost, packer_config_path: str) -> None:
super().__init__(h)
self.config_path = packer_config_path
......
Subproject commit 2c500a6a7527a1305e1a8e03f53ea11e90b71b73
Subproject commit 8e4978a2ece997efa1fccb33e945c0c496587f34
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment