diff --git a/doc/guide/bind10-guide.html b/doc/guide/bind10-guide.html index 39c3f41c9c..0c956e915d 100644 --- a/doc/guide/bind10-guide.html +++ b/doc/guide/bind10-guide.html @@ -1,4 +1,4 @@ -
This is the reference guide for BIND 10 version +
This is the reference guide for BIND 10 version 20120712.
Copyright © 2010-2012 Internet Systems Consortium, Inc.
Abstract
BIND 10 is a framework that features Domain Name System (DNS) suite and Dynamic Host Configuration Protocol (DHCP) servers with development managed by Internet Systems Consortium (ISC). @@ -10,9 +10,9 @@ The most up-to-date version of this document (in PDF, HTML, and plain text formats), along with other documents for BIND 10, can be found at http://bind10.isc.org/docs. -
Table of Contents
List of Tables
Table of Contents
Table of Contents
List of Tables
Table of Contents
ISC would like to acknowledge generous support for BIND 10 development of DHCPv4 and DHCPv6 components provided - by Comcast.
Table of Contents
+ by Comcast.
Table of Contents
BIND is the popular implementation of a DNS server, developer interfaces, and DNS tools. BIND 10 is a rewrite of BIND 9 and ISC DHCP. @@ -25,7 +25,7 @@
This guide covers the experimental prototype of BIND 10 version 20120712. -
+
BIND 10 builds have been tested on (in no particular order) Debian GNU/Linux 6 and unstable, Ubuntu 9.10, NetBSD 5, Solaris 10 and 11, FreeBSD 7 and 8, CentOS Linux 5.3, @@ -174,7 +174,7 @@ documentation and code examples. -
Table of Contents
Table of Contents
Some operating systems or softare package vendors may provide ready-to-use, pre-built software packages for the BIND 10 suite. @@ -283,14 +283,14 @@ downloadable tar file or via BIND 10's Git code revision control service. (It may also be available in pre-compiled ready-to-use packages from operating system vendors.) -
+
Downloading a release tar file is the recommended method to obtain the source code.
The BIND 10 releases are available as tar file downloads from ftp://ftp.isc.org/isc/bind10/. Periodic development snapshots may also be available. -
Downloading this "bleeding edge" code is recommended only for developers or advanced users. Using development code in a production environment is not recommended. @@ -325,7 +325,7 @@ autoheader, automake, and related commands. -
BIND 10 uses the GNU Build System to discover build environment details. To generate the makefiles using the defaults, simply run: @@ -356,12 +356,12 @@
If the configure fails, it may be due to missing or old dependencies. -
After the configure step is complete, to build the executables from the C++ code and prepare the Python scripts, run:
$ make
-
To install the BIND 10 executables, support files, and documentation, run:
$ make install
@@ -432,7 +432,7 @@ started in a special way, with the value of special used for them: -
Table 3.1. Special startup components
Component | Special | Description |
---|---|---|
b10-auth | auth | Authoritative DNS server |
b10-resolver | resolver | DNS resolver |
b10-cmdctl | cmdctl | Command control (remote control interface) |
+
Table 3.1. Special startup components
Component | Special | Description |
---|---|---|
b10-auth | auth | Authoritative DNS server |
b10-resolver | resolver | DNS resolver |
b10-cmdctl | cmdctl | Command control (remote control interface) |
The kind
specifies how a failure of the
component should be handled. If it is set to
@@ -661,13 +661,13 @@
the details and relays (over a b10-msgq command
channel) the configuration on to the specified module.
-
Table of Contents
+
Table of Contents
The b10-auth is the authoritative DNS server. It supports EDNS0, DNSSEC, IPv6, and SQLite3 and in-memory zone data backends. Normally it is started by the bind10 master process. -
+
b10-auth is configured via the
b10-cfgmgr configuration manager.
The module name is “Auth”.
@@ -860,8 +860,19 @@ can use various data source backends.
> config set data_sources/classes/IN[1]/params { "example.org": "/path/to/example.org", "example.com": "/path/to/example.com" }
> config commit
- Unfortunately, due to current technical limitations, the params must - be set as one JSON blob, it can't be edited in + Initially, a map value has to be set, but this value may be an + empty map. After that, key/value pairs can be added with 'config + add' and keys can be removed with 'config remove'. The initial + value may be an empty map, but it has to be set before zones are + added or removed. + +
+>config set data_sources/classes/IN[1]/params {}
+>config add data_sources/classes/IN[1]/params another.example.org /path/to/another.example.org
+>config add data_sources/classes/IN[1]/params another.example.com /path/to/another.example.com
+>config remove data_sources/classes/IN[1]/params another.example.org
+
+ bindctl. To reload a zone, you the same command as above.
@@ -875,7 +886,7 @@ can use various data source backends. and old configuration correspond. The defaults are consistent, so unless you tweaked either the new or the old configuration, you're good. -
RFC 1035 style DNS master zone files may imported into a BIND 10 SQLite3 data source by using the b10-loadzone utility. @@ -904,7 +915,7 @@ can use various data source backends. If you reload a zone already existing in the database, all records from that prior zone disappear and a whole new set appears. -
Table of Contents
+
Table of Contents
Incoming zones are transferred using the b10-xfrin process which is started by bind10. When received, the zone is stored in the corresponding BIND 10 @@ -918,7 +929,7 @@ can use various data source backends. IXFR. Due to some implementation limitations of the current development release, however, it only tries AXFR by default, and care should be taken to enable IXFR. -
+
In practice, you need to specify a list of secondary zones to
enable incoming zone transfers for these zones (you can still
trigger a zone transfer manually, without a prior configuration
@@ -934,7 +945,7 @@ can use various data source backends.
> config commit
(We assume there has been no zone configuration before). -
As noted above, b10-xfrin uses AXFR for
zone transfers by default. To enable IXFR for zone transfers
for a particular zone, set the use_ixfr
@@ -983,13 +994,13 @@ can use various data source backends.
(i.e. no SOA record for it), b10-zonemgr
will automatically tell b10-xfrin
to transfer the zone in.
-
To manually trigger a zone transfer to retrieve a remote zone, you may use the bindctl utility. For example, at the bindctl prompt run:
> Xfrin retransfer zone_name="foo.example.org
" master=192.0.2.99
-
In the case of an incoming zone transfer, the received zone is first stored in the corresponding BIND 10 datasource. In case the secondary zone is served by an in-memory datasource @@ -1045,7 +1056,7 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)
TSIGs in the incoming messages and to sign responses.
The way to specify zone specific configuration (ACLs, etc) is likely to be changed. -
Table of Contents
+
Table of Contents
BIND 10 supports the server side of the Dynamic DNS Update (DDNS) protocol as defined in RFC 2136. This service is provided by the b10-ddns @@ -1092,7 +1103,7 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)
this feature.
-
+
First off, it must be made sure that a few components on which b10-ddns depends are configured to run, which are b10-auth @@ -1153,7 +1164,7 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)
to shutdown gracefully this parameter should also be specified.
-
By default, b10-ddns rejects any update requests from any clients by returning a REFUSED response. To allow updates to take effect, an access control rule @@ -1251,7 +1262,7 @@ DDNS/zones[0]/update_acl[1] {"action": "ACCEPT", "from": "::1", "key": "key. arbitrary clients. There have been other troubles that could have been avoided if the ACL could be checked before the prerequisite check. -
Unlike BIND 9, BIND 10 currently does not support automatic re-signing of DNSSEC-signed zone when it's updated via DDNS. It could be possible to re-sign the updated zone afterwards @@ -1293,7 +1304,7 @@ DDNS/zones[0]/update_acl[1] {"action": "ACCEPT", "from": "::1", "key": "key. IXFR. This is done automatically; it does not require specific configuration to make this possible. -
Table of Contents
The b10-resolver process is started by bind10. @@ -1327,7 +1338,7 @@ DDNS/zones[0]/update_acl[1] {"action": "ACCEPT", "from": "::1", "key": "key.
(Replace the “2
”
as needed; run “config show
- Resolver/listen_on
” if needed.)
+ Resolver/listen_on” if needed.)
By default, the b10-resolver daemon only accepts
DNS queries from the localhost (127.0.0.1 and ::1).
The Resolver/query_acl
configuration may
@@ -1360,7 +1371,7 @@ DDNS/zones[0]/update_acl[1] {"action": "ACCEPT", "from": "::1", "key": "key.
(Replace the “2
”
as needed; run “config show
Resolver/query_acl
” if needed.)
This prototype access control configuration - syntax may be changed.
To enable forwarding, the upstream address and port must be configured to forward queries to, such as: @@ -1639,7 +1650,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
} }
-
Table of Contents
Table of Contents
The logging system in BIND 10 is configured through the Logging module. All BIND 10 modules will look at the @@ -1648,7 +1659,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
-
+
Within BIND 10, a message is logged through a component called a "logger". Different parts of BIND 10 log messages @@ -1669,7 +1680,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
(what to log), and the output_options
(where to log).
-
+
Each logger in the system has a name, the name being that of the component using it to log messages. For instance, if you want to configure logging for the resolver module, @@ -1742,7 +1753,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
“Auth.cache” logger will appear in the output with a logger name of “b10-auth.cache”). -
This specifies the category of messages logged. Each message is logged with an associated severity which @@ -1758,7 +1769,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
-
Each logger can have zero or more
output_options
. These specify where log
@@ -1768,7 +1779,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
The other options for a logger are: -
When a logger's severity is set to DEBUG, this value specifies what debug messages should be printed. It ranges @@ -1777,7 +1788,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
If severity for the logger is not DEBUG, this value is ignored. -
The main settings for an output option are the
destination
and a value called
output
, the meaning of which depends on
the destination that is set.
-
+
The destination is the type of output. It can be one of: -
Depending on what is set as the output destination, this value is interpreted as follows: @@ -1832,12 +1843,12 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
The other options for output_options
are:
-
+
Flush buffers after each log message. Doing this will reduce performance but will ensure that if the program terminates abnormally, all messages up to the point of termination are output. -
Only relevant when destination is file, this is maximum file size of output files in bytes. When the maximum size is reached, the file is renamed and a new file opened. @@ -1846,11 +1857,11 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
etc.)
If this is 0, no maximum file size is used. -
Each message written by BIND 10 to the configured logging
destinations comprises a number of components that identify
the origin of the message and, if the message indicates
diff --git a/doc/guide/bind10-guide.txt b/doc/guide/bind10-guide.txt
index e53885ee97..abec8534ec 100644
--- a/doc/guide/bind10-guide.txt
+++ b/doc/guide/bind10-guide.txt
@@ -968,9 +968,18 @@ Chapter 8. Authoritative Server
> config set data_sources/classes/IN[1]/params { "example.org": "/path/to/example.org", "example.com": "/path/to/example.com" }
> config commit
- Unfortunately, due to current technical limitations, the params must be
- set as one JSON blob, it can't be edited in bindctl. To reload a zone, you
- the same command as above.
+ Initially, a map value has to be set, but this value may be an empty map.
+ After that, key/value pairs can be added with 'config add' and keys can be
+ removed with 'config remove'. The initial value may be an empty map, but
+ it has to be set before zones are added or removed.
+
+ > config set data_sources/classes/IN[1]/params {}
+ > config add data_sources/classes/IN[1]/params another.example.org /path/to/another.example.org
+ > config add data_sources/classes/IN[1]/params another.example.com /path/to/another.example.com
+ > config remove data_sources/classes/IN[1]/params another.example.org
+
+
+ bindctl. To reload a zone, you the same command as above.
Note
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 90ed1f1b5d..7952d99cc5 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -1611,8 +1611,19 @@ can use various data source backends.
>
This is the messages manual for BIND 10 version +
This is the messages manual for BIND 10 version 20120712.
Copyright © 2011-2012 Internet Systems Consortium, Inc.
Abstract
BIND 10 is a Domain Name System (DNS) suite managed by Internet Systems Consortium (ISC). It includes DNS libraries and modular components for controlling authoritative and diff --git a/src/lib/config/tests/testdata/spec40.spec b/src/lib/config/tests/testdata/spec40.spec index f778fc09a2..6fbec10f3f 100644 --- a/src/lib/config/tests/testdata/spec40.spec +++ b/src/lib/config/tests/testdata/spec40.spec @@ -6,6 +6,15 @@ "item_type": "any", "item_optional": false, "item_default": "asdf" + }, + { "item_name": "item2", + "item_type": "any", + "item_optional": true + }, + { "item_name": "item3", + "item_type": "any", + "item_optional": true, + "item_default": null } ] } diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py index 703d1968eb..a95316d6bd 100644 --- a/src/lib/python/isc/config/ccsession.py +++ b/src/lib/python/isc/config/ccsession.py @@ -144,7 +144,7 @@ class ModuleCCSession(ConfigData): module, and one to update the configuration run-time. These callbacks are called when 'check_command' is called on the ModuleCCSession""" - + def __init__(self, spec_file_name, config_handler, command_handler, cc_session=None, handle_logging_config=True, socket_file = None): @@ -178,9 +178,9 @@ class ModuleCCSession(ConfigData): """ module_spec = isc.config.module_spec_from_file(spec_file_name) ConfigData.__init__(self, module_spec) - + self._module_name = module_spec.get_module_name() - + self.set_config_handler(config_handler) self.set_command_handler(command_handler) @@ -248,7 +248,7 @@ class ModuleCCSession(ConfigData): returns nothing. It calls check_command_without_recvmsg() to parse the received message. - + If nonblock is True, it just checks if there's a command and does nothing if there isn't. If nonblock is False, it waits until it arrives. It temporarily sets timeout to infinity, @@ -265,7 +265,7 @@ class ModuleCCSession(ConfigData): """Parse the given message to see if there is a command or a configuration update. Calls the corresponding handler functions if present. Responds on the channel if the - handler returns a message.""" + handler returns a message.""" # should we default to an answer? success-by-default? unhandled error? if msg is not None and not 'result' in msg: answer = None @@ -314,7 +314,7 @@ class ModuleCCSession(ConfigData): answer = create_answer(1, str(exc)) if answer: self._session.group_reply(env, answer) - + def set_config_handler(self, config_handler): """Set the config handler for this module. The handler is a function that takes the full configuration and handles it. @@ -521,7 +521,7 @@ class UIModuleCCSession(MultiConfigData): if not cur_list: cur_list = [] - if value is None: + if value is None and "list_item_spec" in module_spec: if "item_default" in module_spec["list_item_spec"]: value = module_spec["list_item_spec"]["item_default"] @@ -572,8 +572,14 @@ class UIModuleCCSession(MultiConfigData): if module_spec is None: raise isc.cc.data.DataNotFoundError("Unknown item " + str(identifier)) + # for type any, we determine the 'type' by what value is set + # (which would be either list or dict) + cur_value, _ = self.get_value(identifier) + type_any = module_spec['item_type'] == 'any' + # the specified element must be a list or a named_set - if 'list_item_spec' in module_spec: + if 'list_item_spec' in module_spec or\ + (type_any and type(cur_value) == list): value = None # in lists, we might get the value with spaces, making it # the third argument. In that case we interpret both as @@ -583,11 +589,12 @@ class UIModuleCCSession(MultiConfigData): value_str += set_value_str value = isc.cc.data.parse_value_str(value_str) self._add_value_to_list(identifier, value, module_spec) - elif 'named_set_item_spec' in module_spec: + elif 'named_set_item_spec' in module_spec or\ + (type_any and type(cur_value) == dict): item_name = None item_value = None if value_str is not None: - item_name = isc.cc.data.parse_value_str(value_str) + item_name = value_str if set_value_str is not None: item_value = isc.cc.data.parse_value_str(set_value_str) else: @@ -643,12 +650,23 @@ class UIModuleCCSession(MultiConfigData): if value_str is not None: value = isc.cc.data.parse_value_str(value_str) - if 'list_item_spec' in module_spec: - if value is not None: + # for type any, we determine the 'type' by what value is set + # (which would be either list or dict) + cur_value, _ = self.get_value(identifier) + type_any = module_spec['item_type'] == 'any' + + # there's two forms of 'remove from list'; the remove-value-from-list + # form, and the 'remove-by-index' form. We can recognize the second + # case by value is None + if 'list_item_spec' in module_spec or\ + (type_any and type(cur_value) == list) or\ + value is None: + if not type_any and value is not None: isc.config.config_data.check_type(module_spec['list_item_spec'], value) self._remove_value_from_list(identifier, value) - elif 'named_set_item_spec' in module_spec: - self._remove_value_from_named_set(identifier, value) + elif 'named_set_item_spec' in module_spec or\ + (type_any and type(cur_value) == dict): + self._remove_value_from_named_set(identifier, value_str) else: raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list or a named_set") diff --git a/src/lib/python/isc/config/config_data.py b/src/lib/python/isc/config/config_data.py index 174e98c911..413d052b0b 100644 --- a/src/lib/python/isc/config/config_data.py +++ b/src/lib/python/isc/config/config_data.py @@ -204,6 +204,9 @@ def find_spec_part(element, identifier, strict_identifier = True): # 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) + # As soon as we find 'any', return that + if cur_el["item_type"] == "any": + return cur_el 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 + @@ -553,7 +556,6 @@ class MultiConfigData: if 'item_default' in spec: # one special case, named_set if spec['item_type'] == 'named_set': - print("is " + id_part + " in named set?") return spec['item_default'] else: return spec['item_default'] @@ -582,6 +584,14 @@ class MultiConfigData: value = self.get_default_value(identifier) if value is not None: return value, self.DEFAULT + else: + # get_default_value returns None for both + # the cases where there is no default, and where + # it is set to null, so we need to catch the latter + spec_part = self.find_spec_part(identifier) + if spec_part and 'item_default' in spec_part and\ + spec_part['item_default'] is None: + return None, self.DEFAULT return None, self.NONE def _append_value_item(self, result, spec_part, identifier, all, first = False): @@ -742,6 +752,8 @@ class MultiConfigData: # list cur_list = cur_value for list_index in list_indices: + if type(cur_list) != list: + raise isc.cc.data.DataTypeError(id + " is not a list") if list_index >= len(cur_list): raise isc.cc.data.DataNotFoundError("No item " + str(list_index) + " in " + id_part) diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py index d1060bf008..0101d50fb1 100644 --- a/src/lib/python/isc/config/tests/ccsession_test.py +++ b/src/lib/python/isc/config/tests/ccsession_test.py @@ -33,7 +33,7 @@ class TestHelperFunctions(unittest.TestCase): self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [] }) self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 'not_an_rcode' ] }) self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 1, 2 ] }) - + rcode, val = parse_answer({ 'result': [ 0 ] }) self.assertEqual(0, rcode) self.assertEqual(None, val) @@ -107,7 +107,7 @@ class TestModuleCCSession(unittest.TestCase): def spec_file(self, file): return self.data_path + os.sep + file - + def create_session(self, spec_file_name, config_handler = None, command_handler = None, cc_session = None): return ModuleCCSession(self.spec_file(spec_file_name), @@ -335,7 +335,7 @@ class TestModuleCCSession(unittest.TestCase): self.assertEqual(len(fake_session.message_queue), 1) self.assertEqual({'result': [1, 'No config_data specification']}, fake_session.get_message('Spec1', None)) - + def test_check_command3(self): fake_session = FakeModuleCCSession() mccs = self.create_session("spec2.spec", None, None, fake_session) @@ -348,7 +348,7 @@ class TestModuleCCSession(unittest.TestCase): self.assertEqual(len(fake_session.message_queue), 1) self.assertEqual({'result': [0]}, fake_session.get_message('Spec2', None)) - + def test_check_command4(self): fake_session = FakeModuleCCSession() mccs = self.create_session("spec2.spec", None, None, fake_session) @@ -361,7 +361,7 @@ class TestModuleCCSession(unittest.TestCase): self.assertEqual(len(fake_session.message_queue), 1) self.assertEqual({'result': [1, 'aaa should be an integer']}, fake_session.get_message('Spec2', None)) - + def test_check_command5(self): fake_session = FakeModuleCCSession() mccs = self.create_session("spec2.spec", None, None, fake_session) @@ -374,7 +374,7 @@ class TestModuleCCSession(unittest.TestCase): self.assertEqual(len(fake_session.message_queue), 1) self.assertEqual({'result': [1, 'aaa should be an integer']}, fake_session.get_message('Spec2', None)) - + def test_check_command6(self): fake_session = FakeModuleCCSession() mccs = self.create_session("spec2.spec", None, None, fake_session) @@ -460,7 +460,7 @@ class TestModuleCCSession(unittest.TestCase): self.assertEqual(len(fake_session.message_queue), 1) self.assertEqual({'result': [1, 'No config_data specification']}, fake_session.get_message('Spec1', None)) - + def test_check_command_without_recvmsg2(self): "copied from test_check_command3" fake_session = FakeModuleCCSession() @@ -474,7 +474,7 @@ class TestModuleCCSession(unittest.TestCase): self.assertEqual(len(fake_session.message_queue), 1) self.assertEqual({'result': [0]}, fake_session.get_message('Spec2', None)) - + def test_check_command_without_recvmsg3(self): "copied from test_check_command7" fake_session = FakeModuleCCSession() @@ -487,7 +487,7 @@ class TestModuleCCSession(unittest.TestCase): mccs.check_command_without_recvmsg(cmd, env) self.assertEqual({'result': [0]}, fake_session.get_message('Spec2', None)) - + def test_check_command_block_timeout(self): """Check it works if session has timeout and it sets it back.""" def cmd_check(mccs, session): @@ -893,22 +893,22 @@ class fakeUIConn(): def set_get_answer(self, name, answer): self.get_answers[name] = answer - + def set_post_answer(self, name, answer): self.post_answers[name] = answer - + def send_GET(self, name, arg = None): if name in self.get_answers: return self.get_answers[name] else: return {} - + def send_POST(self, name, arg = None): if name in self.post_answers: return self.post_answers[name] else: return fakeAnswer() - + class TestUIModuleCCSession(unittest.TestCase): def setUp(self): @@ -919,9 +919,9 @@ class TestUIModuleCCSession(unittest.TestCase): def spec_file(self, file): return self.data_path + os.sep + file - - def create_uccs2(self, fake_conn): - module_spec = isc.config.module_spec_from_file(self.spec_file("spec2.spec")) + + def create_uccs(self, fake_conn, specfile="spec2.spec"): + module_spec = isc.config.module_spec_from_file(self.spec_file(specfile)) fake_conn.set_get_answer('/module_spec', { module_spec.get_module_name(): module_spec.get_full_spec()}) fake_conn.set_get_answer('/config_data', { 'version': BIND10_CONFIG_DATA_VERSION }) return UIModuleCCSession(fake_conn) @@ -989,7 +989,7 @@ class TestUIModuleCCSession(unittest.TestCase): def test_add_remove_value(self): fake_conn = fakeUIConn() - uccs = self.create_uccs2(fake_conn) + uccs = self.create_uccs(fake_conn) self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, 1, "a") self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "no_such_item", "a") @@ -1020,6 +1020,88 @@ class TestUIModuleCCSession(unittest.TestCase): self.assertRaises(isc.cc.data.DataTypeError, uccs.remove_value, "Spec2/item5", None) + # Check that the difference between no default and default = null + # is recognized + def test_default_null(self): + fake_conn = fakeUIConn() + uccs = self.create_uccs(fake_conn, "spec40.spec") + (value, status) = uccs.get_value("/Spec40/item2") + self.assertIsNone(value) + self.assertEqual(uccs.NONE, status) + (value, status) = uccs.get_value("/Spec40/item3") + self.assertIsNone(value) + self.assertEqual(uccs.DEFAULT, status) + + # Test adding and removing values for type = any + def test_add_remove_value_any(self): + fake_conn = fakeUIConn() + uccs = self.create_uccs(fake_conn, "spec40.spec") + + # Test item set of basic types + items = [ 1234, "foo", True, False ] + items_as_str = [ '1234', 'foo', 'true', 'false' ] + + def test_fails(): + self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "Spec40/item1", "foo") + self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "Spec40/item1", "foo", "bar") + self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, "Spec40/item1", "foo") + self.assertRaises(isc.cc.data.DataTypeError, uccs.remove_value, "Spec40/item1[0]", None) + + # A few helper functions to perform a number of tests + # (to repeat the same test for nested data) + def check_list(identifier): + for item in items_as_str: + uccs.add_value(identifier, item) + self.assertEqual((items, 1), uccs.get_value(identifier)) + + # Removing from list should work in both ways + uccs.remove_value(identifier, "foo") + uccs.remove_value(identifier + "[1]", None) + self.assertEqual(([1234, False], 1), uccs.get_value(identifier)) + + # As should item indexing + self.assertEqual((1234, 1), uccs.get_value(identifier + "[0]")) + self.assertEqual((False, 1), uccs.get_value(identifier + "[1]")) + + def check_named_set(identifier): + for item in items_as_str: + # use string version as key as well + uccs.add_value(identifier, item, item) + + self.assertEqual((1234, 1), uccs.get_value(identifier + "/1234")) + self.assertEqual((True, 1), uccs.get_value(identifier + "/true")) + + for item in items_as_str: + # use string version as key as well + uccs.remove_value(identifier, item) + + + # should fail when set to value of primitive type + for item in items: + uccs.set_value("Spec40/item1", item) + test_fails() + + # When set to list, add and remove should work, and its elements + # should be considered of type 'any' themselves. + uccs.set_value("Spec40/item1", []) + check_list("Spec40/item1") + + # When set to dict, it should have the behaviour of a named set + uccs.set_value("Spec40/item1", {}) + check_named_set("Spec40/item1") + + # And, or course, we may need nesting. + uccs.set_value("Spec40/item1", { "foo": {}, "bar": [] }) + check_named_set("Spec40/item1/foo") + check_list("Spec40/item1/bar") + uccs.set_value("Spec40/item1", [ {}, [] ] ) + check_named_set("Spec40/item1[0]") + check_list("Spec40/item1[1]") + uccs.set_value("Spec40/item1", [[[[[[]]]]]] ) + check_list("Spec40/item1[0][0][0][0][0]") + uccs.set_value("Spec40/item1", { 'a': { 'a': { 'a': {} } } } ) + check_named_set("Spec40/item1/a/a/a") + def test_add_dup_value(self): fake_conn = fakeUIConn() uccs = self.create_uccs_listtest(fake_conn) @@ -1101,7 +1183,7 @@ class TestUIModuleCCSession(unittest.TestCase): def test_commit(self): fake_conn = fakeUIConn() - uccs = self.create_uccs2(fake_conn) + uccs = self.create_uccs(fake_conn) uccs.commit() uccs._local_changes = {'Spec2': {'item5': [ 'a' ]}} uccs.commit()