Skip to content

Commit cff5cb2

Browse files
fpadulatfoote
andauthored
Add support for ulimit flag (#291)
* Barebones extension * Add expected format to help message * Add tests for ulimit extension --------- Co-authored-by: Tully Foote <[email protected]>
1 parent cfdc630 commit cff5cb2

File tree

3 files changed

+170
-3
lines changed

3 files changed

+170
-3
lines changed

setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
'console_scripts': [
4444
'rocker = rocker.cli:main',
4545
'detect_docker_image_os = rocker.cli:detect_image_os',
46-
],
46+
],
4747
'rocker.extensions': [
4848
'cuda = rocker.nvidia_extension:Cuda',
4949
'devices = rocker.extensions:Devices',
@@ -63,11 +63,12 @@
6363
'pulse = rocker.extensions:PulseAudio',
6464
'rmw = rocker.rmw_extension:RMW',
6565
'ssh = rocker.ssh_extension:Ssh',
66+
'ulimit = rocker.ulimit_extension:Ulimit',
6667
'user = rocker.extensions:User',
6768
'volume = rocker.volume_extension:Volume',
6869
'x11 = rocker.nvidia_extension:X11',
6970
]
70-
},
71+
},
7172
'author': 'Tully Foote',
7273
'author_email': '[email protected]',
7374
'keywords': ['Docker'],
@@ -91,4 +92,3 @@
9192
}
9293

9394
setup(**kwargs)
94-

src/rocker/ulimit_extension.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright 2019 Open Source Robotics Foundation
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from argparse import ArgumentTypeError
16+
import re
17+
from rocker.extensions import RockerExtension, name_to_argument
18+
19+
20+
class Ulimit(RockerExtension):
21+
"""
22+
A RockerExtension to handle ulimit settings for Docker containers.
23+
24+
This extension allows specifying ulimit options in the format TYPE=SOFT_LIMIT[:HARD_LIMIT]
25+
and validates the format before passing them as Docker arguments.
26+
"""
27+
EXPECTED_FORMAT = "TYPE=SOFT_LIMIT[:HARD_LIMIT]"
28+
29+
@staticmethod
30+
def get_name():
31+
return 'ulimit'
32+
33+
def get_docker_args(self, cliargs):
34+
args = ['']
35+
ulimits = [x for sublist in cliargs[Ulimit.get_name()] for x in sublist]
36+
for ulimit in ulimits:
37+
if self.arg_format_is_valid(ulimit):
38+
args.append(f"--ulimit {ulimit}")
39+
else:
40+
raise ArgumentTypeError(
41+
f"Error processing {Ulimit.get_name()} flag '{ulimit}': expected format"
42+
f" {Ulimit.EXPECTED_FORMAT}")
43+
return ' '.join(args)
44+
45+
def arg_format_is_valid(self, arg: str):
46+
"""
47+
Validate the format of the ulimit argument.
48+
49+
Args:
50+
arg (str): The ulimit argument to validate.
51+
52+
Returns:
53+
bool: True if the format is valid, False otherwise.
54+
"""
55+
ulimit_format = r'(\w+)=(\w+)(:\w+)?$'
56+
match = re.match(ulimit_format, arg)
57+
return match is not None
58+
59+
@staticmethod
60+
def register_arguments(parser, defaults):
61+
parser.add_argument(name_to_argument(Ulimit.get_name()),
62+
type=str,
63+
nargs='+',
64+
action='append',
65+
metavar=Ulimit.EXPECTED_FORMAT,
66+
default=defaults.get(Ulimit.get_name(), None),
67+
help='ulimit options to add into the container.')

