Exemplo n.º 1
0
 def test_setup_logging_with_root_logger(self):
     """
     Test setup_logging does not duplicate root logger handler.
     """
     with mock.patch('logging.config.dictConfig') as dc:
         config = {'root': {'handlers': ['default']}}
         logging.setup_logging(config)
         dc.assert_called_once_with(DEFAULT_CONFIG)
Exemplo n.º 2
0
 def test_setup_logging_default(self):
     """
     Test setup_logging with default configuration.
     """
     with mock.patch('logging.config.dictConfig') as dc:
         config = {}
         logging.setup_logging(config)
         dc.assert_called_once_with(DEFAULT_CONFIG)
Exemplo n.º 3
0
 def test_setup_logging_with_formatter(self):
     """
     Test setup_logging does not overwrite a custom formatter.
     """
     with mock.patch('logging.config.dictConfig') as dc:
         config = {
             'formatters': {
                 'default': {
                     'format': '%(asctime)s: %(message)s'
                 }
             }
         }
         expected = deepcopy(DEFAULT_CONFIG)
         expected.update(config)
         logging.setup_logging(config)
         dc.assert_called_once_with(expected)
Exemplo n.º 4
0
 def test_setup_logging_with_handler(self):
     """
     Test setup_logging does not overwrite a custom handler.
     """
     with mock.patch('logging.config.dictConfig') as dc:
         config = {
             'handlers': {
                 'default': {
                     'class': 'logging.handlers.RotatingFileHandler',
                     'formatter': 'default',
                     'filename': 'commissaire.log'
                 }
             }
         }
         expected = deepcopy(DEFAULT_CONFIG)
         expected.update(config)
         logging.setup_logging(config)
         dc.assert_called_once_with(expected)
Exemplo n.º 5
0
def read_config_file(path=None, default='/etc/commissaire/commissaire.conf'):
    """
    Attempts to parse a configuration file, formatted as a JSON object.

    If a config file path is explicitly given, then failure to open the
    file will raise an IOError.  Otherwise a default path is tried, but
    no IOError is raised on failure.  If the file can be opened but not
    parsed, an exception is always raised.

    :param path: Full path to the config file, or None
    :type path: str or None
    :param default: The default file path to user
    :type default: str
    :returns: configuration content as a dictionary
    :rtype: dict
    :raises: IOError, TypeError, ValueError
    """
    json_object = {}
    using_default = False

    # As with the fileinput module, replace '-' with sys.stdin.
    if path == '-':
        json_object = json.load(sys.stdin)

    else:
        if path is None:
            path = default
            using_default = True

        try:
            with open(path, 'r') as fp:
                json_object = json.load(fp)
            if using_default:
                print('Using configuration in {}'.format(path))
        except IOError:
            if not using_default:
                raise

    if type(json_object) is not dict:
        raise TypeError('{}: File content must be a JSON object'.format(path))

    # Recursively normalize the JSON member names.
    json_object = _normalize_member_names(json_object)

    # Process any logging configuration straight away.
    # This is NOT included in the returned dictionary.
    if 'logging' in json_object:
        setup_logging(json_object.pop('logging'))

    # Handle the debug log-level option straight away.
    # This is NOT included in the returned dictionary.
    if json_object.pop('debug', False):
        logging.getLogger().setLevel(logging.DEBUG)
        logging.info('Debugging messages enabled')

    # Special case:
    #
    # In the configuration file, the "authentication_plugins" member
    # can also be specified as a list of JSON objects.  Each object must
    # have at least a 'name' member specifying the plugin module name.
    auth_plugins = json_object.get('authentication_plugins', [])
    configured_plugins = {}

    if auth_plugins and not isinstance(auth_plugins, list):
        raise ValueError('{}: "{}" must be a list. Not at {}.'.format(
            path, auth_plugins, type(auth_plugins)))

    for plugin in auth_plugins:
        if isinstance(plugin, dict):
            if 'name' not in plugin.keys():
                raise ValueError('{}: "{}" is missing a "name" member'.format(
                    path, plugin))
            # Since it's valid we can parse it down into the
            # expected format for loading.
            configured_plugins[plugin.pop('name')] = plugin

    # Overwrite authentication_plugins with the configured_plugins
    json_object['authentication_plugins'] = configured_plugins

    # Special case:
    #
    # In the configuration file, the "storage_handlers" member can
    # be specified as a JSON object or a list of JSON objects.
    handler_key = 'storage_handlers'
    handler_list = json_object.get(handler_key)
    if isinstance(handler_list, dict):
        json_object[handler_key] = [handler_list]

    return json_object
