コード例 #1
0
 def __init__(self, config, persistence, messaging):
     """
     Call super-class constructor for common configuration items and
     then do the docker-specific setup
     :return: -
     """
     super(DockerWorker, self).__init__(config=config,
                                        persistence=persistence,
                                        messaging=messaging)
     logging.debug('DockerWorker initialization')
     self.docker = AutoVersionClient(
         base_url=self.config['worker']['docker_url'],
         tls=_get_tls(config['worker']))
     self._image_ports = self._initialize_image()
     self._get_all_allocated_ports()
コード例 #2
0
    def test_clientServer(self):
        """
        A client-server duo. The server outputs the data it receives to its
        output DROP, which in turn is the data held in its input DROP. The graph
        looks like this:

        A --|--> B(client) --|--> D
            |--> C(server) --|

        C is a server application which B connects to. Therefore C must be
        started before B, so B knows C's IP address and connects successfully.
        Although the real writing is done by C, B in this example is also
        treated as a publisher of D. This way D waits for both applications to
        finish before proceeding.
        """
        try:
            AutoVersionClient().close()
        except DockerException:
            warnings.warn(
                "Cannot contact the Docker daemon, skipping docker tests")
            return

        a = FileDROP('a', 'a')
        b = DockerApp('b',
                      'b',
                      image='ubuntu:14.04',
                      command='cat %i0 > /dev/tcp/%containerIp[c]%/8000')
        c = DockerApp('c',
                      'c',
                      image='ubuntu:14.04',
                      command='nc -l 8000 > %o0')
        d = FileDROP('d', 'd')

        b.addInput(a)
        b.addOutput(d)
        c.addInput(a)
        c.addOutput(d)

        # Let 'b' handle its interest in c
        b.handleInterest(c)

        data = os.urandom(10)
        with DROPWaiterCtx(self, d, 100):
            a.write(data)
            a.setCompleted()

        self.assertEqual(data, droputils.allDropContents(d))
コード例 #3
0
    def test_simpleCopy(self):
        """
        Simple test for a dockerized application. It copies the contents of one
        file into another via the command-line cp utility. It then checks that
        the contents of the target DROP are correct, and that the target file is
        actually owned by our process.

        The test will not run if a docker daemon cannot be contacted though;
        this is to avoid failures in machines that don't have a docker service
        running.
        """

        try:
            AutoVersionClient().close()
        except DockerException:
            warnings.warn(
                "Cannot contact the Docker daemon, skipping docker tests")
            return

        a = FileDROP('a', 'a')
        b = DockerApp('b', 'b', image='ubuntu:14.04', command='cp %i0 %o0')
        c = FileDROP('c', 'c')

        b.addInput(a)
        b.addOutput(c)

        # Random data so we always check different contents
        data = os.urandom(10)
        with DROPWaiterCtx(self, c, 100):
            a.write(data)
            a.setCompleted()

        self.assertEqual(data, droputils.allDropContents(c))

        # We own the file, not root
        uid = os.getuid()
        self.assertEqual(uid, os.stat(c.path).st_uid)
