Example #1
0
def on_state_change(settings,
                    tabpy_state,
                    python_service,
                    logger=logging.getLogger(__name__)):
    try:
        logger.log(logging.INFO, "Loading state from state file")
        config = util._get_state_from_file(
            settings[SettingsParameters.StateFilePath], logger=logger)
        new_ps_state = TabPyState(config=config, settings=settings)

        (has_changes,
         changes) = _get_latest_service_state(settings, tabpy_state,
                                              new_ps_state, python_service)
        if not has_changes:
            logger.info("Nothing changed, return.")
            return

        new_endpoints = new_ps_state.get_endpoints()
        for object_name in changes['endpoints']:
            (object_type, object_version,
             object_path) = changes['endpoints'][object_name]

            if not object_path and not object_version:  # removal
                logger.info(f'Removing object: URI={object_name}')

                python_service.manage_request(DeleteObjects([object_name]))

                cleanup_endpoint_files(object_name,
                                       settings[SettingsParameters.UploadDir],
                                       logger=logger)

            else:
                endpoint_info = new_endpoints[object_name]
                is_update = object_version > 1
                if object_type == 'alias':
                    msg = LoadObject(object_name, endpoint_info['target'],
                                     object_version, is_update, 'alias')
                else:
                    local_path = object_path
                    msg = LoadObject(object_name, local_path, object_version,
                                     is_update, object_type)

                python_service.manage_request(msg)
                wait_for_endpoint_loaded(python_service, object_name)

                # cleanup old version of endpoint files
                if object_version > 2:
                    cleanup_endpoint_files(
                        object_name,
                        settings[SettingsParameters.UploadDir],
                        logger=logger,
                        retain_versions=[object_version, object_version - 1])

    except Exception as e:
        err_msg = format_exception(e, 'on_state_change')
        logger.log(logging.ERROR,
                   f'Error submitting update model request: error={err_msg}')
Example #2
0
    def _build_tabpy_state(self):
        pkg_path = os.path.dirname(tabpy.__file__)
        state_file_dir = self.settings[SettingsParameters.StateFilePath]
        state_file_path = os.path.join(state_file_dir, "state.ini")
        if not os.path.isfile(state_file_path):
            state_file_template_path = os.path.join(pkg_path, "tabpy_server",
                                                    "state.ini.template")
            logger.debug(f"File {state_file_path} not found, creating from "
                         f"template {state_file_template_path}...")
            shutil.copy(state_file_template_path, state_file_path)

        logger.info(f"Loading state from state file {state_file_path}")
        tabpy_state = _get_state_from_file(state_file_dir)
        return tabpy_state, TabPyState(config=tabpy_state,
                                       settings=self.settings)
