zonemgr.py.in 24 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!@PYTHON@

# Copyright (C) 2010  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.

18
"""
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
35
import errno
36
37
38
from isc.datasrc import sqlite3_ds
from optparse import OptionParser, OptionValueError
from isc.config.ccsession import *
39
import isc.util.process
Michal Vaner's avatar
Michal Vaner committed
40

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

# 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"

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

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

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

75
76
77
78
79
80
81
82
83
84
85
86
# 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))

87
88
89
class ZonemgrException(Exception):
    pass

90
91
class ZonemgrRefresh:
    """This class will maintain and manage zone refresh info.
92
93
94
    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
95
96
    can be stopped by calling shutdown() in another thread.

97
    """
98

99
    def __init__(self, cc, db_file, slave_socket, config_data):
100
        self._cc = cc
101
        self._check_sock = slave_socket
102
        self._db_file = db_file
103
        self.update_config_data(config_data)
104
        self._zonemgr_refresh_info = {}
105
        self._build_zonemgr_refresh_info()
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"""
Jerry's avatar
Jerry committed
151
152
153
154
155
        if zone_name_class in self._zonemgr_refresh_info.keys():
            return False
        return True

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

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

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

188
189
190
191
192
193
194
195
196
197
198
199
200
    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
201
        zone_info["last_refresh_time"] = self._get_current_time()
202
203
204
205
        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

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

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

        return False

Jerry's avatar
Jerry committed
224
225
    def _get_zone_soa_rdata(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"]
226

Jerry's avatar
Jerry committed
227
228
    def _get_zone_last_refresh_time(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"]
229

Jerry's avatar
Jerry committed
230
231
    def _set_zone_last_refresh_time(self, zone_name_class, time):
        self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"] = time
232

Jerry's avatar
Jerry committed
233
234
    def _get_zone_notifier_master(self, zone_name_class):
        if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
235
            return self._zonemgr_refresh_info[zone_name_class]["notify_master"]
236
237
238

        return None

Jerry's avatar
Jerry committed
239
240
    def _set_zone_notifier_master(self, zone_name_class, master_addr):
        self._zonemgr_refresh_info[zone_name_class]["notify_master"] = master_addr
241

Jerry's avatar
Jerry committed
242
243
244
    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"]
245

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

Jerry's avatar
Jerry committed
249
    def _set_zone_state(self, zone_name_class, zone_state):
250
        self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state
251

Jerry's avatar
Jerry committed
252
253
    def _get_zone_refresh_timeout(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"]
254

Jerry's avatar
Jerry committed
255
256
    def _set_zone_refresh_timeout(self, zone_name_class, time):
        self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"] = time
257

Jerry's avatar
Jerry committed
258
259
    def _get_zone_next_refresh_time(self, zone_name_class):
        return self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"]
260

Jerry's avatar
Jerry committed
261
262
    def _set_zone_next_refresh_time(self, zone_name_class, time):
        self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"] = time
263
264

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

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

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

294
            # Find the zone need do refresh
Jerry's avatar
Jerry committed
295
296
            if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()):
                break
297

298
299
        return zone_need_refresh

300

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

    def _zone_mgr_is_empty(self):
        """Does zone manager has no zone?"""
Jerry's avatar
Jerry committed
324
        if not len(self._zonemgr_refresh_info):
325
326
327
328
            return True

        return False

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

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

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

372
373
374
375
376
377
378
379
380
381
382
383
384
    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
385
        start_event = threading.Event()
386
387

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

        # Return the thread to anyone interested
        return self._thread

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

416
417
418
419
420
421
    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')
422

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

434
        self._lock = threading.Lock()
435
        self._shutdown_event = threading.Event()
Michal Vaner's avatar
Michal Vaner committed
436
        self.running = False
437
438

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

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

Jerry's avatar
Jerry committed
466
        self._slave_socket.close()
Jerry's avatar
Jerry committed
467
        self._master_socket.close()
468
        self._shutdown_event.set()
Michal Vaner's avatar
Michal Vaner committed
469
        self.running = False
470
471

    def config_handler(self, new_config):
472
        """ Update config data. """
473
474
475
476
477
478
        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]
479

Jerry's avatar
Jerry committed
480
        self._config_data_check(self._config_data)
481
482
483
        if (self._zone_refresh):
            self._zone_refresh.update_config_data(self._config_data)

484
485
        return answer

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

Jerry's avatar
Jerry committed
495
    def _parse_cmd_params(self, args, command):
496
497
        zone_name = args.get("zone_name")
        if not zone_name:
498
            raise ZonemgrException("zone name should be provided")
499

Jerry's avatar
Jerry committed
500
501
        zone_class = args.get("zone_class")
        if not zone_class:
502
            raise ZonemgrException("zone class should be provided")
Jerry's avatar
Jerry committed
503
504
505
506

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

507
508
509
        master_str = args.get("master")
        if not master_str:
            raise ZonemgrException("master address should be provided")
510

Jerry's avatar
Jerry committed
511
        return ((zone_name, zone_class), master_str)
512
513
514


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

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

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

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

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

Michal Vaner's avatar
Michal Vaner committed
593
    if zonemgrd and zonemgrd.running:
594
595
        zonemgrd.shutdown()