zonemgr.py.in 23.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.update_config_data(config_data)
105
        self._zonemgr_refresh_info = {}
106
        self._build_zonemgr_refresh_info()
107
        self._running = False
108

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

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

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

Jerry's avatar
Jerry committed
128
    def _set_zone_refresh_timer(self, zone_name_class):
129
        """Set zone next refresh time after zone refresh success.
130
           now + refresh - jitter  <= next_refresh_time <= now + refresh
131
           """
132
        zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[REFRESH_OFFSET])
133
134
        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)
135

Jerry's avatar
Jerry committed
136
    def _set_zone_retry_timer(self, zone_name_class):
137
        """Set zone next refresh time after zone refresh fail.
138
           now + retry - jitter <= next_refresh_time <= now + retry
139
           """
140
        zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[RETRY_OFFSET])
141
142
        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)
143

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

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

    def zone_refresh_success(self, zone_name_class):
155
        """Update zone info after zone refresh success"""
Jerry's avatar
Jerry committed
156
        if (self._zone_not_exist(zone_name_class)):
157
158
            raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
                                   "belong to zonemgr" % zone_name_class)
159
        self.zonemgr_reload_zone(zone_name_class)
Jerry's avatar
Jerry committed
160
161
162
        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())
163

Jerry's avatar
Jerry committed
164
    def zone_refresh_fail(self, zone_name_class):
165
        """Update zone info after zone refresh fail"""
Jerry's avatar
Jerry committed
166
        if (self._zone_not_exist(zone_name_class)):
167
168
            raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
                                   "belong to zonemgr" % zone_name_class)
169
170
171
172
173
        # 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
174
        self._set_zone_retry_timer(zone_name_class)
175

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

184
185
186
187
188
189
190
191
192
193
194
195
196
    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."""
        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

202
    def _build_zonemgr_refresh_info(self):
203
        """ Build zonemgr refresh info map."""
204
        log_msg("Start loading zone into zonemgr.")
Jerry's avatar
Jerry committed
205
        for zone_name, zone_class in sqlite3_ds.get_zones_info(self._db_file):
206
207
208
            zone_name_class = (zone_name, zone_class)
            self.zonemgr_add_zone(zone_name_class)
        log_msg("Finish loading zone into zonemgr.")
209

Jerry's avatar
Jerry committed
210
    def _zone_is_expired(self, zone_name_class):
211
212
        """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
213
214
        zone_last_refresh_time = self._get_zone_last_refresh_time(zone_name_class)
        if (ZONE_EXPIRED == self._get_zone_state(zone_name_class) or
215
216
217
218
219
            zone_last_refresh_time + zone_expired_time <= self._get_current_time()):
            return True

        return False

