zonemgr.py.in 24.9 KB
Newer Older
1
2
3
#!@PYTHON@

# Copyright (C) 2010  Internet Systems Consortium.
4
# Copyright (C) 2010  CZ NIC
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#
# 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.

19
"""
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
This file implements the Secondary Manager program.

The secondary manager is one of the co-operating processes
of BIND10, which keeps track of timers and other information
necessary for BIND10 to act as a slave.
"""

import sys; sys.path.append ('@@PYTHONPATH@@')
import os
import time
import signal
import isc
import random
import threading
import select
import socket
36
import errno
37
38
39
from isc.datasrc import sqlite3_ds
from optparse import OptionParser, OptionValueError
from isc.config.ccsession import *
40
import isc.util.process
Michal Vaner's avatar
Michal Vaner committed
41

42
isc.util.process.rename()
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# If B10_FROM_BUILD is set in the environment, we use data files
# from a directory relative to that, otherwise we use the ones
# installed on the system
if "B10_FROM_BUILD" in os.environ:
    SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/zonemgr"
    AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
else:
    PREFIX = "@prefix@"
    DATAROOTDIR = "@datarootdir@"
    SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
    AUTH_SPECFILE_PATH = SPECFILE_PATH

SPECFILE_LOCATION = SPECFILE_PATH + "/zonemgr.spec"
AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"

__version__ = "BIND10"

61
# define module name
Jerry's avatar
Jerry committed
62
63
XFRIN_MODULE_NAME = 'Xfrin'
AUTH_MODULE_NAME = 'Auth'
64

65
# define command name
Jerry's avatar
Jerry committed
66
67
ZONE_XFRIN_FAILED_COMMAND = 'zone_xfrin_failed'
ZONE_XFRIN_SUCCESS_COMMAND = 'zone_new_data_ready'
68
ZONE_REFRESH_COMMAND = 'refresh_from_zonemgr'
Jerry's avatar
Jerry committed
69
ZONE_NOTIFY_COMMAND = 'notify'
70

71
72
73
74
75
# define zone state
ZONE_OK = 0
ZONE_REFRESHING = 1
ZONE_EXPIRED = 2

76
77
78
79
80
81
82
83
84
85
86
87
# offsets of fields in the SOA RDATA
REFRESH_OFFSET = 3
RETRY_OFFSET = 4
EXPIRED_OFFSET = 5

# verbose mode
VERBOSE_MODE = False

def log_msg(msg):
    if VERBOSE_MODE:
        sys.stdout.write("[b10-zonemgr] %s\n" % str(msg))

88
89
90
class ZonemgrException(Exception):
    pass

91
92
class ZonemgrRefresh:
    """This class will maintain and manage zone refresh info.
93
94
95
    It also provides methods to keep track of zone timers and
    do zone refresh.
    Zone timers can be started by calling run_timer(), and it
Jerry's avatar
Jerry committed
96
97
    can be stopped by calling shutdown() in another thread.

98
    """
99

100
    def __init__(self, cc, db_file, slave_socket, config_data):
101
        self._cc = cc
102
        self._check_sock = slave_socket
103
        self._db_file = db_file
104
        self._zonemgr_refresh_info = {}
105
        self.update_config_data(config_data)
106
        self._running = False
107

108
109
110
    def _random_jitter(self, max, jitter):
        """Imposes some random jitters for refresh and
        retry timers to avoid many zones need to do refresh
111
        at the same time.
112
113
        The value should be between (max - jitter) and max.
        """
114
115
        if 0 == jitter:
            return max
116
        return random.uniform(max - jitter, max)
117
118
119
120

    def _get_current_time(self):
        return time.time()

Jerry's avatar
Jerry committed
121
    def _set_zone_timer(self, zone_name_class, max, jitter):
122
        """Set zone next refresh time.
123
        jitter should not be bigger than half the original value."""
Jerry's avatar
Jerry committed
124
        self._set_zone_next_refresh_time(zone_name_class, self._get_current_time() + \
125
                                            self._random_jitter(max, jitter))