コード例 #4
0
class DockerWorker(Worker):
    def __init__(self, config, persistence, messaging):
        """
        Call super-class constructor for common configuration items and
        then do the docker-specific setup
        :return: -
        """
        super(DockerWorker, self).__init__(config=config,
                                           persistence=persistence,
                                           messaging=messaging)
        logging.debug('DockerWorker initialization')
        self.docker = AutoVersionClient(
            base_url=self.config['worker']['docker_url'],
            tls=_get_tls(config['worker']))
        self._image_ports = self._initialize_image()
        self._get_all_allocated_ports()

    def create_instance(self, message):
        instance = message.copy()
        logging.info('Creating instance id: %s', instance['id'])
        environment = self._create_instance_env()
        ports = self.port_manager.acquire_ports(len(self._image_ports))
        environment = _check_instance_env_port_injection(
            environment=environment,
            ports=ports)
        if ports:
            port_mapping = dict(zip(self._image_ports, ports))
            container = self.docker.create_container(self.worker['image'],
                                                     environment=environment,
                                                     ports=self._image_ports)
            self.docker.start(container, port_bindings=port_mapping)
            instance['container_id'] = container['Id']
            instance['environment'] = environment
            self._set_networking(instance=instance)
            self.local_persistence.update_instance_status(
                instance=instance,
                status=INSTANCE_STATUS.STARTING)
        else:
            self.local_persistence.update_instance_status(
                instance=instance,
                status=INSTANCE_STATUS.FAILED)

    def delete_instance(self, message):
        msg = message.copy()
        instance = self.local_persistence.get_instance(msg['id'])
        if instance['status'] == INSTANCE_STATUS.RUNNING:
            logging.info('Deleting instance id: %s', msg['id'])
            container = self._get_container(
                container_id=instance['container_id'])
            if not container:
                logging.debug('Container does not exist, not stopping it')
                return
            free_ports = self._get_container_ports(instance['container_id'])
            self.docker.kill(container)
            self.docker.remove_container(container)
            self.port_manager.release_ports(free_ports)
            self.local_persistence.update_instance_status(
                instance=instance,
                status=INSTANCE_STATUS.DELETED)
        else:
            self.local_persistence.update_instance_status(
                instance=instance,
                status=INSTANCE_STATUS.DELETED)

    def _initialize_image(self):
        """
        download a docker image and get the ports that we have to link
        :return: list of ports(str)
        """
        logging.info('Initializing image %s', self.config['worker']['image'])
        self.worker['image'] = self.config['worker']['image']
        tmp = self.worker['image'].split(':')
        if len(tmp) == 2:
            self.docker.import_image(image=tmp[0], tag=tmp[1])
        else:
            self.docker.import_image(image=tmp)
        logging.debug('Extracting ports from image')
        ports = []
        docker_image = self.docker.inspect_image(self.worker['image'])
        for port in docker_image[u'ContainerConfig'][u'ExposedPorts'].keys():
            ports.append(port.split('/')[0])
        return ports

    def _get_all_allocated_ports(self):
        """
        get all containers, that have not been stopped, they may have been
        started from outside of the workers scope.
        :return: array with ports to use, None if not enough ports available
        """
        used_ports = set()
        containers = self.docker.containers()
        for container in containers:
            for port in self._get_container_ports(container['Id']):
                used_ports.add(port)
        self.port_manager.update_used_ports(used_ports)

    def _get_container(self, container_id):
        """
        get docker-py s container description
        :param container_id: string, id for the container
        :return: dict, containing the container
        """
        try:
            return self.docker.inspect_container({'Id': container_id})
        except docker.errors.APIError:
            logging.debug('Not able to get container %s', container_id)
            return None

    def _get_container_networking(self, container_id):
        """
        return a dict with the container networking, using the appropriate
        worker ip
        :param container_id: id of the container
        :return: dict with the Ports section of the container representation
        """
        try:
            networking = self._get_container(
                container_id)['NetworkSettings']['Ports']
            if 'ip' in self.config['worker'].keys():
                for port in networking:
                    for index, unused in enumerate(networking[port]):
                        networking[port][index][u'HostIp'] = \
                            unicode(self.config['worker']['ip'])
            return networking
        except TypeError:
            logging.error('Cannot get ports for container_id %s', container_id)
            return None

    def _get_container_ports(self, container_id):
        """
        return a list of the concrete container ports that are used
        :param container_id: id of the container
        :return: list of integers
        """
        networking = self._get_container_networking(container_id)
        ports = list()
        if networking:
            for port in networking.keys():
                for index, unused in enumerate(networking[port]):
                    ports.append(int(networking[port][index][u'HostPort']))
        return ports

    def _publish_updates(self):
        instances = self.local_persistence.get_instances()
        for instance_id in instances.keys():
            if instances[instance_id]['status'] is INSTANCE_STATUS.DELETED:
                continue
            elif instances[instance_id]['status'] is INSTANCE_STATUS.FAILED:
                self.local_persistence.publish_instance(instance_id)
                continue

            container_id = instances[instance_id]['container_id']
            container = self._get_container(container_id)
            if not container_id or not container or not _is_running(container):
                instances[instance_id].pop('connection', None)
                instances[instance_id].pop('urls', None)
                self.local_persistence.update_instance_status(
                    instances[instance_id],
                    INSTANCE_STATUS.STOPPED)
                continue
            elif _is_running(container):
                self._set_networking(instances[instance_id])
                self.local_persistence.update_instance_status(
                    instances[instance_id],
                    INSTANCE_STATUS.RUNNING)
            else:
                logging.error("error while publishing updates")
        self._get_all_allocated_ports()
        self._update_worker_status()

    def _update_worker_status(self):
        number_required_ports = len(self._image_ports)
        if self.port_manager.enough_ports_left(number_required_ports):
            self.worker['available'] = True
            self.worker['status'] = 'Worker available'
        else:
            self.worker['available'] = False
            self.worker['status'] = 'Worker unavailable, ' \
                                    'to many resources in use'

    def _set_networking(self, instance):
        instance['connection'] = \
            self._get_container_networking(instance['container_id'])
        instance['urls'] = self.url_builder.build(instance['connection'])