Jerry's avatar
Jerry committed
220
221
    def _get_zone_soa_rdata(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"]
222

Jerry's avatar
Jerry committed
223
224
    def _get_zone_last_refresh_time(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"]
225

Jerry's avatar
Jerry committed
226
227
    def _set_zone_last_refresh_time(self, zone_name_class, time):
        self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"] = time
228

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

        return None

Jerry's avatar
Jerry committed
235
236
    def _set_zone_notifier_master(self, zone_name_class, master_addr):
        self._zonemgr_refresh_info[zone_name_class]["notify_master"] = master_addr
237

Jerry's avatar
Jerry committed
238
239
240
    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"]
241

Jerry's avatar
Jerry committed
242
243
    def _get_zone_state(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["zone_state"]
244

Jerry's avatar
Jerry committed
245
    def _set_zone_state(self, zone_name_class, zone_state):
246
        self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state
247

Jerry's avatar
Jerry committed
248
249
    def _get_zone_refresh_timeout(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"]
250

Jerry's avatar
Jerry committed
251
252
    def _set_zone_refresh_timeout(self, zone_name_class, time):
        self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"] = time
253

Jerry's avatar
Jerry committed
254
255
    def _get_zone_next_refresh_time(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"]
256

Jerry's avatar
Jerry committed
257
258
    def _set_zone_next_refresh_time(self, zone_name_class, time):
        self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"] = time
259
260

    def _send_command(self, module_name, command_name, params):
261
        """Send command between modules."""
262
        msg = create_command(command_name, params)
263
        try:
264
265
266
267
268
            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
269
        except socket.error:
270
            sys.stderr.write("[b10-zonemgr] Failed to send to module %s, the session has been closed." % module_name)
271

Jerry's avatar
Jerry committed
272
273
274
275
276
277
278
    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)
279
            # If hasn't received refresh response but are within refresh timeout, skip the zone
280
            if (ZONE_REFRESHING == zone_state and
Jerry's avatar
Jerry committed
281
                (self._get_zone_refresh_timeout(zone_name_class) > self._get_current_time())):
282
                continue
283
284
285
286

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

290
            # Find the zone need do refresh
Jerry's avatar
Jerry committed
291
292
            if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()):
                break
293

294
295
        return zone_need_refresh

296

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

    def _zone_mgr_is_empty(self):
        """Does zone manager has no zone?"""
Jerry's avatar
Jerry committed
320
        if not len(self._zonemgr_refresh_info):
321
322
323
324
            return True

        return False

Michal Vaner's avatar
Michal Vaner committed
325
    def _run_timer(self, start_event):
326
        while self._running:
Michal Vaner's avatar
Michal Vaner committed
327
328
            # 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
329
            # a race condition and _running could be set to false before we
Michal Vaner's avatar
Michal Vaner committed
330
331
332
333
            # could enter it
            if start_event:
                start_event.set()
                start_event = None
Jerry's avatar
Jerry committed
334
            # If zonemgr has no zone, set timer timeout to self._lowerbound_retry.
335
            if self._zone_mgr_is_empty():
336
                timeout = self._lowerbound_retry
337
            else:
338
                zone_need_refresh = self._find_need_do_refresh_zone()
339
                # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry.
340
                if not zone_need_refresh:
341
                    timeout = self._lowerbound_retry
342
343
344
345
346
                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
347

348
            """ Wait for the socket notification for a maximum time of timeout
349
            in seconds (as float)."""
350
            try:
Jerry's avatar
Jerry committed
351
                rlist, wlist, xlist = select.select([self._check_sock, self._read_sock], [], [], timeout)
352
353
354
355
            except select.error as e:
                if e.args[0] == errno.EINTR:
                    (rlist, wlist, xlist) = ([], [], [])
                else:
Jerry's avatar
Jerry committed
356
                    sys.stderr.write("[b10-zonemgr] Error with select(); %s\n" % e)
357
358
                    break

Jerry's avatar
Jerry committed
359
            for fd in rlist:
360
                if fd == self._read_sock: # awaken by shutdown socket
361
                    # self._running will be False by now, if it is not a false
Michal Vaner's avatar
Michal Vaner committed
362
363
                    # alarm (linux kernel is said to trigger spurious wakeup
                    # on a filehandle that is not really readable).
364
                    continue
Jerry's avatar
Jerry committed
365
366
                if fd == self._check_sock: # awaken by check socket
                    self._check_sock.recv(32)
Jerry's avatar
Jerry committed
367

368
369
370
371
372
373
374
375
376
377
378
379
380
    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
381
        start_event = threading.Event()
382
383

        # Start the thread
Michal Vaner's avatar
Michal Vaner committed
384
385
        self._thread = threading.Thread(target = self._run_timer,
            args = (start_event,))
386
387
388
        if daemon:
            self._thread.setDaemon(True)
        self._thread.start()
Michal Vaner's avatar
Michal Vaner committed
389
        start_event.wait()
390
391
392
393

        # Return the thread to anyone interested
        return self._thread

Jerry's avatar
Jerry committed
394
    def shutdown(self):
395
396
397
398
399
400
401
402
403
        """
        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
404
        self._write_sock.send(b'shutdown') # make self._read_sock readble
405
406
407
408
409
410
        # 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
411

412
413
414
415
416
417
    def update_config_data(self, new_config):
        """ update ZonemgrRefresh config """
        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')
418

419
420
421
class Zonemgr:
    """Zone manager class."""
    def __init__(self):
422
        self._zone_refresh = None
423
424
        self._setup_session()
        self._db_file = self.get_db_file()
425
        # Create socket pair for communicating between main thread and zonemgr timer thread
Jerry's avatar
Jerry committed
426
        self._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
427
        self._zone_refresh = ZonemgrRefresh(self._cc, self._db_file, self._slave_socket, self._config_data)
428
        self._zone_refresh.run_timer()
429

430
        self._lock = threading.Lock()
431
        self._shutdown_event = threading.Event()
Michal Vaner's avatar
Michal Vaner committed
432
        self.running = False
433
434

    def _setup_session(self):
435
        """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving
436
437
        commands and config data sent from other modules, another one (self._cc)
        is used to send commands to proper modules."""
438
439
440
441
        self._cc = isc.cc.Session()
        self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
                                                  self.config_handler,
                                                  self.command_handler)
442
        self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION)
443
        self._config_data = self._module_cc.get_full_config()
Jerry's avatar
Jerry committed
444
        self._config_data_check(self._config_data)
445
446
447
        self._module_cc.start()

    def get_db_file(self):
Jerry's avatar
Jerry committed
448
        db_file, is_default = self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file")
449
450
451
        # 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)
452
453
454
455
456
        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):
457
458
        """Shutdown the zonemgr process. the thread which is keeping track of zone
        timers should be terminated.
459
        """
Jerry's avatar
Jerry committed
460
461
        self._zone_refresh.shutdown()

Jerry's avatar
Jerry committed
462
        self._slave_socket.close()
Jerry's avatar
Jerry committed
463
        self._master_socket.close()
464
        self._shutdown_event.set()
Michal Vaner's avatar
Michal Vaner committed
465
        self.running = False
466
467

    def config_handler(self, new_config):
468
        """ Update config data. """
469
470
471
472
473
474
        answer = create_answer(0)
        for key in new_config:
            if key not in self._config_data:
                answer = create_answer(1, "Unknown config data: " + str(key))
                continue
            self._config_data[key] = new_config[key]
475

Jerry's avatar
Jerry committed
476
        self._config_data_check(self._config_data)
477
478
479
        if (self._zone_refresh):
            self._zone_refresh.update_config_data(self._config_data)

480
481
        return answer

Jerry's avatar
Jerry committed
482
    def _config_data_check(self, config_data):
483
484
        """Check whether the new config data is valid or 
        not. """ 
Jerry's avatar
Jerry committed
485
486
487
        # 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
488
            log_msg("[b10-zonemgr] jitter_scope is too big, its value will "
Jerry's avatar
Jerry committed
489
                      "be set to 0.5") 
Jerry's avatar
Jerry committed
490

Jerry's avatar
Jerry committed
491
    def _parse_cmd_params(self, args, command):
492
493
        zone_name = args.get("zone_name")
        if not zone_name:
494
            raise ZonemgrException("zone name should be provided")
495

Jerry's avatar
Jerry committed
496
497
        zone_class = args.get("zone_class")
        if not zone_class:
498
            raise ZonemgrException("zone class should be provided")
Jerry's avatar
Jerry committed
499
500
501
502

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

503
504
505
        master_str = args.get("master")
        if not master_str:
            raise ZonemgrException("master address should be provided")
506

Jerry's avatar
Jerry committed
507
        return ((zone_name, zone_class), master_str)
508
509
510


    def command_handler(self, command, args):
511
        """Handle command receivd from command channel.
512
        ZONE_NOTIFY_COMMAND is issued by Auth process; ZONE_XFRIN_SUCCESS_COMMAND
513
514
        and ZONE_XFRIN_FAILED_COMMAND are issued by Xfrin process; shutdown is issued
        by a user or Boss process. """
515
        answer = create_answer(0)
Jerry's avatar
Jerry committed
516
        if command == ZONE_NOTIFY_COMMAND:
Jerry's avatar
Jerry committed
517
            """ Handle Auth notify command"""
518
            # master is the source sender of the notify message.
Jerry's avatar
Jerry committed
519
            zone_name_class, master = self._parse_cmd_params(args, command)
520
            log_msg("Received notify command for zone (%s, %s)." % zone_name_class)
521
            with self._lock:
522
                self._zone_refresh.zone_handle_notify(zone_name_class, master)
523
            # Send notification to zonemgr timer thread
Jerry's avatar
Jerry committed
524
            self._master_socket.send(b" ")# make self._slave_socket readble
525

Jerry's avatar
Jerry committed
526
        elif command == ZONE_XFRIN_SUCCESS_COMMAND:
527
            """ Handle xfrin success command"""
Jerry's avatar
Jerry committed
528
            zone_name_class = self._parse_cmd_params(args, command)
529
            with self._lock:
530
                self._zone_refresh.zone_refresh_success(zone_name_class)
Jerry's avatar
Jerry committed
531
            self._master_socket.send(b" ")# make self._slave_socket readble
532

Jerry's avatar
Jerry committed
533
        elif command == ZONE_XFRIN_FAILED_COMMAND:
Jerry's avatar
Jerry committed
534
            """ Handle xfrin fail command"""
Jerry's avatar
Jerry committed
535
            zone_name_class = self._parse_cmd_params(args, command)
536
            with self._lock:
537
                self._zone_refresh.zone_refresh_fail(zone_name_class)
Jerry's avatar
Jerry committed
538
            self._master_socket.send(b" ")# make self._slave_socket readble
539
540
541
542
543
544
545
546
547
548

        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
549
        self.running = True
550
        while not self._shutdown_event.is_set():
551
            self._module_cc.check_command(False)
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578

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
579
        sys.stderr.write("[b10-zonemgr] exit zonemgr process\n")
580
    except isc.cc.session.SessionError as e:
581
        sys.stderr.write("[b10-zonemgr] Error creating zonemgr, "
Jeremy C. Reed's avatar
Jeremy C. Reed committed
582
                           "is the command channel daemon running?\n")
583
    except isc.cc.session.SessionTimeout as e:
584
        sys.stderr.write("[b10-zonemgr] Error creating zonemgr, "
Jeremy C. Reed's avatar
Jeremy C. Reed committed
585
                           "is the configuration manager running?\n")
586
    except isc.config.ModuleCCSessionError as e:
Jeremy C. Reed's avatar
Jeremy C. Reed committed
587
        sys.stderr.write("[b10-zonemgr] exit zonemgr process: %s\n" % str(e))
588

Michal Vaner's avatar
Michal Vaner committed
589
    if zonemgrd and zonemgrd.running:
590
591
        zonemgrd.shutdown()