Пример #1
0
    def __init__(self):
        Engine.__init__(self, logging.getLogger(''))
        self.config.get('settings')['artifacts-dir'] = os.path.dirname(__file__) + "/../build/test/%Y-%m-%d_%H-%M-%S.%f"
        self._create_artifacts_dir()

        self.finalize_exc = None
        self.was_finalize = False
Пример #2
0
    def __init__(self, options):
        self.signal_count = 0
        self.options = options
        self.setup_logging(options)
        self.log = logging.getLogger('')
        self.log.info("Taurus CLI Tool v%s", VERSION)
        self.log.debug("Build: %s", BUILD)
        self.log.debug("Extended git info: %s", GIT_INFO)
        self.log.debug("Command-line options: %s", self.options)
        self.log.debug("Python: %s %s", platform.python_implementation(),
                       platform.python_version())
        self.log.debug("OS: %s", platform.uname())

        try:
            self.log.debug("Path to interpreter: {}".format(sys.executable))
            self.log.debug("Path to packages: {}".format(sys.path))
            self.log.debug("Default python: {}".format(
                shutil.which('python' or 'not found')))
            self.log.debug("Default python3: {}".format(
                shutil.which('python3' or 'not found')))

        except BaseException as exc:
            self.log.warning(
                "Extended python info getting error: {}".format(exc))

        self.engine = Engine(self.log)
        self.exit_code = 0
Пример #3
0
    def __init__(self):
        Engine.__init__(self, logging.getLogger(''))
        self.artifacts_base_dir = os.path.dirname(__file__) + "/../build/test"
        self._create_artifacts_dir()

        self.finalize_exc = None
        self.was_finalize = False
Пример #4
0
 def __init__(self):
     Engine.__init__(self, logging.getLogger(''))
     self.config.get('settings')['artifacts-dir'] = os.path.dirname(__file__) + "/../build/test/%Y-%m-%d_%H-%M-%S.%f"
     self.create_artifacts_dir()
     self.config.merge({"provisioning": "local"})
     self.finalize_exc = None
     self.was_finalize = False
Пример #5
0
    def __init__(self):
        Engine.__init__(self, logging.getLogger(''))
        self.artifacts_base_dir = os.path.dirname(__file__) + "/../build/test"
        self._create_artifacts_dir()

        self.finalize_exc = None
        self.was_finalize = False
Пример #6
0
 def __init__(self, options):
     self.signal_count = 0
     self.options = options
     self.setup_logging(options)
     self.log = logging.getLogger('')
     self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
     self.log.debug("Command-line options: %s", self.options)
     self.engine = Engine(self.log)
Пример #7
0
 def __init__(self, options):
     self.signal_count = 0
     self.options = options
     self.setup_logging(options)
     self.log = logging.getLogger('')
     self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
     self.log.debug("Command-line options: %s", self.options)
     self.log.debug("Python: %s %s", platform.python_implementation(), platform.python_version())
     self.log.debug("OS: %s", platform.uname())
     self.engine = Engine(self.log)
Пример #8
0
 def __init__(self, options):
     self.signal_count = 0
     self.options = options
     self.setup_logging(options)
     self.log = logging.getLogger('')
     self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
     logging.debug("Command-line options: %s", self.options)
     self.engine = Engine(self.log)
     self.engine.artifacts_base_dir = self.options.datadir
     self.engine.file_search_path = os.getcwd()
Пример #9
0
 def __init__(self, options):
     self.signal_count = 0
     self.options = options
     self.setup_logging(options)
     self.log = logging.getLogger('')
     self.log.info("Taurus CLI Tool v%s", VERSION)
     self.log.debug("Build number: %s", BUILD_NUM)
     self.log.debug("Extended git info: %s", GIT_INFO)
     self.log.debug("Command-line options: %s", self.options)
     self.log.debug("Python: %s %s", platform.python_implementation(), platform.python_version())
     self.log.debug("OS: %s", platform.uname())
     self.engine = Engine(self.log)
     self.exit_code = 0
Пример #10
0
 def __init__(self, options):
     self.signal_count = 0
     self.options = options
     self.setup_logging(options)
     self.log = logging.getLogger('')
     self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
     self.log.debug("Command-line options: %s", self.options)
     self.engine = Engine(self.log)
Пример #11
0
    def __init__(self, load=None, settings=None, has_ctg=None):
        super(MockExecutor, self).__init__()
        if load is None: load = {}
        if settings is None: settings = {}
        if has_ctg is None: has_ctg = True

        self.engine = Engine(logging.getLogger(''))
        self.execution = load
        self.settings = settings
        self.tool = MockReqTool(has_ctg)
Пример #12
0
 def __init__(self, options):
     self.signal_count = 0
     self.options = options
     self.setup_logging(options)
     self.log = logging.getLogger('')
     self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
     logging.debug("Command-line options: %s", self.options)
     self.engine = Engine(self.log)
     self.engine.artifacts_base_dir = self.options.datadir
     self.engine.file_search_path = os.getcwd()
Пример #13
0
 def __init__(self, options):
     self.signal_count = 0
     self.options = options
     self.setup_logging(options)
     self.log = logging.getLogger('')
     self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
     self.log.debug("Command-line options: %s", self.options)
     self.log.debug("Python: %s %s", platform.python_implementation(), platform.python_version())
     self.log.debug("OS: %s", platform.uname())
     self.engine = Engine(self.log)
Пример #14
0
    def __init__(self, load=None, settings=None, has_ctg=None):
        super(MockJMeterExecutor, self).__init__()
        self.mock_install = True
        self.version = None

        if load is None: load = {}
        if settings is None: settings = {}
        if has_ctg is None: has_ctg = True

        self.engine = Engine(logging.getLogger(''))
        self.execution.merge(load)
        self.settings.merge({"detect-plugins": False})
        self.settings.merge(settings)
        self.tool = MockJMeter(has_ctg)
