Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
remram44 committed Aug 21, 2023
1 parent 117c512 commit 07e0fd6
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 101 deletions.
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ RUN curl -Lo /tmp/docker.tgz https://download.docker.com/linux/static/stable/x86
COPY scripts/get_assets.sh scripts/get_assets.sh
RUN scripts/get_assets.sh

# Add rpztar
# TODO: Get it from GitHub
COPY rpztar-x86_64 /bin/rpztar-x86_64

# Add rpzsudo
COPY rpzsudo-x86_64 /bin/rpzsudo-x86_64

# Install package
COPY reproserver /usr/src/app/reproserver
COPY README.md LICENSE.txt /usr/src/app/
Expand Down
2 changes: 1 addition & 1 deletion Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ docker_build_sub(
context='.',
# chown files to allow live update to work
extra_cmds=['USER root', 'RUN chown -R appuser /usr/src/app', 'USER appuser'],
only=['reprozip', 'reproserver', 'pyproject.toml', 'poetry.lock', 'README.md', 'LICENSE.txt', 'scripts'],
only=['reprozip', 'reproserver', 'pyproject.toml', 'poetry.lock', 'README.md', 'LICENSE.txt', 'scripts', 'rpztar-x86_64', 'rpzsudo-x86_64'],
live_update=[
fall_back_on(full_rebuild),
] + [
Expand Down
2 changes: 1 addition & 1 deletion reproserver/run/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ async def init_run_get_info(self, run_id):
'outputs': outputs,
'ports': ports,
'extra_config': extra_config,
'rpz_meta': exp.info,
'rpz_meta': json.loads(run.experiment.info),
}

async def run_started(self, run_id):
Expand Down
192 changes: 94 additions & 98 deletions reproserver/run/docker.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import asyncio
import logging
import os
import random
import shutil
import subprocess
import tempfile
import textwrap

from .base import PROM_RUNS, BaseRunner
from ..utils import subprocess_call_async, subprocess_check_call_async, \
Expand All @@ -29,59 +31,6 @@ async def run_inner(self, run_info):
'0.0.0.0', # Accept connections to proxy from everywhere
)

async def get_image(self, run_info):
experiment_hash = run_info['experiment_hash']

push_process = None
fq_image_name = '%s/%s' % (
DOCKER_REGISTRY,
'rpuz_exp_%s' % experiment_hash,
)
logger.info("Image name: %s", fq_image_name)
pull_proc = await asyncio.create_subprocess_exec(
'docker', 'pull', fq_image_name,
)
ret = await pull_proc.wait()
if ret == 0:
logger.info("Pulled image from cache")
else:
logger.info("Couldn't get image from cache, building")
await self.connector.run_progress(
run_info['id'],
60,
"No cached container for this RPZ, building; "
+ "this might take several minutes",
)
with tempfile.TemporaryDirectory() as directory:
# Get experiment file
logger.info("Downloading file...")
local_path = os.path.join(directory, 'experiment.rpz')
build_dir = os.path.join(directory, 'build_dir')
await self.connector.download_bundle(
run_info,
local_path,
)
logger.info("Got file, %d bytes", os.stat(local_path).st_size)

# Build image
ret = await subprocess_call_async([
'reprounzip', '-v', 'docker', 'setup',
# `RUN --mount` doesn't work with userns-remap
'--dont-use-buildkit',
'--image-name', fq_image_name,
local_path, build_dir,
])
if ret != 0:
raise ValueError("Error: Docker returned %d" % ret)
logger.info("Build over, pushing image")

# Push image to Docker repository in the background
push_process = await asyncio.create_subprocess_exec(
'docker', 'push', fq_image_name,
)

return fq_image_name, push_process

