Exemplo n.º 1
0
    def __init__(self,
                 spec_file_name,
                 config_handler,
                 command_handler,
                 cc_session=None,
                 handle_logging_config=True,
                 socket_file=None):
        """Initialize a ModuleCCSession. This does *NOT* send the
           specification and request the configuration yet. Use start()
           for that once the ModuleCCSession has been initialized.

           specfile_name is the path to the specification file.

           config_handler and command_handler are callback functions,
           see set_config_handler and set_command_handler for more
           information on their signatures.

           cc_session can be used to pass in an existing CCSession,
           if it is None, one will be set up. This is mainly intended
           for testing purposes.

           handle_logging_config: if True, the module session will
           automatically handle logging configuration for the module;
           it will read the system-wide Logging configuration and call
           the logger manager to apply it. It will also inform the
           logger manager when the logging configuration gets updated.
           The module does not need to do anything except initializing
           its loggers, and provide log messages. Defaults to true.

           socket_file: If cc_session was none, this optional argument
           specifies which socket file to use to connect to msgq. It
           will be overridden by the environment variable
           MSGQ_SOCKET_FILE. If none, and no environment variable is
           set, it will use the system default.
        """
        module_spec = isc.config.module_spec_from_file(spec_file_name)
        ConfigData.__init__(self, module_spec)

        self._module_name = module_spec.get_module_name()

        self.set_config_handler(config_handler)
        self.set_command_handler(command_handler)

        if not cc_session:
            self._session = Session(socket_file)
        else:
            self._session = cc_session
        self._session.group_subscribe(self._module_name, CC_INSTANCE_WILDCARD)

        self._remote_module_configs = {}
        self._remote_module_callbacks = {}

        self._notification_callbacks = {}
        self._last_notif_id = 0

        if handle_logging_config:
            self.add_remote_config(
                path_search('logging.spec', bind10_config.PLUGIN_PATHS),
                default_logconfig_handler)
Exemplo n.º 2
0
    def __init__(self, spec_file_name, config_handler, command_handler,
                 cc_session=None, handle_logging_config=True,
                 socket_file = None):
        """Initialize a ModuleCCSession. This does *NOT* send the
           specification and request the configuration yet. Use start()
           for that once the ModuleCCSession has been initialized.

           specfile_name is the path to the specification file.

           config_handler and command_handler are callback functions,
           see set_config_handler and set_command_handler for more
           information on their signatures.

           cc_session can be used to pass in an existing CCSession,
           if it is None, one will be set up. This is mainly intended
           for testing purposes.

           handle_logging_config: if True, the module session will
           automatically handle logging configuration for the module;
           it will read the system-wide Logging configuration and call
           the logger manager to apply it. It will also inform the
           logger manager when the logging configuration gets updated.
           The module does not need to do anything except initializing
           its loggers, and provide log messages. Defaults to true.

           socket_file: If cc_session was none, this optional argument
           specifies which socket file to use to connect to msgq. It
           will be overridden by the environment variable
           MSGQ_SOCKET_FILE. If none, and no environment variable is
           set, it will use the system default.
        """
        module_spec = isc.config.module_spec_from_file(spec_file_name)
        ConfigData.__init__(self, module_spec)

        self._module_name = module_spec.get_module_name()

        self.set_config_handler(config_handler)
        self.set_command_handler(command_handler)

        if not cc_session:
            self._session = Session(socket_file)
        else:
            self._session = cc_session
        self._session.group_subscribe(self._module_name, CC_INSTANCE_WILDCARD)

        self._remote_module_configs = {}
        self._remote_module_callbacks = {}

        self._notification_callbacks = {}
        self._last_notif_id = 0

        if handle_logging_config:
            self.add_remote_config(path_search('logging.spec', bind10_config.PLUGIN_PATHS),
                                   default_logconfig_handler)