Пример #15
0
class CLI(object):
    """
    'cli' means 'tool' in hebrew, did you know that?

    :param options: OptionParser parsed parameters
    """

    def __init__(self, options):
        self.signal_count = 0
        self.options = options
        self.setup_logging(options)
        self.log = logging.getLogger('')
        self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
        logging.debug("Command-line options: %s", self.options)
        self.engine = Engine(self.log)
        self.engine.artifacts_base_dir = self.options.datadir
        self.engine.file_search_path = os.getcwd()

    @staticmethod
    @run_once
    def setup_logging(options):
        """
        Setting up console and file loggind, colored if possible

        :param options: OptionParser parsed options
        """
        colors = {
            'WARNING': 'yellow',
            'ERROR': 'red',
            'CRITICAL': 'bold_red',
        }
        fmt_file = Formatter("[%(asctime)s %(levelname)s %(name)s] %(message)s")
        if sys.stdout.isatty():
            fmt_verbose = ColoredFormatter("%(log_color)s[%(asctime)s %(levelname)s %(name)s] %(message)s",
                                           log_colors=colors)
            fmt_regular = ColoredFormatter("%(log_color)s%(asctime)s %(levelname)s: %(message)s",
                                           "%H:%M:%S", log_colors=colors)
        else:
            fmt_verbose = Formatter("[%(asctime)s %(levelname)s %(name)s] %(message)s")
            fmt_regular = Formatter("%(asctime)s %(levelname)s: %(message)s", "%H:%M:%S")

        logger = logging.getLogger('')
        logger.setLevel(logging.DEBUG)

        # log everything to file
        if options.log:
            file_handler = logging.FileHandler(options.log)
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(fmt_file)
            logger.addHandler(file_handler)

        # log something to console
        console_handler = logging.StreamHandler(sys.stdout)

        if options.verbose:
            console_handler.setLevel(logging.DEBUG)
            console_handler.setFormatter(fmt_verbose)
        elif options.quiet:
            console_handler.setLevel(logging.WARNING)
            console_handler.setFormatter(fmt_regular)
        else:
            console_handler.setLevel(logging.INFO)
            console_handler.setFormatter(fmt_regular)

        logger.addHandler(console_handler)

    def __close_log(self):
        """
        Close log handlers, move log to artifacts dir
        :return:
        """
        if self.options.log:
            if platform.system() == 'Windows':
                # need to finalize the logger before moving file
                for handler in self.log.handlers:
                    if issubclass(handler.__class__, logging.FileHandler):
                        self.log.debug("Closing log handler: %s", handler.baseFilename)
                        handler.close()
                        self.log.handlers.remove(handler)
                self.engine.existing_artifact(self.options.log)
                os.remove(self.options.log)
            else:
                self.engine.existing_artifact(self.options.log, True)

    def perform(self, configs):
        """
        Run the tool

        :type configs: list
        :return: integer exit code
        """
        overrides = []
        jmx_shorthands = []
        try:
            jmx_shorthands = self.__get_jmx_shorthands(configs)
            configs.extend(jmx_shorthands)

            overrides = self.__get_config_overrides()
            configs.extend(overrides)

            logging.info("Starting with configs: %s", configs)
            self.engine.configure(configs)

            # apply aliases
            for alias in self.options.aliases:
                al_config = self.engine.config.get("cli-aliases").get(alias, None)
                if al_config is None:
                    raise RuntimeError("Alias '%s' is not found within configuration" % alias)
                self.engine.config.merge(al_config)

            self.engine.prepare()
            self.engine.run()
            exit_code = 0
        except BaseException as exc:
            self.log.debug("Caught exception in try: %s", traceback.format_exc())
            if isinstance(exc, ManualShutdown):
                self.log.info("Interrupted by user: %s", exc)
            elif isinstance(exc, NormalShutdown):
                self.log.info("Normal shutdown")
            elif isinstance(exc, AutomatedShutdown):
                self.log.info("Automated shutdown")
            else:
                self.log.error("Exception: %s", exc)
            self.log.warning("Please wait for graceful shutdown...")
            exit_code = 1
        finally:
            try:
                for fname in overrides + jmx_shorthands:
                    os.remove(fname)
                self.engine.post_process()
            except KeyboardInterrupt as exc:
                self.log.debug("Exception: %s", traceback.format_exc())
                exit_code = 1
                if isinstance(exc, RCProvider):
                    exit_code = exc.get_rc()
            except BaseException as exc:
                self.log.debug("Caught exception in finally: %s", traceback.format_exc())
                self.log.error("Exception: %s", exc)
                exit_code = 1

        if isinstance(self.engine.stopping_reason, RCProvider):
            exit_code = self.engine.stopping_reason.get_rc()

        self.log.info("Artifacts dir: %s", self.engine.artifacts_dir)
        self.log.info("Done performing with code: %s", exit_code)
        self.__close_log()

        return exit_code

    def __get_config_overrides(self):
        """
        Close log handlers, move log to artifacts dir
        :return:
        """

        if self.options.option:
            self.log.debug("Adding overrides: %s", self.options.option)
            fds, fname = tempfile.mkstemp(".ini", "overrides_", dir=self.engine.artifacts_base_dir)
            os.close(fds)
            with open(fname, 'w') as fds:
                fds.write("[DEFAULT]\n")
                for option in self.options.option:
                    fds.write(option + "\n")
            return [fname]
        else:
            return []

    def __get_jmx_shorthands(self, configs):
        """
        Generate json file with execution, executor and scenario settings
        :type configs: list
        :return: list

        """

        jmxes = []
        for filename in configs[:]:
            if filename.lower().endswith(".jmx"):
                jmxes.append(filename)
                configs.remove(filename)

        if jmxes:
            self.log.debug("Adding JMX shorthand config for: %s", jmxes)
            fds, fname = tempfile.mkstemp(".json", "jmxes_", dir=self.engine.artifacts_base_dir)
            os.close(fds)

            config = Configuration()

            for jmx_file in jmxes:
                config.get("execution", []).append({"executor": "jmeter", "scenario": {"script": jmx_file}})

            config.dump(fname, Configuration.JSON)

            return [fname]
        else:
            return []
