예제 #1
0
    def test_run_detach(self):
        """
        Tests detach=True is passed to the underlying call by default
        """
        mock_container = create_mock_container()

        # Test detach=True is passed in even if not specified
        with MockDockerClient(mock_container) as mock_client:
            safe_docker = SafeDockerClient()
            assert safe_docker.run("image", "command") == 0
            assert mock_client.containers.run.call_count == 1
            _, kwargs = mock_client.containers.run.call_args
            assert kwargs["detach"] is True

        # Test passing in detach=True does not cause any issues
        with MockDockerClient(mock_container) as mock_client:
            safe_docker = SafeDockerClient()
            assert safe_docker.run("image", "command", detach=True) == 0
            assert mock_client.containers.run.call_count == 1
            _, kwargs = mock_client.containers.run.call_args
            assert kwargs["detach"] is True

        # Test detach=False fails
        with MockDockerClient(mock_container) as mock_client:
            safe_docker = SafeDockerClient()
            with self.assertRaises(ValueError):
                safe_docker.run("image", "command", detach=False)
                assert mock_client.containers.run.call_args_list == []
예제 #2
0
    def test_run_successful(self, mock_pthread_sigmask):
        """
        Tests successful run
        """
        mock_container = create_mock_container()

        with MockDockerClient(mock_container) as mock_client:
            safe_docker = SafeDockerClient()

            # Check return code is 0
            assert safe_docker.run("image", "command") == 0

            # Check call to container is correct
            assert mock_client.containers.run.call_args_list == [
                call("image",
                     "command",
                     detach=True,
                     environment={
                         "BUILD_NUMBER": "BUILD_NUMBER_5",
                         "BUILD_ID": "BUILD_ID_1",
                         "BUILD_TAG": "BUILD_TAG_7"
                     })
            ]

            # Check correct signals are blocked then unblocked
            assert mock_pthread_sigmask.call_args_list == [
                call(signal.SIG_BLOCK, {signal.SIGINT, signal.SIGTERM}),
                call(signal.SIG_UNBLOCK, {signal.SIGINT, signal.SIGTERM})
            ]

            # Assert container is stopped and removed
            assert mock_container.stop.call_count == 1
            assert mock_container.remove.call_count == 1
            assert len(safe_docker._containers) == 0
예제 #3
0
    def test_jenkins_vars(self):
        """
        Tests jenkins environment variables are appropriately passed to the underlying docker run call
        """
        # NOTE: It's important that these variables are passed to the underlying docker container
        # These variables are passed to the container so the process tree killer can find runaway
        # process inside the container
        # https://wiki.jenkins.io/display/JENKINS/ProcessTreeKiller
        # https://github.com/jenkinsci/jenkins/blob/578d6bacb33a5e99f149de504c80275796f0b231/core/src/main/java/hudson/model/Run.java#L2393

        jenkins_vars = {
            "BUILD_NUMBER": "BUILD_NUMBER_5",
            "BUILD_ID": "BUILD_ID_1",
            "BUILD_TAG": "BUILD_TAG_7"
        }
        mock_container = create_mock_container()

        # Test environment is empty if the jenkins vars are not present
        with MockDockerClient(mock_container) as mock_client:
            safe_docker = SafeDockerClient()
            assert safe_docker.run("image", "command") == 0
            assert mock_client.containers.run.call_count == 1
            _, kwargs = mock_client.containers.run.call_args
            assert kwargs["environment"] == {}

        # Test environment contains jenkins env vars if they are present
        with MockDockerClient(mock_container) as mock_client:
            with patch.dict(os.environ, jenkins_vars):
                safe_docker = SafeDockerClient()
                assert safe_docker.run("image", "command") == 0
                assert mock_client.containers.run.call_count == 1
                _, kwargs = mock_client.containers.run.call_args
                assert kwargs["environment"] == jenkins_vars

        # Test jenkins env vars are added to callers env vars
        user_env = {"key1": "value1", "key2": "value2"}
        with MockDockerClient(mock_container) as mock_client:
            with patch.dict(os.environ, jenkins_vars):
                safe_docker = SafeDockerClient()
                assert safe_docker.run("image",
                                       "command",
                                       environment=user_env) == 0
                assert mock_client.containers.run.call_count == 1
                _, kwargs = mock_client.containers.run.call_args
                assert kwargs["environment"] == {**jenkins_vars, **user_env}
예제 #4
0
 def test_container_remove_raises_returns_152(self):
     """
     Tests 152 is returned if an error is raised when calling container.remove
     """
     mock_container = create_mock_container()
     mock_container.remove.side_effect = RuntimeError(
         "Something bad happened")
     with MockDockerClient(mock_container):
         safe_docker = SafeDockerClient()
         assert safe_docker.run("image", "command") == 152
예제 #5
0
 def test_container_returns_non_zero_status_code(self):
     """
     Tests non-zero code from container is returned and the container
     is cleaned up
     """
     mock_container = create_mock_container(status_code=10)
     with MockDockerClient(mock_container):
         safe_docker = SafeDockerClient()
         # check return code and that container gets cleaned up
         assert safe_docker.run("image", "command") == 10
         assert mock_container.stop.call_count == 1
         assert mock_container.remove.call_count == 1
         assert len(safe_docker._containers) == 0