test/test_ulimit.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
import unittest
19+
from argparse import ArgumentTypeError
20+
21+
from rocker.ulimit_extension import Ulimit
22+
23+
24+
class UlimitTest(unittest.TestCase):
25+
"""Unit tests for the Ulimit class."""
26+
27+
def setUp(self):
28+
self._instance = Ulimit()
29+
30+
def _is_arg_translation_ok(self, mock_cliargs, expected):
31+
is_ok = False
32+
message_string = ""
33+
try:
34+
docker_args = self._instance.get_docker_args(
35+
{self._instance.get_name(): [mock_cliargs]})
36+
is_ok = docker_args == expected
37+
message_string = f"Expected: '{expected}', got: '{docker_args}'"
38+
except ArgumentTypeError:
39+
message_string = "Incorrect argument format"
40+
return (is_ok, message_string)
41+
42+
def test_args_single_soft(self):
43+
"""Test single soft limit argument."""
44+
mock_cliargs = ["rtprio=99"]
45+
expected = " --ulimit rtprio=99"
46+
self.assertTrue(*self._is_arg_translation_ok(mock_cliargs, expected))
47+
48+
def test_args_multiple_soft(self):
49+
"""Test multiple soft limit arguments."""
50+
mock_cliargs = ["rtprio=99", "memlock=102400"]
51+
expected = " --ulimit rtprio=99 --ulimit memlock=102400"
52+
self.assertTrue(*self._is_arg_translation_ok(mock_cliargs, expected))
53+
54+
def test_args_single_hard(self):
55+
"""Test single hard limit argument."""
56+
mock_cliargs = ["nofile=1024:524288"]
57+
expected = " --ulimit nofile=1024:524288"
58+
self.assertTrue(*self._is_arg_translation_ok(mock_cliargs, expected))
59+
60+
def test_args_multiple_hard(self):
61+
"""Test multiple hard limit arguments."""
62+
mock_cliargs = ["nofile=1024:524288", "rtprio=90:99"]
63+
expected = " --ulimit nofile=1024:524288 --ulimit rtprio=90:99"
64+
self.assertTrue(*self._is_arg_translation_ok(mock_cliargs, expected))
65+
66+
def test_args_multiple_mix(self):
67+
"""Test multiple mixed limit arguments."""
68+
mock_cliargs = ["rtprio=99", "memlock=102400", "nofile=1024:524288"]
69+
expected = " --ulimit rtprio=99 --ulimit memlock=102400 --ulimit nofile=1024:524288"
70+
self.assertTrue(*self._is_arg_translation_ok(mock_cliargs, expected))
71+
72+
def test_args_wrong_single_soft(self):
73+
"""Test if single soft limit argument is wrong."""
74+
mock_cliargs = ["rtprio99"]
75+
expected = " --ulimit rtprio99"
76+
self.assertFalse(*self._is_arg_translation_ok(mock_cliargs, expected))
77+
78+
def test_args_wrong_multiple_soft(self):
79+
"""Test if multiple soft limit arguments are wrong."""
80+
mock_cliargs = ["rtprio=99", "memlock102400"]
81+
expected = " --ulimit rtprio=99 --ulimit memlock=102400"
82+
self.assertFalse(*self._is_arg_translation_ok(mock_cliargs, expected))
83+
84+
def test_args_wrong_single_hard(self):
85+
"""Test if single hard limit arguments are wrong."""
86+
mock_cliargs = ["nofile=1024:524288:"]
87+
expected = " --ulimit nofile=1024:524288"
88+
self.assertFalse(*self._is_arg_translation_ok(mock_cliargs, expected))
89+
90+
def test_args_wrong_multiple_hard(self):
91+
"""Test if multiple hard limit arguments are wrong."""
92+
mock_cliargs = ["nofile1024524288", "rtprio=90:99"]
93+
expected = " --ulimit nofile=1024:524288 --ulimit rtprio=90:99"
94+
self.assertFalse(*self._is_arg_translation_ok(mock_cliargs, expected))
95+
96+
def test_args_wrong_multiple_mix(self):
97+
"""Test if multiple mixed limit arguments are wrong."""
98+
mock_cliargs = ["rtprio=:", "memlock102400", "nofile1024:524288:"]
99+
expected = " --ulimit rtprio=99 --ulimit memlock=102400 --ulimit nofile=1024:524288"
100+
self.assertFalse(*self._is_arg_translation_ok(mock_cliargs, expected))

0 commit comments

Comments
 (0)