コード例 #5
0
ファイル: dockerapp.py プロジェクト: shaoguangleo/dfms
    def run(self):

        # Replace any placeholder in the commandline with the proper path or
        # dataURL, depending on the type of input/output it is
        # In the case of fs-based i/o we replace the command-line with the path
        # that the Drop will receive *inside* the docker container (see below)
        def isFSBased(x):
            return isinstance(x, (FileDROP, DirectoryContainer))

        fsInputs = [i for i in self.inputs if isFSBased(i)]
        fsOutputs = [o for o in self.outputs if isFSBased(o)]
        dockerInputs = [DockerPath(i.uid, DFMS_ROOT + i.path) for i in fsInputs]
        dockerOutputs = [DockerPath(o.uid, DFMS_ROOT + o.path) for o in fsOutputs]
        dataURLInputs = [i for i in self.inputs if not isFSBased(i)]
        dataURLOutputs = [o for o in self.outputs if not isFSBased(o)]

        cmd = droputils.replace_path_placeholders(self._command, dockerInputs, dockerOutputs)
        cmd = droputils.replace_dataurl_placeholders(cmd, dataURLInputs, dataURLOutputs)

        # We bind the inputs and outputs inside the docker under the DFMS_ROOT
        # directory, maintaining the rest of their original paths.
        # Outputs are bound only up to their dirname (see class doc for details)
        # Volume bindings are setup for FileDROPs and DirectoryContainers only
        vols = [x.path for x in dockerInputs] + [os.path.dirname(x.path) for x in dockerOutputs]
        binds = [i.path + ":" + dockerInputs[x].path for x, i in enumerate(fsInputs)]
        binds += [
            os.path.dirname(o.path) + ":" + os.path.dirname(dockerOutputs[x].path) for x, o in enumerate(fsOutputs)
        ]
        binds += [host_path + ":" + container_path for host_path, container_path in self._additionalBindings.items()]
        if logger.isEnabledFor(logging.DEBUG):
            logger.debug("Volume bindings: %r" % (binds))

        # Wait until the DockerApps this application runtime depends on have
        # started, and replace their IP placeholders by the real IPs
        for waiter in self._waiters:
            uid, ip = waiter.waitForIp()
            cmd = cmd.replace("%containerIp[{0}]%".format(uid), ip)
            if logger.isEnabledFor(logging.DEBUG):
                logger.debug("Command after IP replacement is: %s" % (cmd))

        # If a user has been given, we run the container as that user. It is
        # useful to make sure that the USER environment variable is set in those
        # cases (e.g., casapy requires this to correctly operate)
        user = self._user
        env = {}
        if user is not None:
            env = {"USER": user}

        if self._ensureUserAndSwitch is True:
            # Append commands that will make sure a user is present with the
            # same UID of the current user, and that the command that was
            # supplied for this container runs as that user.
            # Also make sure that the output will belong to that user
            uid = os.getuid()
            createUserAndGo = "id -u {0} &> /dev/null || adduser --uid {0} r; ".format(uid)
            for dirname in set([os.path.dirname(x.path) for x in dockerOutputs]):
                createUserAndGo += 'chown -R {0}.{0} "{1}"; '.format(uid, dirname)
            createUserAndGo += "cd; su -l $(getent passwd {0} | cut -f1 -d:) -c /bin/bash -c '{1}'".format(
                uid, utils.escapeQuotes(cmd, doubleQuotes=False)
            )

            cmd = createUserAndGo

        # Wrap everything inside bash
        cmd = '/bin/bash -c "%s"' % (utils.escapeQuotes(cmd, singleQuotes=False))

        if logger.isEnabledFor(logging.DEBUG):
            logger.debug("Command after user creation and wrapping is: %s" % (cmd))

        extra_kwargs = self._kwargs_from_env()
        c = AutoVersionClient(**extra_kwargs)

        # Remove the container unless it's specified that we should keep it
        # (used below)
        def rm(container):
            if self._removeContainer:
                c.remove_container(container)

        # Create container
        host_config = c.create_host_config(binds=binds)
        container = c.create_container(
            self._image, cmd, volumes=vols, host_config=host_config, user=user, environment=env
        )
        self._containerId = cId = container["Id"]
        if logger.isEnabledFor(logging.INFO):
            logger.info("Created container %s for %r" % (cId, self))

        # Start it
        start = time.time()
        c.start(container)
        if logger.isEnabledFor(logging.INFO):
            logger.info("Started container %s" % (cId))

        # Figure out the container's IP and save it
        # Setting self.containerIp will trigger an event being sent to the
        # registered listeners
        inspection = c.inspect_container(container)
        self.containerIp = inspection["NetworkSettings"]["IPAddress"]

        # Wait until it finishes
        self._exitCode = c.wait(container)
        end = time.time()
        if logger.isEnabledFor(logging.INFO):
            logger.info("Container %s finished in %.2f [s] with exit code %d" % (cId, (end - start), self._exitCode))

        if self._exitCode == 0 and logger.isEnabledFor(logging.DEBUG):
            msg = "Container %s finished successfully" % (cId,)
            stdout = c.logs(container, stdout=True, stderr=False)
            stderr = c.logs(container, stdout=False, stderr=True)
            logger.debug(msg + ", output follows.\n==STDOUT==\n%s==STDERR==\n%s" % (stdout, stderr))
        elif self._exitCode != 0:
            stdout = c.logs(container, stdout=True, stderr=False)
            stderr = c.logs(container, stdout=False, stderr=True)
            msg = "Container %s didn't finish successfully (exit code %d)" % (cId, self._exitCode)
            logger.error(msg + ", output follows.\n==STDOUT==\n%s==STDERR==\n%s" % (stdout, stderr))
            rm(container)
            raise Exception(msg)

        rm(container)