async def _docker_run(self, run_info, bind_host):
"""Pull or build an image, then run it.
Expand All @@ -108,32 +57,89 @@ async def _docker_run(self, run_info, bind_host):
)

# Make build directory
directory = tempfile.mkdtemp('build_%s' % run_info['experiment_hash'])
directory = tempfile.mkdtemp('rpz-run')

try:
# Download input files
input_download_future = asyncio.ensure_future(
self.connector.download_inputs(run_info, directory),
)

# Get or build the Docker image
get_image_future = asyncio.ensure_future(
self.get_image(run_info),
)

# Wait for both tasks to finish
run_info, (fq_image_name, push_process) = await asyncio.gather(
input_download_future,
get_image_future,
)
# TODO: Select base image from metadata
run_info['rpz_meta']['meta']['distribution']
image_name = 'ubuntu:22.04'

try:
# Create container
container = 'run_%s' % run_info['id']
logger.info(
"Creating container %s with image %s",
container, fq_image_name,
container, image_name,
)
# Turn parameters into a command-line

working_dir = '/.rpz.%d' % random.randint(0, 1000000)
rpztar = '/.rpztar.%d' % random.randint(0, 1000000)
script = [textwrap.dedent(
f'''\
set -eu
apt-get update && apt-get install -yy curl busybox-static # TODO
mkdir {working_dir}
# Download RPZ
curl -Lo {working_dir}/exp.rpz {shell_escape(run_info['experiment_url'])}
# Download inputs
'''
)]
for i, input_file in enumerate(run_info['inputs']):
script.append(
'curl -Lo'
+ ' ' + f'input_{i}'
+ ' ' + shell_escape(input_file["link"])
+ '\n'
)
script.append(textwrap.dedent(
f'''\
# Extract RPZ
cd /
{rpztar} {working_dir}/exp.rpz
rm {working_dir}/exp.rpz
rm {rpztar}
# Move inputs into position
'''
))
for i, input_file in enumerate(run_info['inputs']):
script.append(
'mv'
+ ' ' + f'{working_dir}/input_{i}'
+ ' ' + shell_escape(input_file["path"])
+ '\n'
)
script.append(textwrap.dedent(
f'''\
# Run commands
'''
))
for k, cmd in sorted(run_info['parameters'].items()):
if k.startswith('cmdline_'):
i = int(k[8:], 10)
run = run_info['rpz_meta']['runs'][i]
# Apply the environment
cmd = '/bin/busybox env -i ' + ' '.join(
f'{k}={shell_escape(v)}'
for k, v in run['environ'].items()
) + ' ' + cmd
# Apply uid/gid
cmd = (
f'/rpzsudo "#{run["uid"]}" "#{run["gid"]}"'
+ ' /bin/busybox sh -c ' + shell_escape(cmd)
)
# Change to the working directory
wd = run['workingdir']
cmd = f'cd {shell_escape(wd)} && {cmd}'

script.append(cmd + '\n')
script = ''.join(script)

cmdline = [
'docker', 'create', '-i', '--name', container,
]
Expand All @@ -142,19 +148,27 @@ async def _docker_run(self, run_info, bind_host):
'-p', '{0}:{1}:{1}'.format(bind_host, port['port_number']),
])
cmdline.extend([
'--', fq_image_name,
'--', image_name,
])
for k, v in sorted(run_info['parameters'].items()):
if k.startswith('cmdline_'):
i = str(int(k[8:], 10))
cmdline.extend(['cmd', v, 'run', i])
cmdline.extend(['sh', '-c', script])
logger.info('$ %s', ' '.join(shell_escape(a) for a in cmdline))

# Create container
await subprocess_check_call_async(cmdline)

# Put input files in container
await self._load_input_files(run_info, container)
# Put rpztar in container
await subprocess_check_call_async([
'docker', 'cp', '--',
'/bin/rpztar-x86_64',
'%s:%s' % (container, rpztar)
])

# Put rpzsudo in container
await subprocess_check_call_async([
'docker', 'cp', '--',
'/bin/rpzsudo-x86_64',
'%s:/rpzsudo' % (container,),
])

# Update status in database
logger.info("Starting container")
Expand Down Expand Up @@ -188,27 +202,9 @@ async def _docker_run(self, run_info, bind_host):
# Remove container if created
if container is not None:
subprocess.call(['docker', 'rm', '-f', '--', container])
# Remove build directory
# Remove temp directory
shutil.rmtree(directory)

# Wait for push process to end
if push_process:
if push_process.returncode is None:
logger.info("Waiting for docker push to finish...")
ret = await push_process.wait()
logger.info("docker push returned %d", ret)

async def _load_input_files(self, run_info, container):
for input_file in run_info['inputs']:
logger.info("Copying file to container")
await subprocess_check_call_async([
'docker', 'cp', '--',
input_file['local_path'],
'%s:%s' % (container, input_file['path']),
])

os.remove(input_file['local_path'])

async def _upload_output_files(self, run_info, container, directory):
logs = []

Expand Down
2 changes: 1 addition & 1 deletion reprozip

0 comments on commit 07e0fd6

Please sign in to comment.