Merge pull request #2575 from GNS3/bugfix/2574

Handle Docker API version requirements
This commit is contained in:
Jeremy Grossmann 2025-11-17 22:31:41 +10:00 committed by GitHub
commit 1f2036defd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 47 additions and 22 deletions

View File

@ -39,9 +39,9 @@ log = logging.getLogger(__name__)
# Be careful to keep it consistent # Be careful to keep it consistent
DOCKER_MINIMUM_API_VERSION = "1.25" DOCKER_MINIMUM_API_VERSION = "1.40"
DOCKER_MINIMUM_VERSION = "1.13" DOCKER_MINIMUM_VERSION = "19.03.8"
DOCKER_PREFERRED_API_VERSION = "1.30" DOCKER_PREFERRED_API_VERSION = "1.44"
CHUNK_SIZE = 1024 * 8 # 8KB CHUNK_SIZE = 1024 * 8 # 8KB
@ -123,22 +123,30 @@ class Docker(BaseManager):
if not self._connected: if not self._connected:
try: try:
self._connected = True self._connected = True
connector = self.connector() self._connector = self.connector()
version = await self.query("GET", "version") docker_info = await self.query("GET", "version")
except (aiohttp.ClientOSError, FileNotFoundError): except (aiohttp.ClientOSError, FileNotFoundError):
self._connected = False self._connected = False
raise DockerError("Can't connect to docker daemon") raise DockerError("Can't connect to Docker daemon")
docker_version = parse_version(version['ApiVersion']) api_version = parse_version(docker_info['ApiVersion'])
version = docker_info["Version"]
if docker_version < parse_version(DOCKER_MINIMUM_API_VERSION): if api_version < parse_version(DOCKER_MINIMUM_API_VERSION):
raise DockerError( raise DockerError("Docker version is {}. GNS3 requires a minimum version of {}".format(
"Docker version is {}. GNS3 requires a minimum version of {}".format( version,
version["Version"], DOCKER_MINIMUM_VERSION)) DOCKER_MINIMUM_VERSION)
)
preferred_api_version = parse_version(DOCKER_PREFERRED_API_VERSION) preferred_api_version = parse_version(DOCKER_PREFERRED_API_VERSION)
if docker_version >= preferred_api_version: if api_version >= preferred_api_version:
self._api_version = DOCKER_PREFERRED_API_VERSION self._api_version = DOCKER_PREFERRED_API_VERSION
else:
# use the Min API version supported by the daemon
self._api_version = docker_info['MinAPIVersion']
log.warning("Using Docker client with the minimum API version {}".format(self._api_version))
log.info("Connected to Docker daemon version {} using API version {}".format(version, self._api_version))
def connector(self): def connector(self):
@ -186,7 +194,7 @@ class Docker(BaseManager):
:param method: HTTP method :param method: HTTP method
:param path: Endpoint in API :param path: Endpoint in API
:param data: Dictionnary with the body. Will be transformed to a JSON :param data: Dictionary with the body. Will be transformed to a JSON
:param params: Parameters added as a query arg :param params: Parameters added as a query arg
:param timeout: Timeout :param timeout: Timeout
:returns: HTTP response :returns: HTTP response
@ -197,12 +205,11 @@ class Docker(BaseManager):
timeout = 60 * 60 * 24 * 31 # One month timeout timeout = 60 * 60 * 24 * 31 # One month timeout
if path == 'version': if path == 'version':
url = "http://docker/v1.24/" + path url = "http://docker/" + path
else: else:
url = "http://docker/v" + DOCKER_MINIMUM_API_VERSION + "/" + path await self._check_connection() # version is use by check connection
url = "http://docker/v" + self._api_version + "/" + path
try: try:
if path != "version": # version is use by check connection
await self._check_connection()
if self._session is None or self._session.closed: if self._session is None or self._session.closed:
connector = self.connector() connector = self.connector()
self._session = aiohttp.ClientSession(connector=connector) self._session = aiohttp.ClientSession(connector=connector)

View File

@ -47,7 +47,7 @@ async def test_query_success(vm):
vm._session.request = AsyncioMagicMock(return_value=response) vm._session.request = AsyncioMagicMock(return_value=response)
data = await vm.query("POST", "test", data={"a": True}, params={"b": 1}) data = await vm.query("POST", "test", data={"a": True}, params={"b": 1})
vm._session.request.assert_called_with('POST', vm._session.request.assert_called_with('POST',
'http://docker/v1.25/test', 'http://docker/v{}/test'.format(DOCKER_MINIMUM_API_VERSION),
data='{"a": true}', data='{"a": true}',
headers={'content-type': 'application/json'}, headers={'content-type': 'application/json'},
params={'b': 1}, params={'b': 1},
@ -69,7 +69,7 @@ async def test_query_error(vm):
with pytest.raises(DockerError): with pytest.raises(DockerError):
await vm.query("POST", "test", data={"a": True}, params={"b": 1}) await vm.query("POST", "test", data={"a": True}, params={"b": 1})
vm._session.request.assert_called_with('POST', vm._session.request.assert_called_with('POST',
'http://docker/v1.25/test', 'http://docker/v{}/test'.format(DOCKER_MINIMUM_API_VERSION),
data='{"a": true}', data='{"a": true}',
headers={'content-type': 'application/json'}, headers={'content-type': 'application/json'},
params={'b': 1}, params={'b': 1},
@ -89,7 +89,7 @@ async def test_query_error_json(vm):
with pytest.raises(DockerError): with pytest.raises(DockerError):
await vm.query("POST", "test", data={"a": True}, params={"b": 1}) await vm.query("POST", "test", data={"a": True}, params={"b": 1})
vm._session.request.assert_called_with('POST', vm._session.request.assert_called_with('POST',
'http://docker/v1.25/test', 'http://docker/v{}/test'.format(DOCKER_MINIMUM_API_VERSION),
data='{"a": true}', data='{"a": true}',
headers={'content-type': 'application/json'}, headers={'content-type': 'application/json'},
params={'b': 1}, params={'b': 1},
@ -180,7 +180,9 @@ async def test_docker_check_connection_docker_minimum_version(vm):
async def test_docker_check_connection_docker_preferred_version_against_newer(vm): async def test_docker_check_connection_docker_preferred_version_against_newer(vm):
response = { response = {
'ApiVersion': '1.31' 'ApiVersion': '1.52',
'Version': '29.0.1',
} }
with patch("gns3server.compute.docker.Docker.connector"), \ with patch("gns3server.compute.docker.Docker.connector"), \
@ -193,7 +195,9 @@ async def test_docker_check_connection_docker_preferred_version_against_newer(vm
async def test_docker_check_connection_docker_preferred_version_against_older(vm): async def test_docker_check_connection_docker_preferred_version_against_older(vm):
response = { response = {
'ApiVersion': '1.27', 'ApiVersion': '1.43',
'Version': '24.0.2',
'MinAPIVersion': '1.40'
} }
with patch("gns3server.compute.docker.Docker.connector"), \ with patch("gns3server.compute.docker.Docker.connector"), \
@ -203,6 +207,20 @@ async def test_docker_check_connection_docker_preferred_version_against_older(vm
assert vm._api_version == DOCKER_MINIMUM_API_VERSION assert vm._api_version == DOCKER_MINIMUM_API_VERSION
async def test_docker_check_connection_docker_unsupported_version(vm):
response = {
'ApiVersion': '1.25',
'Version': '1.13.1',
}
with patch("gns3server.compute.docker.Docker.connector"), \
asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response):
vm._connected = False
with pytest.raises(DockerError) as e:
await vm._check_connection()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_install_busybox(): async def test_install_busybox():