Exemplo n.º 3
0
class ModuleCCSession(ConfigData):
    """This class maintains a connection to the command channel, as
       well as configuration options for modules. The module provides
       a specification file that contains the module name, configuration
       options, and commands. It also gives the ModuleCCSession two callback
       functions, one to call when there is a direct command to the
       module, and one to update the configuration run-time. These
       callbacks are called when 'check_command' is called on the
       ModuleCCSession"""
    def __init__(self,
                 spec_file_name,
                 config_handler,
                 command_handler,
                 cc_session=None,
                 handle_logging_config=True,
                 socket_file=None):
        """Initialize a ModuleCCSession. This does *NOT* send the
           specification and request the configuration yet. Use start()
           for that once the ModuleCCSession has been initialized.

           specfile_name is the path to the specification file.

           config_handler and command_handler are callback functions,
           see set_config_handler and set_command_handler for more
           information on their signatures.

           cc_session can be used to pass in an existing CCSession,
           if it is None, one will be set up. This is mainly intended
           for testing purposes.

           handle_logging_config: if True, the module session will
           automatically handle logging configuration for the module;
           it will read the system-wide Logging configuration and call
           the logger manager to apply it. It will also inform the
           logger manager when the logging configuration gets updated.
           The module does not need to do anything except initializing
           its loggers, and provide log messages. Defaults to true.

           socket_file: If cc_session was none, this optional argument
           specifies which socket file to use to connect to msgq. It
           will be overridden by the environment variable
           MSGQ_SOCKET_FILE. If none, and no environment variable is
           set, it will use the system default.
        """
        module_spec = isc.config.module_spec_from_file(spec_file_name)
        ConfigData.__init__(self, module_spec)

        self._module_name = module_spec.get_module_name()

        self.set_config_handler(config_handler)
        self.set_command_handler(command_handler)

        if not cc_session:
            self._session = Session(socket_file)
        else:
            self._session = cc_session
        self._session.group_subscribe(self._module_name, CC_INSTANCE_WILDCARD)

        self._remote_module_configs = {}
        self._remote_module_callbacks = {}

        self._notification_callbacks = {}
        self._last_notif_id = 0

        if handle_logging_config:
            self.add_remote_config(
                path_search('logging.spec', bind10_config.PLUGIN_PATHS),
                default_logconfig_handler)

    def __del__(self):
        # If the CC Session obejct has been closed, it returns
        # immediately.
        if self._session._closed: return
        self._session.group_unsubscribe(self._module_name,
                                        CC_INSTANCE_WILDCARD)
        for module_name in self._remote_module_configs:
            self._session.group_unsubscribe(module_name)

    def start(self):
        """Send the specification for this module to the configuration
           manager, and request the current non-default configuration.
           The config_handler will be called with that configuration"""
        self.__send_spec()
        self.__request_config()

    def send_stopping(self):
        """Sends a 'stopping' message to the configuration manager. This
           message is just an FYI, and no response is expected. Any errors
           when sending this message (for instance if the msgq session has
           previously been closed) are logged, but ignored."""
        # create_command could raise an exception as well, but except for
        # out of memory related errors, these should all be programming
        # failures and are not caught
        msg = create_command(COMMAND_MODULE_STOPPING,
                             self.get_module_spec().get_full_spec())
        try:
            self._session.group_sendmsg(msg, "ConfigManager")
        except Exception as se:
            # If the session was previously closed, obvously trying to send
            # a message fails. (TODO: check if session is open so we can
            # error on real problems?)
            logger.error(CONFIG_SESSION_STOPPING_FAILED, se)

    def get_socket(self):
        """Returns the socket from the command channel session. This
           should *only* be used for select() loops to see if there
           is anything on the channel. If that loop is not completely
           time-critical, it is strongly recommended to only use
           check_command(), and not look at the socket at all."""
        return self._session._socket

    def close(self):
        """Close the session to the command channel"""
        self._session.close()

    def check_command(self, nonblock=True):
        """Check whether there is a command or configuration update on
           the channel. This function does a read on the cc session, and
           returns nothing.
           It calls check_command_without_recvmsg()
           to parse the received message.

           If nonblock is True, it just checks if there's a command
           and does nothing if there isn't. If nonblock is False, it
           waits until it arrives. It temporarily sets timeout to infinity,
           because commands may not come in arbitrary long time."""
        timeout_orig = self._session.get_timeout()
        self._session.set_timeout(0)
        try:
            msg, env = self._session.group_recvmsg(nonblock)
        finally:
            self._session.set_timeout(timeout_orig)
        self.check_command_without_recvmsg(msg, env)

    def check_command_without_recvmsg(self, msg, env):
        """Parse the given message to see if there is a command or a
           configuration update. Calls the corresponding handler
           functions if present. Responds on the channel if the
           handler returns a message."""
        if msg is None:
            return
        if CC_PAYLOAD_NOTIFICATION in msg:
            group_s = env[CC_HEADER_GROUP].split('/', 1)
            # What to do with these bogus inputs? We just ignore them for now.
            if len(group_s) != 2:
                return
            [prefix, group] = group_s
            if prefix + '/' != CC_GROUP_NOTIFICATION_PREFIX:
                return
            # Now, get the callbacks and call one by one
            callbacks = self._notification_callbacks.get(group, {})
            event = msg[CC_PAYLOAD_NOTIFICATION][0]
            params = None
            if len(msg[CC_PAYLOAD_NOTIFICATION]) > 1:
                params = msg[CC_PAYLOAD_NOTIFICATION][1]
            for key in sorted(callbacks.keys()):
                callbacks[key](event, params)
        elif not CC_PAYLOAD_RESULT in msg:
            # should we default to an answer? success-by-default? unhandled
            # error?
            answer = None
            try:
                module_name = env[CC_HEADER_GROUP]
                cmd, arg = isc.config.ccsession.parse_command(msg)
                if cmd == COMMAND_CONFIG_UPDATE:
                    new_config = arg
                    # If the target channel was not this module
                    # it might be in the remote_module_configs
                    if module_name != self._module_name:
                        if module_name in self._remote_module_configs:
                            # no checking for validity, that's up to the
                            # module itself.
                            newc = self._remote_module_configs[
                                module_name].get_local_config()
                            isc.cc.data.merge(newc, new_config)
                            self._remote_module_configs[
                                module_name].set_local_config(newc)
                            if self._remote_module_callbacks[
                                    module_name] != None:
                                self._remote_module_callbacks[module_name](
                                    new_config,
                                    self._remote_module_configs[module_name])
                        # For other modules, we're not supposed to answer
                        return

                    # ok, so apparently this update is for us.
                    errors = []
                    if not self._config_handler:
                        answer = create_answer(
                            2, self._module_name + " has no config handler")
                    elif not self.get_module_spec().validate_config(
                            False, new_config, errors):
                        answer = create_answer(1, ", ".join(errors))
                    else:
                        isc.cc.data.remove_identical(new_config,
                                                     self.get_local_config())
                        answer = self._config_handler(new_config)
                        rcode, val = parse_answer(answer)
                        if rcode == CC_REPLY_SUCCESS:
                            newc = self.get_local_config()
                            isc.cc.data.merge(newc, new_config)
                            self.set_local_config(newc)
                else:
                    # ignore commands for 'remote' modules
                    if module_name == self._module_name:
                        if self._command_handler:
                            answer = self._command_handler(cmd, arg)
                        else:
                            answer = create_answer(
                                2,
                                self._module_name + " has no command handler")
            except Exception as exc:
                answer = create_answer(1, str(exc))
            if answer:
                self._session.group_reply(env, answer)

    def set_config_handler(self, config_handler):
        """Set the config handler for this module. The handler is a
           function that takes the full configuration and handles it.
           It should return an answer created with create_answer()"""
        self._config_handler = config_handler
        # should we run this right now since we've changed the handler?

    def set_command_handler(self, command_handler):
        """Set the command handler for this module. The handler is a
           function that takes a command as defined in the .spec file
           and return an answer created with create_answer()"""
        self._command_handler = command_handler

    def _add_remote_config_internal(self,
                                    module_spec,
                                    config_update_callback=None):
        """The guts of add_remote_config and add_remote_config_by_name"""
        module_cfg = ConfigData(module_spec)
        module_name = module_spec.get_module_name()

        self._session.group_subscribe(module_name)

        # Get the current config for that module now
        seq = self._session.group_sendmsg(
            create_command(COMMAND_GET_CONFIG, {"module_name": module_name}),
            "ConfigManager")

        try:
            answer, _ = self._session.group_recvmsg(False, seq)
        except isc.cc.SessionTimeout:
            raise ModuleCCSessionError("No answer from ConfigManager when "
                                       "asking about Remote module " +
                                       module_name)
        call_callback = False
        if answer:
            rcode, value = parse_answer(answer)
            if rcode == 0:
                if value != None:
                    if module_spec.validate_config(False, value):
                        module_cfg.set_local_config(value)
                        call_callback = True
                    else:
                        raise ModuleCCSessionError("Bad config data for " +
                                                   module_name + ": " +
                                                   str(value))
            else:
                raise ModuleCCSessionError("Failure requesting remote " +
                                           "configuration data for " +
                                           module_name)

        # all done, add it
        self._remote_module_configs[module_name] = module_cfg
        self._remote_module_callbacks[module_name] = config_update_callback
        if call_callback and config_update_callback is not None:
            config_update_callback(value, module_cfg)

    def add_remote_config_by_name(self,
                                  module_name,
                                  config_update_callback=None):
        """
        This does the same as add_remote_config, but you provide the module name
        instead of the name of the spec file.
        """
        seq = self._session.group_sendmsg(
            create_command(COMMAND_GET_MODULE_SPEC,
                           {"module_name": module_name}), "ConfigManager")
        try:
            answer, env = self._session.group_recvmsg(False, seq)
        except isc.cc.SessionTimeout:
            raise ModuleCCSessionError("No answer from ConfigManager when " +
                                       "asking about for spec of Remote " +
                                       "module " + module_name)
        if answer:
            rcode, value = parse_answer(answer)
            if rcode == 0:
                module_spec = isc.config.module_spec.ModuleSpec(value)
                if module_spec.get_module_name() != module_name:
                    raise ModuleCCSessionError("Module name mismatch: " +
                                               module_name + " and " +
                                               module_spec.get_module_name())
                self._add_remote_config_internal(module_spec,
                                                 config_update_callback)
            else:
                raise ModuleCCSessionError("Error code " + str(rcode) +
                                           "when asking for module spec of " +
                                           module_name)
        else:
            raise ModuleCCSessionError("No answer when asking for module " +
                                       "spec of " + module_name)
        # Just to be consistent with the add_remote_config
        return module_name

    def add_remote_config(self, spec_file_name, config_update_callback=None):
        """Gives access to the configuration of a different module.
           These remote module options can at this moment only be
           accessed through get_remote_config_value(). This function
           also subscribes to the channel of the remote module name
           to receive the relevant updates. It is not possible to
           specify your own handler for this right now, but you can
           specify a callback that is called after the change happened.
           start() must have been called on this CCSession
           prior to the call to this method.
           Returns the name of the module."""
        module_spec = isc.config.module_spec_from_file(spec_file_name)
        self._add_remote_config_internal(module_spec, config_update_callback)
        return module_spec.get_module_name()

    def remove_remote_config(self, module_name):
        """Removes the remote configuration access for this module"""
        if module_name in self._remote_module_configs:
            self._session.group_unsubscribe(module_name)
            del self._remote_module_configs[module_name]
            del self._remote_module_callbacks[module_name]

    def get_remote_config_value(self, module_name, identifier):
        """Returns the current setting for the given identifier at the
           given module. If the module has not been added with
           add_remote_config, a ModuleCCSessionError is raised"""
        if module_name in self._remote_module_configs:
            return self._remote_module_configs[module_name].get_value(
                identifier)
        else:
            raise ModuleCCSessionError("Remote module " + module_name +
                                       " not found")

    def __send_spec(self):
        """Sends the data specification to the configuration manager"""
        msg = create_command(COMMAND_MODULE_SPEC,
                             self.get_module_spec().get_full_spec())
        seq = self._session.group_sendmsg(msg, "ConfigManager")
        try:
            answer, env = self._session.group_recvmsg(False, seq)
        except isc.cc.SessionTimeout:
            # TODO: log an error?
            pass

    def __request_config(self):
        """Asks the configuration manager for the current configuration, and call the config handler if set.
           Raises a ModuleCCSessionError if there is no answer from the configuration manager"""
        seq = self._session.group_sendmsg(
            create_command(COMMAND_GET_CONFIG,
                           {"module_name": self._module_name}),
            "ConfigManager")
        try:
            answer, env = self._session.group_recvmsg(False, seq)
            if answer:
                rcode, value = parse_answer(answer)
                if rcode == 0:
                    errors = []
                    if value != None:
                        if self.get_module_spec().validate_config(
                                False, value, errors):
                            self.set_local_config(value)
                            if self._config_handler:
                                self._config_handler(value)
                        else:
                            raise ModuleCCSessionError(
                                "Wrong data in configuration: " +
                                " ".join(errors))
                else:
                    logger.error(CONFIG_GET_FAILED, value)
            else:
                raise ModuleCCSessionError(
                    "No answer from configuration manager")
        except isc.cc.SessionTimeout:
            raise ModuleCCSessionError(
                "CC Session timeout waiting for configuration manager")

    def rpc_call(self,
                 command,
                 group,
                 instance=CC_INSTANCE_WILDCARD,
                 to=CC_TO_WILDCARD,
                 params=None):
        """
        Create a command with the given name and parameters. Send it to a
        recipient, wait for the answer and parse it.

        This is a wrapper around the group_sendmsg and group_recvmsg on the CC
        session. It exists mostly for convenience.

        Params:
        - command: Name of the command to call on the remote side.
        - group, instance, to: Address specification of the recipient.
        - params: Parameters to pass to the command (as keyword arguments).

        Return: The return value of the remote call (just the value, no status
          code or anything). May be None.

        Raise:
        - RPCRecipientMissing if the given recipient doesn't exist.
        - RPCError if the other side sent an error response. The error string
          is in the exception.
        - ModuleCCSessionError in case of protocol errors, like malformed
          answer.
        """
        cmd = create_command(command, params)
        seq = self._session.group_sendmsg(cmd,
                                          group,
                                          instance=instance,
                                          to=to,
                                          want_answer=True)
        # For non-blocking, we'll have rpc_call_async (once the nonblock
        # actually works)
        reply, rheaders = self._session.group_recvmsg(nonblock=False, seq=seq)
        code, value = parse_answer(reply)
        if code == CC_REPLY_NO_RECPT:
            raise RPCRecipientMissing(value)
        elif code != CC_REPLY_SUCCESS:
            raise RPCError(code, value)
        return value

    def notify(self, notification_group, event_name, params=None):
        """
        Send a notification to subscribed users.

        Send a notification message to all users subscribed to the given
        notification group.

        This method does not block.

        See docs/design/ipc-high.txt for details about notifications
        and the format of messages sent.

        Throws:
        - CCSessionError: for low-level communication errors.
        Params:
        - notification_group (string): This parameter (indirectly) signifies
          what users should receive the notification. Only users that
          subscribed to notifications on the same group receive it.
        - event_name (string): The name of the event to notify about (for
          example `new_group_member`).
        - params: Other parameters that describe the event. This might be, for
          example, the ID of the new member and the name of the group. This can
          be any data that can be sent over the isc.cc.Session, but it is
          common for it to be dict.
        Returns: Nothing
        """
        notification = [event_name]
        if params is not None:
            notification.append(params)
        self._session.group_sendmsg({CC_PAYLOAD_NOTIFICATION: notification},
                                    CC_GROUP_NOTIFICATION_PREFIX +
                                    notification_group,
                                    instance=CC_INSTANCE_WILDCARD,
                                    to=CC_TO_WILDCARD,
                                    want_answer=False)

    def subscribe_notification(self, notification_group, callback):
        """
        Subscribe to receive notifications in given notification group. When a
        notification comes to the group, the callback is called with two
        parameters, the name of the event (the value of `event_name` parameter
        passed to `notify`) and the parameters of the event (the value
        of `params` passed to `notify`).

        This is a fast operation (there may be communication with the message
        queue daemon, but it does not wait for any remote process).

        The callback may get called multiple times (once for each notification).
        It is possible to subscribe multiple callbacks for the same notification,
        by multiple calls of this method, and they will be called in the order
        of registration when the notification comes.

        Throws:
        - CCSessionError: for low-level communication errors.
        Params:
        - notification_group (string): Notification group to subscribe to.
          Notification with the same value of the same parameter of `notify`
          will be received.
        - callback (callable): The callback to be called whenever the
          notification comes.

          The callback should not raise exceptions, such exceptions are
          likely to propagate through the loop and terminate program.
        Returns: Opaque id of the subscription. It can be used to cancel
          the subscription by unsubscribe_notification.
        """
        self._last_notif_id += 1
        my_id = self._last_notif_id
        if notification_group in self._notification_callbacks:
            self._notification_callbacks[notification_group][my_id] = callback
        else:
            self._session.group_subscribe(CC_GROUP_NOTIFICATION_PREFIX +
                                          notification_group)
            self._notification_callbacks[notification_group] = \
                { my_id: callback }
        return (notification_group, my_id)

    def unsubscribe_notification(self, nid):
        """
        Remove previous subscription for notifications. Pass the id returned
        from subscribe_notification.

        Throws:
        - CCSessionError: for low-level communication errors.
        - KeyError: The id does not correspond to valid subscription.
        """
        (group, cid) = nid
        del self._notification_callbacks[group][cid]
        if not self._notification_callbacks[group]:
            # Removed the last one
            self._session.group_unsubscribe(CC_GROUP_NOTIFICATION_PREFIX +
                                            group)
            del self._notification_callbacks[group]