Пример #16
0
class CLI(object):
    """
    'cli' means 'tool' in hebrew, did you know that?

    :param options: OptionParser parsed parameters
    """
    console_handler = logging.StreamHandler(sys.stdout)

    CLI_SETTINGS = "cli"

    def __init__(self, options):
        self.signal_count = 0
        self.options = options
        self.setup_logging(options)
        self.log = logging.getLogger('')
        self.log.info("Taurus CLI Tool v%s", VERSION)
        self.log.debug("Build: %s", BUILD)
        self.log.debug("Extended git info: %s", GIT_INFO)
        self.log.debug("Command-line options: %s", self.options)
        self.log.debug("Python: %s %s", platform.python_implementation(),
                       platform.python_version())
        self.log.debug("OS: %s", platform.uname())

        try:
            self.log.debug("Path to interpreter: {}".format(sys.executable))
            self.log.debug("Path to packages: {}".format(sys.path))
            self.log.debug("Default python: {}".format(
                shutil.which('python' or 'not found')))
            self.log.debug("Default python3: {}".format(
                shutil.which('python3' or 'not found')))

        except BaseException as exc:
            self.log.warning(
                "Extended python info getting error: {}".format(exc))

        self.engine = Engine(self.log)
        self.exit_code = 0

    @staticmethod
    def setup_logging(options):
        """
        Setting up console and file logging, colored if possible

        :param options: OptionParser parsed options
        """
        colors = {
            'WARNING': 'yellow',
            'ERROR': 'red',
            'CRITICAL': 'bold_red',
        }
        fmt_file = Formatter(
            "[%(asctime)s %(levelname)s %(name)s] %(message)s")
        if sys.stdout and sys.stdout.isatty():
            fmt_verbose = ColoredFormatter(
                "%(log_color)s[%(asctime)s %(levelname)s %(name)s] %(message)s",
                log_colors=colors)
            fmt_regular = ColoredFormatter(
                "%(log_color)s%(asctime)s %(levelname)s: %(message)s",
                "%H:%M:%S",
                log_colors=colors)
        else:
            fmt_verbose = Formatter(
                "[%(asctime)s %(levelname)s %(name)s] %(message)s")
            fmt_regular = Formatter("%(asctime)s %(levelname)s: %(message)s",
                                    "%H:%M:%S")

        logger = logging.getLogger('')
        logger.setLevel(logging.DEBUG)

        # log everything to file
        if options.log is None:
            tf = tempfile.NamedTemporaryFile(prefix="bzt_",
                                             suffix=".log",
                                             delete=False)
            tf.close()
            os.chmod(tf.name, 0o644)
            options.log = tf.name

        if options.log:
            file_handler = logging.FileHandler(options.log, encoding="utf-8")
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(fmt_file)
            logger.addHandler(file_handler)

        # log something to console
        if options.verbose:
            CLI.console_handler.setLevel(logging.DEBUG)
            CLI.console_handler.setFormatter(fmt_verbose)
        elif options.quiet:
            CLI.console_handler.setLevel(logging.WARNING)
            CLI.console_handler.setFormatter(fmt_regular)
        else:
            CLI.console_handler.setLevel(logging.INFO)
            CLI.console_handler.setFormatter(fmt_regular)

        logger.addHandler(CLI.console_handler)

        logging.getLogger("requests").setLevel(logging.WARNING)  # misplaced?

    def close_log(self):
        """
        Close log handlers
        :return:
        """
        if self.options.log:
            # need to finalize the logger before finishing
            for handler in self.log.handlers[:]:
                if issubclass(handler.__class__, logging.FileHandler):
                    self.log.debug("Closing log handler: %s",
                                   handler.baseFilename)
                    handler.close()
                    self.log.handlers.remove(handler)

    def __move_log_to_artifacts(self):
        """
        Close log handlers, copy log to artifacts dir, recreate file handlers
        :return:
        """
        if self.options.log:
            for handler in self.log.handlers[:]:
                if issubclass(handler.__class__, logging.FileHandler):
                    self.log.debug("Closing log handler: %s",
                                   handler.baseFilename)
                    handler.close()
                    self.log.handlers.remove(handler)

            if os.path.exists(self.options.log):
                self.engine.existing_artifact(self.options.log,
                                              move=True,
                                              target_filename="bzt.log")
            self.options.log = os.path.join(self.engine.artifacts_dir,
                                            "bzt.log")

            file_handler = logging.FileHandler(self.options.log,
                                               encoding="utf-8")
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(
                Formatter("[%(asctime)s %(levelname)s %(name)s] %(message)s"))

            self.log.addHandler(file_handler)
            self.log.debug("Switched writing logs to %s", self.options.log)

    def __configure(self, configs):
        if self.options.no_system_configs is None:
            self.options.no_system_configs = False

        load_hidden_configs = not self.options.no_system_configs
        if load_hidden_configs:
            bzt_rc = os.path.expanduser(os.path.join('~', ".bzt-rc"))
            if os.path.exists(bzt_rc):
                self.log.debug("Using personal config: %s" % bzt_rc)
            else:
                self.log.debug("Adding personal config: %s", bzt_rc)
                self.log.info("No personal config found, creating one at %s",
                              bzt_rc)
                shutil.copy(os.path.join(RESOURCES_DIR, 'base-bzt-rc.yml'),
                            bzt_rc)

            configs.insert(0, bzt_rc)

        self.log.info("Starting with configs: %s", configs)
        merged_config = self.engine.configure(
            configs, not self.options.no_system_configs)

        # apply aliases
        for alias in self.options.aliases:
            cli_aliases = self.engine.config.get('cli-aliases')
            keys = sorted(cli_aliases.keys())
            err = TaurusConfigError(
                "'%s' not found in aliases. Available aliases are: %s" %
                (alias, ", ".join(keys)))
            self.engine.config.merge(cli_aliases.get(alias, err))

        if self.options.option:
            overrider = ConfigOverrider(self.log)
            overrider.apply_overrides(self.options.option, self.engine.config)

        if self.__is_verbose():
            CLI.console_handler.setLevel(logging.DEBUG)
        self.engine.create_artifacts_dir(configs, merged_config)
        self.engine.default_cwd = os.getcwd()
        self.engine.set_pythonpath()
        self.engine.eval_env(
        )  # yacky, I don't like having it here, but how to apply it after aliases and artif dir?

    def __is_verbose(self):
        settings = self.engine.config.get(SETTINGS, force_set=True)
        settings.get('verbose',
                     bool(self.options.verbose))  # respect value from config
        if self.options.verbose:  # force verbosity if cmdline asked for it
            settings['verbose'] = True

        return settings.get('verbose', False)

    def __lint_config(self):
        settings = self.engine.config.get(CLI.CLI_SETTINGS).get("linter")
        self.log.debug("Linting config")
        self.warn_on_unfamiliar_fields = settings.get(
            "warn-on-unfamiliar-fields", True)
        config_copy = copy.deepcopy(self.engine.config)
        ignored_warnings = settings.get("ignored-warnings", [])
        self.linter = ConfigurationLinter(config_copy, ignored_warnings,
                                          self.log)
        self.linter.register_checkers()
        self.linter.lint()
        warnings = self.linter.get_warnings()
        for warning in warnings:
            self.log.warning(str(warning))

        if settings.get("lint-and-exit", False):
            if warnings:
                raise TaurusConfigError(
                    "Errors were found in the configuration")
            else:
                raise NormalShutdown(
                    "Linting has finished, no errors were found")

    def _level_down_logging(self):
        target = logging.DEBUG if self.__is_verbose() else logging.INFO
        for handler in self.log.handlers:
            if issubclass(handler.__class__, logging.FileHandler):
                if handler.level != target:
                    msg = "Leveling down log file verbosity to %s, use -v option to have DEBUG messages enabled"
                    self.log.debug(msg, logging.getLevelName(target))
                    handler.setLevel(target)

    def _level_up_logging(self):
        for handler in self.log.handlers:
            if issubclass(handler.__class__, logging.FileHandler):
                if handler.level != logging.DEBUG:
                    handler.setLevel(logging.DEBUG)
                    self.log.debug("Leveled up log file verbosity")

    def perform(self, configs):
        """
        Run the tool

        :type configs: list
        :return: integer exit code
        """
        url_shorthands = []
        jmx_shorthands = []
        jtl_shorthands = []
        try:
            url_shorthands = self.__get_url_shorthands(configs)
            configs.extend(url_shorthands)

            jmx_shorthands = self.__get_jmx_shorthands(configs)
            configs.extend(jmx_shorthands)

            jtl_shorthands = self.__get_jtl_shorthands(configs)
            configs.extend(jtl_shorthands)

            if not self.engine.config.get(SETTINGS).get(
                    'verbose', False, force_set=True):
                self.engine.logging_level_down = self._level_down_logging
                self.engine.logging_level_up = self._level_up_logging

            self.__configure(configs)
            self.__move_log_to_artifacts()
            self.__lint_config()

            self.engine.prepare()
            self.engine.run()
        except BaseException as exc:
            self.handle_exception(exc)
        finally:
            try:
                for fname in url_shorthands + jmx_shorthands + jtl_shorthands:
                    os.remove(fname)
                self.engine.post_process()
            except BaseException as exc:
                self.handle_exception(exc)

        if self.options.verbose:
            for module_name in sys.modules:
                version = str(
                    getattr(sys.modules[module_name], '__version__', ""))
                file = getattr(sys.modules[module_name], '__file__', "")
                if version:
                    module_name = "-".join((module_name, version))
                    self.log.debug("\t{}\t{}".format(module_name, file))

        self.log.info("Artifacts dir: %s", self.engine.artifacts_dir)
        if self.engine.artifacts_dir is None:
            self.log.info("Log file: %s", self.options.log)

        if self.exit_code:
            self.log.warning("Done performing with code: %s", self.exit_code)
        else:
            self.log.info("Done performing with code: %s", self.exit_code)

        self.close_log()

        return self.exit_code

    def handle_exception(self, exc):
        log_level = {
            'info': logging.DEBUG,
            'http': logging.DEBUG,
            'default': logging.DEBUG
        }
        if not self.exit_code:  # only fist exception goes to the screen
            log_level['info'] = logging.WARNING
            log_level['http'] = logging.ERROR
            log_level['default'] = logging.ERROR
            if isinstance(exc, RCProvider):
                self.exit_code = exc.get_rc()
            else:
                self.exit_code = 1

        if isinstance(exc, KeyboardInterrupt):
            self.__handle_keyboard_interrupt(exc, log_level)
            log_level['default'] = logging.DEBUG
        elif isinstance(exc, TaurusException):
            self.__handle_taurus_exception(exc, log_level['default'])
            log_level['default'] = logging.DEBUG
        elif isinstance(exc, HTTPError):
            msg = "Response from %s: [%s] %s %s" % (exc.geturl(), exc.code,
                                                    exc.reason, exc.read())
            self.log.log(log_level['http'], msg)
            log_level['default'] = logging.DEBUG

        self.log.log(log_level['default'], "%s: %s\n%s",
                     type(exc).__name__, exc, get_stacktrace(exc))

    def __handle_keyboard_interrupt(self, exc, log_level):
        if isinstance(exc, ManualShutdown):
            self.log.log(log_level['info'], "Interrupted by user")
        elif isinstance(exc, AutomatedShutdown):
            self.log.log(log_level['info'], "Automated shutdown")
        elif isinstance(exc, NormalShutdown):
            self.log.log(logging.DEBUG, "Shutting down by request from code")
        elif isinstance(exc, KeyboardInterrupt):
            self.log.log(log_level['info'], "Keyboard interrupt")
        else:
            msg = "Non-KeyboardInterrupt exception %s: %s\n%s"
            raise ValueError(msg % (type(exc), exc, get_stacktrace(exc)))

    def __handle_taurus_exception(self, exc, log_level):
        if isinstance(exc, TaurusConfigError):
            self.log.log(log_level, "Config Error: %s", exc)
        elif isinstance(exc, TaurusInternalException):
            self.log.log(log_level, "Internal Error: %s", exc)
        elif isinstance(exc, ToolError):
            self.log.log(log_level, "Child Process Error: %s", exc)
            if exc.diagnostics is not None:
                for line in exc.diagnostics:
                    self.log.log(log_level, line)
        elif isinstance(exc, TaurusNetworkError):
            self.log.log(log_level, "Network Error: %s", exc)
        else:
            self.log.log(log_level, "Generic Taurus Error: %s", exc)

    def __get_jmx_shorthands(self, configs):
        """
        Generate json file with execution, executor and scenario settings
        :type configs: list
        :return: list
        """
        jmxes = []
        for filename in configs[:]:
            if filename.lower().endswith(".jmx"):
                jmxes.append(filename)
                configs.remove(filename)

        if jmxes:
            self.log.debug("Adding JMX shorthand config for: %s", jmxes)
            fds = NamedTemporaryFile(prefix="jmx_", suffix=".json")
            fname = fds.name
            fds.close()

            config = Configuration()

            for jmx_file in jmxes:
                piece = BetterDict.from_dict({
                    "executor": "jmeter",
                    "scenario": {
                        "script": jmx_file
                    }
                })
                config.get(EXEC, [], force_set=True).append(
                    piece)  # Does it brake single execution?

            config.dump(fname, Configuration.JSON)

            return [fname]
        else:
            return []

    def __get_jtl_shorthands(self, configs):
        """
        Generate json file with execution, executor and scenario settings
        :type configs: list
        :return: list
        """
        jtls = []
        for filename in configs[:]:
            if filename.lower().endswith(".jtl"):
                jtls.append(filename)
                configs.remove(filename)

        if jtls:
            self.log.debug("Adding JTL shorthand config for: %s", jtls)
            fds = NamedTemporaryFile(prefix="jtl_", suffix=".json")
            fname = fds.name
            fds.close()

            config = Configuration()

            for jtl in jtls:
                piece = BetterDict.from_dict({
                    "executor": "external-results-loader",
                    "data-file": jtl
                })
                config.get(EXEC, [], force_set=True).append(piece)

            config.dump(fname, Configuration.JSON)

            return [fname]
        else:
            return []

    def __get_url_shorthands(self, configs):
        """
        :type configs: list
        :return: list
        """
        urls = []
        for candidate in configs[:]:
            if is_url(candidate):
                urls.append(candidate)
                configs.remove(candidate)

        if urls:
            self.log.debug("Adding HTTP shorthand config for: %s", urls)
            config_fds = NamedTemporaryFile(prefix="http_", suffix=".yml")
            fname = config_fds.name
            config_fds.close()

            config = Configuration.from_dict({
                "execution": [{
                    "concurrency":
                    "${__tstFeedback(Throughput_Limiter,1,${__P(concurrencyCap,1)},2)}",
                    "hold-for": "2m",
                    "throughput": "${__P(throughput,600)}",
                    "scenario": "linear-growth",
                }],
                "scenarios": {
                    "linear-growth": {
                        "retrieve-resources":
                        False,
                        "timeout":
                        "5s",
                        "keepalive":
                        False,
                        "requests": [{
                            "action":
                            "pause",
                            "pause-duration":
                            0,
                            "jsr223": [{
                                "language":
                                "javascript",
                                "execute":
                                "before",
                                "script-text":
                                """
var startTime = parseInt(props.get("startTime"));
if (!startTime) {
    startTime = Math.floor((new Date()).getTime() / 1000);
    props.put("startTime", startTime);
} else {
    var now = Math.floor((new Date()).getTime() / 1000);
    var offset = now - startTime;
    if (offset < 60) {
        var targetOffset = Math.max(offset * 10, 10);
        props.put("throughput", targetOffset.toString());
    }
}"""
                            }]
                        }] + urls,
                    }
                },
                "modules": {
                    "jmeter": {
                        "properties": {
                            "throughput": 1,
                            "concurrencyCap": 500,
                        },
                    }
                }
            })
            config.dump(fname, Configuration.JSON)
            return [fname]
        else:
            return []
