Skip to content

Commit

Permalink
DBUS API for Containerz.StopContainer
Browse files Browse the repository at this point in the history
  • Loading branch information
hdwhdw committed Nov 5, 2024
1 parent 13a5419 commit 4ae5d95
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 0 deletions.
86 changes: 86 additions & 0 deletions host_modules/docker_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Docker service handler"""

from host_modules import host_service
import docker
import signal
import errno

MOD_NAME = "docker_service"


class DockerService(host_service.HostModule):
"""
DBus endpoint that executes the docker command
"""

@host_service.method(
host_service.bus_name(MOD_NAME), in_signature="s", out_signature="is"
)
def stop(self, container):
"""
Stop a running Docker container.
Args:
container (str): The name or ID of the Docker container.
Returns:
tuple: A tuple containing the exit code (int) and a message indicating the result of the operation.
"""
try:
client = docker.from_env()
container = client.containers.get(container)
container.stop()
return 0, "Container {} has been stopped.".format(container.name)
except docker.errors.NotFound:
return errno.ENOENT, "Container {} does not exist.".format(container)
except Exception as e:
return 1, "Failed to stop container {}: {}".format(container, str(e))

@host_service.method(
host_service.bus_name(MOD_NAME), in_signature="si", out_signature="is"
)
def kill(self, container, signal=signal.SIGKILL):
"""
Kill or send a signal to a running Docker container.
Args:
container (str): The name or ID of the Docker container.
signal (int): The signal to send. Defaults to SIGKILL.
Returns:
tuple: A tuple containing the exit code (int) and a message indicating the result of the operation.
"""
try:
client = docker.from_env()
container = client.containers.get(container)
container.kill(signal=signal)
return 0, "Container {} has been killed with signal {}.".format(
container.name, signal
)
except docker.errors.NotFound:
return errno.ENOENT, "Container {} does not exist.".format(container)
except Exception as e:
return 1, "Failed to kill container {}: {}".format(container, str(e))

@host_service.method(
host_service.bus_name(MOD_NAME), in_signature="s", out_signature="is"
)
def restart(self, container):
"""
Restart a running Docker container.
Args:
container (str): The name or ID of the Docker container.
Returns:
tuple: A tuple containing the exit code (int) and a message indicating the result of the operation.
"""
try:
client = docker.from_env()
container = client.containers.get(container)
container.restart()
return 0, "Container {} has been restarted.".format(container.name)
except docker.errors.NotFound:
return errno.ENOENT, "Container {} does not exist.".format(container)
except Exception as e:
return 1, "Failed to restart container {}: {}".format(container, str(e))
170 changes: 170 additions & 0 deletions tests/host_modules/docker_service_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import errno
import docker
from unittest import mock
from host_modules.docker_service import DockerService

MOD_NAME = "docker_service"


class TestDockerService(object):
@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_docker_stop_success(self, MockInit, MockBusName, MockSystemBus):
mock_docker_client = mock.Mock()
mock_docker_client.containers.get.return_value.stop.return_value = None

with mock.patch.object(docker, "from_env", return_value=mock_docker_client):
docker_service = DockerService(MOD_NAME)
rc, _ = docker_service.stop("container_name")

assert rc == 0, "Return code is wrong"
mock_docker_client.containers.get.assert_called_once_with("container_name")
mock_docker_client.containers.get.return_value.stop.assert_called_once()

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_docker_stop_fail_not_exist(self, MockInit, MockBusName, MockSystemBus):
mock_docker_client = mock.Mock()
mock_docker_client.containers.get.side_effect = docker.errors.NotFound(
"Container not found"
)

with mock.patch.object(docker, "from_env", return_value=mock_docker_client):
docker_service = DockerService(MOD_NAME)
rc, msg = docker_service.stop("non_existent_container")

assert rc == errno.ENOENT, "Return code is wrong"
assert (
"not" in msg and "exist" in msg
), "Message should contain 'not' and 'exist'"
mock_docker_client.containers.get.assert_called_once_with(
"non_existent_container"
)

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_docker_stop_fail_api_error(self, MockInit, MockBusName, MockSystemBus):
mock_docker_client = mock.Mock()
mock_docker_client.containers.get.return_value.stop.side_effect = (
docker.errors.APIError("API error")
)

with mock.patch.object(docker, "from_env", return_value=mock_docker_client):
docker_service = DockerService(MOD_NAME)
rc, msg = docker_service.stop("container_name")

assert rc != 0, "Return code is wrong"
assert "API error" in msg, "Message should contain 'API error'"
mock_docker_client.containers.get.assert_called_once_with("container_name")
mock_docker_client.containers.get.return_value.stop.assert_called_once()

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_docker_kill_success(self, MockInit, MockBusName, MockSystemBus):
mock_docker_client = mock.Mock()
mock_docker_client.containers.get.return_value.kill.return_value = None

with mock.patch.object(docker, "from_env", return_value=mock_docker_client):
docker_service = DockerService(MOD_NAME)
rc, _ = docker_service.kill("container_name")

assert rc == 0, "Return code is wrong"
mock_docker_client.containers.get.assert_called_once_with("container_name")
mock_docker_client.containers.get.return_value.kill.assert_called_once()

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_docker_kill_fail_not_found(self, MockInit, MockBusName, MockSystemBus):
mock_docker_client = mock.Mock()
mock_docker_client.containers.get.side_effect = docker.errors.NotFound(
"Container not found"
)

with mock.patch.object(docker, "from_env", return_value=mock_docker_client):
docker_service = DockerService(MOD_NAME)
rc, msg = docker_service.kill("non_existent_container")

assert rc == errno.ENOENT, "Return code is wrong"
assert (
"not" in msg and "exist" in msg
), "Message should contain 'not' and 'exist'"
mock_docker_client.containers.get.assert_called_once_with(
"non_existent_container"
)

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_docker_kill_fail_api_error(self, MockInit, MockBusName, MockSystemBus):
mock_docker_client = mock.Mock()
mock_docker_client.containers.get.return_value.kill.side_effect = (
docker.errors.APIError("API error")
)

with mock.patch.object(docker, "from_env", return_value=mock_docker_client):
docker_service = DockerService(MOD_NAME)
rc, msg = docker_service.kill("container_name")

assert rc != 0, "Return code is wrong"
assert "API error" in msg, "Message should contain 'API error'"
mock_docker_client.containers.get.assert_called_once_with("container_name")
mock_docker_client.containers.get.return_value.kill.assert_called_once()

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_docker_restart_success(self, MockInit, MockBusName, MockSystemBus):
mock_docker_client = mock.Mock()
mock_docker_client.containers.get.return_value.restart.return_value = None

with mock.patch.object(docker, "from_env", return_value=mock_docker_client):
docker_service = DockerService(MOD_NAME)
rc, _ = docker_service.restart("container_name")

assert rc == 0, "Return code is wrong"
mock_docker_client.containers.get.assert_called_once_with("container_name")
mock_docker_client.containers.get.return_value.restart.assert_called_once()

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_docker_restart_fail_not_found(self, MockInit, MockBusName, MockSystemBus):
mock_docker_client = mock.Mock()
mock_docker_client.containers.get.side_effect = docker.errors.NotFound(
"Container not found"
)

with mock.patch.object(docker, "from_env", return_value=mock_docker_client):
docker_service = DockerService(MOD_NAME)
rc, msg = docker_service.restart("non_existent_container")

assert rc == errno.ENOENT, "Return code is wrong"
assert (
"not" in msg and "exist" in msg
), "Message should contain 'not' and 'exist'"
mock_docker_client.containers.get.assert_called_once_with(
"non_existent_container"
)

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_docker_restart_fail_api_error(self, MockInit, MockBusName, MockSystemBus):
mock_docker_client = mock.Mock()
mock_docker_client.containers.get.return_value.restart.side_effect = (
docker.errors.APIError("API error")
)

with mock.patch.object(docker, "from_env", return_value=mock_docker_client):
docker_service = DockerService(MOD_NAME)
rc, msg = docker_service.restart("container_name")

assert rc != 0, "Return code is wrong"
assert "API error" in msg, "Message should contain 'API error'"
mock_docker_client.containers.get.assert_called_once_with("container_name")
mock_docker_client.containers.get.return_value.restart.assert_called_once()

0 comments on commit 4ae5d95

Please sign in to comment.