2019-08-07 12:35:33 +00:00
|
|
|
from sqlalchemy import create_engine
|
|
|
|
from sqlalchemy.orm import sessionmaker
|
2019-07-17 11:34:20 +00:00
|
|
|
import docker
|
2019-08-06 12:27:41 +00:00
|
|
|
import json
|
2019-08-06 12:54:00 +00:00
|
|
|
import os
|
2019-07-17 11:34:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Swarm:
|
2019-08-07 12:35:33 +00:00
|
|
|
def __init__(self, app=None):
|
|
|
|
self.app = app
|
|
|
|
if app is not None:
|
|
|
|
self.init_app(app)
|
2019-07-17 11:34:20 +00:00
|
|
|
self.docker = docker.from_env()
|
|
|
|
|
2019-08-07 12:35:33 +00:00
|
|
|
def init_app(self, app):
|
|
|
|
engine = create_engine(app.config['SQLALCHEMY_DATABASE_URI'])
|
|
|
|
self.Session = sessionmaker(bind=engine)
|
|
|
|
|
2019-07-19 11:28:57 +00:00
|
|
|
'''
|
|
|
|
' Swarm mode is intendet to run containers which serve a non terminating
|
|
|
|
' service like a webserver. For processing an occuring job it is necessary
|
|
|
|
' to use a one-shot container, which stops after the wrapped job process is
|
|
|
|
' completly executed. In order to run these one-shot containers in Swarm
|
|
|
|
' mode, the following run method is implemented analog to the presented
|
|
|
|
' implementation in Alex Ellis' blog post "One-shot containers on Docker
|
|
|
|
' Swarm"¹.
|
|
|
|
'
|
|
|
|
' ¹ https://blog.alexellis.io/containers-on-swarm/
|
|
|
|
'''
|
|
|
|
|
2019-08-11 17:01:19 +00:00
|
|
|
def run(self, job_id):
|
2019-08-06 12:27:41 +00:00
|
|
|
'''
|
2019-08-11 17:01:19 +00:00
|
|
|
Input is a job id.
|
2019-08-06 12:27:41 +00:00
|
|
|
'''
|
2019-08-11 17:01:19 +00:00
|
|
|
from .models import Job
|
|
|
|
session = self.Session()
|
|
|
|
job = session.query(Job).filter_by(id=job_id).first()
|
2019-07-19 11:28:57 +00:00
|
|
|
# Prepare argument values needed for the service creation.
|
2019-08-06 12:27:41 +00:00
|
|
|
service_args = json.loads(job.service_args)
|
|
|
|
_command = (job.service
|
2019-08-06 12:54:00 +00:00
|
|
|
+ ' -i /files'
|
|
|
|
+ ' -o /files/output'
|
2019-08-09 09:48:43 +00:00
|
|
|
+ ' ' + ' '.join(service_args))
|
2019-07-19 11:28:57 +00:00
|
|
|
_constraints = ['node.role==worker']
|
|
|
|
_image = 'gitlab.ub.uni-bielefeld.de:4567/sfb1288inf/{}:{}'.format(
|
2019-08-06 12:27:41 +00:00
|
|
|
job.service,
|
2019-08-09 09:48:43 +00:00
|
|
|
job.service_version
|
2019-07-19 11:28:57 +00:00
|
|
|
)
|
2019-08-06 12:27:41 +00:00
|
|
|
_labels = {'service': job.service}
|
2019-08-06 12:54:00 +00:00
|
|
|
_mounts = [os.path.join('/home/compute/mnt/opaque',
|
|
|
|
str(job.user_id),
|
|
|
|
'jobs',
|
|
|
|
str(job.id))
|
|
|
|
+ ':/files:rw']
|
2019-08-09 07:13:24 +00:00
|
|
|
_name = str(job.id)
|
2019-07-17 11:34:20 +00:00
|
|
|
'''
|
2019-07-19 11:28:57 +00:00
|
|
|
' The Docker SDK for Python expects the cpu_reservation value to be
|
|
|
|
' scaled to nanos (10^9). Because the job object contains unscaled
|
|
|
|
' (10^0) values, it must be conveted.
|
|
|
|
'
|
|
|
|
' While the cpu_reservation value has to be in nanos, the
|
2019-07-31 07:10:37 +00:00
|
|
|
' mem_reservation value must be presented in an unscaled form
|
|
|
|
' (intuitive right?). Bacause the job object provides the memory value
|
|
|
|
' in megabytes, it is also necessary to convert the value.
|
2019-07-17 11:34:20 +00:00
|
|
|
'''
|
2019-07-19 11:28:57 +00:00
|
|
|
_resources = docker.types.Resources(
|
2019-08-09 09:48:43 +00:00
|
|
|
cpu_reservation=job.n_cores * (10 ** 9),
|
|
|
|
mem_reservation=job.mem_mb * (10 ** 6)
|
2019-07-19 11:28:57 +00:00
|
|
|
)
|
|
|
|
_restart_policy = docker.types.RestartPolicy(condition='none')
|
2019-07-17 11:34:20 +00:00
|
|
|
'''
|
2019-07-19 11:28:57 +00:00
|
|
|
' Create the service with the prepared values.
|
|
|
|
'
|
|
|
|
' Note: A service reserves hardware ressources. In case no worker node
|
|
|
|
' has the required ressources available (not reserved), the
|
|
|
|
' service gets queued by the Docker engine until a node is able
|
|
|
|
' to meet the requirements.
|
|
|
|
'
|
|
|
|
' TODO: The name argument should be used with the prepared value
|
|
|
|
' (name=_name). Because there is no id generator for now, it is
|
|
|
|
' not set, so that the Docker engine assigns a random name.
|
2019-07-17 11:34:20 +00:00
|
|
|
'''
|
2019-07-19 11:28:57 +00:00
|
|
|
service = self.docker.services.create(
|
|
|
|
_image,
|
|
|
|
command=_command,
|
|
|
|
constraints=_constraints,
|
|
|
|
labels=_labels,
|
|
|
|
mounts=_mounts,
|
2019-08-08 10:01:55 +00:00
|
|
|
name=_name,
|
2019-07-19 11:28:57 +00:00
|
|
|
resources=_resources,
|
|
|
|
restart_policy=_restart_policy
|
|
|
|
)
|
2019-07-17 11:34:20 +00:00
|
|
|
|
2019-08-12 15:03:12 +00:00
|
|
|
return service
|