Пример #17
0
class CLI(object):
    """
    'cli' means 'tool' in hebrew, did you know that?

    :param options: OptionParser parsed parameters
    """
    def __init__(self, options):
        self.signal_count = 0
        self.options = options
        self.setup_logging(options)
        self.log = logging.getLogger('')
        self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
        logging.debug("Command-line options: %s", self.options)
        self.engine = Engine(self.log)
        self.engine.artifacts_base_dir = self.options.datadir
        self.engine.file_search_path = os.getcwd()

    @staticmethod
    @run_once
    def setup_logging(options):
        """
        Setting up console and file loggind, colored if possible

        :param options: OptionParser parsed options
        """
        colors = {
            'WARNING': 'yellow',
            'ERROR': 'red',
            'CRITICAL': 'bold_red',
        }
        fmt_file = Formatter(
            "[%(asctime)s %(levelname)s %(name)s] %(message)s")
        if sys.stdout.isatty():
            fmt_verbose = ColoredFormatter(
                "%(log_color)s[%(asctime)s %(levelname)s %(name)s] %(message)s",
                log_colors=colors)
            fmt_regular = ColoredFormatter(
                "%(log_color)s%(asctime)s %(levelname)s: %(message)s",
                "%H:%M:%S",
                log_colors=colors)
        else:
            fmt_verbose = Formatter(
                "[%(asctime)s %(levelname)s %(name)s] %(message)s")
            fmt_regular = Formatter("%(asctime)s %(levelname)s: %(message)s",
                                    "%H:%M:%S")

        logger = logging.getLogger('')
        logger.setLevel(logging.DEBUG)

        # log everything to file
        if options.log:
            file_handler = logging.FileHandler(options.log)
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(fmt_file)
            logger.addHandler(file_handler)

        # log something to console
        console_handler = logging.StreamHandler(sys.stdout)

        if options.verbose:
            console_handler.setLevel(logging.DEBUG)
            console_handler.setFormatter(fmt_verbose)
        elif options.quiet:
            console_handler.setLevel(logging.WARNING)
            console_handler.setFormatter(fmt_regular)
        else:
            console_handler.setLevel(logging.INFO)
            console_handler.setFormatter(fmt_regular)

        logger.addHandler(console_handler)

    def __close_log(self):
        """
        Close log handlers, move log to artifacts dir
        :return:
        """
        if self.options.log:
            if platform.system() == 'Windows':
                # need to finalize the logger before moving file
                for handler in self.log.handlers:
                    if issubclass(handler.__class__, logging.FileHandler):
                        self.log.debug("Closing log handler: %s",
                                       handler.baseFilename)
                        handler.close()
                        self.log.handlers.remove(handler)
                self.engine.existing_artifact(self.options.log)
                os.remove(self.options.log)
            else:
                self.engine.existing_artifact(self.options.log, True)

    def perform(self, configs):
        """
        Run the tool

        :type configs: list
        :return: integer exit code
        """
        overrides = []
        jmx_shorthands = []
        try:
            jmx_shorthands = self.__get_jmx_shorthands(configs)
            configs.extend(jmx_shorthands)

            overrides = self.__get_config_overrides()
            configs.extend(overrides)

            logging.info("Starting with configs: %s", configs)
            self.engine.configure(configs)

            # apply aliases
            for alias in self.options.aliases:
                al_config = self.engine.config.get("cli-aliases").get(
                    alias, None)
                if al_config is None:
                    raise RuntimeError(
                        "Alias '%s' is not found within configuration" % alias)
                self.engine.config.merge(al_config)

            self.engine.prepare()
            self.engine.run()
            exit_code = 0
        except BaseException as exc:
            self.log.debug("Caught exception in try: %s",
                           traceback.format_exc())
            if isinstance(exc, ManualShutdown):
                self.log.info("Interrupted by user: %s", exc)
            elif isinstance(exc, NormalShutdown):
                self.log.info("Normal shutdown")
            elif isinstance(exc, AutomatedShutdown):
                self.log.info("Automated shutdown")
            else:
                self.log.error("Exception: %s", exc)
            self.log.warning("Please wait for graceful shutdown...")
            exit_code = 1
        finally:
            try:
                for fname in overrides + jmx_shorthands:
                    os.remove(fname)
                self.engine.post_process()
            except KeyboardInterrupt as exc:
                self.log.debug("Exception: %s", traceback.format_exc())
                exit_code = 1
                if isinstance(exc, RCProvider):
                    exit_code = exc.get_rc()
            except BaseException as exc:
                self.log.debug("Caught exception in finally: %s",
                               traceback.format_exc())
                self.log.error("Exception: %s", exc)
                exit_code = 1

        if isinstance(self.engine.stopping_reason, RCProvider):
            exit_code = self.engine.stopping_reason.get_rc()

        self.log.info("Artifacts dir: %s", self.engine.artifacts_dir)
        self.log.info("Done performing with code: %s", exit_code)
        self.__close_log()

        return exit_code

    def __get_config_overrides(self):
        """
        Close log handlers, move log to artifacts dir
        :return:
        """

        if self.options.option:
            self.log.debug("Adding overrides: %s", self.options.option)
            fds, fname = tempfile.mkstemp(".ini",
                                          "overrides_",
                                          dir=self.engine.artifacts_base_dir)
            os.close(fds)
            with open(fname, 'w') as fds:
                fds.write("[DEFAULT]\n")
                for option in self.options.option:
                    fds.write(option + "\n")
            return [fname]
        else:
            return []

    def __get_jmx_shorthands(self, configs):
        """
        Generate json file with execution, executor and scenario settings
        :type configs: list
        :return: list

        """

        jmxes = []
        for filename in configs[:]:
            if filename.lower().endswith(".jmx"):
                jmxes.append(filename)
                configs.remove(filename)

        if jmxes:
            self.log.debug("Adding JMX shorthand config for: %s", jmxes)
            fds, fname = tempfile.mkstemp(".json",
                                          "jmxes_",
                                          dir=self.engine.artifacts_base_dir)
            os.close(fds)

            config = Configuration()

            for jmx_file in jmxes:
                config.get("execution", []).append({
                    "executor": "jmeter",
                    "scenario": {
                        "script": jmx_file
                    }
                })

            config.dump(fname, Configuration.JSON)

            return [fname]
        else:
            return []