Exemplo n.º 6
0
def read_config_file(path=None, default=C.DEFAULT_CONFIGURATION_FILE):
    """
    Attempts to parse configuration data, formatted as a JSON object.

    This first tries to read configuration from etcd, if an environment
    variable named ``ETCD_MACHINES`` is defined (a comma-delimited list
    of URLs).  The etcd key is derived from the basename of the default
    configuration file (e.g. for ``/path/to/storage.conf`` the etcd key
    would be ``/commissaire/config/storage``).

    Failing that, the function then tries to read a local config file.

    If a config file path is explicitly given, then failure to open the
    file will raise an IOError.  Otherwise a default path is tried, but
    no IOError is raised on failure.  If the file can be opened but not
    parsed, an exception is always raised.

    :param path: Full path to the config file, or None
    :type path: str or None
    :param default: The default file path to user
    :type default: str
    :returns: configuration content as a dictionary
    :rtype: dict
    :raises: IOError, TypeError, ValueError
    """
    json_object = {}
    using_default = False

    if 'ETCD_MACHINES' in os.environ:
        # Derive etcd key from the default file path.
        basename = os.path.basename(default).split('.', 1)[0]
        key = '/commissaire/config/' + basename
        _read_etcd_config_key(key, json_object)

    if not json_object:
        # As with the fileinput module, replace '-' with sys.stdin.
        if path == '-':
            json_object = json.load(sys.stdin)

        else:
            if path is None:
                path = default
                using_default = True

            try:
                with open(path, 'r') as fp:
                    json_object = json.load(fp)
                if using_default:
                    print('Using configuration in {}'.format(path))
            except IOError:
                if not using_default:
                    raise

        if not isinstance(json_object, dict):
            raise TypeError(
                '{}: File content must be a JSON object'.format(path))

    # Recursively normalize the JSON member names.
    json_object = _normalize_member_names(json_object)

    # Process any logging configuration straight away.
    # This is NOT included in the returned dictionary.
    if 'logging' in json_object:
        setup_logging(json_object.pop('logging'))

    # Handle the debug log-level option straight away.
    # This is NOT included in the returned dictionary.
    if json_object.pop('debug', False):
        logging.getLogger().setLevel(logging.DEBUG)
        logging.info('Debugging messages enabled')

    # Special case:
    #
    # In the configuration file, the "authentication_plugins" member
    # can also be specified as a list of JSON objects.  Each object must
    # have at least a 'name' member specifying the plugin module name.
    auth_plugins = json_object.get('authentication_plugins', [])
    configured_plugins = {}

    if auth_plugins and not isinstance(auth_plugins, list):
        raise ValueError(
            '{}: "{}" must be a list. Not at {}.'.format(
                path, auth_plugins, type(auth_plugins)))

    for plugin in auth_plugins:
        if isinstance(plugin, dict):
            if 'name' not in plugin.keys():
                raise ValueError(
                    '{}: "{}" is missing a "name" member'.format(
                        path, plugin))
            # Since it's valid we can parse it down into the
            # expected format for loading.
            configured_plugins[plugin.pop('name')] = plugin

    # Overwrite authentication_plugins with the configured_plugins
    json_object['authentication_plugins'] = configured_plugins

    # Special case:
    #
    # In the configuration file, the "storage_handlers" member can
    # be specified as a JSON object or a list of JSON objects.
    handler_key = 'storage_handlers'
    handler_list = json_object.get(handler_key)
    if isinstance(handler_list, dict):
        json_object[handler_key] = [handler_list]

    return json_object