From a689aa63cab9f16cfb70a448644e89e76567ec57 Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Wed, 15 Nov 2017 19:06:40 -0800 Subject: [PATCH] PYTHON-1418 - Update initial DNS seedlist implementation for spec changes --- pymongo/uri_parser.py | 26 +++++++++++++++---- test/dns/longer-parent-in-return.json | 12 +++++++++ test/dns/no-results.json | 4 ++- test/dns/not-enough-parts.json | 7 +++++ test/dns/one-result-default-port.json | 2 +- test/dns/one-txt-record-multiple-strings.json | 16 ++++++++++++ test/dns/one-txt-record.json | 2 +- test/dns/parent-part-mismatch1.json | 7 +++++ test/dns/parent-part-mismatch2.json | 7 +++++ test/dns/parent-part-mismatch3.json | 7 +++++ test/dns/parent-part-mismatch4.json | 7 +++++ test/dns/returned-parent-too-short.json | 7 +++++ test/dns/two-results-default-port.json | 4 +-- test/dns/two-results-nonstandard-port.json | 4 +-- test/dns/two-txt-records-with-override.json | 2 +- test/dns/two-txt-records.json | 2 +- ...-record-with-listable-option-override.json | 25 ++++++++++++++++++ test/dns/txt-record-wrong-value-type.json | 7 +++++ test/dns/uri-with-port.json | 7 +++++ test/dns/uri-with-two-hosts.json | 7 +++++ test/test_dns.py | 17 +++++++++--- 21 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 test/dns/longer-parent-in-return.json create mode 100644 test/dns/not-enough-parts.json create mode 100644 test/dns/one-txt-record-multiple-strings.json create mode 100644 test/dns/parent-part-mismatch1.json create mode 100644 test/dns/parent-part-mismatch2.json create mode 100644 test/dns/parent-part-mismatch3.json create mode 100644 test/dns/parent-part-mismatch4.json create mode 100644 test/dns/returned-parent-too-short.json create mode 100644 test/dns/txt-record-with-listable-option-override.json create mode 100644 test/dns/txt-record-wrong-value-type.json create mode 100644 test/dns/uri-with-port.json create mode 100644 test/dns/uri-with-two-hosts.json diff --git a/pymongo/uri_parser.py b/pymongo/uri_parser.py index a604134da..b3f7b0ff6 100644 --- a/pymongo/uri_parser.py +++ b/pymongo/uri_parser.py @@ -295,8 +295,8 @@ def _get_dns_txt_options(hostname): return None except Exception as exc: raise ConfigurationError(str(exc)) - return '&'.join([maybe_decode(val) - for res in results for val in res.strings]) + return ( + b'&'.join([b''.join(res.strings) for res in results])).decode('utf-8') def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False): @@ -386,12 +386,28 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False): raise InvalidURI( "%s URIs must include one, " "and only one, hostname" % (SRV_SCHEME,)) - hostname, port = nodes[0] + fqdn, port = nodes[0] if port is not None: raise InvalidURI( "%s URIs must not include a port number" % (SRV_SCHEME,)) - nodes = _get_dns_srv_hosts(hostname) - dns_options = _get_dns_txt_options(hostname) + nodes = _get_dns_srv_hosts(fqdn) + + try: + plist = fqdn.split(".")[1:] + except Exception: + raise ConfigurationError("Invalid URI host") + slen = len(plist) + if slen < 2: + raise ConfigurationError("Invalid URI host") + for node in nodes: + try: + nlist = node[0].split(".")[1:][-slen:] + except Exception: + raise ConfigurationError("Invalid SRV host") + if plist != nlist: + raise ConfigurationError("Invalid SRV host") + + dns_options = _get_dns_txt_options(fqdn) if dns_options: options = split_options(dns_options, validate, warn) else: diff --git a/test/dns/longer-parent-in-return.json b/test/dns/longer-parent-in-return.json new file mode 100644 index 000000000..c0979a825 --- /dev/null +++ b/test/dns/longer-parent-in-return.json @@ -0,0 +1,12 @@ +{ + "uri": "mongodb+srv://test18.test.build.10gen.cc/?replicaSet=repl0", + "seeds": [ + "localhost.sub.test.build.10gen.cc:27017" + ], + "hosts": [ + "localhost:27017", + "localhost:27018", + "localhost:27019" + ], + "comment": "Is correct, as returned host name shared the URI root \"test.build.10gen.cc\"." +} diff --git a/test/dns/no-results.json b/test/dns/no-results.json index 16f7167ff..c1dc02d28 100644 --- a/test/dns/no-results.json +++ b/test/dns/no-results.json @@ -1,5 +1,7 @@ { "uri": "mongodb+srv://test4.test.build.10gen.cc/", "seeds": [], - "hosts": [] + "hosts": [], + "error": true, + "comment": "Should fail because no SRV records are present for this URI." } diff --git a/test/dns/not-enough-parts.json b/test/dns/not-enough-parts.json new file mode 100644 index 000000000..7cfce2ec5 --- /dev/null +++ b/test/dns/not-enough-parts.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://10gen.cc/", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because host in URI does not have {hostname}, {domainname} and {tld}." +} diff --git a/test/dns/one-result-default-port.json b/test/dns/one-result-default-port.json index 37d51168b..7946daa4b 100644 --- a/test/dns/one-result-default-port.json +++ b/test/dns/one-result-default-port.json @@ -1,7 +1,7 @@ { "uri": "mongodb+srv://test3.test.build.10gen.cc/?replicaSet=repl0", "seeds": [ - "localhost.build.10gen.cc:27017" + "localhost.test.build.10gen.cc:27017" ], "hosts": [ "localhost:27017", diff --git a/test/dns/one-txt-record-multiple-strings.json b/test/dns/one-txt-record-multiple-strings.json new file mode 100644 index 000000000..78017687e --- /dev/null +++ b/test/dns/one-txt-record-multiple-strings.json @@ -0,0 +1,16 @@ +{ + "uri": "mongodb+srv://test11.test.build.10gen.cc/?replicaSet=repl0", + "seeds": [ + "localhost.test.build.10gen.cc:27017" + ], + "hosts": [ + "localhost:27017", + "localhost:27018", + "localhost:27019" + ], + "options": { + "connectTimeoutMS": 150000, + "replicaSet": "repl0", + "socketTimeoutMS": 250000 + } +} diff --git a/test/dns/one-txt-record.json b/test/dns/one-txt-record.json index aadf73786..0833cb4ec 100644 --- a/test/dns/one-txt-record.json +++ b/test/dns/one-txt-record.json @@ -1,7 +1,7 @@ { "uri": "mongodb+srv://test5.test.build.10gen.cc/?replicaSet=repl0", "seeds": [ - "localhost.build.10gen.cc:27017" + "localhost.test.build.10gen.cc:27017" ], "hosts": [ "localhost:27017", diff --git a/test/dns/parent-part-mismatch1.json b/test/dns/parent-part-mismatch1.json new file mode 100644 index 000000000..8d0147a48 --- /dev/null +++ b/test/dns/parent-part-mismatch1.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test14.test.build.10gen.cc/", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because returned host name's part \"not-test\" mismatches URI parent part \"test\"." +} diff --git a/test/dns/parent-part-mismatch2.json b/test/dns/parent-part-mismatch2.json new file mode 100644 index 000000000..996249eb9 --- /dev/null +++ b/test/dns/parent-part-mismatch2.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test15.test.build.10gen.cc/", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because returned host name's part \"not-build\" mismatches URI parent part \"build\"." +} diff --git a/test/dns/parent-part-mismatch3.json b/test/dns/parent-part-mismatch3.json new file mode 100644 index 000000000..69e724af6 --- /dev/null +++ b/test/dns/parent-part-mismatch3.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test16.test.build.10gen.cc/", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because returned host name's part \"not-10gen\" mismatches URI parent part \"10gen\"." +} diff --git a/test/dns/parent-part-mismatch4.json b/test/dns/parent-part-mismatch4.json new file mode 100644 index 000000000..254168e34 --- /dev/null +++ b/test/dns/parent-part-mismatch4.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test17.test.build.10gen.cc/", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because returned host name's TLD \"not-cc\" mismatches URI TLD \"cc\"." +} diff --git a/test/dns/returned-parent-too-short.json b/test/dns/returned-parent-too-short.json new file mode 100644 index 000000000..676eb0c0d --- /dev/null +++ b/test/dns/returned-parent-too-short.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test13.test.build.10gen.cc/", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because returned host name's parent (build.10gen.cc) misses \"test.\"" +} diff --git a/test/dns/two-results-default-port.json b/test/dns/two-results-default-port.json index a436bdaa9..b31800f7e 100644 --- a/test/dns/two-results-default-port.json +++ b/test/dns/two-results-default-port.json @@ -1,8 +1,8 @@ { "uri": "mongodb+srv://test1.test.build.10gen.cc/?replicaSet=repl0", "seeds": [ - "localhost.build.10gen.cc:27017", - "localhost.build.10gen.cc:27018" + "localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018" ], "hosts": [ "localhost:27017", diff --git a/test/dns/two-results-nonstandard-port.json b/test/dns/two-results-nonstandard-port.json index 536db6ed3..2ccaa5b20 100644 --- a/test/dns/two-results-nonstandard-port.json +++ b/test/dns/two-results-nonstandard-port.json @@ -1,8 +1,8 @@ { "uri": "mongodb+srv://test2.test.build.10gen.cc/?replicaSet=repl0", "seeds": [ - "localhost.build.10gen.cc:27018", - "localhost.build.10gen.cc:27019" + "localhost.test.build.10gen.cc:27018", + "localhost.test.build.10gen.cc:27019" ], "hosts": [ "localhost:27017", diff --git a/test/dns/two-txt-records-with-override.json b/test/dns/two-txt-records-with-override.json index 266d46763..a02168db0 100644 --- a/test/dns/two-txt-records-with-override.json +++ b/test/dns/two-txt-records-with-override.json @@ -1,7 +1,7 @@ { "uri": "mongodb+srv://test6.test.build.10gen.cc/?replicaSet=repl0&connectTimeoutMS=250000", "seeds": [ - "localhost.build.10gen.cc:27017" + "localhost.test.build.10gen.cc:27017" ], "hosts": [ "localhost:27017", diff --git a/test/dns/two-txt-records.json b/test/dns/two-txt-records.json index 6a02fccf1..21d1b395f 100644 --- a/test/dns/two-txt-records.json +++ b/test/dns/two-txt-records.json @@ -1,7 +1,7 @@ { "uri": "mongodb+srv://test6.test.build.10gen.cc/?replicaSet=repl0", "seeds": [ - "localhost.build.10gen.cc:27017" + "localhost.test.build.10gen.cc:27017" ], "hosts": [ "localhost:27017", diff --git a/test/dns/txt-record-with-listable-option-override.json b/test/dns/txt-record-with-listable-option-override.json new file mode 100644 index 000000000..d3b106240 --- /dev/null +++ b/test/dns/txt-record-with-listable-option-override.json @@ -0,0 +1,25 @@ +{ + "uri": "mongodb+srv://test7.test.build.10gen.cc/?replicaSet=repl0&readPreferenceTags=dc:fr,item:cheese&readPreferenceTags=dc:de,item:hotdog", + "seeds": [ + "localhost.test.build.10gen.cc:27017" + ], + "hosts": [ + "localhost:27017", + "localhost:27018", + "localhost:27019" + ], + "options": { + "replicaSet": "repl0", + "readPreference": "secondaryPreferred", + "readPreferenceTags": [ + { + "dc": "fr", + "item": "cheese" + }, + { + "dc": "de", + "item": "hotdog" + } + ] + } +} diff --git a/test/dns/txt-record-wrong-value-type.json b/test/dns/txt-record-wrong-value-type.json new file mode 100644 index 000000000..897054f10 --- /dev/null +++ b/test/dns/txt-record-wrong-value-type.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test10.test.build.10gen.cc/?replicaSet=repl0", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because the value of socketTimeoutMS is not an integer." +} diff --git a/test/dns/uri-with-port.json b/test/dns/uri-with-port.json new file mode 100644 index 000000000..b981e2a1b --- /dev/null +++ b/test/dns/uri-with-port.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test5.test.build.10gen.cc:8123/?replicaSet=repl0", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because the mongodb+srv URI includes a port." +} diff --git a/test/dns/uri-with-two-hosts.json b/test/dns/uri-with-two-hosts.json new file mode 100644 index 000000000..5261a39cf --- /dev/null +++ b/test/dns/uri-with-two-hosts.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test5.test.build.10gen.cc,test6.test.build.10gen.cc/?replicaSet=repl0", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because the mongodb+srv URI includes two host names." +} diff --git a/test/test_dns.py b/test/test_dns.py index ebd4e2371..5721b6e5d 100644 --- a/test/test_dns.py +++ b/test/test_dns.py @@ -21,6 +21,7 @@ import sys sys.path[0:0] = [""] +from pymongo.common import validate_read_preference_tags from pymongo.errors import ConfigurationError from pymongo.mongo_client import MongoClient from pymongo.uri_parser import parse_uri, split_hosts, _HAVE_DNSPYTHON @@ -57,12 +58,18 @@ def create_test(test_case): if options: for key, value in options.items(): # Convert numbers to strings for comparison - options[key] = str(value) + if isinstance(value, (int, float)): + options[key] = str(value) if seeds: result = parse_uri(uri, validate=False) self.assertEqual(sorted(result['nodelist']), sorted(seeds)) if options: + opts = result['options'] + if 'readpreferencetags' in opts: + rpts = validate_read_preference_tags( + 'readPreferenceTags', opts.pop('readpreferencetags')) + opts['readPreferenceTags'] = rpts self.assertEqual(result['options'], options) hostname = next(iter(client_context.client.nodes))[0] @@ -75,8 +82,12 @@ def create_test(test_case): lambda: hosts == client.nodes, 'match test hosts to client nodes') else: - self.assertRaises( - ConfigurationError, parse_uri, uri, validate=False) + try: + parse_uri(uri) + except (ConfigurationError, ValueError): + pass + else: + self.fail("failed to raise an exception") return run_test