Пример #18
0
class CLI(object):
    """
    'cli' means 'tool' in hebrew, did you know that?

    :param options: OptionParser parsed parameters
    """
    def __init__(self, options):
        self.signal_count = 0
        self.options = options
        self.setup_logging(options)
        self.log = logging.getLogger('')
        self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
        self.log.debug("Command-line options: %s", self.options)
        self.log.debug("Python: %s %s", platform.python_implementation(),
                       platform.python_version())
        self.log.debug("OS: %s", platform.uname())
        self.engine = Engine(self.log)
        self.exit_code = 0

    @staticmethod
    @run_once
    def setup_logging(options):
        """
        Setting up console and file loggind, colored if possible

        :param options: OptionParser parsed options
        """
        colors = {
            'WARNING': 'yellow',
            'ERROR': 'red',
            'CRITICAL': 'bold_red',
        }
        fmt_file = Formatter(
            "[%(asctime)s %(levelname)s %(name)s] %(message)s")
        if sys.stdout.isatty():
            fmt_verbose = ColoredFormatter(
                "%(log_color)s[%(asctime)s %(levelname)s %(name)s] %(message)s",
                log_colors=colors)
            fmt_regular = ColoredFormatter(
                "%(log_color)s%(asctime)s %(levelname)s: %(message)s",
                "%H:%M:%S",
                log_colors=colors)
        else:
            fmt_verbose = Formatter(
                "[%(asctime)s %(levelname)s %(name)s] %(message)s")
            fmt_regular = Formatter("%(asctime)s %(levelname)s: %(message)s",
                                    "%H:%M:%S")

        logger = logging.getLogger('')
        logger.setLevel(logging.DEBUG)

        # log everything to file
        if options.log is None:
            tf = tempfile.NamedTemporaryFile(prefix="bzt_",
                                             suffix=".log",
                                             delete=False)
            tf.close()
            options.log = tf.name

        if options.log:
            file_handler = logging.FileHandler(options.log)
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(fmt_file)
            logger.addHandler(file_handler)

        # log something to console
        console_handler = logging.StreamHandler(sys.stdout)

        if options.verbose:
            console_handler.setLevel(logging.DEBUG)
            console_handler.setFormatter(fmt_verbose)
        elif options.quiet:
            console_handler.setLevel(logging.WARNING)
            console_handler.setFormatter(fmt_regular)
        else:
            console_handler.setLevel(logging.INFO)
            console_handler.setFormatter(fmt_regular)

        logger.addHandler(console_handler)

        logging.getLogger("requests").setLevel(logging.WARNING)  # misplaced?

    def __close_log(self):
        """
        Close log handlers
        :return:
        """
        if self.options.log:
            # need to finalize the logger before finishing
            for handler in self.log.handlers:
                if issubclass(handler.__class__, logging.FileHandler):
                    self.log.debug("Closing log handler: %s",
                                   handler.baseFilename)
                    handler.close()
                    self.log.handlers.remove(handler)

    def __move_log_to_artifacts(self):
        """
        Close log handlers, copy log to artifacts dir, recreate file handlers
        :return:
        """
        if self.options.log:
            for handler in self.log.handlers:
                if issubclass(handler.__class__, logging.FileHandler):
                    self.log.debug("Closing log handler: %s",
                                   handler.baseFilename)
                    handler.close()
                    self.log.handlers.remove(handler)

            if os.path.exists(self.options.log):
                self.engine.existing_artifact(self.options.log,
                                              move=True,
                                              target_filename="bzt.log")
            self.options.log = os.path.join(self.engine.artifacts_dir,
                                            "bzt.log")

            file_handler = logging.FileHandler(self.options.log)
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(
                Formatter("[%(asctime)s %(levelname)s %(name)s] %(message)s"))

            self.log.addHandler(file_handler)
            self.log.debug("Switched writing logs to %s", self.options.log)

    def __configure(self, configs):
        self.log.info("Starting with configs: %s", configs)

        if self.options.no_system_configs is None:
            self.options.no_system_configs = False

        merged_config = self.engine.configure(
            configs, not self.options.no_system_configs)

        # apply aliases
        for alias in self.options.aliases:
            cli_aliases = self.engine.config.get('cli-aliases')
            keys = sorted(cli_aliases.keys())
            err = TaurusConfigError(
                "'%s' not found in aliases. Available aliases are: %s" %
                (alias, ", ".join(keys)))
            self.engine.config.merge(cli_aliases.get(alias, err))

        if self.options.option:
            overrider = ConfigOverrider(self.log)
            overrider.apply_overrides(self.options.option, self.engine.config)

        self.engine.create_artifacts_dir(configs, merged_config)
        self.engine.default_cwd = os.getcwd()

    def _level_down_logging(self):
        self.log.debug("Leveling down log file verbosity")
        for handler in self.log.handlers:
            if issubclass(handler.__class__, logging.FileHandler):
                handler.setLevel(logging.INFO)

    def _level_up_logging(self):
        for handler in self.log.handlers:
            if issubclass(handler.__class__, logging.FileHandler):
                handler.setLevel(logging.DEBUG)
        self.log.debug("Leveled up log file verbosity")

    def perform(self, configs):
        """
        Run the tool

        :type configs: list
        :return: integer exit code
        """
        jmx_shorthands = []
        try:
            jmx_shorthands = self.__get_jmx_shorthands(configs)
            configs.extend(jmx_shorthands)

            if not self.options.verbose:
                self.engine.post_startup_hook = self._level_down_logging
                self.engine.pre_shutdown_hook = self._level_up_logging
            self.__configure(configs)
            self.__move_log_to_artifacts()

            self.engine.prepare()
            self.engine.run()
        except BaseException as exc:
            self.handle_exception(exc)
        finally:
            try:
                for fname in jmx_shorthands:
                    os.remove(fname)
                self.engine.post_process()
            except BaseException as exc:
                self.handle_exception(exc)

        self.log.info("Artifacts dir: %s", self.engine.artifacts_dir)
        if self.engine.artifacts_dir is None:
            self.log.info("Log file: %s", self.options.log)

        if self.exit_code:
            self.log.warning("Done performing with code: %s", self.exit_code)
        else:
            self.log.info("Done performing with code: %s", self.exit_code)

        self.__close_log()

        return self.exit_code

    def handle_exception(self, exc):
        log_level = {
            'info': logging.DEBUG,
            'http': logging.DEBUG,
            'default': logging.DEBUG
        }
        if not self.exit_code:  # only fist exception goes to the screen
            log_level['info'] = logging.WARNING
            log_level['http'] = logging.ERROR
            log_level['default'] = logging.ERROR
            if isinstance(exc, RCProvider):
                self.exit_code = exc.get_rc()
            else:
                self.exit_code = 1

        if isinstance(exc, KeyboardInterrupt):
            self.__handle_keyboard_interrupt(exc, log_level)
            log_level['default'] = logging.DEBUG
        elif isinstance(exc, TaurusException):
            self.__handle_taurus_exception(exc, log_level['default'])
            log_level['default'] = logging.DEBUG
        elif isinstance(exc, HTTPError):
            msg = "Response from %s: [%s] %s %s" % (exc.geturl(), exc.code,
                                                    exc.reason, exc.read())
            self.log.log(log_level['http'], msg)
            log_level['default'] = logging.DEBUG

        self.log.log(log_level['default'], "%s: %s\n%s",
                     type(exc).__name__, exc, get_stacktrace(exc))

    def __handle_keyboard_interrupt(self, exc, log_level):
        if isinstance(exc, ManualShutdown):
            self.log.log(log_level['info'], "Interrupted by user")
        elif isinstance(exc, AutomatedShutdown):
            self.log.log(log_level['info'], "Automated shutdown")
        elif isinstance(exc, NormalShutdown):
            self.log.log(logging.DEBUG, "Shutting down by request from code")
        elif isinstance(exc, KeyboardInterrupt):
            self.log.log(log_level['info'], "Keyboard interrupt")
        else:
            msg = "Non-KeyboardInterrupt exception %s: %s\n%s"
            raise ValueError(msg % (type(exc), exc, get_stacktrace(exc)))

    def __handle_taurus_exception(self, exc, log_level):
        if isinstance(exc, TaurusConfigError):
            self.log.log(log_level, "Config Error: %s", exc)
        elif isinstance(exc, TaurusInternalException):
            self.log.log(log_level, "Internal Error: %s", exc)
        elif isinstance(exc, ToolError):
            self.log.log(log_level, "Child Process Error: %s", exc)
        elif isinstance(exc, TaurusNetworkError):
            self.log.log(log_level, "Network Error: %s", exc)
        else:
            self.log.log(log_level, "Generic Taurus Error: %s", exc)

    def __get_jmx_shorthands(self, configs):
        """
        Generate json file with execution, executor and scenario settings
        :type configs: list
        :return: list
        """
        jmxes = []
        for filename in configs[:]:
            if filename.lower().endswith(".jmx"):
                jmxes.append(filename)
                configs.remove(filename)

        if jmxes:
            self.log.debug("Adding JMX shorthand config for: %s", jmxes)
            fds = NamedTemporaryFile(prefix="jmx_", suffix=".json")
            fname = fds.name
            fds.close()

            config = Configuration()

            for jmx_file in jmxes:
                config.get(ScenarioExecutor.EXEC, []).append({
                    "executor": "jmeter",
                    "scenario": {
                        "script": jmx_file
                    }
                })

            config.dump(fname, Configuration.JSON)

            return [fname]
        else:
            return []
