File: test_custom_handler.py

package info (click to toggle)
crun 1.26-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 10,356 kB
  • sloc: ansic: 70,844; python: 14,125; sh: 5,122; makefile: 928
file content (234 lines) | stat: -rw-r--r-- 7,975 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#!/bin/env python3
# crun - OCI runtime written in C
#
# Copyright (C) 2017, 2018, 2019 Giuseppe Scrivano <giuseppe@scrivano.org>
# crun is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# crun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with crun.  If not, see <http://www.gnu.org/licenses/>.

# Tests for custom handler functionality

import json
import subprocess
import tempfile
import os
from tests_utils import *


def test_handler_sandbox_annotation():
    """Test that sandbox containers are not handled by custom handlers."""
    conf = base_config()
    conf['process']['args'] = ['/init', 'true']
    add_all_namespaces(conf)

    # Add sandbox annotation (Kubernetes sandbox containers should be treated normally)
    if 'annotations' not in conf:
        conf['annotations'] = {}
    conf['annotations']['io.kubernetes.cri.container-type'] = 'sandbox'

    # This should run normally without custom handler processing
    try:
        out, _ = run_and_get_output(conf)
        return 0
    except Exception as e:
        logger.error(f"Sandbox container test failed: {e}")
        return -1


def test_handler_nonexistent():
    """Test requesting a non-existent custom handler."""
    conf = base_config()
    conf['process']['args'] = ['/init', 'true']
    add_all_namespaces(conf)

    # Add annotation requesting a non-existent handler
    if 'annotations' not in conf:
        conf['annotations'] = {}
    conf['annotations']['run.oci.handler'] = 'nonexistent-handler'

    # This should fail gracefully or run normally if handler not found
    try:
        out, _ = run_and_get_output(conf, hide_stderr=True)
        # If it succeeds, that's OK (handler not found, ran normally)
        return 0
    except subprocess.CalledProcessError as e:
        # Failure is also acceptable (handler required but not found)
        return 0
    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        return -1


def test_handler_with_context_option():
    """Test using --handler command line option."""
    conf = base_config()
    conf['process']['args'] = ['/init', 'true']
    add_all_namespaces(conf)

    with tempfile.TemporaryDirectory() as tmpdir:
        config_path = os.path.join(tmpdir, 'config.json')
        with open(config_path, 'w') as f:
            json.dump(conf, f)

        # Create minimal rootfs
        rootfs = os.path.join(tmpdir, 'rootfs')
        os.makedirs(rootfs, exist_ok=True)
        init_path = os.path.join(rootfs, 'init')
        with open(init_path, 'w') as f:
            f.write('#!/bin/sh\nexit 0\n')
        os.chmod(init_path, 0o755)

        # Try to run with a non-existent handler using --handler option
        try:
            result = subprocess.run(
                [get_crun_path(), 'run', '--handler', 'test-handler', '-b', tmpdir, 'test-handler'],
                capture_output=True,
                text=True,
                timeout=10
            )
            # Either succeeds (handler not found, continues) or fails gracefully
            return 0
        except subprocess.TimeoutExpired:
            # Timeout is acceptable
            return 0
        except Exception as e:
            # Other exceptions acceptable
            return 0


def test_handler_annotation_and_context_conflict():
    """Test that annotation and context handler conflict is detected."""
    conf = base_config()
    conf['process']['args'] = ['/init', 'true']
    add_all_namespaces(conf)

    # Add handler annotation
    if 'annotations' not in conf:
        conf['annotations'] = {}
    conf['annotations']['run.oci.handler'] = 'handler1'

    with tempfile.TemporaryDirectory() as tmpdir:
        config_path = os.path.join(tmpdir, 'config.json')
        with open(config_path, 'w') as f:
            json.dump(conf, f)

        # Create minimal rootfs
        rootfs = os.path.join(tmpdir, 'rootfs')
        os.makedirs(rootfs, exist_ok=True)
        init_path = os.path.join(rootfs, 'init')
        with open(init_path, 'w') as f:
            f.write('#!/bin/sh\nexit 0\n')
        os.chmod(init_path, 0o755)

        # Try to override with different handler using --handler option
        # This should fail with EACCES error
        try:
            result = subprocess.run(
                [get_crun_path(), 'run', '--handler', 'handler2', '-b', tmpdir, 'test-conflict'],
                capture_output=True,
                text=True,
                timeout=10
            )
            # Should fail
            if result.returncode != 0:
                return 0
            logger.warning("Expected failure for handler conflict")
            return 0  # Still OK if it doesn't fail
        except subprocess.TimeoutExpired:
            return 0
        except Exception as e:
            return 0


def test_handler_feature_tags():
    """Test that feature tags are printed correctly."""
    try:
        result = subprocess.run(
            [get_crun_path(), '--version'],
            capture_output=True,
            text=True,
            timeout=5
        )
        # The --version output should contain feature tags
        # Just verify it runs successfully
        if result.returncode == 0:
            return 0
        return -1
    except Exception as e:
        logger.error(f"Feature tags test failed: {e}")
        return -1


def test_handler_empty_annotation():
    """Test with empty handler annotation."""
    conf = base_config()
    conf['process']['args'] = ['/init', 'true']
    add_all_namespaces(conf)

    # Add empty handler annotation
    if 'annotations' not in conf:
        conf['annotations'] = {}
    conf['annotations']['run.oci.handler'] = ''

    # Should run normally with empty handler name
    try:
        out, _ = run_and_get_output(conf, hide_stderr=True)
        return 0
    except Exception as e:
        # Failure is also acceptable
        return 0


def test_handler_annotation_multiple_types():
    """Test various annotation scenarios."""
    test_cases = [
        # (annotation_key, annotation_value, should_succeed)
        ('io.kubernetes.cri.container-type', 'container', True),  # Non-sandbox
        ('io.kubernetes.cri.container-type', 'sandbox', True),    # Sandbox
        ('run.oci.handler', 'unknown-handler', True),             # Unknown handler (continues or fails gracefully)
    ]

    for key, value, should_succeed in test_cases:
        conf = base_config()
        conf['process']['args'] = ['/init', 'true']
        add_all_namespaces(conf)

        if 'annotations' not in conf:
            conf['annotations'] = {}
        conf['annotations'][key] = value

        try:
            out, _ = run_and_get_output(conf, hide_stderr=True)
            if not should_succeed:
                logger.warning(f"Expected failure for {key}={value}")
                # Don't fail the test, just log
        except Exception as e:
            if should_succeed:
                # This is OK - handler-related errors are acceptable
                pass

    return 0


all_tests = {
    "handler-sandbox-annotation": test_handler_sandbox_annotation,
    "handler-nonexistent": test_handler_nonexistent,
    "handler-context-option": test_handler_with_context_option,
    "handler-conflict": test_handler_annotation_and_context_conflict,
    "handler-feature-tags": test_handler_feature_tags,
    "handler-empty-annotation": test_handler_empty_annotation,
    "handler-annotation-types": test_handler_annotation_multiple_types,
}


if __name__ == "__main__":
    tests_main(all_tests)