Example #3
0
    def _parse_config(self, config_file):
        """Provide consistent mechanism for pulling in configuration.

        Attempt to retain backward compatibility for
        existing implementations by grabbing port
        setting from CLI first.

        Take settings in the following order:

        1. CLI arguments if present
        2. config file
        3. OS environment variables (for ease of
           setting defaults if not present)
        4. current defaults if a setting is not present in any location

        Additionally provide similar configuration capabilities in between
        config file and environment variables.
        For consistency use the same variable name in the config file as
        in the os environment.
        For naming standards use all capitals and start with 'TABPY_'
        """
        self.settings = {}
        self.subdirectory = ""
        self.tabpy_state = None
        self.python_service = None
        self.credentials = {}

        parser = configparser.ConfigParser(os.environ)

        if os.path.isfile(config_file):
            with open(config_file) as f:
                parser.read_string(f.read())
        else:
            logger.warning(f'Unable to find config file at {config_file}, '
                           'using default settings.')

        def set_parameter(settings_key, config_key, default_val=None):
            key_is_set = False

            if config_key is not None and\
               parser.has_section('TabPy') and\
               parser.has_option('TabPy', config_key):
                self.settings[settings_key] = parser.get('TabPy', config_key)
                key_is_set = True
                logger.debug(f'Parameter {settings_key} set to '
                             f'"{self.settings[settings_key]}" '
                             'from config file or environment variable')

            if not key_is_set and default_val is not None:
                self.settings[settings_key] = default_val
                key_is_set = True
                logger.debug(f'Parameter {settings_key} set to '
                             f'"{self.settings[settings_key]}" '
                             'from default value')

            if not key_is_set:
                logger.debug(f'Parameter {settings_key} is not set')

        set_parameter(SettingsParameters.Port,
                      ConfigParameters.TABPY_PORT,
                      default_val=9004)
        set_parameter(SettingsParameters.ServerVersion,
                      None,
                      default_val=__version__)

        set_parameter(SettingsParameters.EvaluateTimeout,
                      ConfigParameters.TABPY_EVALUATE_TIMEOUT,
                      default_val=30)

        try:
            self.settings[SettingsParameters.EvaluateTimeout] = float(
                self.settings[SettingsParameters.EvaluateTimeout])
        except ValueError:
            logger.warning('Evaluate timeout must be a float type. Defaulting '
                           'to evaluate timeout of 30 seconds.')
            self.settings[SettingsParameters.EvaluateTimeout] = 30

        pkg_path = os.path.dirname(tabpy.__file__)
        set_parameter(SettingsParameters.UploadDir,
                      ConfigParameters.TABPY_QUERY_OBJECT_PATH,
                      default_val=os.path.join(pkg_path, 'tmp',
                                               'query_objects'))
        if not os.path.exists(self.settings[SettingsParameters.UploadDir]):
            os.makedirs(self.settings[SettingsParameters.UploadDir])

        # set and validate transfer protocol
        set_parameter(SettingsParameters.TransferProtocol,
                      ConfigParameters.TABPY_TRANSFER_PROTOCOL,
                      default_val='http')
        self.settings[SettingsParameters.TransferProtocol] =\
            self.settings[SettingsParameters.TransferProtocol].lower()

        set_parameter(SettingsParameters.CertificateFile,
                      ConfigParameters.TABPY_CERTIFICATE_FILE)
        set_parameter(SettingsParameters.KeyFile,
                      ConfigParameters.TABPY_KEY_FILE)
        self._validate_transfer_protocol_settings()

        # if state.ini does not exist try and create it - remove
        # last dependence on batch/shell script
        set_parameter(SettingsParameters.StateFilePath,
                      ConfigParameters.TABPY_STATE_PATH,
                      default_val=os.path.join(pkg_path, 'tabpy_server'))
        self.settings[SettingsParameters.StateFilePath] = os.path.realpath(
            os.path.normpath(
                os.path.expanduser(
                    self.settings[SettingsParameters.StateFilePath])))
        state_file_dir = self.settings[SettingsParameters.StateFilePath]
        state_file_path = os.path.join(state_file_dir, 'state.ini')
        if not os.path.isfile(state_file_path):
            state_file_template_path = os.path.join(pkg_path, 'tabpy_server',
                                                    'state.ini.template')
            logger.debug(f'File {state_file_path} not found, creating from '
                         f'template {state_file_template_path}...')
            shutil.copy(state_file_template_path, state_file_path)

        logger.info(f'Loading state from state file {state_file_path}')
        tabpy_state = _get_state_from_file(state_file_dir)
        self.tabpy_state = TabPyState(config=tabpy_state,
                                      settings=self.settings)

        self.python_service = PythonServiceHandler(PythonService())
        self.settings['compress_response'] = True
        set_parameter(SettingsParameters.StaticPath,
                      ConfigParameters.TABPY_STATIC_PATH,
                      default_val='./')
        self.settings[SettingsParameters.StaticPath] =\
            os.path.abspath(self.settings[SettingsParameters.StaticPath])
        logger.debug(f'Static pages folder set to '
                     f'"{self.settings[SettingsParameters.StaticPath]}"')

        # Set subdirectory from config if applicable
        if tabpy_state.has_option("Service Info", "Subdirectory"):
            self.subdirectory = "/" + \
                tabpy_state.get("Service Info", "Subdirectory")

        # If passwords file specified load credentials
        set_parameter(ConfigParameters.TABPY_PWD_FILE,
                      ConfigParameters.TABPY_PWD_FILE)
        if ConfigParameters.TABPY_PWD_FILE in self.settings:
            if not self._parse_pwd_file():
                msg = ('Failed to read passwords file '
                       f'{self.settings[ConfigParameters.TABPY_PWD_FILE]}')
                logger.critical(msg)
                raise RuntimeError(msg)
        else:
            logger.info("Password file is not specified: "
                        "Authentication is not enabled")

        features = self._get_features()
        self.settings[SettingsParameters.ApiVersions] =\
            {'v1': {'features': features}}

        set_parameter(SettingsParameters.LogRequestContext,
                      ConfigParameters.TABPY_LOG_DETAILS,
                      default_val='false')
        self.settings[SettingsParameters.LogRequestContext] = (
            self.settings[SettingsParameters.LogRequestContext].lower() !=
            'false')
        call_context_state =\
            'enabled' if self.settings[SettingsParameters.LogRequestContext]\
            else 'disabled'
        logger.info(f'Call context logging is {call_context_state}')

        set_parameter(SettingsParameters.MaxRequestSizeInMb,
                      ConfigParameters.TABPY_MAX_REQUEST_SIZE_MB,
                      default_val=100)