예제 #6
0
    def test_run_args_kwargs_passed(self):
        """
        Tests args and kwargs are passed to the container run call
        """
        mock_container = create_mock_container()

        # Test detach=True is passed in even if not specified
        with MockDockerClient(mock_container) as mock_client:
            safe_docker = SafeDockerClient()
            assert safe_docker.run("image",
                                   "command",
                                   "another_arg",
                                   str_param="value",
                                   bool_param=True,
                                   none_param=None,
                                   int_param=5,
                                   float_param=5.2,
                                   list_param=["this", "is", "a", "list"],
                                   map_param={
                                       "a": "5",
                                       "b": True,
                                       "c": 2
                                   }) == 0
            assert mock_client.containers.run.call_args_list == [
                call("image",
                     "command",
                     "another_arg",
                     detach=True,
                     environment={},
                     str_param="value",
                     bool_param=True,
                     none_param=None,
                     int_param=5,
                     float_param=5.2,
                     list_param=["this", "is", "a", "list"],
                     map_param={
                         "a": "5",
                         "b": True,
                         "c": 2
                     })
            ]
예제 #7
0
def container_run(docker_client: SafeDockerClient,
                  platform: str,
                  nvidia_runtime: bool,
                  docker_registry: str,
                  shared_memory_size: str,
                  local_ccache_dir: str,
                  command: List[str],
                  environment: Dict[str, str],
                  dry_run: bool = False) -> int:
    """Run command in a container"""
    container_wait_s = 600
    #
    # Environment setup
    #
    environment.update({
        'CCACHE_MAXSIZE': '500G',
        'CCACHE_TEMPDIR':
        '/tmp/ccache',  # temp dir should be local and not shared
        'CCACHE_DIR':
        '/work/ccache',  # this path is inside the container as /work/ccache is
        # mounted
        'CCACHE_LOGFILE':
        '/tmp/ccache.log',  # a container-scoped log, useful for ccache
        # verification.
    })
    environment.update(
        {k: os.environ[k]
         for k in ['CCACHE_MAXSIZE'] if k in os.environ})

    tag = get_docker_tag(platform=platform, registry=docker_registry)
    mx_root = get_mxnet_root()
    local_build_folder = buildir()
    # We need to create it first, otherwise it will be created by the docker daemon with root only permissions
    os.makedirs(local_build_folder, exist_ok=True)
    os.makedirs(local_ccache_dir, exist_ok=True)
    logging.info("Using ccache directory: %s", local_ccache_dir)

    # Equivalent command
    docker_cmd_list = [
        get_docker_binary(nvidia_runtime),
        'run',
        "--cap-add",
        "SYS_PTRACE",  # Required by ASAN
        '--rm',
        '--shm-size={}'.format(shared_memory_size),
        # mount mxnet root
        '-v',
        "{}:/work/mxnet".format(mx_root),
        # mount mxnet/build for storing build
        '-v',
        "{}:/work/build".format(local_build_folder),
        '-v',
        "{}:/work/ccache".format(local_ccache_dir),
        '-u',
        '{}:{}'.format(os.getuid(), os.getgid()),
        '-e',
        'CCACHE_MAXSIZE={}'.format(environment['CCACHE_MAXSIZE']),
        # temp dir should be local and not shared
        '-e',
        'CCACHE_TEMPDIR={}'.format(environment['CCACHE_TEMPDIR']),
        # this path is inside the container as /work/ccache is mounted
        '-e',
        "CCACHE_DIR={}".format(environment['CCACHE_DIR']),
        # a container-scoped log, useful for ccache verification.
        '-e',
        "CCACHE_LOGFILE={}".format(environment['CCACHE_LOGFILE']),
        '-ti',
        tag
    ]
    docker_cmd_list.extend(command)
    docker_cmd = ' \\\n\t'.join(docker_cmd_list)
    logging.info("Running %s in container %s", command, tag)
    logging.info("Executing the equivalent of:\n%s\n", docker_cmd)

    if not dry_run:
        #############################
        #
        signal.pthread_sigmask(signal.SIG_BLOCK,
                               {signal.SIGINT, signal.SIGTERM})
        # noinspection PyShadowingNames
        runtime = None
        if nvidia_runtime:
            # noinspection PyShadowingNames
            # runc is default (docker info | grep -i runtime)
            runtime = 'nvidia'

        return docker_client.run(tag,
                                 runtime=runtime,
                                 command=command,
                                 shm_size=shared_memory_size,
                                 user='******'.format(os.getuid(), os.getgid()),
                                 cap_add='SYS_PTRACE',
                                 volumes={
                                     mx_root: {
                                         'bind': '/work/mxnet',
                                         'mode': 'rw'
                                     },
                                     local_build_folder: {
                                         'bind': '/work/build',
                                         'mode': 'rw'
                                     },
                                     local_ccache_dir: {
                                         'bind': '/work/ccache',
                                         'mode': 'rw'
                                     },
                                 },
                                 environment=environment)
    return 0