def test_failure_for_incorrect_urlpath(self): labels = self.good_container_dict["Labels"] labels[SIMPHONY_NS_RUNINFO.urlpath] = ( labels[SIMPHONY_NS_RUNINFO.urlpath] + '/') with self.assertRaises(ValueError): Container.from_docker_dict(self.good_container_dict)
def test_from_docker_dict_inspect_container(self): client = VirtualDockerClient.with_containers() actual = Container.from_docker_dict( client.inspect_container( 'd2b56bffb5655cb7668b685b80116041a20ee8662ebfa5b5cb68cfc423d9dc30' )) # noqa expected = Container( docker_id= 'd2b56bffb5655cb7668b685b80116041a20ee8662ebfa5b5cb68cfc423d9dc30', # noqa name= "/myrealm-johndoe-5b34ce60d95742fa828cdced12b4c342-ascvbefsda", # noqa image_name='simphonyproject/simphony-mayavi:0.6.0', image_id= 'sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', # noqa user="******", ip='0.0.0.0', port=666, url_id="20dcb84cdbea4b1899447246789093d0", mapping_id="5b34ce60d95742fa828cdced12b4c342", realm="myrealm", urlpath= "/user/johndoe/containers/20dcb84cdbea4b1899447246789093d0" # noqa ) assert_containers_equal(self, actual, expected)
def test_no_realm(self): labels = self.good_container_dict["Labels"] labels[SIMPHONY_NS_RUNINFO.realm] = "" container = Container.from_docker_dict(self.good_container_dict) self.assertEqual(container.realm, "")
def test_find_from_mapping_id(self): """ Test containers_for_mapping_id returns a list of Container """ result = yield self.manager.find_containers( user_name="johndoe", mapping_id="5b34ce60d95742fa828cdced12b4c342") expected = Container( docker_id= 'd2b56bffb5655cb7668b685b80116041a20ee8662ebfa5b5cb68cfc423d9dc30', # noqa mapping_id="5b34ce60d95742fa828cdced12b4c342", name= "/myrealm-johndoe-5b34ce60d95742fa828cdced12b4c342-ascvbefsda", # noqa image_name='simphonyproject/simphony-mayavi:0.6.0', # noqa user="******", image_id= "sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", # noqa ip='127.0.0.1', port=666, url_id='20dcb84cdbea4b1899447246789093d0', realm="myrealm", urlpath= "/user/johndoe/containers/20dcb84cdbea4b1899447246789093d0" # noqa ) self.assertEqual(len(result), 1) utils.assert_containers_equal(self, result[0], expected)
def test_retrieve(self): _, data = self.get("/api/v1/applications/one/", httpstatus.OK) self.assertEqual( data, { 'mapping_id': "one", 'image': { 'configurables': [], 'description': '', 'type': '', 'icon_128': '', 'name': 'boo', 'policy': { "allow_home": True, "volume_mode": 'ro', "volume_source": "foo", "volume_target": "bar", }, 'ui_name': 'foo_ui' } }) self._app.container_manager.find_containers = \ mock_coro_factory(return_value=[Container( name="container", image_name="xxx", url_id="yyy")]) _, data = self.get("/api/v1/applications/one/", httpstatus.OK) self.assertEqual( data, { 'mapping_id': "one", 'container': { 'image_name': 'xxx', 'name': 'container', 'url_id': 'yyy' }, 'image': { 'description': '', 'icon_128': '', 'type': '', 'name': 'boo', 'ui_name': 'foo_ui', 'policy': { "allow_home": True, "volume_mode": 'ro', "volume_source": "foo", "volume_target": "bar", }, 'configurables': [], } }) self.get("/api/v1/applications/three/", httpstatus.NOT_FOUND) # Check the not found case if the image is not present self._app.container_manager.image = mock_coro_factory(None) self.get("/api/v1/applications/one/", httpstatus.NOT_FOUND)
def test_from_docker_dict_without_public_port(self): container_dict = self.good_container_dict del container_dict["Ports"][0]["PublicPort"] # Container without public port actual = Container.from_docker_dict(container_dict) expected = self.expected expected.port = 80 assert_containers_equal(self, actual, expected)
def setUp(self): self.good_container_dict = { 'Command': '/startup.sh', 'Created': 1466756760, 'HostConfig': { 'NetworkMode': 'default' }, 'Id': 'b55a25bdda5273a4a835dbf7843937daff2f124cd6e39e6546bb0f9e6a84a76c', # noqa 'Image': 'empty-ubuntu:latest', 'ImageID': 'sha256:14f98aa95d388cabbc4aa44b4b547b729c64673f51fc3321dccdf42fee20f01a', # noqa 'Labels': { SIMPHONY_NS.ui_name: 'Empty Ubuntu', SIMPHONY_NS_RUNINFO.user: '******', SIMPHONY_NS_RUNINFO.url_id: "8e2fe66d5de74db9bbab50c0d2f92b33", # noqa SIMPHONY_NS_RUNINFO.realm: "myrealm", SIMPHONY_NS_RUNINFO.mapping_id: "492b7c27bb2041278ae851be1c551f4b", # noqa SIMPHONY_NS_RUNINFO.urlpath: "/user/johndoe/containers/8e2fe66d5de74db9bbab50c0d2f92b33" }, # noqa 'Names': ['/myrealm-johndoe-empty-ubuntu_3Alatest'], 'Ports': [{ 'IP': '0.0.0.0', 'PrivatePort': 8888, 'PublicPort': 32823, 'Type': 'tcp' }], 'State': 'running', 'Status': 'Up 56 minutes' } self.expected = Container( docker_id= 'b55a25bdda5273a4a835dbf7843937daff2f124cd6e39e6546bb0f9e6a84a76c', # noqa name='/myrealm-johndoe-empty-ubuntu_3Alatest', image_name='empty-ubuntu:latest', image_id= 'sha256:14f98aa95d388cabbc4aa44b4b547b729c64673f51fc3321dccdf42fee20f01a', # noqa user="******", ip='0.0.0.0', port=32823, mapping_id="492b7c27bb2041278ae851be1c551f4b", url_id="8e2fe66d5de74db9bbab50c0d2f92b33", urlpath= "/user/johndoe/containers/8e2fe66d5de74db9bbab50c0d2f92b33", # noqa realm="myrealm")
def _stop_and_remove_container(self, container_id): """Idempotent removal of a container by id. If the container is there, it will be removed. If it's not there, the unexpected conditions will be logged. Note: The container is only stopped if it belongs to the same realm. """ self.log.info("Stopping container {}".format(container_id)) container_info = yield self._get_container_info(container_id) if container_info is None: self.log.error('Could not find requested container {} ' 'during removal'.format(container_id)) return container = Container.from_docker_dict(container_info) if container.realm != self.realm: self.log.error( 'Container {} belongs to realm {} ' 'instead of {}. Refusing to stop.'.format( container_id, container.realm, self.realm) ) return # Technically, we have a race condition here, but it's pretty much # impossible to solve, and would only affect us if the container # id is identical. # Stop the container try: yield self._docker_client.stop(container_id) except APIError: self.log.exception( "Container '{}' could not be stopped.".format( container_id, ) ) else: self.log.info("Container '{}' is stopped.".format(container_id)) # Remove the container from docker try: yield self._docker_client.remove_container(container_id) except NotFound: self.log.error('Could not find requested container {} ' 'during removal'.format(container_id)) except APIError: self.log.exception( "Removal failed for container '{}'.".format(container_id) ) else: self.log.info("Container '{}' is removed.".format(container_id))
def test_from_docker_dict_with_public_port(self): """Test convertion from "docker ps" to Container with public port""" # With public port container_dict = self.good_container_dict # Container with public port actual = Container.from_docker_dict(container_dict) expected = self.expected assert_containers_equal(self, actual, expected)
def test_from_docker_dict_inspect_container(self): client = VirtualDockerClient.with_containers() actual = Container.from_docker_dict( client.inspect_container('d2b56bffb5655cb7668b685b80116041a20ee8662ebfa5b5cb68cfc423d9dc30')) # noqa expected = Container( docker_id='d2b56bffb5655cb7668b685b80116041a20ee8662ebfa5b5cb68cfc423d9dc30', # noqa name="/myrealm-johndoe-5b34ce60d95742fa828cdced12b4c342-ascvbefsda", # noqa image_name='simphonyproject/simphony-mayavi:0.6.0', image_id='sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', # noqa user="******", ip='0.0.0.0', port=666, url_id="20dcb84cdbea4b1899447246789093d0", mapping_id="5b34ce60d95742fa828cdced12b4c342", realm="myrealm", urlpath="/user/johndoe/containers/20dcb84cdbea4b1899447246789093d0" # noqa ) assert_containers_equal(self, actual, expected)
def containers_from_filters(self, filters): """Returns the currently running containers for a given filter Parameters ---------- filters: dict A dictionary of filters as in dockerpy Return ------ A list of Container objects, or an empty list if nothing is found. """ containers = [] infos = yield self._docker_client.containers(filters=filters) for info in infos: try: container = Container.from_docker_dict(info) except ValueError: self.log.exception("Unable to parse container info.") continue # override the ip and port obtained by the docker info with the # appropriate ip and port, considering that we might be using a # separate docker machine try: ip, port = yield from self._get_ip_and_port( container.docker_id) except RuntimeError: self.log.exception( "Unable to retrieve ip/port " "for container {}".format(container.docker_id)) continue container.ip = ip container.port = port containers.append(container) return containers
def test_multiple_ports_data(self): docker_dict = {'Id': 'container_id1', 'Config': { 'Labels': { 'eu.simphony-project.docker.ui_name': 'Mayavi 4.4.4', # noqa 'eu.simphony-project.docker.env.x11-depth': '', 'eu.simphony-project.docker.type': 'vncapp', 'eu.simphony-project.docker.env.x11-height': '', 'eu.simphony-project.docker.env.x11-width': '', 'eu.simphony-project.docker.description': 'Ubuntu machine with mayavi preinstalled'}, 'Image': 'image_name1' }, 'NetworkSettings': { 'Ports': { '8889/tcp': [ {'HostPort': '667', 'HostIp': '0.0.0.0'} ], '8888/tcp': [ {'HostPort': '666', 'HostIp': '0.0.0.0'} ] } }, 'Name': '/container_name1', 'State': 'running', 'Image': 'image_id1' } docker_dict["NetworkSettings"]["Ports"] = { '8888/tcp': [{'HostIp': '0.0.0.0', 'HostPort': '666'}], '8889/tcp': [{'HostIp': '0.0.0.0', 'HostPort': '667'}] } with self.assertRaises(ValueError): Container.from_docker_dict(docker_dict) docker_dict["NetworkSettings"]["Ports"] = { '8888/tcp': [ {'HostIp': '0.0.0.0', 'HostPort': '32782'}, {'HostIp': '0.0.0.0', 'HostPort': '32783'} ] } with self.assertRaises(ValueError): Container.from_docker_dict(docker_dict) docker_dict = { 'Labels': { 'eu.simphony-project.docker.runinfo.user': '******', 'eu.simphony-project.docker.env.x11-depth': '', 'eu.simphony-project.docker.runinfo.mapping_id': 'mapping_id', 'eu.simphony-project.docker.env.x11-height': '', 'eu.simphony-project.docker.ui_name': 'Mayavi 4.4.4', 'eu.simphony-project.docker.runinfo.urlpath': '/user/username/containers/url_id', # noqa 'eu.simphony-project.docker.type': 'vncapp', 'eu.simphony-project.docker.runinfo.realm': 'myrealm', 'eu.simphony-project.docker.description': 'Ubuntu machine with mayavi preinstalled', # noqa 'eu.simphony-project.docker.env.x11-width': '', 'eu.simphony-project.docker.runinfo.url_id': 'url_id' }, 'Names': ['/myrealm-username-mapping_5Fid'], 'Ports': [{'Type': 'tcp', 'IP': '0.0.0.0', 'PublicPort': 666, 'PrivatePort': 8888}], # noqa 'State': 'running', 'Command': '/sbin/init -D', 'Image': 'image_name1', 'Id': 'container_id1', 'ImageID': 'image_id1' } docker_dict["Ports"] = [ { 'IP': '0.0.0.0', 'PublicIP': 34567, 'PrivatePort': 22, 'Type': 'tcp' }, { 'IP': '0.0.0.0', 'PublicIP': 34562, 'PrivatePort': 21, 'Type': 'tcp' } ] with self.assertRaises(ValueError): Container.from_docker_dict(docker_dict)
def test_host_url(self): container = Container(ip="123.45.67.89", port=31337) self.assertEqual(container.host_url, "http://123.45.67.89:31337")
def test_multiple_ports_data(self): docker_dict = { 'Id': 'container_id1', 'Config': { 'Labels': { 'eu.simphony-project.docker.ui_name': 'Mayavi 4.4.4', # noqa 'eu.simphony-project.docker.env.x11-depth': '', 'eu.simphony-project.docker.type': 'vncapp', 'eu.simphony-project.docker.env.x11-height': '', 'eu.simphony-project.docker.env.x11-width': '', 'eu.simphony-project.docker.description': 'Ubuntu machine with mayavi preinstalled' }, 'Image': 'image_name1' }, 'NetworkSettings': { 'Ports': { '8889/tcp': [{ 'HostPort': '667', 'HostIp': '0.0.0.0' }], '8888/tcp': [{ 'HostPort': '666', 'HostIp': '0.0.0.0' }] } }, 'Name': '/container_name1', 'State': 'running', 'Image': 'image_id1' } docker_dict["NetworkSettings"]["Ports"] = { '8888/tcp': [{ 'HostIp': '0.0.0.0', 'HostPort': '666' }], '8889/tcp': [{ 'HostIp': '0.0.0.0', 'HostPort': '667' }] } with self.assertRaises(ValueError): Container.from_docker_dict(docker_dict) docker_dict["NetworkSettings"]["Ports"] = { '8888/tcp': [{ 'HostIp': '0.0.0.0', 'HostPort': '32782' }, { 'HostIp': '0.0.0.0', 'HostPort': '32783' }] } with self.assertRaises(ValueError): Container.from_docker_dict(docker_dict) docker_dict = { 'Labels': { 'eu.simphony-project.docker.runinfo.user': '******', 'eu.simphony-project.docker.env.x11-depth': '', 'eu.simphony-project.docker.runinfo.mapping_id': 'mapping_id', 'eu.simphony-project.docker.env.x11-height': '', 'eu.simphony-project.docker.ui_name': 'Mayavi 4.4.4', 'eu.simphony-project.docker.runinfo.urlpath': '/user/username/containers/url_id', # noqa 'eu.simphony-project.docker.type': 'vncapp', 'eu.simphony-project.docker.runinfo.realm': 'myrealm', 'eu.simphony-project.docker.description': 'Ubuntu machine with mayavi preinstalled', # noqa 'eu.simphony-project.docker.env.x11-width': '', 'eu.simphony-project.docker.runinfo.url_id': 'url_id' }, 'Names': ['/myrealm-username-mapping_5Fid'], 'Ports': [{ 'Type': 'tcp', 'IP': '0.0.0.0', 'PublicPort': 666, 'PrivatePort': 8888 }], # noqa 'State': 'running', 'Command': '/sbin/init -D', 'Image': 'image_name1', 'Id': 'container_id1', 'ImageID': 'image_id1' } docker_dict["Ports"] = [{ 'IP': '0.0.0.0', 'PublicIP': 34567, 'PrivatePort': 22, 'Type': 'tcp' }, { 'IP': '0.0.0.0', 'PublicIP': 34562, 'PrivatePort': 21, 'Type': 'tcp' }] with self.assertRaises(ValueError): Container.from_docker_dict(docker_dict)
def _start_container(self, user_name, image_name, mapping_id, base_urlpath, volumes, environment): """Helper method that performs the physical operation of starting the container. If successful, returns a Container object. If any exception occurs, it logs it and re-raises an exception. """ try: image_info = yield self._docker_client.inspect_image(image_name) image_id = image_info["Id"] except NotFound as e: self.log.error('Could not find requested image {}'.format( image_name)) raise e except Exception as e: self.log.exception("Could not inspect image {}".format( image_name )) raise e self.log.info('Got container image: {}'.format(image_name)) # Check if the container is present. container = yield self.find_container( user_name=user_name, mapping_id=mapping_id) if container is not None: # Make sure we stop and remove it if by any chance is already # there. This will guarantee a fresh start every time. self.log.info('Container for image {} ' 'already present. Stopping.'.format(image_name)) yield self.stop_and_remove_container(container.docker_id) # Data volume binding to be used with Docker Client # volumes = {volume_source: {'bind': volume_target, # 'mode': volume_mode} volumes = volumes if volumes else {} # Filter away the volume sources that do not exist, # otherwise Docker would create non-existing host directory # See Docker PR #21666 filtered_volumes = {source: volumes[source] for source in volumes if os.path.exists(source)} volume_targets = [binding['bind'] for binding in filtered_volumes.values()] # Log the paths that are not being mounted if volumes.keys() - filtered_volumes.keys(): self.log.error('Path(s) does not exist, not mounting:\n%s', '\n'.join(volumes.keys() - filtered_volumes.keys())) self.log.info( 'Mounting these volumes: \n%s', '\n'.join('{0} -> {1}'.format(source, target['bind']) for source, target in filtered_volumes.items())) container_url_id = _generate_container_url_id() container_urlpath = without_end_slash( url_path_join(base_urlpath, "containers", container_url_id)) container_name = _generate_container_name(self.realm, user_name, mapping_id) create_kwargs = dict( image=image_name, name=container_name, environment=_get_container_env(user_name, container_url_id, environment, base_urlpath), volumes=volume_targets, labels=_get_container_labels(user_name, mapping_id, container_url_id, container_urlpath, self.realm)) # build the dictionary of keyword arguments for host_config host_config = dict( port_bindings={ self.container_port: None }, binds=filtered_volumes ) self.log.debug("Starting host with config: %s", host_config) host_config = yield self._docker_client.create_host_config( **host_config) # Get the host_config configuration in create_kwargs. # If it's not there, create an empty one. # Then update it with the current configuration. create_kwargs.setdefault('host_config', {}).update(host_config) resp = yield self._docker_client.create_container(**create_kwargs) container_id = resp['Id'] self.log.info("Created container '%s' (id: %s) from image %s", container_name, container_id, image_name) # start the container try: yield self._docker_client.start(container_id) except Exception as e: self.log.exception("Could not start container {}".format( container_id)) yield self.stop_and_remove_container(container_id) raise e try: ip, port = yield from self._get_ip_and_port(container_id) except Exception as e: self.log.exception( "Could not retrieve ip/port information " "for container {}".format(container_id)) yield self.stop_and_remove_container(container_id) raise e container = Container( docker_id=container_id, name=container_name, image_name=image_name, image_id=image_id, mapping_id=mapping_id, ip=ip, port=port, url_id=container_url_id, urlpath=container_urlpath, ) self.log.info( ("Started container '{}' (id: {}). " "Exported port reachable at {}:{}").format( container_name, container_id, ip, port ) ) return container