ccsession.py 8.18 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# Copyright (C) 2009  Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

#
# Client-side functionality for configuration and commands
#
# It keeps a cc-channel session with the configuration manager daemon,
# and handles configuration updates and direct commands

# modeled after ccsession.h/cc 'protocol' changes here need to be
# made there as well
Jelte Jansen's avatar
Jelte Jansen committed
24 25 26
"""This module provides the CCSession class, as well as a set of
   utility functions to create and parse messages related to commands
   and configuration"""
27

28
from isc.cc import Session
29
import isc
30

31 32 33
class CCSessionError(Exception): pass

def parse_answer(msg):
Jelte Jansen's avatar
Jelte Jansen committed
34 35 36
    """Returns a tuple (rcode, value), where value depends on the
       command that was called. If rcode != 0, value is a string
       containing an error message"""
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
    if 'result' not in msg:
        raise CCSessionError("answer message does not contain 'result' element")
    elif type(msg['result']) != list:
        raise CCSessionError("wrong result type in answer message")
    elif len(msg['result']) < 1:
        raise CCSessionError("empty result list in answer message")
    elif type(msg['result'][0]) != int:
        raise CCSessionError("wrong rcode type in answer message")
    else:
        if len(msg['result']) > 1:
            return msg['result'][0], msg['result'][1]
        else:
            return msg['result'][0], None

def create_answer(rcode, arg = None):
    """Creates an answer packet for config&commands. rcode must be an
       integer. If rcode == 0, arg is an optional value that depends
       on what the command or option was. If rcode != 0, arg must be
       a string containing an error message"""
    if type(rcode) != int:
        raise CCSessionError("rcode in create_answer() must be an integer")
    if rcode != 0 and type(arg) != str:
        raise CCSessionError("arg in create_answer for rcode != 0 must be a string describing the error")
60
    if arg != None:
61 62
        return { 'result': [ rcode, arg ] }
    else:
63
        return { 'result': [ rcode ] }
64

65
class CCSession:
Jelte Jansen's avatar
Jelte Jansen committed
66 67 68 69 70 71 72 73 74
    """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 CCSession 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
       CCSession"""
       
75
    def __init__(self, spec_file_name, config_handler, command_handler):
Jelte Jansen's avatar
Jelte Jansen committed
76 77 78 79 80 81 82
        """Initialize a CCSession. This does *NOT* send the
           specification and request the configuration yet. Use start()
           for that once the CCSession 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."""
Jelte Jansen's avatar
Jelte Jansen committed
83
        data_definition = isc.config.module_spec_from_file(spec_file_name)
84 85
        self._config_data = isc.config.config_data.ConfigData(data_definition)
        self._module_name = data_definition.get_module_name()
86
        
87 88
        self.set_config_handler(config_handler)
        self.set_command_handler(command_handler)
89

90
        self._session = Session()
91
        self._session.group_subscribe(self._module_name, "*")
92

93
    def start(self):
Jelte Jansen's avatar
Jelte Jansen committed
94 95 96
        """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"""
97
        self.__send_spec()
98
        self.__request_config()
99

100
    def get_socket(self):
Jelte Jansen's avatar
Jelte Jansen committed
101 102 103 104
        """Returns the socket from the command channel session. This can
           be used in select() loops to see if there is anything on the
           channel. This is not strictly necessary as long as
           check_command is called periodically."""
105 106
        return self._session._socket
    
107
    def get_session(self):
108
        """Returns the command-channel session that is used, so the
Jelte Jansen's avatar
Jelte Jansen committed
109
           application can use it directly."""
110
        return self._session
111

112
    def set_config(self, new_config):
Jelte Jansen's avatar
Jelte Jansen committed
113
        """Sets the current or non-default configuration"""
114 115 116
        return self._config_data.set_local_config(new_config)

    def get_config(self):
Jelte Jansen's avatar
Jelte Jansen committed
117
        """Returns the current or non-default configuration"""
118 119
        return self._config_data.get_local_config()

Jelte Jansen's avatar
Jelte Jansen committed
120 121 122 123
    def get_full_config(self):
        """Returns the current or non-default configuration"""
        return self._config_data.get_full_config()

Jelte Jansen's avatar
Jelte Jansen committed
124 125
    def get_module_spec(self):
        return self._config_data.get_module_spec()
126

127
    def close(self):
Jelte Jansen's avatar
Jelte Jansen committed
128
        """Close the session to the command channel"""
129 130
        self._session.close()

131
    def check_command(self):
Jelte Jansen's avatar
Jelte Jansen committed
132 133 134
        """Check whether there is a command or configuration update
           on the channel. Call the corresponding callback function if
           there is."""
135
        msg, env = self._session.group_recvmsg(False)
136
        # should we default to an answer? success-by-default? unhandled error?
Jelte Jansen's avatar
Jelte Jansen committed
137 138 139
        if msg:
            answer = None
            try:
140 141 142 143 144 145
                print("[XX] got msg: ")
                print(msg)
                if "config_update" in msg and self._config_handler:
                    answer = self._config_handler(msg["config_update"])
                if "command" in msg and self._command_handler:
                    answer = self._command_handler(msg["command"])
Jelte Jansen's avatar
Jelte Jansen committed
146 147 148 149
            except Exception as exc:
                answer = create_answer(1, str(exc))
            if answer:
                self._session.group_reply(env, answer)
150
    
151
    def set_config_handler(self, config_handler):
152 153
        """Set the config handler for this module. The handler is a
           function that takes the full configuration and handles it.
154
           It should return an answer created with create_answer()"""
155 156 157
        self._config_handler = config_handler
        # should we run this right now since we've changed the handler?

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

164
    def __send_spec(self):
165
        """Sends the data specification to the configuration manager"""
166
        print("[XX] send spec for " + self._module_name + " to ConfigManager")
Jelte Jansen's avatar
Jelte Jansen committed
167
        self._session.group_sendmsg({ "module_spec": self._config_data.get_module_spec().get_full_spec() }, "ConfigManager")
168
        answer, env = self._session.group_recvmsg(False)
169 170
        print("[XX] got answer from cfgmgr:")
        print(answer)
171
        
172
    def __request_config(self):
173 174 175
        """Asks the configuration manager for the current configuration, and call the config handler if set"""
        self._session.group_sendmsg({ "command": [ "get_config", { "module_name": self._module_name } ] }, "ConfigManager")
        answer, env = self._session.group_recvmsg(False)
176 177
        rcode, value = parse_answer(answer)
        if rcode == 0:
Jelte Jansen's avatar
Jelte Jansen committed
178
            if self._config_data.get_module_spec().validate(False, value):
179 180 181 182 183 184
                self._config_data.set_local_config(value);
                if self._config_handler:
                    self._config_handler(value)
        else:
            # log error
            print("Error requesting configuration: " + value)
185