Пример #19
0
class CLI(object):
    """
    'cli' means 'tool' in hebrew, did you know that?

    :param options: OptionParser parsed parameters
    """

    def __init__(self, options):
        self.signal_count = 0
        self.options = options
        self.setup_logging(options)
        self.log = logging.getLogger('')
        self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
        self.log.debug("Command-line options: %s", self.options)
        self.log.debug("Python: %s %s", platform.python_implementation(), platform.python_version())
        self.log.debug("OS: %s", platform.uname())
        self.engine = Engine(self.log)

    @staticmethod
    @run_once
    def setup_logging(options):
        """
        Setting up console and file loggind, colored if possible

        :param options: OptionParser parsed options
        """
        colors = {
            'WARNING': 'yellow',
            'ERROR': 'red',
            'CRITICAL': 'bold_red',
        }
        fmt_file = Formatter("[%(asctime)s %(levelname)s %(name)s] %(message)s")
        if sys.stdout.isatty():
            fmt_verbose = ColoredFormatter("%(log_color)s[%(asctime)s %(levelname)s %(name)s] %(message)s",
                                           log_colors=colors)
            fmt_regular = ColoredFormatter("%(log_color)s%(asctime)s %(levelname)s: %(message)s",
                                           "%H:%M:%S", log_colors=colors)
        else:
            fmt_verbose = Formatter("[%(asctime)s %(levelname)s %(name)s] %(message)s")
            fmt_regular = Formatter("%(asctime)s %(levelname)s: %(message)s", "%H:%M:%S")

        logger = logging.getLogger('')
        logger.setLevel(logging.DEBUG)

        # log everything to file
        if options.log:
            file_handler = logging.FileHandler(options.log)
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(fmt_file)
            logger.addHandler(file_handler)

        # log something to console
        console_handler = logging.StreamHandler(sys.stdout)

        if options.verbose:
            console_handler.setLevel(logging.DEBUG)
            console_handler.setFormatter(fmt_verbose)
        elif options.quiet:
            console_handler.setLevel(logging.WARNING)
            console_handler.setFormatter(fmt_regular)
        else:
            console_handler.setLevel(logging.INFO)
            console_handler.setFormatter(fmt_regular)

        logger.addHandler(console_handler)

    def __close_log(self):
        """
        Close log handlers, move log to artifacts dir
        :return:
        """
        if self.options.log:
            if is_windows():
                # need to finalize the logger before moving file
                for handler in self.log.handlers:
                    if issubclass(handler.__class__, logging.FileHandler):
                        self.log.debug("Closing log handler: %s", handler.baseFilename)
                        handler.close()
                        self.log.handlers.remove(handler)
                if os.path.exists(self.options.log):
                    self.engine.existing_artifact(self.options.log)
                    os.remove(self.options.log)
            else:
                self.engine.existing_artifact(self.options.log, True)

    def perform(self, configs):
        """
        Run the tool

        :type configs: list
        :return: integer exit code
        """
        overrides = []
        jmx_shorthands = []
        try:
            jmx_shorthands = self.__get_jmx_shorthands(configs)
            configs.extend(jmx_shorthands)

            self.log.info("Starting with configs: %s", configs)

            if self.options.no_system_configs is None:
                self.options.no_system_configs = False

            merged_config = self.engine.configure(configs, not self.options.no_system_configs)

            # apply aliases
            for alias in self.options.aliases:
                al_config = self.engine.config.get("cli-aliases").get(alias, None)
                if al_config is None:
                    raise RuntimeError("Alias '%s' is not found within configuration" % alias)
                self.engine.config.merge(al_config)

            if self.options.option:
                overrider = ConfigOverrider(self.log)
                overrider.apply_overrides(self.options.option, self.engine.config)

            self.engine.create_artifacts_dir(configs, merged_config)
            self.engine.default_cwd = None
            self.engine.prepare()
            self.engine.run()
            exit_code = 0
        except BaseException as exc:
            self.log.debug("Caught exception in try: %s", traceback.format_exc())
            if isinstance(exc, ManualShutdown):
                self.log.info("Interrupted by user: %s", exc)
            elif isinstance(exc, NormalShutdown):
                self.log.info("Normal shutdown")
            elif isinstance(exc, AutomatedShutdown):
                self.log.info("Automated shutdown")
            else:
                if isinstance(exc, HTTPError):
                    assert isinstance(exc, HTTPError)
                    self.log.warning("Response from %s: %s", exc.geturl(), exc.read())
                self.log.error("%s: %s", type(exc).__name__, exc)
            exit_code = 1
        finally:
            try:
                for fname in overrides + jmx_shorthands:
                    os.remove(fname)
                self.engine.post_process()
            except KeyboardInterrupt as exc:
                self.log.debug("Exception: %s", traceback.format_exc())
                exit_code = 1
                if isinstance(exc, RCProvider):
                    exit_code = exc.get_rc()
            except BaseException as exc:
                self.log.debug("Caught exception in finally: %s", traceback.format_exc())
                self.log.error("%s: %s", type(exc).__name__, exc)
                exit_code = 1

        if isinstance(self.engine.stopping_reason, RCProvider):
            exit_code = self.engine.stopping_reason.get_rc()

        self.log.info("Artifacts dir: %s", self.engine.artifacts_dir)
        if exit_code:
            self.log.warning("Done performing with code: %s", exit_code)
        else:
            self.log.info("Done performing with code: %s", exit_code)

        self.__close_log()

        return exit_code

    def __get_jmx_shorthands(self, configs):
        """
        Generate json file with execution, executor and scenario settings
        :type configs: list
        :return: list
        """

        jmxes = []
        for filename in configs[:]:
            if filename.lower().endswith(".jmx"):
                jmxes.append(filename)
                configs.remove(filename)

        if jmxes:
            self.log.debug("Adding JMX shorthand config for: %s", jmxes)
            fds = NamedTemporaryFile(prefix="jmx_", suffix=".json")
            fname = fds.name
            fds.close()

            config = Configuration()

            for jmx_file in jmxes:
                config.get(ScenarioExecutor.EXEC, []).append({"executor": "jmeter", "scenario": jmx_file})

            config.dump(fname, Configuration.JSON)

            return [fname]
        else:
            return []
