def popen(self, **kwargs):
        """Convenience method runs the command using Popen.

        Args:
            kwargs: passed through to Popen

        Returns:
            Popen instance
        """
        if self._shell:
            # on Windows, with shell=True Python interprets the args as NOT quoted
            # and quotes them, but assumes a single string parameter is pre-quoted
            # which is what we want.
            # on Unix, with shell=True we interpret args[0]
            # the same as a single string (it's the parameter after -c in sh -c)
            # and anything after args[0] is passed as another flag to sh.
            # (we never have anything after args[0])
            # So if we always use the single string to popen when shell=True, things
            # should work OK on all platforms.
            assert len(self._args) == 1
            args = self._args[0]
        else:

            args = self._args
        return logged_subprocess.Popen(args=args,
                                       env=py2_compat.env_without_unicode(self._env),
                                       cwd=self._cwd,
                                       shell=self._shell,
                                       **kwargs)
Esempio n. 2
0
        def ensure_redis(run_state):
            # this is pretty lame, we'll want to get fancier at a
            # future time (e.g. use Chalmers, stuff like
            # that). The desired semantic is a new copy of Redis
            # dedicated to this project directory; it should not
            # require the user to have set up anything in advance,
            # e.g. if we use Chalmers we should automatically take
            # care of configuring/starting Chalmers itself.
            url = context.status.analysis.existing_scoped_instance_url
            if url is not None:
                frontend.info(
                    "Using redis-server we started previously at {url}".format(
                        url=url))
                return url

            run_state.clear()

            workdir = context.ensure_service_directory(requirement.env_var)
            pidfile = os.path.join(workdir, "redis.pid")
            logfile = os.path.join(workdir, "redis.log")

            # 6379 is the default Redis port; leave that one free
            # for a systemwide Redis. Try looking for a port above
            # it. This is a pretty huge hack and a race condition,
            # but Redis doesn't as far as I know have "let the OS
            # pick the port" mode.
            LOWER_PORT = config['lower_port']
            UPPER_PORT = config['upper_port']
            port = LOWER_PORT
            while port <= UPPER_PORT:
                if not network_util.can_connect_to_socket(host='localhost',
                                                          port=port):
                    break
                port += 1
            if port > UPPER_PORT:
                frontend.error(
                    ("All ports from {lower} to {upper} were in use, " +
                     "could not start redis-server on one of them.").format(
                         lower=LOWER_PORT, upper=UPPER_PORT))
                return None

            # be sure we don't get confused by an old log file
            try:
                os.remove(logfile)
            except IOError:  # pragma: no cover (py3 only)
                pass
            except OSError:  # pragma: no cover (py2 only)
                pass

            command = [
                'redis-server', '--pidfile', pidfile, '--logfile', logfile,
                '--daemonize', 'yes', '--port',
                str(port)
            ]
            frontend.info("Starting " + repr(command))

            # we don't close_fds=True because on Windows that is documented to
            # keep us from collected stderr. But on Unix it's kinda broken not
            # to close_fds. Hmm.
            try:
                popen = logged_subprocess.Popen(
                    args=command,
                    stderr=subprocess.PIPE,
                    env=py2_compat.env_without_unicode(context.environ))
            except Exception as e:
                frontend.error("Error executing redis-server: %s" % (str(e)))
                return None

            # communicate() waits for the process to exit, which
            # is supposed to happen immediately due to --daemonize
            (out, err) = popen.communicate()
            assert out is None  # because we didn't PIPE it
            err = err.decode(errors='replace')

            url = None
            if popen.returncode == 0:
                # now we need to wait for Redis to be ready; we
                # are not sure whether it will create the port or
                # pidfile first, so wait for both.
                port_is_ready = False
                pidfile_is_ready = False
                MAX_WAIT_TIME = 10
                so_far = 0
                while so_far < MAX_WAIT_TIME:
                    increment = MAX_WAIT_TIME / 500.0
                    time.sleep(increment)
                    so_far += increment
                    if not port_is_ready:
                        if network_util.can_connect_to_socket(host='localhost',
                                                              port=port):
                            port_is_ready = True

                    if not pidfile_is_ready:
                        if os.path.exists(pidfile):
                            pidfile_is_ready = True

                    if port_is_ready and pidfile_is_ready:
                        break

                # if we time out with no pidfile we forge ahead at this point
                if port_is_ready:
                    run_state['port'] = port
                    url = "redis://localhost:{port}".format(port=port)

                    # note: --port doesn't work, only -p, and the failure with --port is silent.
                    run_state['shutdown_commands'] = [[
                        'redis-cli', '-p',
                        str(port), 'shutdown'
                    ]]
                else:
                    frontend.info(
                        "redis-server started successfully, but we timed out trying to connect to it on port %d"
                        % (port))

            if url is None:
                for line in err.split("\n"):
                    if line != "":
                        frontend.info(line)
                try:
                    with codecs.open(logfile, 'r', 'utf-8') as log:
                        for line in log.readlines():
                            frontend.info(line)
                except IOError as e:
                    # just be silent if redis-server failed before creating a log file,
                    # that's fine. Hopefully it had some stderr.
                    if e.errno != errno.ENOENT:
                        frontend.info(
                            "Failed to read {logfile}: {error}".format(
                                logfile=logfile, error=e))

                frontend.error(
                    "redis-server process failed or timed out, exited with code {code}"
                    .format(code=popen.returncode))

            return url