# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time from docker import errors from requests import exceptions as req_exceptions from magnum.common import docker_utils import magnum.conf from magnum.i18n import _LI from magnum.tests.functional.python_client_base import ClusterTest CONF = magnum.conf.CONF class TestSwarmAPIs(ClusterTest): """This class will cover swarm cluster basic functional testing. Will test all kinds of container action with tls_disabled=False mode. """ coe = "swarm" cluster_template_kwargs = { "tls_disabled": False, "network_driver": None, "volume_driver": None, "labels": {} } @classmethod def setUpClass(cls): super(TestSwarmAPIs, cls).setUpClass() cls.cluster_is_ready = None def setUp(self): super(TestSwarmAPIs, self).setUp() if self.cluster_is_ready is True: return # Note(eliqiao): In our test cases, docker client or magnum client will # try to connect to swarm service which is running on master node, # the endpoint is cluster.api_address(listen port is included), but the # service is not ready right after the cluster was created, sleep for # an acceptable time to wait for service being started. # This is required, without this any api call will fail as # 'ConnectionError: [Errno 111] Connection refused'. msg = ("If you see this error in the functional test, it means " "the docker service took too long to come up. This may not " "be an actual error, so an option is to rerun the " "functional test.") if self.cluster_is_ready is False: # In such case, no need to test below cases on gate, raise a # meanful exception message to indicate ca setup failed after # cluster creation, better to do a `recheck` # We don't need to test since cluster is not ready. raise Exception(msg) url = self.cs.clusters.get(self.cluster.uuid).api_address # FIXME (strigazi) until we upgrade to docker-py 1.8.0 use # only the https protocol instead of tcp. https_url = url.replace('tcp', 'https') # Note(eliqiao): docker_utils.CONF.docker.default_timeout is 10, # tested this default configure option not works on gate, it will # cause container creation failed due to time out. # Debug more found that we need to pull image when the first time to # create a container, set it as 180s. docker_api_time_out = 180 self.docker_client = docker_utils.DockerHTTPClient( https_url, CONF.docker.docker_remote_api_version, docker_api_time_out, client_key=self.key_file, client_cert=self.cert_file, ca_cert=self.ca_file) self.docker_client_non_tls = docker_utils.DockerHTTPClient( https_url, CONF.docker.docker_remote_api_version, docker_api_time_out) def _container_operation(self, func, *args, **kwargs): # NOTE(hongbin): Swarm cluster occasionally aborts the connection, # so we re-try the operation several times here. In long-term, we # need to investigate the cause of this issue. See bug #1583337. for i in range(150): try: self.LOG.info(_LI("Calling function ") + func.__name__) return func(*args, **kwargs) except req_exceptions.ConnectionError: self.LOG.info(_LI("Connection aborted on calling Swarm API. " "Will retry in 2 seconds.")) except errors.APIError as e: if e.response.status_code != 500: raise self.LOG.info(_LI("Internal Server Error: ") + str(e)) time.sleep(2) raise Exception("Cannot connect to Swarm API.") def _create_container(self, **kwargs): image = kwargs.get('image', 'docker.io/cirros') command = kwargs.get('command', 'ping -c 1000 8.8.8.8') return self._container_operation(self.docker_client.create_container, image=image, command=command) def test_start_stop_container_from_api(self): # Leverage docker client to create a container on the cluster we # created, and try to start and stop it then delete it. resp = self._create_container(image="docker.io/cirros", command="ping -c 1000 8.8.8.8") resp = self._container_operation(self.docker_client.containers, all=True) container_id = resp[0].get('Id') self._container_operation(self.docker_client.start, container=container_id) resp = self._container_operation(self.docker_client.containers) self.assertEqual(1, len(resp)) resp = self._container_operation(self.docker_client.inspect_container, container=container_id) self.assertTrue(resp['State']['Running']) self._container_operation(self.docker_client.stop, container=container_id) resp = self._container_operation(self.docker_client.inspect_container, container=container_id) self.assertFalse(resp['State']['Running']) self._container_operation(self.docker_client.remove_container, container=container_id) resp = self._container_operation(self.docker_client.containers) self.assertEqual([], resp) def test_access_with_non_tls_client(self): self.assertRaises(req_exceptions.SSLError, self.docker_client_non_tls.containers)