Пример #20
0
class CLI(object):
    """
    'cli' means 'tool' in hebrew, did you know that?

    :param options: OptionParser parsed parameters
    """
    console_handler = logging.StreamHandler(sys.stdout)

    CLI_SETTINGS = "cli"

    def __init__(self, options):
        self.signal_count = 0
        self.options = options
        self.setup_logging(options)
        self.log = logging.getLogger('')
        self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
        self.log.debug("Command-line options: %s", self.options)
        self.log.debug("Python: %s %s", platform.python_implementation(), platform.python_version())
        self.log.debug("OS: %s", platform.uname())
        self.engine = Engine(self.log)
        self.exit_code = 0

    @staticmethod
    def setup_logging(options):
        """
        Setting up console and file logging, colored if possible

        :param options: OptionParser parsed options
        """
        colors = {
            'WARNING': 'yellow',
            'ERROR': 'red',
            'CRITICAL': 'bold_red',
        }
        fmt_file = Formatter("[%(asctime)s %(levelname)s %(name)s] %(message)s")
        if sys.stdout.isatty():
            fmt_verbose = ColoredFormatter("%(log_color)s[%(asctime)s %(levelname)s %(name)s] %(message)s",
                                           log_colors=colors)
            fmt_regular = ColoredFormatter("%(log_color)s%(asctime)s %(levelname)s: %(message)s",
                                           "%H:%M:%S", log_colors=colors)
        else:
            fmt_verbose = Formatter("[%(asctime)s %(levelname)s %(name)s] %(message)s")
            fmt_regular = Formatter("%(asctime)s %(levelname)s: %(message)s", "%H:%M:%S")

        logger = logging.getLogger('')
        logger.setLevel(logging.DEBUG)

        # log everything to file
        if options.log is None:
            tf = tempfile.NamedTemporaryFile(prefix="bzt_", suffix=".log", delete=False)
            tf.close()
            os.chmod(tf.name, 0o644)
            options.log = tf.name

        if options.log:
            file_handler = logging.FileHandler(options.log, encoding="utf-8")
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(fmt_file)
            logger.addHandler(file_handler)

        # log something to console
        if options.verbose:
            CLI.console_handler.setLevel(logging.DEBUG)
            CLI.console_handler.setFormatter(fmt_verbose)
        elif options.quiet:
            CLI.console_handler.setLevel(logging.WARNING)
            CLI.console_handler.setFormatter(fmt_regular)
        else:
            CLI.console_handler.setLevel(logging.INFO)
            CLI.console_handler.setFormatter(fmt_regular)

        logger.addHandler(CLI.console_handler)

        logging.getLogger("requests").setLevel(logging.WARNING)  # misplaced?

    def close_log(self):
        """
        Close log handlers
        :return:
        """
        if self.options.log:
            # need to finalize the logger before finishing
            for handler in self.log.handlers[:]:
                if issubclass(handler.__class__, logging.FileHandler):
                    self.log.debug("Closing log handler: %s", handler.baseFilename)
                    handler.close()
                    self.log.handlers.remove(handler)

    def __move_log_to_artifacts(self):
        """
        Close log handlers, copy log to artifacts dir, recreate file handlers
        :return:
        """
        if self.options.log:
            for handler in self.log.handlers[:]:
                if issubclass(handler.__class__, logging.FileHandler):
                    self.log.debug("Closing log handler: %s", handler.baseFilename)
                    handler.close()
                    self.log.handlers.remove(handler)

            if os.path.exists(self.options.log):
                self.engine.existing_artifact(self.options.log, move=True, target_filename="bzt.log")
            self.options.log = os.path.join(self.engine.artifacts_dir, "bzt.log")

            file_handler = logging.FileHandler(self.options.log, encoding="utf-8")
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(Formatter("[%(asctime)s %(levelname)s %(name)s] %(message)s"))

            self.log.addHandler(file_handler)
            self.log.debug("Switched writing logs to %s", self.options.log)

    def __configure(self, configs):
        self.log.info("Starting with configs: %s", configs)

        if self.options.no_system_configs is None:
            self.options.no_system_configs = False

        bzt_rc = os.path.expanduser(os.path.join('~', ".bzt-rc"))
        if os.path.exists(bzt_rc):
            self.log.debug("Using personal config: %s" % bzt_rc)
        else:
            self.log.debug("Adding personal config: %s", bzt_rc)
            self.log.info("No personal config found, creating one at %s", bzt_rc)
            shutil.copy(os.path.join(RESOURCES_DIR, 'base-bzt-rc.yml'), bzt_rc)

        merged_config = self.engine.configure([bzt_rc] + configs, not self.options.no_system_configs)

        # apply aliases
        for alias in self.options.aliases:
            cli_aliases = self.engine.config.get('cli-aliases')
            keys = sorted(cli_aliases.keys())
            err = TaurusConfigError("'%s' not found in aliases. Available aliases are: %s" % (alias, ", ".join(keys)))
            self.engine.config.merge(cli_aliases.get(alias, err))

        if self.options.option:
            overrider = ConfigOverrider(self.log)
            overrider.apply_overrides(self.options.option, self.engine.config)

        if self.__is_verbose():
            CLI.console_handler.setLevel(logging.DEBUG)
        self.engine.create_artifacts_dir(configs, merged_config)
        self.engine.default_cwd = os.getcwd()
        self.engine.eval_env()  # yacky, I don't like having it here, but how to apply it after aliases and artif dir?

    def __is_verbose(self):
        settings = self.engine.config.get(SETTINGS, force_set=True)
        settings.get('verbose', bool(self.options.verbose))  # respect value from config
        if self.options.verbose:  # force verbosity if cmdline asked for it
            settings['verbose'] = True

        return settings.get('verbose', False)

    def __lint_config(self):
        settings = self.engine.config.get(CLI.CLI_SETTINGS).get("linter")
        self.log.debug("Linting config")
        self.warn_on_unfamiliar_fields = settings.get("warn-on-unfamiliar-fields", True)
        config_copy = copy.deepcopy(self.engine.config)
        ignored_warnings = settings.get("ignored-warnings", [])
        self.linter = ConfigurationLinter(config_copy, ignored_warnings, self.log)
        self.linter.register_checkers()
        self.linter.lint()
        warnings = self.linter.get_warnings()
        for warning in warnings:
            self.log.warning(str(warning))

        if settings.get("lint-and-exit", False):
            if warnings:
                raise TaurusConfigError("Errors were found in the configuration")
            else:
                raise NormalShutdown("Linting has finished, no errors were found")

    def _level_down_logging(self):
        target = logging.DEBUG if self.__is_verbose() else logging.INFO
        for handler in self.log.handlers:
            if issubclass(handler.__class__, logging.FileHandler):
                if handler.level != target:
                    msg = "Leveling down log file verbosity to %s, use -v option to have DEBUG messages enabled"
                    self.log.debug(msg, logging.getLevelName(target))
                    handler.setLevel(target)

    def _level_up_logging(self):
        for handler in self.log.handlers:
            if issubclass(handler.__class__, logging.FileHandler):
                if handler.level != logging.DEBUG:
                    handler.setLevel(logging.DEBUG)
                    self.log.debug("Leveled up log file verbosity")

    def perform(self, configs):
        """
        Run the tool

        :type configs: list
        :return: integer exit code
        """
        url_shorthands = []
        jmx_shorthands = []
        jtl_shorthands = []
        try:
            url_shorthands = self.__get_url_shorthands(configs)
            configs.extend(url_shorthands)

            jmx_shorthands = self.__get_jmx_shorthands(configs)
            configs.extend(jmx_shorthands)

            jtl_shorthands = self.__get_jtl_shorthands(configs)
            configs.extend(jtl_shorthands)

            if not self.engine.config.get(SETTINGS).get('verbose', False, force_set=True):
                self.engine.logging_level_down = self._level_down_logging
                self.engine.logging_level_up = self._level_up_logging

            self.__configure(configs)
            self.__move_log_to_artifacts()
            self.__lint_config()

            self.engine.prepare()
            self.engine.run()
        except BaseException as exc:
            self.handle_exception(exc)
        finally:
            try:
                for fname in url_shorthands + jmx_shorthands + jtl_shorthands:
                    os.remove(fname)
                self.engine.post_process()
            except BaseException as exc:
                self.handle_exception(exc)

        self.log.info("Artifacts dir: %s", self.engine.artifacts_dir)
        if self.engine.artifacts_dir is None:
            self.log.info("Log file: %s", self.options.log)

        if self.exit_code:
            self.log.warning("Done performing with code: %s", self.exit_code)
        else:
            self.log.info("Done performing with code: %s", self.exit_code)

        self.close_log()

        return self.exit_code

    def handle_exception(self, exc):
        log_level = {'info': logging.DEBUG, 'http': logging.DEBUG, 'default': logging.DEBUG}
        if not self.exit_code:  # only fist exception goes to the screen
            log_level['info'] = logging.WARNING
            log_level['http'] = logging.ERROR
            log_level['default'] = logging.ERROR
            if isinstance(exc, RCProvider):
                self.exit_code = exc.get_rc()
            else:
                self.exit_code = 1

        if isinstance(exc, KeyboardInterrupt):
            self.__handle_keyboard_interrupt(exc, log_level)
            log_level['default'] = logging.DEBUG
        elif isinstance(exc, TaurusException):
            self.__handle_taurus_exception(exc, log_level['default'])
            log_level['default'] = logging.DEBUG
        elif isinstance(exc, HTTPError):
            msg = "Response from %s: [%s] %s %s" % (exc.geturl(), exc.code, exc.reason, exc.read())
            self.log.log(log_level['http'], msg)
            log_level['default'] = logging.DEBUG

        self.log.log(log_level['default'], "%s: %s\n%s", type(exc).__name__, exc, get_stacktrace(exc))

    def __handle_keyboard_interrupt(self, exc, log_level):
        if isinstance(exc, ManualShutdown):
            self.log.log(log_level['info'], "Interrupted by user")
        elif isinstance(exc, AutomatedShutdown):
            self.log.log(log_level['info'], "Automated shutdown")
        elif isinstance(exc, NormalShutdown):
            self.log.log(logging.DEBUG, "Shutting down by request from code")
        elif isinstance(exc, KeyboardInterrupt):
            self.log.log(log_level['info'], "Keyboard interrupt")
        else:
            msg = "Non-KeyboardInterrupt exception %s: %s\n%s"
            raise ValueError(msg % (type(exc), exc, get_stacktrace(exc)))

    def __handle_taurus_exception(self, exc, log_level):
        if isinstance(exc, TaurusConfigError):
            self.log.log(log_level, "Config Error: %s", exc)
        elif isinstance(exc, TaurusInternalException):
            self.log.log(log_level, "Internal Error: %s", exc)
        elif isinstance(exc, ToolError):
            self.log.log(log_level, "Child Process Error: %s", exc)
            if exc.diagnostics is not None:
                for line in exc.diagnostics:
                    self.log.log(log_level, line)
        elif isinstance(exc, TaurusNetworkError):
            self.log.log(log_level, "Network Error: %s", exc)
        else:
            self.log.log(log_level, "Generic Taurus Error: %s", exc)

    def __get_jmx_shorthands(self, configs):
        """
        Generate json file with execution, executor and scenario settings
        :type configs: list
        :return: list
        """
        jmxes = []
        for filename in configs[:]:
            if filename.lower().endswith(".jmx"):
                jmxes.append(filename)
                configs.remove(filename)

        if jmxes:
            self.log.debug("Adding JMX shorthand config for: %s", jmxes)
            fds = NamedTemporaryFile(prefix="jmx_", suffix=".json")
            fname = fds.name
            fds.close()

            config = Configuration()

            for jmx_file in jmxes:
                piece = BetterDict.from_dict({"executor": "jmeter", "scenario": {"script": jmx_file}})
                config.get(ScenarioExecutor.EXEC, [], force_set=True).append(piece)  # Does it brake single execution?

            config.dump(fname, Configuration.JSON)

            return [fname]
        else:
            return []

    def __get_jtl_shorthands(self, configs):
        """
        Generate json file with execution, executor and scenario settings
        :type configs: list
        :return: list
        """
        jtls = []
        for filename in configs[:]:
            if filename.lower().endswith(".jtl"):
                jtls.append(filename)
                configs.remove(filename)

        if jtls:
            self.log.debug("Adding JTL shorthand config for: %s", jtls)
            fds = NamedTemporaryFile(prefix="jtl_", suffix=".json")
            fname = fds.name
            fds.close()

            config = Configuration()

            for jtl in jtls:
                piece = BetterDict.from_dict({"executor": "external-results-loader", "data-file": jtl})
                config.get(ScenarioExecutor.EXEC, [], force_set=True).append(piece)

            config.dump(fname, Configuration.JSON)

            return [fname]
        else:
            return []

    def __get_url_shorthands(self, configs):
        """
        :type configs: list
        :return: list
        """
        urls = []
        for candidate in configs[:]:
            if is_url(candidate):
                urls.append(candidate)
                configs.remove(candidate)

        if urls:
            self.log.debug("Adding HTTP shorthand config for: %s", urls)
            config_fds = NamedTemporaryFile(prefix="http_", suffix=".yml")
            fname = config_fds.name
            config_fds.close()

            config = Configuration.from_dict({
                "execution": [{
                    "concurrency": "${__tstFeedback(Throughput_Limiter,1,${__P(concurrencyCap,1)},2)}",
                    "hold-for": "2m",
                    "throughput": "${__P(throughput,600)}",
                    "scenario": "linear-growth",
                }],
                "scenarios": {
                    "linear-growth": {
                        "retrieve-resources": False,
                        "timeout": "5s",
                        "keepalive": False,
                        "requests": [{
                            "action": "pause",
                            "pause-duration": 0,
                            "jsr223": [{
                                "language": "javascript",
                                "execute": "before",
                                "script-text": """
var startTime = parseInt(props.get("startTime"));
if (!startTime) {
    startTime = Math.floor((new Date()).getTime() / 1000);
    props.put("startTime", startTime);
} else {
    var now = Math.floor((new Date()).getTime() / 1000);
    var offset = now - startTime;
    if (offset < 60) {
        var targetOffset = Math.max(offset * 10, 10);
        props.put("throughput", targetOffset.toString());
    }
}"""
                            }]
                        }] + urls,
                    }
                },
                "modules": {
                    "jmeter": {
                        "properties": {
                            "throughput": 1,
                            "concurrencyCap": 500,
                        },
                    }
                }
            })
            config.dump(fname, Configuration.JSON)
            return [fname]
        else:
            return []