コード例 #6
0
ファイル: dockerapp.py プロジェクト: shaoguangleo/dfms
    def initialize(self, **kwargs):
        BarrierAppDROP.initialize(self, **kwargs)

        self._image = self._getArg(kwargs, "image", None)
        if not self._image:
            raise Exception("No docker image specified, cannot create DockerApp")

        if ":" not in self._image:
            logger.warn("%r: Image %s is too generic since it doesn't specify a tag" % (self, self._image))

        self._command = self._getArg(kwargs, "command", None)
        if not self._command:
            raise Exception("No command specified, cannot create DockerApp")

        # The user used to run the process in the docker container
        # By default docker containers run as root, but we don't want to run
        # a process using a different user because otherwise anything that that
        # process writes to the filesystem
        self._user = self._getArg(kwargs, "user", None)

        # In some cases we want to make sure the command in the container runs
        # as a certain user, so we wrap up the command line in a small script
        # that will create the user if missing and switch to it
        self._ensureUserAndSwitch = self._getArg(kwargs, "ensureUserAndSwitch", self._user is None)

        # By default containers are removed from the filesystem, but people
        # might want to preserve them.
        # TODO: This might be something that the data lifecycle manager could
        # handle, but for the time being we do it here
        self._removeContainer = self._getArg(kwargs, "removeContainer", True)

        # Additional volume bindings can be specified for existing files/dirs
        # on the host system.
        self._additionalBindings = {}
        for binding in self._getArg(kwargs, "additionalBindings", []):
            if binding.find(":") == -1:
                host_path = container_path = binding
            else:
                host_path, container_path = binding.split(":")
            if not os.path.exists(host_path):
                raise ValueError("'Path %s doesn't exist, cannot use as additional volume binding" % (host_path,))
            self._additionalBindings[host_path] = container_path

        if logger.isEnabledFor(logging.INFO):
            logger.info("%r with image '%s' and command '%s' created" % (self, self._image, self._command))

        # Check if we have the image; otherwise pull it.
        extra_kwargs = self._kwargs_from_env()
        c = AutoVersionClient(**extra_kwargs)
        found = reduce(lambda a, b: a or self._image in b["RepoTags"], c.images(), False)

        if not found:
            if logger.isEnabledFor(logging.DEBUG):
                logger.debug("Image '%s' not found, pulling it" % (self._image))
            start = time.time()
            c.pull(self._image)
            end = time.time()
            if logger.isEnabledFor(logging.DEBUG):
                logger.debug("Took %.2f [s] to pull image '%s'" % ((end - start), self._image))
        else:
            if logger.isEnabledFor(logging.DEBUG):
                logger.debug("Image '%s' found, no need to pull it" % (self._image))

        self._containerIp = None
        self._containerId = None
        self._waiters = []