126

Jerry's avatar
Jerry committed
127
    def _set_zone_refresh_timer(self, zone_name_class):
128
        """Set zone next refresh time after zone refresh success.
129
           now + refresh - jitter  <= next_refresh_time <= now + refresh
130
           """
131
        zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[REFRESH_OFFSET])
132
133
        zone_refresh_time = max(self._lowerbound_refresh, zone_refresh_time)
        self._set_zone_timer(zone_name_class, zone_refresh_time, self._jitter_scope * zone_refresh_time)
134

Jerry's avatar
Jerry committed
135
    def _set_zone_retry_timer(self, zone_name_class):
136
        """Set zone next refresh time after zone refresh fail.
137
           now + retry - jitter <= next_refresh_time <= now + retry
138
           """
139
        zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[RETRY_OFFSET])
140
141
        zone_retry_time = max(self._lowerbound_retry, zone_retry_time)
        self._set_zone_timer(zone_name_class, zone_retry_time, self._jitter_scope * zone_retry_time)
142

Jerry's avatar
Jerry committed
143
    def _set_zone_notify_timer(self, zone_name_class):
144
        """Set zone next refresh time after receiving notify
145
           next_refresh_time = now
146
        """
Jerry's avatar
Jerry committed
147
        self._set_zone_timer(zone_name_class, 0, 0)
148

Jerry's avatar
Jerry committed
149
    def _zone_not_exist(self, zone_name_class):
150
        """ Zone doesn't belong to zonemgr"""
151
        return not zone_name_class in self._zonemgr_refresh_info
Jerry's avatar
Jerry committed
152
153

    def zone_refresh_success(self, zone_name_class):
154
        """Update zone info after zone refresh success"""
Jerry's avatar
Jerry committed
155
        if (self._zone_not_exist(zone_name_class)):
156
157
            raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
                                   "belong to zonemgr" % zone_name_class)
158
        self.zonemgr_reload_zone(zone_name_class)
Jerry's avatar
Jerry committed
159
160
161
        self._set_zone_refresh_timer(zone_name_class)
        self._set_zone_state(zone_name_class, ZONE_OK)
        self._set_zone_last_refresh_time(zone_name_class, self._get_current_time())
162

Jerry's avatar
Jerry committed
163
    def zone_refresh_fail(self, zone_name_class):
164
        """Update zone info after zone refresh fail"""
Jerry's avatar
Jerry committed
165
        if (self._zone_not_exist(zone_name_class)):
166
167
            raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
                                   "belong to zonemgr" % zone_name_class)
168
169
170
171
172
        # Is zone expired?
        if (self._zone_is_expired(zone_name_class)):
            self._set_zone_state(zone_name_class, ZONE_EXPIRED)
        else:
            self._set_zone_state(zone_name_class, ZONE_OK)
Jerry's avatar
Jerry committed
173
        self._set_zone_retry_timer(zone_name_class)
174

Jerry's avatar
Jerry committed
175
    def zone_handle_notify(self, zone_name_class, master):
176
        """Handle zone notify"""
Jerry's avatar
Jerry committed
177
        if (self._zone_not_exist(zone_name_class)):
178
179
            raise ZonemgrException("[b10-zonemgr] Notified zone (%s, %s) "
                                   "doesn't belong to zonemgr" % zone_name_class)
Jerry's avatar
Jerry committed
180
181
        self._set_zone_notifier_master(zone_name_class, master)
        self._set_zone_notify_timer(zone_name_class)
182

183
184
185
186
187
188
189
    def zonemgr_reload_zone(self, zone_name_class):
        """ Reload a zone."""
        zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
        self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"] = zone_soa[7]

    def zonemgr_add_zone(self, zone_name_class):
        """ Add a zone into zone manager."""
190
        log_msg("Loading zone (%s, %s)" % zone_name_class)
