Files
2020-04-08 10:39:44 -07:00

303 lines
13 KiB
Python

import mock
import pytest
from ddtrace.compat import PY2
from ddtrace.internal.runtime.container import CGroupInfo, get_container_info
from .utils import cgroup_line_valid_test_cases
# Map expected Py2 exception to Py3 name
if PY2:
FileNotFoundError = IOError # noqa: A001
def get_mock_open(read_data=None):
mock_open = mock.mock_open(read_data=read_data)
return mock.patch('ddtrace.internal.runtime.container.open', mock_open)
def test_cgroup_info_init():
# Assert default all attributes to `None`
info = CGroupInfo()
for attr in ('id', 'groups', 'path', 'container_id', 'controllers', 'pod_id'):
assert getattr(info, attr) is None
# Assert init with property sets property
info = CGroupInfo(container_id='test-container-id')
assert info.container_id == 'test-container-id'
@pytest.mark.parametrize(
'line,expected_info',
# Valid generated cases + one off cases
cgroup_line_valid_test_cases() + [
# Valid, extra spaces
(
' 13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 ',
CGroupInfo(
id='13',
groups='name=systemd',
controllers=['name=systemd'],
path='/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
container_id='3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
pod_id=None,
),
),
# Valid, bookended newlines
(
'\r\n13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\r\n',
CGroupInfo(
id='13',
groups='name=systemd',
controllers=['name=systemd'],
path='/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
container_id='3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
pod_id=None,
),
),
# Invalid container_ids
(
# One character too short
'13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f86986',
CGroupInfo(
id='13',
groups='name=systemd',
controllers=['name=systemd'],
path='/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f86986',
container_id=None,
pod_id=None,
),
),
(
# One character too long
'13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f8698600',
CGroupInfo(
id='13',
groups='name=systemd',
controllers=['name=systemd'],
path='/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f8698600',
container_id=None,
pod_id=None,
),
),
(
# Non-hex
'13:name=systemd:/docker/3726184226f5d3147c25fzyxw5b60097e378e8a720503a5e19ecfdf29f869860',
CGroupInfo(
id='13',
groups='name=systemd',
controllers=['name=systemd'],
path='/docker/3726184226f5d3147c25fzyxw5b60097e378e8a720503a5e19ecfdf29f869860',
container_id=None,
pod_id=None,
),
),
# Invalid id
(
# non-digit
'a:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
None,
),
(
# missing
':name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
None,
),
# Missing group
(
# empty
'13::/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
CGroupInfo(
id='13',
groups='',
controllers=[],
path='/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
container_id='3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
pod_id=None,
),
),
(
# missing
'13:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
None,
),
# Empty line
(
'',
None,
),
],
)
def test_cgroup_info_from_line(line, expected_info):
info = CGroupInfo.from_line(line)
if expected_info is None:
assert info is None, line
else:
for attr in ('id', 'groups', 'path', 'container_id', 'controllers', 'pod_id'):
assert getattr(info, attr) == getattr(expected_info, attr), line
@pytest.mark.parametrize(
'file_contents,container_id',
(
# Docker file
(
"""
13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
""",
'3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860',
),
# k8s file
(
"""
11:perf_event:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
10:pids:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
9:memory:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
8:cpu,cpuacct:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
7:blkio:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
6:cpuset:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
5:devices:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
4:freezer:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
3:net_cls,net_prio:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
2:hugetlb:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
1:name=systemd:/kubepods/test/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
""",
'3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1',
),
# ECS file
(
"""
9:perf_event:/ecs/test-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
8:memory:/ecs/test-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
7:hugetlb:/ecs/test-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
6:freezer:/ecs/test-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
5:devices:/ecs/test-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
4:cpuset:/ecs/test-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
3:cpuacct:/ecs/test-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
2:cpu:/ecs/test-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
1:blkio:/ecs/test-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
""",
'38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce',
),
# Fargate file
(
"""
11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
""",
'432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da',
),
# Linux non-containerized file
(
"""
11:blkio:/user.slice/user-0.slice/session-14.scope
10:memory:/user.slice/user-0.slice/session-14.scope
9:hugetlb:/
8:cpuset:/
7:pids:/user.slice/user-0.slice/session-14.scope
6:freezer:/
5:net_cls,net_prio:/
4:perf_event:/
3:cpu,cpuacct:/user.slice/user-0.slice/session-14.scope
2:devices:/user.slice/user-0.slice/session-14.scope
1:name=systemd:/user.slice/user-0.slice/session-14.scope
""",
None,
),
# Empty file
(
'',
None,
),
# Missing file
(
None,
None,
)
)
)
def test_get_container_info(file_contents, container_id):
with get_mock_open(read_data=file_contents) as mock_open:
# simulate the file not being found
if file_contents is None:
mock_open.side_effect = FileNotFoundError
info = get_container_info()
if container_id is None:
assert info is None
else:
assert info.container_id == container_id
mock_open.assert_called_once_with('/proc/self/cgroup', mode='r')
@pytest.mark.parametrize(
'pid,file_name',
(
('13', '/proc/13/cgroup'),
(13, '/proc/13/cgroup'),
('self', '/proc/self/cgroup'),
)
)
def test_get_container_info_with_pid(pid, file_name):
# DEV: We need at least 1 line for the loop to call `CGroupInfo.from_line`
with get_mock_open(read_data='\r\n') as mock_open:
assert get_container_info(pid=pid) is None
mock_open.assert_called_once_with(file_name, mode='r')
@mock.patch('ddtrace.internal.runtime.container.CGroupInfo.from_line')
@mock.patch('ddtrace.internal.runtime.container.log')
def test_get_container_info_exception(mock_log, mock_from_line):
exception = Exception()
mock_from_line.side_effect = exception
# DEV: We need at least 1 line for the loop to call `CGroupInfo.from_line`
with get_mock_open(read_data='\r\n') as mock_open:
# Assert calling `get_container_info()` does not bubble up the exception
assert get_container_info() is None
# Assert we called everything we expected
mock_from_line.assert_called_once_with('\r\n')
mock_open.assert_called_once_with('/proc/self/cgroup', mode='r')
# Ensure we logged the exception
mock_log.debug.assert_called_once_with('Failed to parse cgroup file for pid %r', 'self', exc_info=True)