Merge pull request #2363 from dnephin/pr-2261

Rebase of PR 2261
This commit is contained in:
Aanand Prasad 2015-11-11 15:48:28 +00:00
commit 7466d14826
6 changed files with 164 additions and 0 deletions

View File

@ -345,6 +345,15 @@ def validate_extended_service_dict(service_dict, filename, service):
"%s services with 'net: container' cannot be extended" % error_prefix)
def validate_ulimits(ulimit_config):
for limit_name, soft_hard_values in six.iteritems(ulimit_config):
if isinstance(soft_hard_values, dict):
if not soft_hard_values['soft'] <= soft_hard_values['hard']:
raise ConfigurationError(
"ulimit_config \"{}\" cannot contain a 'soft' value higher "
"than 'hard' value".format(ulimit_config))
def process_container_options(working_dir, service_dict):
service_dict = dict(service_dict)
@ -357,6 +366,9 @@ def process_container_options(working_dir, service_dict):
if 'labels' in service_dict:
service_dict['labels'] = parse_labels(service_dict['labels'])
if 'ulimits' in service_dict:
validate_ulimits(service_dict['ulimits'])
return service_dict

View File

@ -116,6 +116,25 @@
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"stdin_open": {"type": "boolean"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"volume_driver": {"type": "string"},

View File

@ -676,6 +676,7 @@ class Service(object):
devices = options.get('devices', None)
cgroup_parent = options.get('cgroup_parent', None)
ulimits = build_ulimits(options.get('ulimits', None))
return self.client.create_host_config(
links=self._get_links(link_to_self=one_off),
@ -692,6 +693,7 @@ class Service(object):
cap_drop=cap_drop,
mem_limit=options.get('mem_limit'),
memswap_limit=options.get('memswap_limit'),
ulimits=ulimits,
log_config=log_config,
extra_hosts=extra_hosts,
read_only=read_only,
@ -1073,6 +1075,23 @@ def parse_restart_spec(restart_config):
return {'Name': name, 'MaximumRetryCount': int(max_retry_count)}
# Ulimits
def build_ulimits(ulimit_config):
if not ulimit_config:
return None
ulimits = []
for limit_name, soft_hard_values in six.iteritems(ulimit_config):
if isinstance(soft_hard_values, six.integer_types):
ulimits.append({'name': limit_name, 'soft': soft_hard_values, 'hard': soft_hard_values})
elif isinstance(soft_hard_values, dict):
ulimit_dict = {'name': limit_name}
ulimit_dict.update(soft_hard_values)
ulimits.append(ulimit_dict)
return ulimits
# Extra hosts

View File

@ -333,6 +333,18 @@ Override the default labeling scheme for each container.
- label:user:USER
- label:role:ROLE
### ulimits
Override the default ulimits for a container. You can either specify a single
limit as an integer or soft/hard limits as a mapping.
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000
### volumes, volume\_driver
Mount paths as volumes, optionally specifying a path on the host machine

View File

@ -349,6 +349,66 @@ class ConfigTest(unittest.TestCase):
)
)
def test_config_ulimits_invalid_keys_validation_error(self):
expected_error_msg = "Service 'web' configuration key 'ulimits' contains unsupported option: 'not_soft_or_hard'"
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
config.load(
build_config_details(
{'web': {
'image': 'busybox',
'ulimits': {
'nofile': {
"not_soft_or_hard": 100,
"soft": 10000,
"hard": 20000,
}
}
}},
'working_dir',
'filename.yml'
)
)
def test_config_ulimits_required_keys_validation_error(self):
expected_error_msg = "Service 'web' configuration key 'ulimits' u?'hard' is a required property"
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
config.load(
build_config_details(
{'web': {
'image': 'busybox',
'ulimits': {
'nofile': {
"soft": 10000,
}
}
}},
'working_dir',
'filename.yml'
)
)
def test_config_ulimits_soft_greater_than_hard_error(self):
expected_error_msg = "cannot contain a 'soft' value higher than 'hard' value"
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
config.load(
build_config_details(
{'web': {
'image': 'busybox',
'ulimits': {
'nofile': {
"soft": 10000,
"hard": 1000
}
}
}},
'working_dir',
'filename.yml'
)
)
def test_valid_config_which_allows_two_type_definitions(self):
expose_values = [["8000"], [8000]]
for expose in expose_values:

View File

@ -12,6 +12,7 @@ from compose.const import LABEL_ONE_OFF
from compose.const import LABEL_PROJECT
from compose.const import LABEL_SERVICE
from compose.container import Container
from compose.service import build_ulimits
from compose.service import build_volume_binding
from compose.service import ConfigError
from compose.service import ContainerNet
@ -497,6 +498,47 @@ class ServiceTest(unittest.TestCase):
self.assertEqual(service._get_links(link_to_self=True), [])
def sort_by_name(dictionary_list):
return sorted(dictionary_list, key=lambda k: k['name'])
class BuildUlimitsTestCase(unittest.TestCase):
def test_build_ulimits_with_dict(self):
ulimits = build_ulimits(
{
'nofile': {'soft': 10000, 'hard': 20000},
'nproc': {'soft': 65535, 'hard': 65535}
}
)
expected = [
{'name': 'nofile', 'soft': 10000, 'hard': 20000},
{'name': 'nproc', 'soft': 65535, 'hard': 65535}
]
assert sort_by_name(ulimits) == sort_by_name(expected)
def test_build_ulimits_with_ints(self):
ulimits = build_ulimits({'nofile': 20000, 'nproc': 65535})
expected = [
{'name': 'nofile', 'soft': 20000, 'hard': 20000},
{'name': 'nproc', 'soft': 65535, 'hard': 65535}
]
assert sort_by_name(ulimits) == sort_by_name(expected)
def test_build_ulimits_with_integers_and_dicts(self):
ulimits = build_ulimits(
{
'nproc': 65535,
'nofile': {'soft': 10000, 'hard': 20000}
}
)
expected = [
{'name': 'nofile', 'soft': 10000, 'hard': 20000},
{'name': 'nproc', 'soft': 65535, 'hard': 65535}
]
assert sort_by_name(ulimits) == sort_by_name(expected)
class NetTestCase(unittest.TestCase):
def test_net(self):