191
192
193
194
195
196
        zone_info = {}
        zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
        if not zone_soa:
            raise ZonemgrException("[b10-zonemgr] zone (%s, %s) doesn't have soa." % zone_name_class)
        zone_info["zone_soa_rdata"] = zone_soa[7]
        zone_info["zone_state"] = ZONE_OK
197
        zone_info["last_refresh_time"] = self._get_current_time()
198
199
200
201
        zone_info["next_refresh_time"] = self._get_current_time() + \
                                         float(zone_soa[7].split(" ")[REFRESH_OFFSET])
        self._zonemgr_refresh_info[zone_name_class] = zone_info

Jerry's avatar
Jerry committed
202
    def _zone_is_expired(self, zone_name_class):
203
204
        """Judge whether a zone is expired or not."""
        zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[EXPIRED_OFFSET])
Jerry's avatar
Jerry committed
205
206
        zone_last_refresh_time = self._get_zone_last_refresh_time(zone_name_class)
        if (ZONE_EXPIRED == self._get_zone_state(zone_name_class) or
207
208
209
210
211
            zone_last_refresh_time + zone_expired_time <= self._get_current_time()):
            return True

        return False

Jerry's avatar
Jerry committed
212
213
    def _get_zone_soa_rdata(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"]
214

Jerry's avatar
Jerry committed
215
216
    def _get_zone_last_refresh_time(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"]
217

Jerry's avatar
Jerry committed
218
219
    def _set_zone_last_refresh_time(self, zone_name_class, time):
        self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"] = time
220

Jerry's avatar
Jerry committed
221
222
    def _get_zone_notifier_master(self, zone_name_class):
        if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
223
            return self._zonemgr_refresh_info[zone_name_class]["notify_master"]
224
225
226

        return None

Jerry's avatar
Jerry committed
227
228
    def _set_zone_notifier_master(self, zone_name_class, master_addr):
        self._zonemgr_refresh_info[zone_name_class]["notify_master"] = master_addr
229

Jerry's avatar
Jerry committed
230
231
232
    def _clear_zone_notifier_master(self, zone_name_class):
        if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
            del self._zonemgr_refresh_info[zone_name_class]["notify_master"]
233

Jerry's avatar
Jerry committed
234
235
    def _get_zone_state(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["zone_state"]
236

Jerry's avatar
Jerry committed
237
    def _set_zone_state(self, zone_name_class, zone_state):
238
        self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state
239

Jerry's avatar
Jerry committed
240
241
    def _get_zone_refresh_timeout(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"]
242

Jerry's avatar
Jerry committed
243
244
    def _set_zone_refresh_timeout(self, zone_name_class, time):
        self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"] = time
245

Jerry's avatar
Jerry committed
246
247
    def _get_zone_next_refresh_time(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"]
248

Jerry's avatar
Jerry committed
249
250
    def _set_zone_next_refresh_time(self, zone_name_class, time):
        self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"] = time
251
252

    def _send_command(self, module_name, command_name, params):
253
        """Send command between modules."""
254
        msg = create_command(command_name, params)
255
        try:
256
257
258
259
260
            seq = self._cc.group_sendmsg(msg, module_name)
            try:
                answer, env = self._cc.group_recvmsg(False, seq)
            except isc.cc.session.SessionTimeout:
                pass        # for now we just ignore the failure
261
        except socket.error:
262
            sys.stderr.write("[b10-zonemgr] Failed to send to module %s, the session has been closed." % module_name)
263

Jerry's avatar
Jerry committed
264
265
266
267
268
269
270
    def _find_need_do_refresh_zone(self):
        """Find the first zone need do refresh, if no zone need
        do refresh, return the zone with minimum next_refresh_time.
        """
        zone_need_refresh = None
        for zone_name_class in self._zonemgr_refresh_info.keys():
            zone_state = self._get_zone_state(zone_name_class)
271
            # If hasn't received refresh response but are within refresh timeout, skip the zone
272
            if (ZONE_REFRESHING == zone_state and
Jerry's avatar
Jerry committed
273
                (self._get_zone_refresh_timeout(zone_name_class) > self._get_current_time())):
274
                continue
275
276
277
278

            # Get the zone with minimum next_refresh_time
            if ((zone_need_refresh is None) or
                (self._get_zone_next_refresh_time(zone_name_class) <
279
                 self._get_zone_next_refresh_time(zone_need_refresh))):
Jerry's avatar
Jerry committed
280
281
                zone_need_refresh = zone_name_class

282
            # Find the zone need do refresh
Jerry's avatar
Jerry committed
283
284
            if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()):
                break
285

286
287
        return zone_need_refresh

288

Jerry's avatar
Jerry committed
289
    def _do_refresh(self, zone_name_class):
290
        """Do zone refresh."""
291
        log_msg("Do refresh for zone (%s, %s)." % zone_name_class)
Jerry's avatar
Jerry committed
292
        self._set_zone_state(zone_name_class, ZONE_REFRESHING)
293
        self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + self._max_transfer_timeout)
Jerry's avatar
Jerry committed
294
        notify_master = self._get_zone_notifier_master(zone_name_class)
295
        # If the zone has notify master, send notify command to xfrin module
296
        if notify_master:
Jerry's avatar
Jerry committed
297
298
            param = {"zone_name" : zone_name_class[0],
                     "zone_class" : zone_name_class[1],
299
                     "master" : notify_master
300
                     }
301
            self._send_command(XFRIN_MODULE_NAME, ZONE_NOTIFY_COMMAND, param)
Jerry's avatar
Jerry committed
302
            self._clear_zone_notifier_master(zone_name_class)
303
304
        # Send refresh command to xfrin module
        else:
Jerry's avatar
Jerry committed
305
306
307
            param = {"zone_name" : zone_name_class[0],
                     "zone_class" : zone_name_class[1]
                    }
Jerry's avatar
Jerry committed
308
            self._send_command(XFRIN_MODULE_NAME, ZONE_REFRESH_COMMAND, param)
309
310
311

    def _zone_mgr_is_empty(self):
        """Does zone manager has no zone?"""
Jerry's avatar
Jerry committed
312
        if not len(self._zonemgr_refresh_info):
313
314
315
316
            return True

        return False

Michal Vaner's avatar
Michal Vaner committed
317
    def _run_timer(self, start_event):
318
        while self._running:
Michal Vaner's avatar
Michal Vaner committed
319
320
            # Notify run_timer that we already started and are inside the loop.
            # It is set only once, but when it was outside the loop, there was
Michal Vaner's avatar
Michal Vaner committed
321
            # a race condition and _running could be set to false before we
Michal Vaner's avatar
Michal Vaner committed
322
323
324
325
            # could enter it
            if start_event:
                start_event.set()
                start_event = None
Jerry's avatar
Jerry committed
326
            # If zonemgr has no zone, set timer timeout to self._lowerbound_retry.
327
            if self._zone_mgr_is_empty():
328
                timeout = self._lowerbound_retry
329
            else:
330
                zone_need_refresh = self._find_need_do_refresh_zone()
331
                # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry.
332
                if not zone_need_refresh:
333
                    timeout = self._lowerbound_retry
334
335
336
337
338
                else:
                    timeout = self._get_zone_next_refresh_time(zone_need_refresh) - self._get_current_time()
                    if (timeout < 0):
                        self._do_refresh(zone_need_refresh)
                        continue
339

340
            """ Wait for the socket notification for a maximum time of timeout
341
            in seconds (as float)."""
342
            try:
Jerry's avatar
Jerry committed
343
                rlist, wlist, xlist = select.select([self._check_sock, self._read_sock], [], [], timeout)
344
345
346
347
            except select.error as e:
                if e.args[0] == errno.EINTR:
                    (rlist, wlist, xlist) = ([], [], [])
                else:
Jerry's avatar
Jerry committed
348
                    sys.stderr.write("[b10-zonemgr] Error with select(); %s\n" % e)
349
350
                    break

Jerry's avatar
Jerry committed
351
            for fd in rlist:
352
                if fd == self._read_sock: # awaken by shutdown socket
353
                    # self._running will be False by now, if it is not a false
Michal Vaner's avatar
Michal Vaner committed
354
355
                    # alarm (linux kernel is said to trigger spurious wakeup
                    # on a filehandle that is not really readable).
356
                    continue
Jerry's avatar
Jerry committed
357
358
                if fd == self._check_sock: # awaken by check socket
                    self._check_sock.recv(32)
Jerry's avatar
Jerry committed
359

360
361
362
363
364
365
366
367
368
369
370
371
372
    def run_timer(self, daemon=False):
        """
        Keep track of zone timers. Spawns and starts a thread. The thread object is returned.

        You can stop it by calling shutdown().
        """
        # Small sanity check
        if self._running:
            raise RuntimeError("Trying to run the timers twice at the same time")

        # Prepare the launch
        self._running = True
        (self._read_sock, self._write_sock) = socket.socketpair()
Michal Vaner's avatar
Michal Vaner committed
373
        start_event = threading.Event()
374
375

        # Start the thread
Michal Vaner's avatar
Michal Vaner committed
376
377
        self._thread = threading.Thread(target = self._run_timer,
            args = (start_event,))
378
379
380
        if daemon:
            self._thread.setDaemon(True)
        self._thread.start()
Michal Vaner's avatar
Michal Vaner committed
381
        start_event.wait()
382
383
384
385

        # Return the thread to anyone interested
        return self._thread

Jerry's avatar
Jerry committed
386
    def shutdown(self):
387
388
389
390
391
392
393
394
395
        """
        Stop the run_timer() thread. Block until it finished. This must be
        called from a different thread.
        """
        if not self._running:
            raise RuntimeError("Trying to shutdown, but not running")

        # Ask the thread to stop
        self._running = False
Jerry's avatar
Jerry committed
396
        self._write_sock.send(b'shutdown') # make self._read_sock readble
397
398
399
400
401
402
        # Wait for it to actually finnish
        self._thread.join()
        # Wipe out what we do not need
        self._thread = None
        self._read_sock = None
        self._write_sock = None
403

404
405
    def update_config_data(self, new_config):
        """ update ZonemgrRefresh config """
406
        backup = self._zonemgr_refresh_info.copy()
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
        try:
            required = {}
            # Add new zones
            for secondary_zone in new_config.get('secondary_zones'):
                name_class = (secondary_zone['name'], secondary_zone['class'])
                required[name_class] = True
                # Add it only if it isn't there already
                if not name_class in self._zonemgr_refresh_info:
                    self.zonemgr_add_zone(name_class)
            # Drop the zones that are no longer there
            # Do it in two phases, python doesn't like deleting while iterating
            to_drop = []
            for old_zone in self._zonemgr_refresh_info:
                if not old_zone in required:
                    to_drop.append(old_zone)
            for drop in to_drop:
                del self._zonemgr_refresh_info[drop]
        # If we are not able to find it in database, restore the original
        except:
            self._zonemgr_refresh_info = backup
            raise
428
429
430
431
        self._lowerbound_refresh = new_config.get('lowerbound_refresh')
        self._lowerbound_retry = new_config.get('lowerbound_retry')
        self._max_transfer_timeout = new_config.get('max_transfer_timeout')
        self._jitter_scope = new_config.get('jitter_scope')
432

433
434
435
class Zonemgr:
    """Zone manager class."""
    def __init__(self):
436
        self._zone_refresh = None
437
438
        self._setup_session()
        self._db_file = self.get_db_file()
439
        # Create socket pair for communicating between main thread and zonemgr timer thread
Jerry's avatar
Jerry committed
440
        self._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
441
        self._zone_refresh = ZonemgrRefresh(self._cc, self._db_file, self._slave_socket, self._config_data)
442
        self._zone_refresh.run_timer()
443

444
        self._lock = threading.Lock()
445
        self._shutdown_event = threading.Event()
Michal Vaner's avatar
Michal Vaner committed
446
        self.running = False
447
448

    def _setup_session(self):
449
        """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving
450
451
        commands and config data sent from other modules, another one (self._cc)
        is used to send commands to proper modules."""
452
453
454
455
        self._cc = isc.cc.Session()
        self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
                                                  self.config_handler,
                                                  self.command_handler)
456
        self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION)
457
        self._config_data = self._module_cc.get_full_config()
Jerry's avatar
Jerry committed
458
        self._config_data_check(self._config_data)
459
460
461
        self._module_cc.start()

    def get_db_file(self):
Jerry's avatar
Jerry committed
462
        db_file, is_default = self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file")
463
464
465
        # this too should be unnecessary, but currently the
        # 'from build' override isn't stored in the config
        # (and we don't have indirect python access to datasources yet)
466
467
468
469
470
        if is_default and "B10_FROM_BUILD" in os.environ:
            db_file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
        return db_file

    def shutdown(self):
471
472
        """Shutdown the zonemgr process. the thread which is keeping track of zone
        timers should be terminated.
473
        """
Jerry's avatar
Jerry committed
474
475
        self._zone_refresh.shutdown()

Jerry's avatar
Jerry committed
476
        self._slave_socket.close()
Jerry's avatar
Jerry committed
477
        self._master_socket.close()
478
        self._shutdown_event.set()
Michal Vaner's avatar
Michal Vaner committed
479
        self.running = False
480
481

    def config_handler(self, new_config):
482
        """ Update config data. """
483
        answer = create_answer(0)
484
        ok = True
485
        complete = self._config_data.copy()
486
        for key in new_config:
487
            if key not in complete:
488
                answer = create_answer(1, "Unknown config data: " + str(key))
489
                ok = False
490
                continue
491
            complete[key] = new_config[key]
492

493
        self._config_data_check(complete)
494
        if self._zone_refresh is not None:
495
496
497
498
499
500
501
            try:
                self._zone_refresh.update_config_data(complete)
            except Exception as e:
                answer = create_answer(1, str(e))
                ok = False
        if ok:
            self._config_data = complete
502

503
504
        return answer

Jerry's avatar
Jerry committed
505
    def _config_data_check(self, config_data):
506
507
508
        """Check whether the new config data is valid or
        not. It contains only basic logic, not full check against
        database."""
Jerry's avatar
Jerry committed
509
510
511
        # jitter should not be bigger than half of the original value
        if config_data.get('jitter_scope') > 0.5:
            config_data['jitter_scope'] = 0.5
512
            log_msg("[b10-zonemgr] jitter_scope is too big, its value will "
513
                      "be set to 0.5")
Jerry's avatar
Jerry committed
514

515

Jerry's avatar
Jerry committed
516
    def _parse_cmd_params(self, args, command):
517
518
        zone_name = args.get("zone_name")
        if not zone_name:
519
            raise ZonemgrException("zone name should be provided")
520

Jerry's avatar
Jerry committed
521
522
        zone_class = args.get("zone_class")
        if not zone_class:
523
            raise ZonemgrException("zone class should be provided")
Jerry's avatar
Jerry committed
524
525
526
527

        if (command != ZONE_NOTIFY_COMMAND):
            return (zone_name, zone_class)

528
529
530
        master_str = args.get("master")
        if not master_str:
            raise ZonemgrException("master address should be provided")
531

Jerry's avatar
Jerry committed
532
        return ((zone_name, zone_class), master_str)
533
534
535


    def command_handler(self, command, args):
536
        """Handle command receivd from command channel.
537
        ZONE_NOTIFY_COMMAND is issued by Auth process; ZONE_XFRIN_SUCCESS_COMMAND
538
539
        and ZONE_XFRIN_FAILED_COMMAND are issued by Xfrin process; shutdown is issued
        by a user or Boss process. """
540
        answer = create_answer(0)
Jerry's avatar
Jerry committed
541
        if command == ZONE_NOTIFY_COMMAND:
Jerry's avatar
Jerry committed
542
            """ Handle Auth notify command"""
543
            # master is the source sender of the notify message.
Jerry's avatar
Jerry committed
544
            zone_name_class, master = self._parse_cmd_params(args, command)
545
            log_msg("Received notify command for zone (%s, %s)." % zone_name_class)
546
            with self._lock:
547
                self._zone_refresh.zone_handle_notify(zone_name_class, master)
548
            # Send notification to zonemgr timer thread
Jerry's avatar
Jerry committed
549
            self._master_socket.send(b" ")# make self._slave_socket readble
550

Jerry's avatar
Jerry committed
551
        elif command == ZONE_XFRIN_SUCCESS_COMMAND:
552
            """ Handle xfrin success command"""
Jerry's avatar
Jerry committed
553
            zone_name_class = self._parse_cmd_params(args, command)
554
            with self._lock:
555
                self._zone_refresh.zone_refresh_success(zone_name_class)
Jerry's avatar
Jerry committed
556
            self._master_socket.send(b" ")# make self._slave_socket readble
557

Jerry's avatar
Jerry committed
558
        elif command == ZONE_XFRIN_FAILED_COMMAND:
Jerry's avatar
Jerry committed
559
            """ Handle xfrin fail command"""
Jerry's avatar
Jerry committed
560
            zone_name_class = self._parse_cmd_params(args, command)
561
            with self._lock:
562
                self._zone_refresh.zone_refresh_fail(zone_name_class)
Jerry's avatar
Jerry committed
563
            self._master_socket.send(b" ")# make self._slave_socket readble
564
565
566
567
568
569
570
571
572
573

        elif command == "shutdown":
            self.shutdown()

        else:
            answer = create_answer(1, "Unknown command:" + str(command))

        return answer

    def run(self):
Michal Vaner's avatar
Michal Vaner committed
574
        self.running = True
575
        while not self._shutdown_event.is_set():
576
            self._module_cc.check_command(False)
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603

zonemgrd = None

def signal_handler(signal, frame):
    if zonemgrd:
        zonemgrd.shutdown()
        sys.exit(0)

def set_signal_handler():
    signal.signal(signal.SIGTERM, signal_handler)
    signal.signal(signal.SIGINT, signal_handler)

def set_cmd_options(parser):
    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
            help="display more about what is going on")