Exemplo n.º 4
0
class ModuleCCSession(ConfigData):
    """This class maintains a connection to the command channel, as
       well as configuration options for modules. The module provides
       a specification file that contains the module name, configuration
       options, and commands. It also gives the ModuleCCSession two callback
       functions, one to call when there is a direct command to the
       module, and one to update the configuration run-time. These
       callbacks are called when 'check_command' is called on the
       ModuleCCSession"""

    def __init__(self, spec_file_name, config_handler, command_handler,
                 cc_session=None, handle_logging_config=True,
                 socket_file = None):
        """Initialize a ModuleCCSession. This does *NOT* send the
           specification and request the configuration yet. Use start()
           for that once the ModuleCCSession has been initialized.

           specfile_name is the path to the specification file.

           config_handler and command_handler are callback functions,
           see set_config_handler and set_command_handler for more
           information on their signatures.

           cc_session can be used to pass in an existing CCSession,
           if it is None, one will be set up. This is mainly intended
           for testing purposes.

           handle_logging_config: if True, the module session will
           automatically handle logging configuration for the module;
           it will read the system-wide Logging configuration and call
           the logger manager to apply it. It will also inform the
           logger manager when the logging configuration gets updated.
           The module does not need to do anything except initializing
           its loggers, and provide log messages. Defaults to true.

           socket_file: If cc_session was none, this optional argument
           specifies which socket file to use to connect to msgq. It
           will be overridden by the environment variable
           MSGQ_SOCKET_FILE. If none, and no environment variable is
           set, it will use the system default.
        """
        module_spec = isc.config.module_spec_from_file(spec_file_name)
        ConfigData.__init__(self, module_spec)

        self._module_name = module_spec.get_module_name()

        self.set_config_handler(config_handler)
        self.set_command_handler(command_handler)

        if not cc_session:
            self._session = Session(socket_file)
        else:
            self._session = cc_session
        self._session.group_subscribe(self._module_name, CC_INSTANCE_WILDCARD)

        self._remote_module_configs = {}
        self._remote_module_callbacks = {}

        self._notification_callbacks = {}
        self._last_notif_id = 0

        if handle_logging_config:
            self.add_remote_config(path_search('logging.spec', bind10_config.PLUGIN_PATHS),
                                   default_logconfig_handler)

    def __del__(self):
        # If the CC Session obejct has been closed, it returns
        # immediately.
        if self._session._closed: return
        self._session.group_unsubscribe(self._module_name,
                                        CC_INSTANCE_WILDCARD)
        for module_name in self._remote_module_configs:
            self._session.group_unsubscribe(module_name)

    def start(self):
        """Send the specification for this module to the configuration
           manager, and request the current non-default configuration.
           The config_handler will be called with that configuration"""
        self.__send_spec()
        self.__request_config()

    def send_stopping(self):
        """Sends a 'stopping' message to the configuration manager. This
           message is just an FYI, and no response is expected. Any errors
           when sending this message (for instance if the msgq session has
           previously been closed) are logged, but ignored."""
        # create_command could raise an exception as well, but except for
        # out of memory related errors, these should all be programming
        # failures and are not caught
        msg = create_command(COMMAND_MODULE_STOPPING,
                             self.get_module_spec().get_full_spec())
        try:
            self._session.group_sendmsg(msg, "ConfigManager")
        except Exception as se:
            # If the session was previously closed, obvously trying to send
            # a message fails. (TODO: check if session is open so we can
            # error on real problems?)
            logger.error(CONFIG_SESSION_STOPPING_FAILED, se)

    def get_socket(self):
        """Returns the socket from the command channel session. This
           should *only* be used for select() loops to see if there
           is anything on the channel. If that loop is not completely
           time-critical, it is strongly recommended to only use
           check_command(), and not look at the socket at all."""
        return self._session._socket

    def close(self):
        """Close the session to the command channel"""
        self._session.close()

    def check_command(self, nonblock=True):
        """Check whether there is a command or configuration update on
           the channel. This function does a read on the cc session, and
           returns nothing.
           It calls check_command_without_recvmsg()
           to parse the received message.

           If nonblock is True, it just checks if there's a command
           and does nothing if there isn't. If nonblock is False, it
           waits until it arrives. It temporarily sets timeout to infinity,
           because commands may not come in arbitrary long time."""
        timeout_orig = self._session.get_timeout()
        self._session.set_timeout(0)
        try:
            msg, env = self._session.group_recvmsg(nonblock)
        finally:
            self._session.set_timeout(timeout_orig)
        self.check_command_without_recvmsg(msg, env)

    def check_command_without_recvmsg(self, msg, env):
        """Parse the given message to see if there is a command or a
           configuration update. Calls the corresponding handler
           functions if present. Responds on the channel if the
           handler returns a message."""
        if msg is None:
            return
        if CC_PAYLOAD_NOTIFICATION in msg:
            group_s = env[CC_HEADER_GROUP].split('/', 1)
            # What to do with these bogus inputs? We just ignore them for now.
            if len(group_s) != 2:
                return
            [prefix, group] = group_s
            if prefix + '/' != CC_GROUP_NOTIFICATION_PREFIX:
                return
            # Now, get the callbacks and call one by one
            callbacks = self._notification_callbacks.get(group, {})
            event = msg[CC_PAYLOAD_NOTIFICATION][0]
            params = None
            if len(msg[CC_PAYLOAD_NOTIFICATION]) > 1:
                params = msg[CC_PAYLOAD_NOTIFICATION][1]
            for key in sorted(callbacks.keys()):
                callbacks[key](event, params)
        elif not CC_PAYLOAD_RESULT in msg:
            # should we default to an answer? success-by-default? unhandled
            # error?
            answer = None
            try:
                module_name = env[CC_HEADER_GROUP]
                cmd, arg = isc.config.ccsession.parse_command(msg)
                if cmd == COMMAND_CONFIG_UPDATE:
                    new_config = arg
                    # If the target channel was not this module
                    # it might be in the remote_module_configs
                    if module_name != self._module_name:
                        if module_name in self._remote_module_configs:
                            # no checking for validity, that's up to the
                            # module itself.
                            newc = self._remote_module_configs[module_name].get_local_config()
                            isc.cc.data.merge(newc, new_config)
                            self._remote_module_configs[module_name].set_local_config(newc)
                            if self._remote_module_callbacks[module_name] != None:
                                self._remote_module_callbacks[module_name](new_config,
                                                                           self._remote_module_configs[module_name])
                        # For other modules, we're not supposed to answer
                        return

                    # ok, so apparently this update is for us.
                    errors = []
                    if not self._config_handler:
                        answer = create_answer(2, self._module_name + " has no config handler")
                    elif not self.get_module_spec().validate_config(False, new_config, errors):
                        answer = create_answer(1, ", ".join(errors))
                    else:
                        isc.cc.data.remove_identical(new_config, self.get_local_config())
                        answer = self._config_handler(new_config)
                        rcode, val = parse_answer(answer)
                        if rcode == CC_REPLY_SUCCESS:
                            newc = self.get_local_config()
                            isc.cc.data.merge(newc, new_config)
                            self.set_local_config(newc)
                else:
                    # ignore commands for 'remote' modules
                    if module_name == self._module_name:
                        if self._command_handler:
                            answer = self._command_handler(cmd, arg)
                        else:
                            answer = create_answer(2, self._module_name + " has no command handler")
            except Exception as exc:
                answer = create_answer(1, str(exc))
            if answer:
                self._session.group_reply(env, answer)

    def set_config_handler(self, config_handler):
        """Set the config handler for this module. The handler is a
           function that takes the full configuration and handles it.
           It should return an answer created with create_answer()"""
        self._config_handler = config_handler
        # should we run this right now since we've changed the handler?

    def set_command_handler(self, command_handler):
        """Set the command handler for this module. The handler is a
           function that takes a command as defined in the .spec file
           and return an answer created with create_answer()"""
        self._command_handler = command_handler

    def _add_remote_config_internal(self, module_spec,
                                    config_update_callback=None):
        """The guts of add_remote_config and add_remote_config_by_name"""
        module_cfg = ConfigData(module_spec)
        module_name = module_spec.get_module_name()

        self._session.group_subscribe(module_name)

        # Get the current config for that module now
        seq = self._session.group_sendmsg(create_command(COMMAND_GET_CONFIG, { "module_name": module_name }), "ConfigManager")

        try:
            answer, _ = self._session.group_recvmsg(False, seq)
        except isc.cc.SessionTimeout:
            raise ModuleCCSessionError("No answer from ConfigManager when "
                                       "asking about Remote module " +
                                       module_name)
        call_callback = False
        if answer:
            rcode, value = parse_answer(answer)
            if rcode == 0:
                if value != None:
                    if module_spec.validate_config(False, value):
                        module_cfg.set_local_config(value)
                        call_callback = True
                    else:
                        raise ModuleCCSessionError("Bad config data for " +
                                                   module_name + ": " +
                                                   str(value))
            else:
                raise ModuleCCSessionError("Failure requesting remote " +
                                           "configuration data for " +
                                           module_name)

        # all done, add it
        self._remote_module_configs[module_name] = module_cfg
        self._remote_module_callbacks[module_name] = config_update_callback
        if call_callback and config_update_callback is not None:
            config_update_callback(value, module_cfg)

    def add_remote_config_by_name(self, module_name,
                                  config_update_callback=None):
        """
        This does the same as add_remote_config, but you provide the module name
        instead of the name of the spec file.
        """
        seq = self._session.group_sendmsg(create_command(COMMAND_GET_MODULE_SPEC,
                                                         { "module_name":
                                                         module_name }),
                                          "ConfigManager")
        try:
            answer, env = self._session.group_recvmsg(False, seq)
        except isc.cc.SessionTimeout:
            raise ModuleCCSessionError("No answer from ConfigManager when " +
                                       "asking about for spec of Remote " +
                                       "module " + module_name)
        if answer:
            rcode, value = parse_answer(answer)
            if rcode == 0:
                module_spec = isc.config.module_spec.ModuleSpec(value)
                if module_spec.get_module_name() != module_name:
                    raise ModuleCCSessionError("Module name mismatch: " +
                                               module_name + " and " +
                                               module_spec.get_module_name())
                self._add_remote_config_internal(module_spec,
                                                 config_update_callback)
            else:
                raise ModuleCCSessionError("Error code " + str(rcode) +
                                           "when asking for module spec of " +
                                           module_name)
        else:
            raise ModuleCCSessionError("No answer when asking for module " +
                                       "spec of " + module_name)
        # Just to be consistent with the add_remote_config
        return module_name

    def add_remote_config(self, spec_file_name, config_update_callback=None):
        """Gives access to the configuration of a different module.
           These remote module options can at this moment only be
           accessed through get_remote_config_value(). This function
           also subscribes to the channel of the remote module name
           to receive the relevant updates. It is not possible to
           specify your own handler for this right now, but you can
           specify a callback that is called after the change happened.
           start() must have been called on this CCSession
           prior to the call to this method.
           Returns the name of the module."""
        module_spec = isc.config.module_spec_from_file(spec_file_name)
        self._add_remote_config_internal(module_spec, config_update_callback)
        return module_spec.get_module_name()

    def remove_remote_config(self, module_name):
        """Removes the remote configuration access for this module"""
        if module_name in self._remote_module_configs:
            self._session.group_unsubscribe(module_name)
            del self._remote_module_configs[module_name]
            del self._remote_module_callbacks[module_name]

    def get_remote_config_value(self, module_name, identifier):
        """Returns the current setting for the given identifier at the
           given module. If the module has not been added with
           add_remote_config, a ModuleCCSessionError is raised"""
        if module_name in self._remote_module_configs:
            return self._remote_module_configs[module_name].get_value(identifier)
        else:
            raise ModuleCCSessionError("Remote module " + module_name +
                                       " not found")

    def __send_spec(self):
        """Sends the data specification to the configuration manager"""
        msg = create_command(COMMAND_MODULE_SPEC, self.get_module_spec().get_full_spec())
        seq = self._session.group_sendmsg(msg, "ConfigManager")
        try:
            answer, env = self._session.group_recvmsg(False, seq)
        except isc.cc.SessionTimeout:
            # TODO: log an error?
            pass

    def __request_config(self):
        """Asks the configuration manager for the current configuration, and call the config handler if set.
           Raises a ModuleCCSessionError if there is no answer from the configuration manager"""
        seq = self._session.group_sendmsg(create_command(COMMAND_GET_CONFIG, { "module_name": self._module_name }), "ConfigManager")
        try:
            answer, env = self._session.group_recvmsg(False, seq)
            if answer:
                rcode, value = parse_answer(answer)
                if rcode == 0:
                    errors = []
                    if value != None:
                        if self.get_module_spec().validate_config(False,
                                                                  value,
                                                                  errors):
                            self.set_local_config(value)
                            if self._config_handler:
                                self._config_handler(value)
                        else:
                            raise ModuleCCSessionError(
                                "Wrong data in configuration: " +
                                " ".join(errors))
                else:
                    logger.error(CONFIG_GET_FAILED, value)
            else:
                raise ModuleCCSessionError("No answer from configuration manager")
        except isc.cc.SessionTimeout:
            raise ModuleCCSessionError("CC Session timeout waiting for configuration manager")

    def rpc_call(self, command, group, instance=CC_INSTANCE_WILDCARD,
                 to=CC_TO_WILDCARD, params=None):
        """
        Create a command with the given name and parameters. Send it to a
        recipient, wait for the answer and parse it.

        This is a wrapper around the group_sendmsg and group_recvmsg on the CC
        session. It exists mostly for convenience.

        Params:
        - command: Name of the command to call on the remote side.
        - group, instance, to: Address specification of the recipient.
        - params: Parameters to pass to the command (as keyword arguments).

        Return: The return value of the remote call (just the value, no status
          code or anything). May be None.

        Raise:
        - RPCRecipientMissing if the given recipient doesn't exist.
        - RPCError if the other side sent an error response. The error string
          is in the exception.
        - ModuleCCSessionError in case of protocol errors, like malformed
          answer.
        """
        cmd = create_command(command, params)
        seq = self._session.group_sendmsg(cmd, group, instance=instance,
                                          to=to, want_answer=True)
        # For non-blocking, we'll have rpc_call_async (once the nonblock
        # actually works)
        reply, rheaders = self._session.group_recvmsg(nonblock=False, seq=seq)
        code, value = parse_answer(reply)
        if code == CC_REPLY_NO_RECPT:
            raise RPCRecipientMissing(value)
        elif code != CC_REPLY_SUCCESS:
            raise RPCError(code, value)
        return value

    def notify(self, notification_group, event_name, params=None):
        """
        Send a notification to subscribed users.

        Send a notification message to all users subscribed to the given
        notification group.

        This method does not block.

        See docs/design/ipc-high.txt for details about notifications
        and the format of messages sent.

        Throws:
        - CCSessionError: for low-level communication errors.
        Params:
        - notification_group (string): This parameter (indirectly) signifies
          what users should receive the notification. Only users that
          subscribed to notifications on the same group receive it.
        - event_name (string): The name of the event to notify about (for
          example `new_group_member`).
        - params: Other parameters that describe the event. This might be, for
          example, the ID of the new member and the name of the group. This can
          be any data that can be sent over the isc.cc.Session, but it is
          common for it to be dict.
        Returns: Nothing
        """
        notification = [event_name]
        if params is not None:
            notification.append(params)
        self._session.group_sendmsg({CC_PAYLOAD_NOTIFICATION: notification},
                                    CC_GROUP_NOTIFICATION_PREFIX +
                                    notification_group,
                                    instance=CC_INSTANCE_WILDCARD,
                                    to=CC_TO_WILDCARD,
                                    want_answer=False)

    def subscribe_notification(self, notification_group, callback):
        """
        Subscribe to receive notifications in given notification group. When a
        notification comes to the group, the callback is called with two
        parameters, the name of the event (the value of `event_name` parameter
        passed to `notify`) and the parameters of the event (the value
        of `params` passed to `notify`).

        This is a fast operation (there may be communication with the message
        queue daemon, but it does not wait for any remote process).

        The callback may get called multiple times (once for each notification).
        It is possible to subscribe multiple callbacks for the same notification,
        by multiple calls of this method, and they will be called in the order
        of registration when the notification comes.

        Throws:
        - CCSessionError: for low-level communication errors.
        Params:
        - notification_group (string): Notification group to subscribe to.
          Notification with the same value of the same parameter of `notify`
          will be received.
        - callback (callable): The callback to be called whenever the
          notification comes.

          The callback should not raise exceptions, such exceptions are
          likely to propagate through the loop and terminate program.
        Returns: Opaque id of the subscription. It can be used to cancel
          the subscription by unsubscribe_notification.
        """
        self._last_notif_id += 1
        my_id = self._last_notif_id
        if notification_group in self._notification_callbacks:
            self._notification_callbacks[notification_group][my_id] = callback
        else:
            self._session.group_subscribe(CC_GROUP_NOTIFICATION_PREFIX +
                                          notification_group)
            self._notification_callbacks[notification_group] = \
                { my_id: callback }
        return (notification_group, my_id)

    def unsubscribe_notification(self, nid):
        """
        Remove previous subscription for notifications. Pass the id returned
        from subscribe_notification.

        Throws:
        - CCSessionError: for low-level communication errors.
        - KeyError: The id does not correspond to valid subscription.
        """
        (group, cid) = nid
        del self._notification_callbacks[group][cid]
        if not self._notification_callbacks[group]:
            # Removed the last one
            self._session.group_unsubscribe(CC_GROUP_NOTIFICATION_PREFIX +
                                            group)
            del self._notification_callbacks[group]
