From 1532b3ed9b2cac6fc9a877e010552e0e69146078 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Fri, 12 Feb 2016 11:57:56 +0100 Subject: [PATCH] Support for mounting volumes Fix #425 --- gns3server/modules/docker/docker_vm.py | 32 ++++++++++++++-- gns3server/schemas/docker.py | 6 ++- tests/modules/docker/test_docker_vm.py | 51 ++++++++++++++++++++++---- 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/gns3server/modules/docker/docker_vm.py b/gns3server/modules/docker/docker_vm.py index 4bd7fc12..aa6a4e69 100644 --- a/gns3server/modules/docker/docker_vm.py +++ b/gns3server/modules/docker/docker_vm.py @@ -25,6 +25,7 @@ import psutil import shlex import aiohttp import json +import os from ...ubridge.hypervisor import Hypervisor from .docker_error import * @@ -84,7 +85,8 @@ class DockerVM(BaseVM): "adapters": self.adapters, "console": self.console, "start_command": self.start_command, - "environment": self.environment + "environment": self.environment, + "vm_directory": self.working_dir } @property @@ -118,9 +120,31 @@ class DockerVM(BaseVM): return "running" return "exited" + @asyncio.coroutine + def _get_image_informations(self): + """ + :returns: Dictionnary informations about the container image + """ + result = yield from self.manager.query("GET", "images/{}/json".format(self._image)) + return result + + def _mount_binds(self, image_infos): + """ + :returns: Return the path that we need to map to local folders + """ + binds = [] + for volume in image_infos.get("ContainerConfig", {}).get("Volumes", {}).keys(): + source = os.path.join(self.working_dir, os.path.relpath(volume, "/")) + os.makedirs(source, exist_ok=True) + binds.append("{}:{}".format(source, volume)) + return binds + @asyncio.coroutine def create(self): """Creates the Docker container.""" + + image_infos = yield from self._get_image_informations() + params = { "Name": self._name, "Image": self._image, @@ -130,8 +154,10 @@ class DockerVM(BaseVM): "StdinOnce": False, "HostConfig": { "CapAdd": ["ALL"], - "Privileged": True - } + "Privileged": True, + "Binds": self._mount_binds(image_infos) + }, + "Volumes": {} } if self._start_command: params.update({"Cmd": shlex.split(self._start_command)}) diff --git a/gns3server/schemas/docker.py b/gns3server/schemas/docker.py index 666ffacc..c3ad5554 100644 --- a/gns3server/schemas/docker.py +++ b/gns3server/schemas/docker.py @@ -159,10 +159,14 @@ DOCKER_OBJECT_SCHEMA = { "description": "Docker environment", "type": ["string", "null"], "minLength": 0, + }, + "vm_directory": { + "decription": "Path to the VM working directory", + "type": "string" } }, "additionalProperties": False, - "required": ["vm_id", "project_id", "image", "container_id", "adapters", "console", "start_command", "environment"] + "required": ["vm_id", "project_id", "image", "container_id", "adapters", "console", "start_command", "environment", "vm_directory"] } diff --git a/tests/modules/docker/test_docker_vm.py b/tests/modules/docker/test_docker_vm.py index 182f2f69..67d7d92c 100644 --- a/tests/modules/docker/test_docker_vm.py +++ b/tests/modules/docker/test_docker_vm.py @@ -18,6 +18,7 @@ import pytest import uuid import asyncio +import os from tests.utils import asyncio_patch from gns3server.ubridge.ubridge_error import UbridgeNamespaceError @@ -54,7 +55,8 @@ def test_json(vm, project): 'adapters': 1, 'console': vm.console, 'start_command': vm.start_command, - 'environment': vm.environment + 'environment': vm.environment, + 'vm_directory': vm.working_dir } @@ -75,8 +77,10 @@ def test_create(loop, project, manager): "HostConfig": { "CapAdd": ["ALL"], + "Binds": [], "Privileged": True }, + "Volumes": {}, "NetworkDisabled": True, "Name": "test", "Image": "ubuntu" @@ -102,8 +106,10 @@ def test_create_start_cmd(loop, project, manager): "HostConfig": { "CapAdd": ["ALL"], + "Binds": [], "Privileged": True }, + "Volumes": {}, "Cmd": ["/bin/ls"], "NetworkDisabled": True, "Name": "test", @@ -130,12 +136,11 @@ def test_create_environment(loop, project, manager): "HostConfig": { "CapAdd": ["ALL"], + "Binds": [], "Privileged": True }, - "Env": [ - "YES=1", - "NO=0" - ], + "Env": ["YES=1", "NO=0"], + "Volumes": {}, "NetworkDisabled": True, "Name": "test", "Image": "ubuntu" @@ -161,8 +166,10 @@ def test_create_image_not_available(loop, project, manager): "HostConfig": { "CapAdd": ["ALL"], + "Binds": [], "Privileged": True }, + "Volumes": {}, "NetworkDisabled": True, "Name": "test", "Image": "ubuntu" @@ -358,11 +365,13 @@ def test_update(loop, vm): "HostConfig": { "CapAdd": ["ALL"], + "Binds": [], "Privileged": True }, - "NetworkDisabled": True, - "Name": "test", - "Image": "ubuntu" + "Volumes": {}, + "NetworkDisabled": True, + "Name": "test", + "Image": "ubuntu" }) @@ -602,3 +611,29 @@ def test_get_log(loop, vm): with asyncio_patch("gns3server.modules.docker.Docker.http_query", return_value=mock_query) as mock: images = loop.run_until_complete(asyncio.async(vm._get_log())) mock.assert_called_with("GET", "containers/e90e34656842/logs", params={"stderr": 1, "stdout": 1}, data={}) + + +def test_get_image_informations(project, manager, loop): + response = { + } + with asyncio_patch("gns3server.modules.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), project, manager, "ubuntu") + loop.run_until_complete(asyncio.async(vm._get_image_informations())) + mock.assert_called_with("GET", "images/ubuntu/json") + + +def test_mount_binds(vm, tmpdir): + image_infos = { + "ContainerConfig": { + "Volumes": { + "/test/experimental": {} + } + } + } + + dst = os.path.join(vm.working_dir, "test/experimental") + assert vm._mount_binds(image_infos) == [ + "{}:{}".format(dst, "/test/experimental") + ] + + assert os.path.exists(dst)