if '__main__' == __name__:
    try:
        parser = OptionParser()
        set_cmd_options(parser)
        (options, args) = parser.parse_args()
        VERBOSE_MODE = options.verbose

        set_signal_handler()
        zonemgrd = Zonemgr()
        zonemgrd.run()
    except KeyboardInterrupt:
Jeremy C. Reed's avatar
Jeremy C. Reed committed
604
        sys.stderr.write("[b10-zonemgr] exit zonemgr process\n")
605
    except isc.cc.session.SessionError as e:
606
        sys.stderr.write("[b10-zonemgr] Error creating zonemgr, "
Jeremy C. Reed's avatar
Jeremy C. Reed committed
607
                           "is the command channel daemon running?\n")
608
    except isc.cc.session.SessionTimeout as e:
609
        sys.stderr.write("[b10-zonemgr] Error creating zonemgr, "
Jeremy C. Reed's avatar
Jeremy C. Reed committed
610
                           "is the configuration manager running?\n")
611
    except isc.config.ModuleCCSessionError as e:
Jeremy C. Reed's avatar
Jeremy C. Reed committed
612
        sys.stderr.write("[b10-zonemgr] exit zonemgr process: %s\n" % str(e))
613

Michal Vaner's avatar
Michal Vaner committed
614
    if zonemgrd and zonemgrd.running:
615
616
        zonemgrd.shutdown()