Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

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

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

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

676

677

678

679

680

681

682

683

684

685

686

687

688

689

690

691

692

693

694

695

696

697

698

699

700

701

702

703

704

705

706

707

708

709

710

711

712

713

714

715

#!/usr/bin/python3 

 

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

 

""" 

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 isc.dns 

import random 

import threading 

import select 

import socket 

import errno 

from isc.datasrc import sqlite3_ds 

from optparse import OptionParser, OptionValueError 

from isc.config.ccsession import * 

import isc.util.process 

from isc.log_messages.zonemgr_messages import * 

 

# Initialize logging for called modules. 

isc.log.init("b10-zonemgr") 

logger = isc.log.Logger("zonemgr") 

 

# Pending system-wide debug level definitions, the ones we 

# use here are hardcoded for now 

DBG_PROCESS = logger.DBGLVL_TRACE_BASIC 

DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL 

 

# Constants for debug levels. 

DBG_START_SHUT = logger.DBGLVL_START_SHUT 

DBG_ZONEMGR_COMMAND = logger.DBGLVL_COMMAND 

DBG_ZONEMGR_BASIC = logger.DBGLVL_TRACE_BASIC 

 

isc.util.process.rename() 

 

# 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 

66if "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 = "/home/jelte/opt/bind10" 

    DATAROOTDIR = "${prefix}/share" 

    SPECFILE_PATH = "${datarootdir}/bind10-devel".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" 

 

# define module name 

XFRIN_MODULE_NAME = 'Xfrin' 

AUTH_MODULE_NAME = 'Auth' 

 

# define command name 

ZONE_XFRIN_FAILED_COMMAND = 'zone_xfrin_failed' 

ZONE_XFRIN_SUCCESS_COMMAND = 'zone_new_data_ready' 

ZONE_REFRESH_COMMAND = 'refresh_from_zonemgr' 

ZONE_NOTIFY_COMMAND = 'notify' 

 

# define zone state 

ZONE_OK = 0 

ZONE_REFRESHING = 1 

ZONE_EXPIRED = 2 

 

# offsets of fields in the SOA RDATA 

REFRESH_OFFSET = 3 

RETRY_OFFSET = 4 

EXPIRED_OFFSET = 5 

 

class ZonemgrException(Exception): 

    pass 

 

class ZonemgrRefresh: 

    """This class will maintain and manage zone refresh info. 

    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 

    can be stopped by calling shutdown() in another thread. 

    """ 

 

    def __init__(self, cc, db_file, slave_socket, module_cc_session): 

        self._cc = cc 

        self._check_sock = slave_socket 

        self._db_file = db_file 

        self._zonemgr_refresh_info = {} 

        self._lowerbound_refresh = None 

        self._lowerbound_retry = None 

        self._max_transfer_timeout = None 

        self._refresh_jitter = None 

        self._reload_jitter = None 

        self.update_config_data(module_cc_session.get_full_config(), 

                                module_cc_session) 

        self._running = False 

 

    def _random_jitter(self, max, jitter): 

        """Imposes some random jitters for refresh and 

        retry timers to avoid many zones need to do refresh 

        at the same time. 

        The value should be between (max - jitter) and max. 

        """ 

        if 0 == jitter: 

            return max 

        return random.uniform(max - jitter, max) 

 

    def _get_current_time(self): 

        return time.time() 

 

    def _set_zone_timer(self, zone_name_class, max, jitter): 

        """Set zone next refresh time. 

        jitter should not be bigger than half the original value.""" 

        self._set_zone_next_refresh_time(zone_name_class, self._get_current_time() + \ 

                                            self._random_jitter(max, jitter)) 

 

    def _set_zone_refresh_timer(self, zone_name_class): 

        """Set zone next refresh time after zone refresh success. 

           now + refresh - refresh_jitter <= next_refresh_time <= now + refresh 

           """ 

        zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[REFRESH_OFFSET]) 

        zone_refresh_time = max(self._lowerbound_refresh, zone_refresh_time) 

        self._set_zone_timer(zone_name_class, zone_refresh_time, self._refresh_jitter * zone_refresh_time) 

 

    def _set_zone_retry_timer(self, zone_name_class): 

        """Set zone next refresh time after zone refresh fail. 

           now + retry - retry_jitter <= next_refresh_time <= now + retry 

           """ 

        if (self._get_zone_soa_rdata(zone_name_class) is not None): 

            zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[RETRY_OFFSET]) 

        else: 

            zone_retry_time = 0.0 

        zone_retry_time = max(self._lowerbound_retry, zone_retry_time) 

        self._set_zone_timer(zone_name_class, zone_retry_time, self._refresh_jitter * zone_retry_time) 

 

    def _set_zone_notify_timer(self, zone_name_class): 

        """Set zone next refresh time after receiving notify 

           next_refresh_time = now 

        """ 

        self._set_zone_timer(zone_name_class, 0, 0) 

 

    def _zone_not_exist(self, zone_name_class): 

        """ Zone doesn't belong to zonemgr""" 

        return not zone_name_class in self._zonemgr_refresh_info 

 

    def zone_refresh_success(self, zone_name_class): 

        """Update zone info after zone refresh success""" 

        if (self._zone_not_exist(zone_name_class)): 

            logger.error(ZONEMGR_UNKNOWN_ZONE_SUCCESS, zone_name_class[0], zone_name_class[1]) 

            raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't " 

                                   "belong to zonemgr" % zone_name_class) 

        self.zonemgr_reload_zone(zone_name_class) 

        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()) 

 

    def zone_refresh_fail(self, zone_name_class): 

        """Update zone info after zone refresh fail""" 

        if (self._zone_not_exist(zone_name_class)): 

            logger.error(ZONEMGR_UNKNOWN_ZONE_FAIL, zone_name_class[0], zone_name_class[1]) 

            raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't " 

                                   "belong to zonemgr" % zone_name_class) 

        # Is zone expired? 

        if ((self._get_zone_soa_rdata(zone_name_class) is None) or 

            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) 

        self._set_zone_retry_timer(zone_name_class) 

 

    def zone_handle_notify(self, zone_name_class, master): 

        """Handle zone notify""" 

        if (self._zone_not_exist(zone_name_class)): 

            logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0], zone_name_class[1]) 

            raise ZonemgrException("[b10-zonemgr] Notified zone (%s, %s) " 

                                   "doesn't belong to zonemgr" % zone_name_class) 

        self._set_zone_notifier_master(zone_name_class, master) 

        self._set_zone_notify_timer(zone_name_class) 

 

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

 

        logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_LOAD_ZONE, zone_name_class[0], zone_name_class[1]) 

        zone_info = {} 

        zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file) 

        if zone_soa is None: 

            logger.warn(ZONEMGR_NO_SOA, zone_name_class[0], zone_name_class[1]) 

            zone_info["zone_soa_rdata"] = None 

            zone_reload_time = 0.0 

        else: 

            zone_info["zone_soa_rdata"] = zone_soa[7] 

            zone_reload_time = float(zone_soa[7].split(" ")[RETRY_OFFSET]) 

        zone_info["zone_state"] = ZONE_OK 

        zone_info["last_refresh_time"] = self._get_current_time() 

        self._zonemgr_refresh_info[zone_name_class] = zone_info 

        # Imposes some random jitters to avoid many zones need to do refresh at the same time. 

        zone_reload_time = max(self._lowerbound_retry, zone_reload_time) 

        self._set_zone_timer(zone_name_class, zone_reload_time, self._reload_jitter * zone_reload_time) 

 

    def _zone_is_expired(self, zone_name_class): 

        """Judge whether a zone is expired or not.""" 

        zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[EXPIRED_OFFSET]) 

        zone_last_refresh_time = self._get_zone_last_refresh_time(zone_name_class) 

        if (ZONE_EXPIRED == self._get_zone_state(zone_name_class) or 

            zone_last_refresh_time + zone_expired_time <= self._get_current_time()): 

            return True 

 

        return False 

 

    def _get_zone_soa_rdata(self, zone_name_class): 

        return self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"] 

 

    def _get_zone_last_refresh_time(self, zone_name_class): 

        return self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"] 

 

    def _set_zone_last_refresh_time(self, zone_name_class, time): 

        self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"] = time 

 

    def _get_zone_notifier_master(self, zone_name_class): 

        if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()): 

            return self._zonemgr_refresh_info[zone_name_class]["notify_master"] 

 

        return None 

 

    def _set_zone_notifier_master(self, zone_name_class, master_addr): 

        self._zonemgr_refresh_info[zone_name_class]["notify_master"] = master_addr 

 

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

 

    def _get_zone_state(self, zone_name_class): 

        return self._zonemgr_refresh_info[zone_name_class]["zone_state"] 

 

    def _set_zone_state(self, zone_name_class, zone_state): 

        self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state 

 

    def _get_zone_refresh_timeout(self, zone_name_class): 

        return self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"] 

 

    def _set_zone_refresh_timeout(self, zone_name_class, time): 

        self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"] = time 

 

    def _get_zone_next_refresh_time(self, zone_name_class): 

        return self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"] 

 

    def _set_zone_next_refresh_time(self, zone_name_class, time): 

        self._zonemgr_refresh_info[zone_name_class]["next_refresh_time"] = time 

 

    def _send_command(self, module_name, command_name, params): 

        """Send command between modules.""" 

        msg = create_command(command_name, params) 

        try: 

            seq = self._cc.group_sendmsg(msg, module_name) 

            try: 

287                answer, env = self._cc.group_recvmsg(False, seq) 

            except isc.cc.session.SessionTimeout: 

                pass        # for now we just ignore the failure 

        except socket.error: 

            logger.error(ZONEMGR_SEND_FAIL, module_name) 

 

    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) 

            # If hasn't received refresh response but are within refresh 

            # timeout, skip the zone 

            if (ZONE_REFRESHING == zone_state and 

                (self._get_zone_refresh_timeout(zone_name_class) > self._get_current_time())): 

                continue 

 

            # Get the zone with minimum next_refresh_time 

310            if ((zone_need_refresh is None) or 

                (self._get_zone_next_refresh_time(zone_name_class) < 

                 self._get_zone_next_refresh_time(zone_need_refresh))): 

                zone_need_refresh = zone_name_class 

 

            # Find the zone need do refresh 

            if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()): 

                break 

 

        return zone_need_refresh 

 

 

    def _do_refresh(self, zone_name_class): 

        """Do zone refresh.""" 

        logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_REFRESH_ZONE, zone_name_class[0], zone_name_class[1]) 

        self._set_zone_state(zone_name_class, ZONE_REFRESHING) 

        self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + self._max_transfer_timeout) 

        notify_master = self._get_zone_notifier_master(zone_name_class) 

        # If the zone has notify master, send notify command to xfrin module 

        if notify_master: 

            param = {"zone_name" : zone_name_class[0], 

                     "zone_class" : zone_name_class[1], 

                     "master" : notify_master 

                     } 

            self._send_command(XFRIN_MODULE_NAME, ZONE_NOTIFY_COMMAND, param) 

            self._clear_zone_notifier_master(zone_name_class) 

        # Send refresh command to xfrin module 

        else: 

            param = {"zone_name" : zone_name_class[0], 

                     "zone_class" : zone_name_class[1] 

                    } 

            self._send_command(XFRIN_MODULE_NAME, ZONE_REFRESH_COMMAND, param) 

 

    def _zone_mgr_is_empty(self): 

        """Does zone manager has no zone?""" 

        if not len(self._zonemgr_refresh_info): 

            return True 

 

        return False 

 

    def _run_timer(self, start_event): 

        while self._running: 

            # 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 

            # a race condition and _running could be set to false before we 

            # could enter it 

            if start_event: 

                start_event.set() 

                start_event = None 

            # If zonemgr has no zone, set timer timeout to self._lowerbound_retry. 

355            if self._zone_mgr_is_empty(): 

                timeout = self._lowerbound_retry 

            else: 

                zone_need_refresh = self._find_need_do_refresh_zone() 

                # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry. 

                if not zone_need_refresh: 

                    timeout = self._lowerbound_retry 

                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 

 

            """ Wait for the socket notification for a maximum time of timeout 

            in seconds (as float).""" 

            try: 

                rlist, wlist, xlist = select.select([self._check_sock, self._read_sock], [], [], timeout) 

            except select.error as e: 

                if e.args[0] == errno.EINTR: 

                    (rlist, wlist, xlist) = ([], [], []) 

                else: 

                    logger.error(ZONEMGR_SELECT_ERROR, e); 

                    break 

 

            for fd in rlist: 

384                if fd == self._read_sock: # awaken by shutdown socket 

                    # self._running will be False by now, if it is not a false 

                    # alarm (linux kernel is said to trigger spurious wakeup 

                    # on a filehandle that is not really readable). 

                    continue 

                if fd == self._check_sock: # awaken by check socket 

                    self._check_sock.recv(32) 

 

    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 

396        if self._running: 

            logger.error(ZONEMGR_TIMER_THREAD_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() 

        start_event = threading.Event() 

 

        # Start the thread 

        self._thread = threading.Thread(target = self._run_timer, 

            args = (start_event,)) 

        if daemon: 

            self._thread.setDaemon(True) 

        self._thread.start() 

        start_event.wait() 

 

        # Return the thread to anyone interested 

        return self._thread 

 

    def shutdown(self): 

        """ 

        Stop the run_timer() thread. Block until it finished. This must be 

        called from a different thread. 

        """ 

421        if not self._running: 

            logger.error(ZONEMGR_NO_TIMER_THREAD) 

            raise RuntimeError("Trying to shutdown, but not running") 

 

        # Ask the thread to stop 

        self._running = False 

        self._write_sock.send(b'shutdown') # make self._read_sock readble 

        # 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 

 

    def update_config_data(self, new_config, module_cc_session): 

        """ update ZonemgrRefresh config """ 

        # Get a new value, but only if it is defined (commonly used below) 

        # We don't use "value or default", because if value would be 

        # 0, we would take default 

        def val_or_default(value, default): 

            if value is not None: 

                return value 

            else: 

                return default 

 

        self._lowerbound_refresh = val_or_default( 

            new_config.get('lowerbound_refresh'), self._lowerbound_refresh) 

 

        self._lowerbound_retry = val_or_default( 

            new_config.get('lowerbound_retry'), self._lowerbound_retry) 

 

        self._max_transfer_timeout = val_or_default( 

            new_config.get('max_transfer_timeout'), self._max_transfer_timeout) 

 

        self._refresh_jitter = val_or_default( 

            new_config.get('refresh_jitter'), self._refresh_jitter) 

 

        self._reload_jitter = val_or_default( 

            new_config.get('reload_jitter'), self._reload_jitter) 

 

        try: 

            required = {} 

            secondary_zones = new_config.get('secondary_zones') 

            if secondary_zones is not None: 

                # Add new zones 

                for secondary_zone in new_config.get('secondary_zones'): 

                    if 'name' not in secondary_zone: 

                        raise ZonemgrException("Secondary zone specified " 

                                               "without a name") 

                    name = secondary_zone['name'] 

 

                    # Convert to Name and back (both to check and to normalize) 

                    try: 

                        name = isc.dns.Name(name, True).to_text() 

                    # Name() can raise a number of different exceptions, just 

                    # catch 'em all. 

                    except Exception as isce: 

                        raise ZonemgrException("Bad zone name '" + name + 

                                               "': " + str(isce)) 

 

                    # Currently we use an explicit get_default_value call 

                    # in case the class hasn't been set. Alternatively, we 

                    # could use 

                    # module_cc_session.get_value('secondary_zones[INDEX]/class') 

                    # To get either the value that was set, or the default if 

                    # it wasn't set. 

                    # But the real solution would be to make new_config a type 

                    # that contains default values itself 

                    # (then this entire method can be simplified a lot, and we 

                    # wouldn't need direct access to the ccsession object) 

                    if 'class' in secondary_zone: 

                        rr_class = secondary_zone['class'] 

                    else: 

                        rr_class = module_cc_session.get_default_value( 

                                        'secondary_zones/class') 

                    # Convert rr_class to and from RRClass to check its value 

                    try: 

                        name_class = (name, isc.dns.RRClass(rr_class).to_text()) 

                    except isc.dns.InvalidRRClass: 

                        raise ZonemgrException("Bad RR class '" + 

                                               rr_class + 

                                               "' for zone " + name) 

                    required[name_class] = True 

                    # Add it only if it isn't there already 

                    if not name_class in self._zonemgr_refresh_info: 

                        # If we are not able to find it in database, log an warning 

                        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] 

        except: 

            raise 

 

class Zonemgr: 

    """Zone manager class.""" 

    def __init__(self): 

        self._zone_refresh = None 

        self._setup_session() 

        self._db_file = self.get_db_file() 

        # Create socket pair for communicating between main thread and zonemgr timer thread 

        self._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) 

        self._zone_refresh = ZonemgrRefresh(self._cc, self._db_file, self._slave_socket, self._module_cc) 

        self._zone_refresh.run_timer() 

 

        self._lock = threading.Lock() 

        self._shutdown_event = threading.Event() 

        self.running = False 

 

    def _setup_session(self): 

        """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving 

        commands and config data sent from other modules, another one (self._cc) 

        is used to send commands to proper modules.""" 

        self._cc = isc.cc.Session() 

        self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, 

                                                  self.config_handler, 

                                                  self.command_handler) 

        self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION) 

        self._config_data = self._module_cc.get_full_config() 

        self._config_data_check(self._config_data) 

        self._module_cc.start() 

 

    def get_db_file(self): 

        db_file, is_default = self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file") 

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

552        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): 

        """Shutdown the zonemgr process. The thread which is keeping track of 

           zone timers should be terminated. 

        """ 

        self._zone_refresh.shutdown() 

 

        self._slave_socket.close() 

        self._master_socket.close() 

        self._shutdown_event.set() 

        self.running = False 

 

    def config_handler(self, new_config): 

        """ Update config data. """ 

        answer = create_answer(0) 

        ok = True 

        complete = self._config_data.copy() 

        for key in new_config: 

            if key not in complete: 

                answer = create_answer(1, "Unknown config data: " + str(key)) 

                ok = False 

                continue 

            complete[key] = new_config[key] 

 

        self._config_data_check(complete) 

        if self._zone_refresh is not None: 

            try: 

                self._zone_refresh.update_config_data(complete, self._module_cc) 

            except Exception as e: 

                answer = create_answer(1, str(e)) 

                ok = False 

        if ok: 

            self._config_data = complete 

 

        return answer 

 

    def _config_data_check(self, config_data): 

        """Check whether the new config data is valid or 

        not. It contains only basic logic, not full check against 

        database.""" 

        # jitter should not be bigger than half of the original value 

        if config_data.get('refresh_jitter') > 0.5: 

            config_data['refresh_jitter'] = 0.5 

            logger.warn(ZONEMGR_JITTER_TOO_BIG) 

 

    def _parse_cmd_params(self, args, command): 

        zone_name = args.get("zone_name") 

602        if not zone_name: 

            logger.error(ZONEMGR_NO_ZONE_NAME) 

            raise ZonemgrException("zone name should be provided") 

 

        zone_class = args.get("zone_class") 

607        if not zone_class: 

            logger.error(ZONEMGR_NO_ZONE_CLASS) 

            raise ZonemgrException("zone class should be provided") 

 

        if (command != ZONE_NOTIFY_COMMAND): 

            return (zone_name, zone_class) 

 

        master_str = args.get("master") 

        if not master_str: 

            logger.error(ZONEMGR_NO_MASTER_ADDRESS) 

            raise ZonemgrException("master address should be provided") 

 

        return ((zone_name, zone_class), master_str) 

 

 

    def command_handler(self, command, args): 

        """Handle command receivd from command channel. 

        ZONE_NOTIFY_COMMAND is issued by Auth process; 

        ZONE_XFRIN_SUCCESS_COMMAND and ZONE_XFRIN_FAILED_COMMAND are issued by 

        Xfrin process; 

        shutdown is issued by a user or Boss process. """ 

        answer = create_answer(0) 

        if command == ZONE_NOTIFY_COMMAND: 

            """ Handle Auth notify command""" 

            # master is the source sender of the notify message. 

            zone_name_class, master = self._parse_cmd_params(args, command) 

            logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_NOTIFY, zone_name_class[0], zone_name_class[1]) 

            with self._lock: 

                self._zone_refresh.zone_handle_notify(zone_name_class, master) 

            # Send notification to zonemgr timer thread 

            self._master_socket.send(b" ")# make self._slave_socket readble 

 

        elif command == ZONE_XFRIN_SUCCESS_COMMAND: 

            """ Handle xfrin success command""" 

            zone_name_class = self._parse_cmd_params(args, command) 

            logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_SUCCESS, zone_name_class[0], zone_name_class[1]) 

            with self._lock: 

                self._zone_refresh.zone_refresh_success(zone_name_class) 

            self._master_socket.send(b" ")# make self._slave_socket readble 

 

        elif command == ZONE_XFRIN_FAILED_COMMAND: 

            """ Handle xfrin fail command""" 

            zone_name_class = self._parse_cmd_params(args, command) 

            logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_FAILED, zone_name_class[0], zone_name_class[1]) 

            with self._lock: 

                self._zone_refresh.zone_refresh_fail(zone_name_class) 

            self._master_socket.send(b" ")# make self._slave_socket readble 

 

        elif command == "shutdown": 

            logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_SHUTDOWN) 

            self.shutdown() 

 

        else: 

            logger.warn(ZONEMGR_RECEIVE_UNKNOWN, str(command)) 

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

 

        return answer 

 

    def run(self): 

        logger.debug(DBG_PROCESS, ZONEMGR_STARTED) 

        self.running = True 

        try: 

669            while not self._shutdown_event.is_set(): 

                self._module_cc.check_command(False) 

        finally: 

            self._module_cc.send_stopping() 

 

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

 

689if '__main__' == __name__: 

    try: 

        logger.debug(DBG_START_SHUT, ZONEMGR_STARTING) 

        parser = OptionParser() 

        set_cmd_options(parser) 

        (options, args) = parser.parse_args() 

        if options.verbose: 

            logger.set_severity("DEBUG", 99) 

 

        set_signal_handler() 

        zonemgrd = Zonemgr() 

        zonemgrd.run() 

    except KeyboardInterrupt: 

        logger.info(ZONEMGR_KEYBOARD_INTERRUPT) 

 

    except isc.cc.session.SessionError as e: 

        logger.error(ZONEMGR_SESSION_ERROR) 

 

    except isc.cc.session.SessionTimeout as e: 

        logger.error(ZONEMGR_SESSION_TIMEOUT) 

 

    except isc.config.ModuleCCSessionError as e: 

        logger.error(ZONEMGR_CCSESSION_ERROR, str(e)) 

 

    if zonemgrd and zonemgrd.running: 

        zonemgrd.shutdown() 

 

    logger.debug(DBG_START_SHUT, ZONEMGR_SHUTDOWN)