Пример #21
0
class CLI(object):
    """
    'cli' means 'tool' in hebrew, did you know that?

    :param options: OptionParser parsed parameters
    """
    def __init__(self, options):
        self.signal_count = 0
        self.options = options
        self.setup_logging(options)
        self.log = logging.getLogger('')
        self.log.info("Taurus CLI Tool v%s", bzt.VERSION)
        self.log.debug("Command-line options: %s", self.options)
        self.log.debug("Python: %s %s", platform.python_implementation(),
                       platform.python_version())
        self.log.debug("OS: %s", platform.uname())
        self.engine = Engine(self.log)
        self.exit_code = 0

    @staticmethod
    @run_once
    def setup_logging(options):
        """
        Setting up console and file loggind, colored if possible

        :param options: OptionParser parsed options
        """
        colors = {
            'WARNING': 'yellow',
            'ERROR': 'red',
            'CRITICAL': 'bold_red',
        }
        fmt_file = Formatter(
            "[%(asctime)s %(levelname)s %(name)s] %(message)s")
        if sys.stdout.isatty():
            fmt_verbose = ColoredFormatter(
                "%(log_color)s[%(asctime)s %(levelname)s %(name)s] %(message)s",
                log_colors=colors)
            fmt_regular = ColoredFormatter(
                "%(log_color)s%(asctime)s %(levelname)s: %(message)s",
                "%H:%M:%S",
                log_colors=colors)
        else:
            fmt_verbose = Formatter(
                "[%(asctime)s %(levelname)s %(name)s] %(message)s")
            fmt_regular = Formatter("%(asctime)s %(levelname)s: %(message)s",
                                    "%H:%M:%S")

        logger = logging.getLogger('')
        logger.setLevel(logging.DEBUG)

        # log everything to file
        if options.log:
            file_handler = logging.FileHandler(options.log)
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(fmt_file)
            logger.addHandler(file_handler)

        # log something to console
        console_handler = logging.StreamHandler(sys.stdout)

        if options.verbose:
            console_handler.setLevel(logging.DEBUG)
            console_handler.setFormatter(fmt_verbose)
        elif options.quiet:
            console_handler.setLevel(logging.WARNING)
            console_handler.setFormatter(fmt_regular)
        else:
            console_handler.setLevel(logging.INFO)
            console_handler.setFormatter(fmt_regular)

        logger.addHandler(console_handler)

    def __close_log(self):
        """
        Close log handlers, move log to artifacts dir
        :return:
        """
        if self.options.log:
            if is_windows():
                # need to finalize the logger before moving file
                for handler in self.log.handlers:
                    if issubclass(handler.__class__, logging.FileHandler):
                        self.log.debug("Closing log handler: %s",
                                       handler.baseFilename)
                        handler.close()
                        self.log.handlers.remove(handler)
                if os.path.exists(self.options.log):
                    self.engine.existing_artifact(self.options.log)
                    os.remove(self.options.log)
            else:
                self.engine.existing_artifact(self.options.log, True)

    def __configure(self, configs):
        self.log.info("Starting with configs: %s", configs)

        if self.options.no_system_configs is None:
            self.options.no_system_configs = False

        merged_config = self.engine.configure(
            configs, not self.options.no_system_configs)

        # apply aliases
        for alias in self.options.aliases:
            al_config = self.engine.config.get("cli-aliases").get(alias, None)
            if al_config is None:
                raise RuntimeError(
                    "Alias '%s' is not found within configuration" % alias)
            self.engine.config.merge(al_config)

        if self.options.option:
            overrider = ConfigOverrider(self.log)
            overrider.apply_overrides(self.options.option, self.engine.config)

        self.engine.create_artifacts_dir(configs, merged_config)
        self.engine.default_cwd = os.getcwd()

    def perform(self, configs):
        """
        Run the tool

        :type configs: list
        :return: integer exit code
        """
        jmx_shorthands = []
        try:
            jmx_shorthands = self.__get_jmx_shorthands(configs)
            configs.extend(jmx_shorthands)

            self.__configure(configs)
            self.engine.prepare()
            self.engine.run()
        except BaseException as exc:
            self.handle_exception(exc)
        finally:
            try:
                for fname in jmx_shorthands:
                    os.remove(fname)
                self.engine.post_process()
            except BaseException as exc:
                self.handle_exception(exc)

        self.log.info("Artifacts dir: %s", self.engine.artifacts_dir)

        if self.exit_code:
            self.log.warning("Done performing with code: %s", self.exit_code)
        else:
            self.log.info("Done performing with code: %s", self.exit_code)

        self.__close_log()

        return self.exit_code

    def handle_exception(self, exc):
        info_level = http_level = default_level = logging.DEBUG
        if not self.exit_code:  # only fist exception goes to the screen
            info_level = logging.WARNING
            http_level = logging.WARNING
            default_level = logging.ERROR
            if isinstance(exc, RCProvider):
                self.exit_code = exc.get_rc()
            else:
                self.exit_code = 1

        if isinstance(exc, ManualShutdown):
            self.log.log(info_level, "Interrupted by user")
        elif isinstance(exc, AutomatedShutdown):
            self.log.log(info_level, "Automated shutdown")
        elif isinstance(exc, NormalShutdown):
            self.log.log(info_level, "Normal shutdown")
        elif isinstance(exc, HTTPError):
            self.log.log(http_level, "Response from %s: %s", exc.geturl(),
                         exc.read())
        else:
            self.log.log(default_level, "%s: %s", type(exc).__name__, exc)
            self.log.log(default_level, get_stacktrace(exc))

    def __get_jmx_shorthands(self, configs):
        """
        Generate json file with execution, executor and scenario settings
        :type configs: list
        :return: list
        """
        jmxes = []
        for filename in configs[:]:
            if filename.lower().endswith(".jmx"):
                jmxes.append(filename)
                configs.remove(filename)

        if jmxes:
            self.log.debug("Adding JMX shorthand config for: %s", jmxes)
            fds = NamedTemporaryFile(prefix="jmx_", suffix=".json")
            fname = fds.name
            fds.close()

            config = Configuration()

            for jmx_file in jmxes:
                config.get(ScenarioExecutor.EXEC, []).append({
                    "executor":
                    "jmeter",
                    "scenario":
                    jmx_file
                })

            config.dump(fname, Configuration.JSON)

            return [fname]
        else:
            return []