Commit abc03f50 authored by Valentin Reis's avatar Valentin Reis
Browse files

Merge branch '52-support-adding-bind-mounts-to-container-for-runtimes-that-allow-it' into 'master'

Resolve "Support adding bind mounts to container for runtimes that allow it"

Closes #52

See merge request !92
parents c7771b4a 57980d37
Pipeline #7444 passed with stages
in 40 minutes and 51 seconds
...@@ -17,14 +17,6 @@ include: ...@@ -17,14 +17,6 @@ include:
- https://xgitlab.cels.anl.gov/argo/argopkgs/raw/master/gitlab-ci/applications.yml - https://xgitlab.cels.anl.gov/argo/argopkgs/raw/master/gitlab-ci/applications.yml
- https://xgitlab.cels.anl.gov/argo/argopkgs/raw/master/gitlab-ci/repoquality.yml - https://xgitlab.cels.anl.gov/argo/argopkgs/raw/master/gitlab-ci/repoquality.yml
py.test:
stage: test
script:
- pipenv install --dev
- pipenv run py.test --deselect=test/test_messaging.py
tags:
- rapl
flake8: flake8:
stage: style stage: style
script: script:
......
...@@ -147,7 +147,6 @@ class CommandLineInterface(object): ...@@ -147,7 +147,6 @@ class CommandLineInterface(object):
print(msg.payload, file=sys.stderr) print(msg.payload, file=sys.stderr)
sys.stderr.flush() sys.stderr.flush()
elif msg.tag == 'exit': elif msg.tag == 'exit':
logger.info("process ended: %r", msg)
state = 'exiting' state = 'exiting'
exitmsg = msg exitmsg = msg
else: else:
...@@ -155,7 +154,7 @@ class CommandLineInterface(object): ...@@ -155,7 +154,7 @@ class CommandLineInterface(object):
if outeof and erreof and state == 'exiting': if outeof and erreof and state == 'exiting':
state = 'exit' state = 'exit'
istatus = int(exitmsg.status) istatus = int(exitmsg.status)
logger.info("command ended: %r", exitmsg) logger.debug("command ended with istatus %r.", exitmsg)
if os.WIFSIGNALED(istatus): if os.WIFSIGNALED(istatus):
logger.error("command ended due to signal %s" % logger.error("command ended due to signal %s" %
str(os.WTERMSIG(istatus))) str(os.WTERMSIG(istatus)))
...@@ -163,7 +162,7 @@ class CommandLineInterface(object): ...@@ -163,7 +162,7 @@ class CommandLineInterface(object):
elif os.WIFEXITED(istatus): elif os.WIFEXITED(istatus):
s = int(os.WTERMSIG(istatus)) s = int(os.WTERMSIG(istatus))
if s > 0: if s > 0:
logger.debug("command ended with exit code %s" % logger.error("command ended with exit code %s" %
str(s)) str(s))
sys.exit(s) sys.exit(s)
else: else:
......
{
"acKind": "ImageManifest",
"acVersion": "0.6.0",
"name": "test",
"app": {
"isolators": [
{
"name": "argo/scheduler",
"value": {
"policy": "SCHED_OTHER",
"priority": "0"
}
},
{
"name": "argo/container",
"value": {
"cpus": "24",
"mems": "2"
}
},
{
"name": "argo/perfwrapper",
"value": {
"enabled": "1"
}
},
{
"name": "argo/power",
"value": {
"enabled": "1",
"profile": "1",
"policy": "NONE",
"damper": "1e9",
"slowdown": "1.1"
}
},
{
"name": "argo/monitoring",
"value": {
"enabled": "1",
"ratelimit": "10000000"
}
},
{
"name": "argo/hwbind",
"value": {
"enabled": "1"
}
}
]
}
}
{
"acKind": "ImageManifest",
"acVersion": "0.6.0",
"name": "test",
"app": {
"isolators": [
{
"name": "argo/scheduler",
"value": {
"policy": "SCHED_OTHER",
"priority": "0"
}
},
{
"name": "argo/container",
"value": {
"cpus": "1",
"mems": "1"
}
},
{
"name": "argo/perfwrapper",
"value": {
"enabled": "0"
}
},
{
"name": "argo/power",
"value": {
"enabled": "1",
"profile": "1",
"policy": "NONE",
"damper": "1e9",
"slowdown": "1.1"
}
},
{
"name": "argo/monitoring",
"value": {
"enabled": "1",
"ratelimit": "10000000"
}
},
{
"name": "argo/hwbind",
"value": {
"enabled": "0"
}
}
]
},
"image": {
"path": "docker://ubuntu",
"type": "docker"
}
}
{
"name": "basic",
"version": "0.0.1",
"app": {
"container": {
"cpus": 2,
"mems": 1
}
}
}
{
"name": "basic",
"version": "0.0.1",
"app": {
"container": {
"cpus": 2,
"mems": 1
}
},
"image": {
"path": "docker://ubuntu",
"type": "docker",
"binds": [
"/nix"
]
}
}
...@@ -9,380 +9,15 @@ ...@@ -9,380 +9,15 @@
############################################################################### ###############################################################################
"""Parse and Represent the APPC ACI specification.""" """Parse and Represent the APPC ACI specification."""
import collections
import logging import logging
import json from schema import loadschema
logger = logging.getLogger('nrm') logger = logging.getLogger('nrm')
spec = collections.namedtuple('Field', ['cls', 'required'])
class SpecField(object): def has(self, f):
return(f in self.app.keys())
"""Object part of the ACI Image Manifest fields."""
fields = {} ImageManifest = loadschema("manifest")
setattr(ImageManifest, "is_feature_enabled", has)
def __init__(self):
"""Create empty field."""
pass
def load(self, data):
"""Load fields."""
for key in self.fields:
spec = self.fields[key]
if key not in data:
if spec.required:
logger.error("Missing key from manifest: %s", key)
return False
else:
ok, v = self.loadfield(data[key], spec.cls)
if not ok:
logger.error("Error for key %s in %s", key, self.__class__)
return False
setattr(self, key, v)
return True
def loadfield(self, data, cls):
"""load data as if from a field of the provided cls.
Make sure the basic types are also respected.
"""
ret = cls()
if not hasattr(ret, 'load'):
if not isinstance(data, cls):
logger.error("Wrong data type %s, expected: %s", cls,
data.__class__)
return (False, None)
else:
return (True, data)
else:
return (ret.load(data), ret)
class Scheduler(SpecField):
"""Scheduler information for a container."""
classes = ['SCHED_FIFO', 'SCHED_HPC', 'SCHED_OTHER']
fields = {"policy": spec(unicode, True),
"priority": spec(unicode, False),
"enabled": spec(unicode, False),
}
def __init__(self):
"""Create scheduler object."""
pass
def load(self, data):
"""Load configuration from json text."""
ret = super(Scheduler, self).load(data)
if not ret:
return ret
# check scheduler class & prio
if self.policy not in self.classes:
logger.error("Wrong scheduling class %s, not any of %r", data,
Scheduler.classes)
return False
if self.policy != "SCHED_OTHER":
logger.warning("scheduler priority forced as 0 " +
"for non default policies")
self.priority = "0"
if getattr(self, "enabled", "1") not in ["0", "False", "1", "True"]:
logger.error("Invalid value for scheduler enabled: %s",
self.enabled)
return False
return True
class CPUSet(SpecField):
"""Represent a CPUSet field."""
def __init__(self):
"""Create an empty set."""
pass
def load(self, data):
"""Load from json object."""
self.value = data
return True
class MemSet(SpecField):
"""Represent a MemSet field."""
def __init__(self):
"""Create an empty set."""
pass
def load(self, data):
"""Load from json object."""
self.value = data
return True
class Container(SpecField):
"""Container Information."""
fields = {"cpus": spec(CPUSet, True),
"mems": spec(MemSet, True)
}
def __init__(self):
"""Create empty container."""
pass
def load(self, data):
"""Load container information."""
return super(Container, self).load(data)
class PerfWrapper(SpecField):
"""Information on whether to use perf for a container."""
fields = {"enabled": spec(unicode, False)
}
def __init__(self):
"""Create empty perf wrapper."""
pass
def load(self, data):
"""Load perf wrapper information."""
ret = super(PerfWrapper, self).load(data)
if not ret:
return ret
if getattr(self, "enabled", "1") not in ["0", "False", "1", "True"]:
logger.error("Invalid value for perfwrapper enabled: %s",
self.enabled)
return False
return True
class Power(SpecField):
"""Power settings for a container."""
policies = ['NONE', 'DDCM', 'DVFS', 'COMBINED']
fields = {"enabled": spec(unicode, False),
"profile": spec(unicode, False),
"policy": spec(unicode, False),
"damper": spec(unicode, False),
"slowdown": spec(unicode, False)
}
def __init__(self):
"""Create empty power settings object."""
pass
def load(self, data):
"""Load power settings."""
ret = super(Power, self).load(data)
if not ret:
return ret
if self.enabled not in ["0", "False", "1", "True"]:
logger.error("Invalid value for power enabled: %s",
self.enabled)
return False
if self.profile not in ["0", "False", "1", "True"]:
logger.error("Invalid value for power profile: %s",
self.enabled)
return False
if self.policy not in self.policies:
logger.error("Invalid value for power policy: %s",
self.policy)
return False
if self.damper < 0.0:
logger.error("Invalid value for power policy damper: %s",
self.policy)
return False
if self.slowdown < 1.0:
logger.error("Invalid value for power policy slowdown: %s",
self.policy)
return False
if self.damper < 0.0:
logger.error("Invalid value of powerpolicy damper: %s",
self.policy)
return False
if self.slowdown < 1.0:
logger.error("Invalid value of powerpolicy slowdown: %s",
self.policy)
return False
return True
class HwBind(SpecField):
"""Hardware bindings for a container."""
fields = {"enabled": spec(unicode, False),
}
def __init__(self):
"""Create empty hardware bindings settings object."""
pass
def load(self, data):
"""Load hardware bindings settings."""
ret = super(HwBind, self).load(data)
if not ret:
return ret
if self.enabled not in ["0", "False", "1", "True"]:
logger.error("Invalid value for hardware bindings enabled: %s",
self.enabled)
return False
return True
class Monitoring(SpecField):
"""Monitoring options (libnrm)."""
fields = {"enabled": spec(unicode, False),
"ratelimit": spec(unicode, False),
}
def __init__(self):
"""Create empty monitoring option object."""
pass
def load(self, data):
"""Load monitoring options."""
ret = super(Monitoring, self).load(data)
if not ret:
return ret
if self.enabled not in ["0", "False", "1", "True"]:
logger.error("Invalid value for monitoring options enabled: %s",
self.enabled)
return False
if self.ratelimit < 0:
logger.error("Invalid value for monitoring ratelimit: %s",
self.ratelimit)
return False
return True
class IsolatorList(SpecField):
"""Represent the list of isolator in a Manifest."""
types = {"argo/scheduler": spec(Scheduler, False),
"argo/container": spec(Container, True),
"argo/perfwrapper": spec(PerfWrapper, False),
"argo/power": spec(Power, False),
"argo/hwbind": spec(HwBind, False),
"argo/monitoring": spec(Monitoring, False),
}
def __init__(self):
"""Create empty list."""
pass
def load(self, data):
"""Load from json struct."""
for e in data:
name = e['name']
if name in self.types:
t = self.types[name]
ok, v = super(IsolatorList, self).loadfield(e['value'], t.cls)
if not ok:
logger.error("Error with %s in %s", name, self.__class__)
return False
setattr(self, name.lstrip("argo/"), v)
for k in self.types:
if self.types[k].required:
if not hasattr(self, k.lstrip("argo/")):
logger.error("Missing mandatory isolator: %s", k)
return False
return True
class App(SpecField):
"""Represent the App part of an Image Manifest."""
# attribute, subclass, required
fields = {"environment": spec(list, False),
"isolators": spec(IsolatorList, True),
}
def __init__(self):
"""Create empty container."""
pass
def load(self, data):
"""Load from json dict."""
return super(App, self).load(data)
class Image(SpecField):
"""Information on the container image to use."""
fields = {"path": spec(unicode, True),
"type": spec(unicode, True),
}
def __init__(self):
"""Create an empty image."""
pass
def load(self, data):
"""Load from json dict."""
ret = super(Image, self).load(data)
if not ret:
return ret
if self.type not in ['sif', 'docker']:
logger.error("Image type not recognized")
return False
return True
class ImageManifest(SpecField):
"""Represent an ACI Image Manifest."""
fields = {"acKind": spec(unicode, True),
"acVersion": spec(unicode, True),
"name": spec(unicode, True),
"app": spec(App, True),
"image": spec(Image, False),
}
def __init__(self):
"""Create empty manifest."""
pass
def load(self, filename):
"""Load a manifest from JSON file."""
with open(filename, 'r') as f:
data = json.load(f)
return super(ImageManifest, self).load(data)
def load_dict(self, data):
"""Load a manifest in dictionary form."""
return super(ImageManifest, self).load(data)
def is_feature_enabled(self, feature, true_values=["1", "True"]):
"""Check if a specific feature is enabled.
Since the enabled field itself is optional, we return true if an
isolator is present in a manifest or the enabled field is not true."""
typename = "argo/{}".format(feature)
assert typename in IsolatorList.types, \
"{} in not a valid feature".format(feature)
logger.debug(repr(self))
if hasattr(self.app.isolators, feature):
isolator = getattr(self.app.isolators, feature)
if hasattr(isolator, 'enabled'):
if isolator.enabled not in true_values:
return False
return True
else:
return False
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
from __future__ import print_function from __future__ import print_function
from aci import ImageManifest from aci import ImageManifest
from json import load
from collections import namedtuple from collections import namedtuple
import logging import logging
from subprograms import ChrtClient, NodeOSClient, resources, SingularityClient from subprograms import ChrtClient, NodeOSClient, resources, SingularityClient
...@@ -54,8 +55,8 @@ class ContainerManager(object): ...@@ -54,8 +55,8 @@ class ContainerManager(object):
return (False, self.containers[container_name]) return (False, self.containers[container_name])
# ask the resource manager for resources # ask the resource manager for resources
ncpus = int(manifest.app.isolators.container.cpus.value) ncpus = manifest.app['container']['cpus']
nmems = int(manifest.app.isolators.container.mems.value) nmems = manifest.app['container']['mems']
req = resources(ncpus, nmems) req = resources(ncpus, nmems)
allocated = self.resourcemanager.schedule(container_name, req) allocated = self.resourcemanager.schedule(container_name, req)
logger.info("create: allocation: %r", allocated) logger.info("create: allocation: %r", allocated)
...@@ -69,15 +70,15 @@ class ContainerManager(object): ...@@ -69,15 +70,15 @@ class ContainerManager(object):
container_power['manager'] = None container_power['manager'] = None
if manifest.is_feature_enabled('power'): if manifest.is_feature_enabled('power'):
pp = manifest.app.isolators.power pp = manifest.app['power']
if pp.profile in ["1", "True"]: