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

716

717

718

719

720

721

722

723

724

725

726

727

728

729

730

731

732

733

734

735

736

737

738

739

740

741

742

743

744

745

746

747

748

749

750

751

752

753

754

755

756

757

758

759

760

761

762

763

764

765

766

767

768

769

770

771

772

773

774

775

776

777

778

779

780

781

782

783

784

785

786

787

788

789

790

791

792

793

794

795

796

797

798

799

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

 

""" 

Classes to store configuration data and module specifications 

 

Used by the config manager, (python) modules, and UI's (those last 

two through the classes in ccsession) 

""" 

 

import isc.cc.data 

import isc.config.module_spec 

import ast 

import copy 

 

class ConfigDataError(Exception): pass 

 

BIND10_CONFIG_DATA_VERSION = 2 

 

# Helper functions 

def spec_part_is_list(spec_part): 

    """Returns True if the given spec_part is a dict that contains a 

       list specification, and False otherwise.""" 

    return (type(spec_part) == dict and 'list_item_spec' in spec_part) 

 

def spec_part_is_map(spec_part): 

    """Returns True if the given spec_part is a dict that contains a 

       map specification, and False otherwise.""" 

    return (type(spec_part) == dict and 'map_item_spec' in spec_part) 

 

def spec_part_is_named_set(spec_part): 

    """Returns True if the given spec_part is a dict that contains a 

       named_set specification, and False otherwise.""" 

    return (type(spec_part) == dict and 'named_set_item_spec' in spec_part) 

 

def check_type(spec_part, value): 

    """Does nothing if the value is of the correct type given the 

       specification part relevant for the value. Raises an 

       isc.cc.data.DataTypeError exception if not. spec_part can be 

       retrieved with find_spec_part()""" 

    if type(spec_part) == dict and 'item_type' in spec_part: 

        data_type = spec_part['item_type'] 

    else: 

        raise isc.cc.data.DataTypeError(str("Incorrect specification part for type checking")) 

 

    if data_type == "integer" and type(value) != int: 

        raise isc.cc.data.DataTypeError(str(value) + " is not an integer") 

    elif data_type == "real" and type(value) != float: 

        raise isc.cc.data.DataTypeError(str(value) + " is not a real") 

    elif data_type == "boolean" and type(value) != bool: 

        raise isc.cc.data.DataTypeError(str(value) + " is not a boolean") 

    elif data_type == "string" and type(value) != str: 

        raise isc.cc.data.DataTypeError(str(value) + " is not a string") 

    elif data_type == "list": 

        if type(value) != list: 

            raise isc.cc.data.DataTypeError(str(value) + " is not a list") 

        else: 

            for element in value: 

                check_type(spec_part['list_item_spec'], element) 

    elif data_type == "map" and type(value) != dict: 

        # todo: check types of map contents too 

        raise isc.cc.data.DataTypeError(str(value) + " is not a map") 

 

def convert_type(spec_part, value): 

    """Convert the given value(type is string) according specification  

    part relevant for the value. Raises an isc.cc.data.DataTypeError  

    exception if conversion failed. 

    """ 

    if type(spec_part) == dict and 'item_type' in spec_part: 

        data_type = spec_part['item_type'] 

    else: 

        raise isc.cc.data.DataTypeError(str("Incorrect specification part for type conversion")) 

 

    try: 

        if data_type == "integer": 

            return int(value) 

        elif data_type == "real": 

            return float(value) 

        elif data_type == "boolean": 

            return str.lower(str(value)) != 'false' 

        elif data_type == "string": 

            return str(value) 

        elif data_type == "list": 

            ret = [] 

            if type(value) == list: 

                for item in value: 

                    ret.append(convert_type(spec_part['list_item_spec'], item)) 

            elif type(value) == str: 

                value = value.split(',') 

                for item in value: 

                    sub_value = item.split() 

                    for sub_item in sub_value: 

                        ret.append(convert_type(spec_part['list_item_spec'], 

                                                sub_item)) 

 

            if ret == []: 

                raise isc.cc.data.DataTypeError(str(value) + " is not a list") 

 

            return ret 

        elif data_type == "map": 

            try: 

                map = ast.literal_eval(value) 

                if type(map) == dict: 

                    # todo: check types of map contents too 

                    return map 

                else: 

                    raise isc.cc.data.DataTypeError( 

                               "Value in convert_type not a string " 

                               "specifying a dict") 

            except SyntaxError as se: 

                raise isc.cc.data.DataTypeError("Error parsing map: " + str(se)) 

        else: 

            return value 

    except ValueError as err: 

        raise isc.cc.data.DataTypeError(str(err)) 

    except TypeError as err: 

        raise isc.cc.data.DataTypeError(str(err)) 

 

def _get_map_or_list(spec_part): 

    """Returns the list or map specification if this is a list or a 

       map specification part. If not, returns the given spec_part 

       itself""" 

    if spec_part_is_map(spec_part): 

        return spec_part["map_item_spec"] 

    elif spec_part_is_list(spec_part): 

        return spec_part["list_item_spec"] 

    else: 

        return spec_part 

 

def _find_spec_part_single(cur_spec, id_part): 

    """Find the spec part for the given (partial) name. This partial 

       name does not contain separators ('/'), and the specification 

       part should be a direct child of the given specification part. 

       id_part may contain list selectors, which will be ignored. 

       Returns the child part. 

       Raises DataNotFoundError if it was not found.""" 

    # strip list selector part 

    # don't need it for the spec part, so just drop it 

    id, list_indices = isc.cc.data.split_identifier_list_indices(id_part) 

 

    # The specification we want a sub-part for should be either a 

    # list or a map, which is internally represented by a dict with 

    # an element 'map_item_spec', a dict with an element 'list_item_spec', 

    # or a list (when it is the 'main' config_data element of a module). 

    if spec_part_is_map(cur_spec): 

        for cur_spec_item in cur_spec['map_item_spec']: 

            if cur_spec_item['item_name'] == id: 

                return cur_spec_item 

        # not found 

        raise isc.cc.data.DataNotFoundError(id + " not found") 

    elif spec_part_is_list(cur_spec): 

        if cur_spec['item_name'] == id: 

            return cur_spec['list_item_spec'] 

        # not found 

        raise isc.cc.data.DataNotFoundError(id + " not found") 

    elif type(cur_spec) == dict and 'named_set_item_spec' in cur_spec.keys(): 

        return cur_spec['named_set_item_spec'] 

    elif type(cur_spec) == list: 

        for cur_spec_item in cur_spec: 

            if cur_spec_item['item_name'] == id: 

                return cur_spec_item 

        # not found 

        raise isc.cc.data.DataNotFoundError(id + " not found") 

    else: 

        raise isc.cc.data.DataNotFoundError("Not a correct config specification") 

 

def find_spec_part(element, identifier, strict_identifier = True): 

    """find the data definition for the given identifier 

       returns either a map with 'item_name' etc, or a list of those 

       Parameters: 

       element: The specification element to start the search in 

       identifier: The element to find (relative to element above) 

       strict_identifier: If True (the default), additional checking occurs. 

                          Currently the only check is whether a list index is 

                          specified (except for the last part of the 

                          identifier) 

       Raises a DataNotFoundError if the data is not found, or if 

       strict_identifier is True and any non-final identifier parts 

       (i.e. before the last /) identify a list element and do not contain 

       an index. 

       Returns the spec element identified by the given identifier. 

    """ 

    if identifier == "": 

        return element 

    id_parts = identifier.split("/") 

    id_parts[:] = (value for value in id_parts if value != "") 

    cur_el = element 

 

    # up to the last element, if the result is a map or a list, 

    # we want its subspecification (i.e. list_item_spec or 

    # map_item_spec). For the last element in the identifier we 

    # always want the 'full' spec of the item 

    for id_part in id_parts[:-1]: 

        cur_el = _find_spec_part_single(cur_el, id_part) 

        if strict_identifier and spec_part_is_list(cur_el) and\ 

           not isc.cc.data.identifier_has_list_index(id_part): 

            raise isc.cc.data.DataNotFoundError(id_part + 

                                                " is a list and needs an index") 

        cur_el = _get_map_or_list(cur_el) 

 

    cur_el = _find_spec_part_single(cur_el, id_parts[-1]) 

    # Due to the raw datatypes we use, it is safer to return a deep copy here 

    return copy.deepcopy(cur_el) 

 

def spec_name_list(spec, prefix="", recurse=False): 

    """Returns a full list of all possible item identifiers in the 

       specification (part). Raises a ConfigDataError if spec is not 

       a correct spec (as returned by ModuleSpec.get_config_spec()""" 

    result = [] 

    if prefix != "" and not prefix.endswith("/"): 

        prefix += "/" 

    if type(spec) == dict: 

        if spec_part_is_map(spec): 

            for map_el in spec['map_item_spec']: 

                name = map_el['item_name'] 

                if map_el['item_type'] == 'map': 

                    name += "/" 

                if recurse and spec_part_is_map(map_el): 

                    result.extend(spec_name_list(map_el['map_item_spec'], prefix + map_el['item_name'], recurse)) 

                else: 

                    result.append(prefix + name) 

        elif 'named_set_item_spec' in spec: 

            # we added a '/' above, but in this one case we don't want it 

            result.append(prefix[:-1]) 

        else: 

            for name in spec: 

                result.append(prefix + name + "/") 

                if recurse: 

                    result.extend(spec_name_list(spec[name], name, recurse)) 

    elif type(spec) == list: 

        for list_el in spec: 

            if 'item_name' in list_el: 

                if list_el['item_type'] == "map" and recurse: 

                    result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse)) 

                else: 

                    name = list_el['item_name'] 

                    result.append(prefix + name) 

            else: 

                raise ConfigDataError("Bad specification") 

    else: 

        raise ConfigDataError("Bad specification") 

    return result 

 

class ConfigData: 

    """This class stores the module specs and the current non-default 

       config values. It provides functions to get the actual value or 

       the default value if no non-default value has been set""" 

 

    def __init__(self, specification): 

        """Initialize a ConfigData instance. If specification is not 

           of type ModuleSpec, a ConfigDataError is raised.""" 

        if type(specification) != isc.config.ModuleSpec: 

            raise ConfigDataError("specification is of type " + str(type(specification)) + ", not ModuleSpec") 

        self.specification = specification 

        self.data = {} 

 

    def get_value(self, identifier): 

        """Returns a tuple where the first item is the value at the 

           given identifier, and the second item is a bool which is 

           true if the value is an unset default. Raises an 

           isc.cc.data.DataNotFoundError if the identifier is bad""" 

        value = isc.cc.data.find_no_exc(self.data, identifier) 

        if value != None: 

            return value, False 

        spec = find_spec_part(self.specification.get_config_spec(), identifier) 

        if spec and 'item_default' in spec: 

            return spec['item_default'], True 

        return None, False 

 

    def get_default_value(self, identifier): 

        """Returns the default from the specification, or None if there 

           is no default""" 

        # We are searching for the default value, so we can set 

        # strict_identifier to false (in fact, we need to; we may not know 

        # some list indices, or they may not exist, we are looking for 

        # a default value for a reason here). 

        spec = find_spec_part(self.specification.get_config_spec(), 

                              identifier, False) 

        if spec and 'item_default' in spec: 

            return spec['item_default'] 

        else: 

            return None 

 

    def get_module_spec(self): 

        """Returns the ModuleSpec object associated with this ConfigData""" 

        return self.specification 

 

    def set_local_config(self, data): 

        """Set the non-default config values, as passed by cfgmgr""" 

        self.data = data 

 

    def get_local_config(self): 

        """Returns the non-default config values in a dict""" 

        return self.data 

 

    def get_item_list(self, identifier = None, recurse = False): 

        """Returns a list of strings containing the full identifiers of 

           all 'sub'options at the given identifier. If recurse is True, 

           it will also add all identifiers of all children, if any""" 

        if identifier: 

            spec = find_spec_part(self.specification.get_config_spec(), identifier) 

            return spec_name_list(spec, identifier + "/") 

        return spec_name_list(self.specification.get_config_spec(), "", recurse) 

 

    def get_full_config(self): 

        """Returns a dict containing identifier: value elements, for 

           all configuration options for this module. If there is 

           a local setting, that will be used. Otherwise the value 

           will be the default as specified by the module specification. 

           If there is no default and no local setting, the value will 

           be None""" 

        items = self.get_item_list(None, True) 

        result = {} 

        for item in items: 

            value, default = self.get_value(item) 

            result[item] = value 

        return result 

 

# should we just make a class for these? 

def _create_value_map_entry(name, type, value, status = None): 

    entry = {} 

    entry['name'] = name 

    entry['type'] = type 

    entry['value'] = value 

    entry['modified'] = False 

    entry['default'] = False 

    if status == MultiConfigData.LOCAL: 

        entry['modified'] = True 

    if status == MultiConfigData.DEFAULT: 

        entry['default'] = True 

    return entry 

 

class MultiConfigData: 

    """This class stores the module specs, current non-default 

       configuration values and 'local' (uncommitted) changes for 

       multiple modules""" 

    LOCAL   = 1 

    CURRENT = 2 

    DEFAULT = 3 

    NONE    = 4 

 

    def __init__(self): 

        self._specifications = {} 

        self._current_config = {} 

        self._local_changes = {} 

 

    def clear_specifications(self): 

        """Remove all known module specifications""" 

        self._specifications = {} 

 

    def set_specification(self, spec): 

        """Add or update a ModuleSpec. Raises a ConfigDataError is spec is not a ModuleSpec""" 

        if type(spec) != isc.config.ModuleSpec: 

            raise ConfigDataError("not a datadef: " + str(type(spec))) 

        self._specifications[spec.get_module_name()] = spec 

 

    def remove_specification(self, module_name): 

        """Removes the specification with the given module name. Does nothing if it wasn't there.""" 

        if module_name in self._specifications: 

            del self._specifications[module_name] 

 

    def have_specification(self, module_name): 

        """Returns True if we have a specification for the module with the given name. 

           Returns False if we do not.""" 

        return module_name in self._specifications 

 

    def get_module_spec(self, module): 

        """Returns the ModuleSpec for the module with the given name. 

           If there is no such module, it returns None""" 

        if module in self._specifications: 

            return self._specifications[module] 

        else: 

            return None 

 

    def find_spec_part(self, identifier): 

        """Returns the specification for the item at the given 

           identifier, or None if not found. The first part of the 

           identifier (up to the first /) is interpreted as the module 

           name. Returns None if not found, or if identifier is not a 

           string.""" 

        if type(identifier) != str or identifier == "": 

            return None 

        if identifier[0] == '/': 

            identifier = identifier[1:] 

        module, sep, id = identifier.partition("/") 

        try: 

            return find_spec_part(self._specifications[module].get_config_spec(), id) 

        except isc.cc.data.DataNotFoundError as dnfe: 

            return None 

        except KeyError as ke: 

            return None 

 

    # this function should only be called by __request_config 

    def _set_current_config(self, config): 

        """Replace the full current config values.""" 

        self._current_config = config 

 

    def get_current_config(self): 

        """Returns the current configuration as it is known by the 

           configuration manager. It is a dict where the first level is 

           the module name, and the value is the config values for 

           that module""" 

        return self._current_config 

 

    def get_local_changes(self): 

        """Returns the local config changes, i.e. those that have not 

           been committed yet and are not known by the configuration 

           manager or the modules.""" 

        return self._local_changes 

 

    def set_local_changes(self, new_local_changes): 

        """Sets the entire set of local changes, used when reverting 

           changes done automatically in case there was a problem (e.g. 

           when executing commands from a script that fails halfway 

           through). 

        """ 

        self._local_changes = new_local_changes 

 

    def clear_local_changes(self): 

        """Reverts all local changes""" 

        self._local_changes = {} 

 

    def get_local_value(self, identifier): 

        """Returns a specific local (uncommitted) configuration value, 

           as specified by the identifier. If the local changes do not 

           contain a new setting for this identifier, or if the 

           identifier cannot be found, None is returned. See 

           get_value() for a general way to find a configuration value 

           """ 

        return isc.cc.data.find_no_exc(self._local_changes, identifier) 

 

    def get_current_value(self, identifier): 

        """Returns the current non-default value as known by the 

           configuration manager, or None if it is not set. 

           See get_value() for a general way to find a configuration 

           value 

        """ 

        return isc.cc.data.find_no_exc(self._current_config, identifier) 

 

    def get_default_value(self, identifier): 

        """Returns the default value for the given identifier as 

           specified by the module specification, or None if there is 

           no default or the identifier could not be found. 

           See get_value() for a general way to find a configuration 

           value 

        """ 

        try: 

            if identifier[0] == '/': 

                identifier = identifier[1:] 

            module, sep, id = identifier.partition("/") 

            # if there is a 'higher-level' list index specified, we need 

            # to check if that list specification has a default that 

            # overrides the more specific default in the final spec item 

            # (ie. list_default = [1, 2, 3], list_item_spec=int, default=0) 

            # def default list[1] should return 2, not 0 

            id_parts = isc.cc.data.split_identifier(id) 

            id_prefix = "" 

            while len(id_parts) > 0: 

                id_part = id_parts.pop(0) 

                item_id, list_indices = isc.cc.data.split_identifier_list_indices(id_part) 

                id_list = module + "/" + id_prefix + "/" + item_id 

                id_prefix += "/" + id_part 

                part_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix) 

                if part_spec['item_type'] == 'named_set': 

                    # For named sets, the identifier is partly defined 

                    # by which values are actually present, and not 

                    # purely by the specification. 

                    # So if there is a part of the identifier left, 

                    # we need to look up the value, then see if that 

                    # contains the next part of the identifier we got 

                    if len(id_parts) == 0: 

487                        if 'item_default' in part_spec: 

                            return part_spec['item_default'] 

                        else: 

                            return None 

                    id_part = id_parts.pop(0) 

 

                    named_set_value, type = self.get_value(id_list) 

                    if id_part in named_set_value: 

                        if len(id_parts) > 0: 

                            # we are looking for the *default* value. 

                            # so if not present in here, we need to 

                            # lookup the one from the spec 

                            rest_of_id = "/".join(id_parts) 

                            result = isc.cc.data.find_no_exc(named_set_value[id_part], rest_of_id) 

502                            if result is None: 

                                spec_part = self.find_spec_part(identifier) 

501                                if 'item_default' in spec_part: 

                                    return spec_part['item_default'] 

                            return result 

                        else: 

                            return named_set_value[id_part] 

                    else: 

                        return None 

                elif list_indices is not None: 

                    # there's actually two kinds of default here for 

                    # lists; they can have a default value (like an 

                    # empty list), but their elements can  also have 

                    # default values. 

                    # So if the list item *itself* is a default, 

                    # we need to get the value out of that. If not, we 

                    # need to find the default for the specific element. 

                    list_value, type = self.get_value(id_list) 

                    list_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix) 

535                    if type == self.DEFAULT: 

470                        if 'item_default' in list_spec: 

                            list_value = list_spec['item_default'] 

                            for i in list_indices: 

                                if i < len(list_value): 

                                    list_value = list_value[i] 

                                else: 

                                    # out of range, return None 

                                    return None 

 

528                            if len(id_parts) > 0: 

                                rest_of_id = "/".join(id_parts) 

                                return isc.cc.data.find(list_value, rest_of_id) 

                            else: 

                                return list_value 

                    else: 

                        # we do have a non-default list, see if our indices 

                        # exist 

                        for i in list_indices: 

                            if i < len(list_value): 

                                list_value = list_value[i] 

                            else: 

                                # out of range, return None 

                                return None 

 

            spec = find_spec_part(self._specifications[module].get_config_spec(), id) 

            if 'item_default' in spec: 

                # one special case, named_set 

546                if spec['item_type'] == 'named_set': 

                    print("is " + id_part + " in named set?") 

                    return spec['item_default'] 

                else: 

                    return spec['item_default'] 

            else: 

                return None 

 

        except isc.cc.data.DataNotFoundError as dnfe: 

            return None 

 

    def get_value(self, identifier, default = True): 

        """Returns a tuple containing value,status. 

           The value contains the configuration value for the given 

           identifier. The status reports where this value came from; 

           it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding 

           (local change, current setting, default as specified by the 

           specification, or not found at all). Does not check and 

           set DEFAULT if the argument 'default' is False (default 

           defaults to True)""" 

        value = self.get_local_value(identifier) 

        if value != None: 

            return value, self.LOCAL 

        value = self.get_current_value(identifier) 

        if value != None: 

            return value, self.CURRENT 

        if default: 

            value = self.get_default_value(identifier) 

            if value is not None: 

                return value, self.DEFAULT 

        return None, self.NONE 

 

    def _append_value_item(self, result, spec_part, identifier, all, first = False): 

        # Look at the spec; it is a list of items, or a map containing 'item_name' etc 

        if type(spec_part) == list: 

            for spec_part_element in spec_part: 

                spec_part_element_name = spec_part_element['item_name'] 

                self._append_value_item(result, spec_part_element, identifier + "/" + spec_part_element_name, all) 

        elif type(spec_part) == dict: 

            # depending on item type, and the value of argument 'all' 

            # we need to either add an item, or recursively go on 

            # In the case of a list that is empty, we do need to show that 

            item_name = spec_part['item_name'] 

            item_type = spec_part['item_type'] 

            if item_type == "list" and (all or first): 

                spec_part_list = spec_part['list_item_spec'] 

                list_value, status = self.get_value(identifier) 

                # If not set, and no default, lists will show up as 'None', 

                # but it's better to treat it as an empty list then 

                if list_value is None: 

                    list_value = [] 

 

                if type(list_value) != list: 

                    # the identifier specified a single element 

                    self._append_value_item(result, spec_part_list, identifier, all) 

                else: 

                    list_len = len(list_value) 

                    if len(list_value) == 0 and (all or first): 

                        entry = _create_value_map_entry(identifier, 

                                                        item_type, 

                                                        [], status) 

                        result.append(entry) 

                    else: 

                        for i in range(len(list_value)): 

                            self._append_value_item(result, spec_part_list, "%s[%d]" % (identifier, i), all) 

            elif item_type == "map": 

                value, status = self.get_value(identifier) 

                # just show the specific contents of a map, we are 

                # almost never interested in just its name 

                spec_part_map = spec_part['map_item_spec'] 

                self._append_value_item(result, spec_part_map, identifier, all) 

            elif item_type == "named_set": 

                value, status = self.get_value(identifier) 

 

                # show just the one entry, when either the map is empty, 

                # or when this is element is not requested specifically 

622                if len(value.keys()) == 0: 

                    entry = _create_value_map_entry(identifier, 

                                                    item_type, 

                                                    {}, status) 

                    result.append(entry) 

627                elif not first and not all: 

                    entry = _create_value_map_entry(identifier, 

                                                    item_type, 

                                                    None, status) 

                    result.append(entry) 

                else: 

                    spec_part_named_set = spec_part['named_set_item_spec'] 

                    for entry in value: 

                        self._append_value_item(result, 

                                                spec_part_named_set, 

                                                identifier + "/" + entry, 

                                                all) 

            else: 

                value, status = self.get_value(identifier) 

641                if status == self.NONE and not spec_part['item_optional']: 

                    raise isc.cc.data.DataNotFoundError(identifier + " not found") 

 

                entry = _create_value_map_entry(identifier, 

                                                item_type, 

                                                value, status) 

                result.append(entry) 

        return 

 

 

    def get_value_maps(self, identifier = None, all = False): 

        """Returns a list of dicts, containing the following values: 

           name: name of the entry (string) 

           type: string containing the type of the value (or 'module') 

           value: value of the entry if it is a string, int, double or bool 

           modified: true if the value is a local change that has not 

                     been committed 

           default: true if the value has not been changed (i.e. the 

                    value is the default from the specification) 

           TODO: use the consts for those last ones 

           Throws DataNotFoundError if the identifier is bad 

        """ 

        result = [] 

        if not identifier or identifier == "/": 

            # No identifier, so we need the list of current modules 

            for module in self._specifications.keys(): 

667                if all: 

                    spec = self.get_module_spec(module) 

                    if spec: 

                        spec_part = spec.get_config_spec() 

                        self._append_value_item(result, spec_part, module, all, True) 

                else: 

                    entry = _create_value_map_entry(module, 'module', None) 

                    result.append(entry) 

        else: 

            # Strip off start and end slashes, if they are there 

            if len(identifier) > 0 and identifier[0] == '/': 

                identifier = identifier[1:] 

            if len(identifier) > 0 and identifier[-1] == '/': 

                identifier = identifier[:-1] 

            module, sep, id = identifier.partition('/') 

            spec = self.get_module_spec(module) 

            if spec: 

                spec_part = find_spec_part(spec.get_config_spec(), id) 

                self._append_value_item(result, spec_part, identifier, all, True) 

        return result 

 

    def unset(self, identifier): 

        """ 

        Reset the value to default. 

        """ 

        spec_part = self.find_spec_part(identifier) 

        if spec_part is not None: 

            isc.cc.data.unset(self._local_changes, identifier) 

        else: 

            raise isc.cc.data.DataNotFoundError(identifier + "not found") 

 

    def set_value(self, identifier, value): 

        """Set the local value at the given identifier to value. If 

           there is a specification for the given identifier, the type 

           is checked.""" 

        spec_part = self.find_spec_part(identifier) 

        if spec_part is not None: 

            if value is not None: 

                id, list_indices = isc.cc.data.split_identifier_list_indices(identifier) 

                if list_indices is not None \ 

                   and spec_part['item_type'] == 'list': 

                    spec_part = spec_part['list_item_spec'] 

                check_type(spec_part, value) 

        else: 

            raise isc.cc.data.DataNotFoundError(identifier + " not found") 

 

        # Since we do not support list diffs (yet?), we need to 

        # copy the currently set list of items to _local_changes 

        # if we want to modify an element in there 

        # (for any list indices specified in the full identifier) 

        id_parts = isc.cc.data.split_identifier(identifier) 

        cur_id_part = '/' 

        for id_part in id_parts: 

            id, list_indices = isc.cc.data.split_identifier_list_indices(id_part) 

            cur_value, status = self.get_value(cur_id_part + id) 

            # Check if the value was there in the first place 

            # If we are at the final element, we do not care whether we found 

            # it, since if we have reached this point and it did not exist, 

            # it was apparently an optional value without a default. 

            if status == MultiConfigData.NONE and cur_id_part != "/" and\ 

               cur_id_part + id != identifier: 

                raise isc.cc.data.DataNotFoundError(id_part + 

                                                    " not found in " + 

                                                    cur_id_part) 

            if list_indices is not None: 

                # And check if we don't set something outside of any 

                # list 

                cur_list = cur_value 

                for list_index in list_indices: 

                    if list_index >= len(cur_list): 

                        raise isc.cc.data.DataNotFoundError("No item " + 

                                  str(list_index) + " in " + id_part) 

                    else: 

                        cur_list = cur_list[list_index] 

                if status != MultiConfigData.LOCAL: 

                    isc.cc.data.set(self._local_changes, 

                                    cur_id_part + id, 

                                    cur_value) 

            cur_id_part = cur_id_part + id_part + "/" 

 

            # We also need to copy to local if we are changing a named set, 

            # so that the other items in the set do not disappear 

            if spec_part_is_named_set(self.find_spec_part(cur_id_part)): 

                ns_value, ns_status = self.get_value(cur_id_part) 

                if ns_status != MultiConfigData.LOCAL: 

                    isc.cc.data.set(self._local_changes, 

                                    cur_id_part, 

                                    ns_value) 

        isc.cc.data.set(self._local_changes, identifier, value) 

 

    def _get_list_items(self, item_name): 

        """This method is used in get_config_item_list, to add list 

           indices and named_set names to the completion list. If 

           the given item_name is for a list or named_set, it'll 

           return a list of those (appended to item_name), otherwise 

           the list will only contain the item_name itself.""" 

        spec_part = self.find_spec_part(item_name) 

        if 'item_type' in spec_part and \ 

           spec_part['item_type'] == 'named_set': 

            subslash = "" 

768   768            if spec_part['named_set_item_spec']['item_type'] == 'map' or\ 

               spec_part['named_set_item_spec']['item_type'] == 'named_set': 

                subslash = "/" 

            values, status = self.get_value(item_name) 

773            if len(values) > 0: 

                return [ item_name + "/" + v + subslash for v in values.keys() ] 

            else: 

                return [ item_name ] 

        else: 

            return [ item_name ] 

 

    def get_config_item_list(self, identifier = None, recurse = False): 

        """Returns a list of strings containing the item_names of 

           the child items at the given identifier. If no identifier is 

           specified, returns a list of module names. The first part of 

           the identifier (up to the first /) is interpreted as the 

           module name""" 

        if identifier and identifier != "/": 

            if identifier.startswith("/"): 

                identifier = identifier[1:] 

            spec = self.find_spec_part(identifier) 

            spec_list = spec_name_list(spec, identifier + "/", recurse) 

            result_list = [] 

            for spec_name in spec_list: 

                result_list.extend(self._get_list_items(spec_name)) 

            return result_list 

        else: 

            if recurse: 

                id_list = [] 

                for module in self._specifications.keys(): 

                    id_list.extend(spec_name_list(self.find_spec_part(module), module, recurse)) 

                return id_list 

            else: 

                return list(self._specifications.keys())