Exemplo n.º 5
0
class ModuleCCSession(ConfigData):
    """This class maintains a connection to the command channel, as
       well as configuration options for modules. The module provides
       a specification file that contains the module name, configuration
       options, and commands. It also gives the ModuleCCSession two callback
       functions, one to call when there is a direct command to the
       module, and one to update the configuration run-time. These
       callbacks are called when 'check_command' is called on the
       ModuleCCSession"""

    def __init__(self, spec_file_name, config_handler, command_handler,
                 cc_session=None, handle_logging_config=True,
                 socket_file = None):
        """Initialize a ModuleCCSession. This does *NOT* send the
           specification and request the configuration yet. Use start()
           for that once the ModuleCCSession has been initialized.

           specfile_name is the path to the specification file.

           config_handler and command_handler are callback functions,
           see set_config_handler and set_command_handler for more
           information on their signatures.

           cc_session can be used to pass in an existing CCSession,
           if it is None, one will be set up. This is mainly intended
           for testing purposes.

           handle_logging_config: if True, the module session will
           automatically handle logging configuration for the module;
           it will read the system-wide Logging configuration and call
           the logger manager to apply it. It will also inform the
           logger manager when the logging configuration gets updated.
           The module does not need to do anything except intializing
           its loggers, and provide log messages. Defaults to true.

           socket_file: If cc_session was none, this optional argument
           specifies which socket file to use to connect to msgq. It
           will be overridden by the environment variable
           MSGQ_SOCKET_FILE. If none, and no environment variable is
           set, it will use the system default.
        """
        module_spec = isc.config.module_spec_from_file(spec_file_name)
        ConfigData.__init__(self, module_spec)

        self._module_name = module_spec.get_module_name()

        self.set_config_handler(config_handler)
        self.set_command_handler(command_handler)

        if not cc_session:
            self._session = Session(socket_file)
        else:
            self._session = cc_session
        self._session.group_subscribe(self._module_name, "*")

        self._remote_module_configs = {}
        self._remote_module_callbacks = {}

        if handle_logging_config:
            self.add_remote_config(path_search('logging.spec', bind10_config.PLUGIN_PATHS),
                                   default_logconfig_handler)

    def __del__(self):
        # If the CC Session obejct has been closed, it returns
        # immediately.
        if self._session._closed: return
        self._session.group_unsubscribe(self._module_name, "*")
        for module_name in self._remote_module_configs:
            self._session.group_unsubscribe(module_name)

    def start(self):
        """Send the specification for this module to the configuration
           manager, and request the current non-default configuration.
           The config_handler will be called with that configuration"""
        self.__send_spec()
        self.__request_config()

    def send_stopping(self):
        """Sends a 'stopping' message to the configuration manager. This
           message is just an FYI, and no response is expected. Any errors
           when sending this message (for instance if the msgq session has
           previously been closed) are logged, but ignored."""
        # create_command could raise an exception as well, but except for
        # out of memory related errors, these should all be programming
        # failures and are not caught
        msg = create_command(COMMAND_MODULE_STOPPING,
                             self.get_module_spec().get_full_spec())
        try:
            self._session.group_sendmsg(msg, "ConfigManager")
        except Exception as se:
            # If the session was previously closed, obvously trying to send
            # a message fails. (TODO: check if session is open so we can
            # error on real problems?)
            logger.error(CONFIG_SESSION_STOPPING_FAILED, se)

    def get_socket(self):
        """Returns the socket from the command channel session. This
           should *only* be used for select() loops to see if there
           is anything on the channel. If that loop is not completely
           time-critical, it is strongly recommended to only use
           check_command(), and not look at the socket at all."""
        return self._session._socket

    def close(self):
        """Close the session to the command channel"""
        self._session.close()

    def check_command(self, nonblock=True):
        """Check whether there is a command or configuration update on
           the channel. This function does a read on the cc session, and
           returns nothing.
           It calls check_command_without_recvmsg()
           to parse the received message.

           If nonblock is True, it just checks if there's a command
           and does nothing if there isn't. If nonblock is False, it
           waits until it arrives. It temporarily sets timeout to infinity,
           because commands may not come in arbitrary long time."""
        timeout_orig = self._session.get_timeout()
        self._session.set_timeout(0)
        try:
            msg, env = self._session.group_recvmsg(nonblock)
        finally:
            self._session.set_timeout(timeout_orig)
        self.check_command_without_recvmsg(msg, env)

    def check_command_without_recvmsg(self, msg, env):
        """Parse the given message to see if there is a command or a
           configuration update. Calls the corresponding handler
           functions if present. Responds on the channel if the
           handler returns a message."""
        # should we default to an answer? success-by-default? unhandled error?
        if msg is not None and not 'result' in msg:
            answer = None
            try:
                module_name = env['group']
                cmd, arg = isc.config.ccsession.parse_command(msg)
                if cmd == COMMAND_CONFIG_UPDATE:
                    new_config = arg
                    # If the target channel was not this module
                    # it might be in the remote_module_configs
                    if module_name != self._module_name:
                        if module_name in self._remote_module_configs:
                            # no checking for validity, that's up to the
                            # module itself.
                            newc = self._remote_module_configs[module_name].get_local_config()
                            isc.cc.data.merge(newc, new_config)
                            self._remote_module_configs[module_name].set_local_config(newc)
                            if self._remote_module_callbacks[module_name] != None:
                                self._remote_module_callbacks[module_name](new_config,
                                                                           self._remote_module_configs[module_name])
                        # For other modules, we're not supposed to answer
                        return

                    # ok, so apparently this update is for us.
                    errors = []
                    if not self._config_handler:
                        answer = create_answer(2, self._module_name + " has no config handler")
                    elif not self.get_module_spec().validate_config(False, new_config, errors):
                        answer = create_answer(1, ", ".join(errors))
                    else:
                        isc.cc.data.remove_identical(new_config, self.get_local_config())
                        answer = self._config_handler(new_config)
                        rcode, val = parse_answer(answer)
                        if rcode == 0:
                            newc = self.get_local_config()
                            isc.cc.data.merge(newc, new_config)
                            self.set_local_config(newc)
                else:
                    # ignore commands for 'remote' modules
                    if module_name == self._module_name:
                        if self._command_handler:
                            answer = self._command_handler(cmd, arg)
                        else:
                            answer = create_answer(2, self._module_name + " has no command handler")
            except Exception as exc:
                answer = create_answer(1, str(exc))
            if answer:
                self._session.group_reply(env, answer)

    def set_config_handler(self, config_handler):
        """Set the config handler for this module. The handler is a
           function that takes the full configuration and handles it.
           It should return an answer created with create_answer()"""
        self._config_handler = config_handler
        # should we run this right now since we've changed the handler?

    def set_command_handler(self, command_handler):
        """Set the command handler for this module. The handler is a
           function that takes a command as defined in the .spec file
           and return an answer created with create_answer()"""
        self._command_handler = command_handler

    def _add_remote_config_internal(self, module_spec,
                                    config_update_callback=None):
        """The guts of add_remote_config and add_remote_config_by_name"""
        module_cfg = ConfigData(module_spec)
        module_name = module_spec.get_module_name()

        self._session.group_subscribe(module_name)

        # Get the current config for that module now
        seq = self._session.group_sendmsg(create_command(COMMAND_GET_CONFIG, { "module_name": module_name }), "ConfigManager")

        try:
            answer, _ = self._session.group_recvmsg(False, seq)
        except isc.cc.SessionTimeout:
            raise ModuleCCSessionError("No answer from ConfigManager when "
                                       "asking about Remote module " +
                                       module_name)
        call_callback = False
        if answer:
            rcode, value = parse_answer(answer)
            if rcode == 0:
                if value != None:
                    if module_spec.validate_config(False, value):
                        module_cfg.set_local_config(value)
                        call_callback = True
                    else:
                        raise ModuleCCSessionError("Bad config data for " +
                                                   module_name + ": " +
                                                   str(value))
            else:
                raise ModuleCCSessionError("Failure requesting remote " +
                                           "configuration data for " +
                                           module_name)

        # all done, add it
        self._remote_module_configs[module_name] = module_cfg
        self._remote_module_callbacks[module_name] = config_update_callback
        if call_callback and config_update_callback is not None:
            config_update_callback(value, module_cfg)

    def add_remote_config_by_name(self, module_name,
                                  config_update_callback=None):
        """
        This does the same as add_remote_config, but you provide the module name
        instead of the name of the spec file.
        """
        seq = self._session.group_sendmsg(create_command(COMMAND_GET_MODULE_SPEC,
                                                         { "module_name":
                                                         module_name }),
                                          "ConfigManager")
        try:
            answer, env = self._session.group_recvmsg(False, seq)
        except isc.cc.SessionTimeout:
            raise ModuleCCSessionError("No answer from ConfigManager when " +
                                       "asking about for spec of Remote " +
                                       "module " + module_name)
        if answer:
            rcode, value = parse_answer(answer)
            if rcode == 0:
                module_spec = isc.config.module_spec.ModuleSpec(value)
                if module_spec.get_module_name() != module_name:
                    raise ModuleCCSessionError("Module name mismatch: " +
                                               module_name + " and " +
                                               module_spec.get_module_name())
                self._add_remote_config_internal(module_spec,
                                                 config_update_callback)
            else:
                raise ModuleCCSessionError("Error code " + str(rcode) +
                                           "when asking for module spec of " +
                                           module_name)
        else:
            raise ModuleCCSessionError("No answer when asking for module " +
                                       "spec of " + module_name)
        # Just to be consistent with the add_remote_config
        return module_name

    def add_remote_config(self, spec_file_name, config_update_callback=None):
        """Gives access to the configuration of a different module.
           These remote module options can at this moment only be
           accessed through get_remote_config_value(). This function
           also subscribes to the channel of the remote module name
           to receive the relevant updates. It is not possible to
           specify your own handler for this right now, but you can
           specify a callback that is called after the change happened.
           start() must have been called on this CCSession
           prior to the call to this method.
           Returns the name of the module."""
        module_spec = isc.config.module_spec_from_file(spec_file_name)
        self._add_remote_config_internal(module_spec, config_update_callback)
        return module_spec.get_module_name()

    def remove_remote_config(self, module_name):
        """Removes the remote configuration access for this module"""
        if module_name in self._remote_module_configs:
            self._session.group_unsubscribe(module_name)
            del self._remote_module_configs[module_name]
            del self._remote_module_callbacks[module_name]

    def get_remote_config_value(self, module_name, identifier):
        """Returns the current setting for the given identifier at the
           given module. If the module has not been added with
           add_remote_config, a ModuleCCSessionError is raised"""
        if module_name in self._remote_module_configs:
            return self._remote_module_configs[module_name].get_value(identifier)
        else:
            raise ModuleCCSessionError("Remote module " + module_name +
                                       " not found")

    def __send_spec(self):
        """Sends the data specification to the configuration manager"""
        msg = create_command(COMMAND_MODULE_SPEC, self.get_module_spec().get_full_spec())
        seq = self._session.group_sendmsg(msg, "ConfigManager")
        try:
            answer, env = self._session.group_recvmsg(False, seq)
        except isc.cc.SessionTimeout:
            # TODO: log an error?
            pass

    def __request_config(self):
        """Asks the configuration manager for the current configuration, and call the config handler if set.
           Raises a ModuleCCSessionError if there is no answer from the configuration manager"""
        seq = self._session.group_sendmsg(create_command(COMMAND_GET_CONFIG, { "module_name": self._module_name }), "ConfigManager")
        try:
            answer, env = self._session.group_recvmsg(False, seq)
            if answer:
                rcode, value = parse_answer(answer)
                if rcode == 0:
                    errors = []
                    if value != None:
                        if self.get_module_spec().validate_config(False,
                                                                  value,
                                                                  errors):
                            self.set_local_config(value)
                            if self._config_handler:
                                self._config_handler(value)
                        else:
                            raise ModuleCCSessionError(
                                "Wrong data in configuration: " +
                                " ".join(errors))
                else:
                    logger.error(CONFIG_GET_FAILED, value)
            else:
                raise ModuleCCSessionError("No answer from configuration manager")
        except isc.cc.SessionTimeout:
            raise ModuleCCSessionError("CC Session timeout waiting for configuration manager")