def data(self): # We need to set up a repository to hold the "zone-list" file. self.hg = LocalMercurialRepository() # Create a zone list for testing. self.hg.put_file("zone-list", "testdomain1.test.\ntestdomain2.test.\n") self.hg.commit("zone file for testing") # Configure Jinx API to use the repo. jinx_global_settings["DNS_REPOSITORY_URL"] = self.hg.repository_path # Now create a very simple dynamic nameserver to use for testing. self.bind_dir = bind_dir = tempfile.mkdtemp() self.tsig_key = "RFyyAJE2aLyBfVvfUZbLYw==" conf_text = """ options { directory "%s"; allow-transfer { 127.0.0.1; }; }; key localhost { algorithm "HMAC-MD5"; secret "%s"; }; zone "testdomain1.test." { type master; file "testdomain1.test."; allow-update { key localhost; }; }; zone "testdomain2.test." { type master; file "testdomain2.test."; allow-update { key localhost; }; }; """ % ( bind_dir, self.tsig_key, ) named_conf = open("%s/named.conf" % bind_dir, "w") named_conf.write(conf_text) named_conf.close() self.testdomain1_text = """ $TTL 3600 testdomain1.test. IN SOA localhost. hostmaster.testdomain1.test. 2011090103 600 600 1209600 60 IN NS ns1.testdomain1.test. ns1 IN A 127.0.0.1 baz 300 IN A 1.1.1.1 moof 320 IN A 1.1.1.1 320 IN A 1.1.1.2 320 IN A 1.1.1.3 """ zone_file = open("%s/testdomain1.test." % bind_dir, "w") zone_file.write(self.testdomain1_text) zone_file.close() self.testdomain1 = dns.zone.from_text(self.testdomain1_text, "testdomain1.test.", relativize=False) self.testdomain2_text = self.testdomain1_text.replace("testdomain1", "testdomain2") self.testdomain2 = dns.zone.from_text(self.testdomain2_text, "testdomain2.test.", relativize=False) zone_file = open("%s/testdomain2.test." % bind_dir, "w") zone_file.write(self.testdomain2_text) zone_file.close() dev_null = open("/dev/null", "w") log = open("%s/named.log" % bind_dir, "w") self.named = subprocess.Popen( ["/usr/sbin/named", "-g", "-p", "53000", "-c", "%s/named.conf" % bind_dir], stdout=dev_null, stderr=log ) # print "logging to %s/named.log" % bind_dir jinx_global_settings["DNS_NAMESERVER"] = "localhost" jinx_global_settings["DNS_NAMESERVER_PORT"] = "53000" tsig_key = open("%s/localhost.key" % bind_dir, "w") tsig_key.write(self.tsig_key + "\n") tsig_key.close() jinx_global_settings["DNS_TSIG_KEY"] = "%s/localhost.key" % bind_dir Lock(name="dns", value=1).save()
class TestUpdateDnsRecords(JinxTestCase): api_call_path = "/jinx/2.0/update_dns_records" def data(self): # We need to set up a repository to hold the "zone-list" file. self.hg = LocalMercurialRepository() # Create a zone list for testing. self.hg.put_file("zone-list", "testdomain1.test.\ntestdomain2.test.\n") self.hg.commit("zone file for testing") # Configure Jinx API to use the repo. jinx_global_settings["DNS_REPOSITORY_URL"] = self.hg.repository_path # Now create a very simple dynamic nameserver to use for testing. self.bind_dir = bind_dir = tempfile.mkdtemp() self.tsig_key = "RFyyAJE2aLyBfVvfUZbLYw==" conf_text = """ options { directory "%s"; allow-transfer { 127.0.0.1; }; }; key localhost { algorithm "HMAC-MD5"; secret "%s"; }; zone "testdomain1.test." { type master; file "testdomain1.test."; allow-update { key localhost; }; }; zone "testdomain2.test." { type master; file "testdomain2.test."; allow-update { key localhost; }; }; """ % ( bind_dir, self.tsig_key, ) named_conf = open("%s/named.conf" % bind_dir, "w") named_conf.write(conf_text) named_conf.close() self.testdomain1_text = """ $TTL 3600 testdomain1.test. IN SOA localhost. hostmaster.testdomain1.test. 2011090103 600 600 1209600 60 IN NS ns1.testdomain1.test. ns1 IN A 127.0.0.1 baz 300 IN A 1.1.1.1 moof 320 IN A 1.1.1.1 320 IN A 1.1.1.2 320 IN A 1.1.1.3 """ zone_file = open("%s/testdomain1.test." % bind_dir, "w") zone_file.write(self.testdomain1_text) zone_file.close() self.testdomain1 = dns.zone.from_text(self.testdomain1_text, "testdomain1.test.", relativize=False) self.testdomain2_text = self.testdomain1_text.replace("testdomain1", "testdomain2") self.testdomain2 = dns.zone.from_text(self.testdomain2_text, "testdomain2.test.", relativize=False) zone_file = open("%s/testdomain2.test." % bind_dir, "w") zone_file.write(self.testdomain2_text) zone_file.close() dev_null = open("/dev/null", "w") log = open("%s/named.log" % bind_dir, "w") self.named = subprocess.Popen( ["/usr/sbin/named", "-g", "-p", "53000", "-c", "%s/named.conf" % bind_dir], stdout=dev_null, stderr=log ) # print "logging to %s/named.log" % bind_dir jinx_global_settings["DNS_NAMESERVER"] = "localhost" jinx_global_settings["DNS_NAMESERVER_PORT"] = "53000" tsig_key = open("%s/localhost.key" % bind_dir, "w") tsig_key.write(self.tsig_key + "\n") tsig_key.close() jinx_global_settings["DNS_TSIG_KEY"] = "%s/localhost.key" % bind_dir Lock(name="dns", value=1).save() def tearDown(self): self.named.terminate() self.named.wait() del self.named del self.hg shutil.rmtree(self.bind_dir, ignore_errors=True) def _axfr(self, zone): axfr = dns.query.xfr("localhost", zone, relativize=False, port=53000) return dns.zone.from_xfr(axfr, relativize=False) def _check_zone_in_hg(self, zone_name, zone_data, description): """Ensure that the version of the zone in hg equals zone_data.""" # Jinx will have pushed changes into my local repo; bring them into my # working dir. self.hg.run_hg_command("update") hg_zone = dns.zone.from_text(self.hg.get_file("zones/%s" % zone_name), origin=zone_name, relativize=False) self.assertEquals( hg_zone, zone_data, "when %s, zone %s was not properly updated in hg." % (description, zone_name) ) def _increment_serial(self, zone): """Increment the serial number in the zone. Arguments: zone -- a dnspython.zone.Zone object """ soa_rd = zone.find_rdataset(zone.origin, "SOA") soa = soa_rd.items[0] soa.serial += 1 def testAddARecord(self): response = self.do_api_call([["foo.testdomain1.test.", 300, "IN", "A", ["1.2.3.4"]]], [], [], {}, "description") self.assert_response_code(response, 200, response.data) # Construct a version of the zone with the "foo" record added rd = dns.rdataset.from_text_list("IN", "A", 300, ["1.2.3.4"]) self.testdomain1.replace_rdataset("foo.testdomain1.test.", rd) self._increment_serial(self.testdomain1) # Now see if the "foo" A record and only this record was added to the zone on the nameserver. new_zone = self._axfr("testdomain1.test.") self.assertEquals(new_zone, self.testdomain1, "A record was not added to zone") self._check_zone_in_hg("testdomain1.test.", new_zone, "adding A record") def testAddCNAMERecord(self): response = self.do_api_call( [["foo.testdomain1.test.", 300, "IN", "CNAME", ["bar.testdomain1.test."]]], [], [], {}, "description" ) self.assert_response_code(response, 200, response.data) # Construct a version of the zone with the "foo" record added rd = dns.rdataset.from_text_list("IN", "CNAME", 300, ["bar.testdomain1.test."]) self.testdomain1.replace_rdataset("foo.testdomain1.test.", rd) self._increment_serial(self.testdomain1) # Now see if the "foo" A record and only this record was added to the zone on the nameserver. new_zone = self._axfr("testdomain1.test.") self.assertEquals(new_zone, self.testdomain1, "CNAME record was not added to zone") self._check_zone_in_hg("testdomain1.test.", new_zone, "adding CNAME record") def testAddRoundRobin(self): response = self.do_api_call( [["foo.testdomain1.test.", 300, "IN", "A", ["1.1.1.2", "1.1.1.3", "1.1.1.4"]]], [], [], {}, "description" ) self.assert_response_code(response, 200, response.data) # Construct a version of the zone with the "foo" rrset added rd = dns.rdataset.from_text_list("IN", "A", 300, ["1.1.1.2", "1.1.1.3", "1.1.1.4"]) self.testdomain1.replace_rdataset("foo.testdomain1.test.", rd) self._increment_serial(self.testdomain1) # Now see if the "foo" A record and only this record was added to the zone on the nameserver. new_zone = self._axfr("testdomain1.test.") self.assertEquals(new_zone, self.testdomain1, "A record round-robin was not added to zone") self._check_zone_in_hg("testdomain1.test.", new_zone, "adding A record round-robin") def testAddBadZone(self): response = self.do_api_call( [["foo.testdomain3.test.", 300, "IN", "A", ["1.1.1.2", "1.1.1.3", "1.1.1.4"]]], [], [], {}, "description" ) self.assert_response_code(response, 400) def testRelativeDomainName(self): response = self.do_api_call([["foo", 300, "IN", "A", ["1.1.1.2"]]], [], [], {}, "description") self.assert_response_code(response, 400) def testBadRdType(self): response = self.do_api_call( [["foo.testdomain1.test.", 300, "IN", "APPLE", ["1.1.1.2"]]], [], [], {}, "description" ) self.assert_response_code(response, 400) def testBadRdClass(self): response = self.do_api_call( [["foo.testdomain1.test.", 300, "INCHWORM", "A", ["1.1.1.2"]]], [], [], {}, "description" ) self.assert_response_code(response, 400) def testBadRecordData(self): response = self.do_api_call( [["foo.testdomain1.test.", 300, "IN", "A", ["bad IP address"]]], [], [], {}, "description" ) self.assert_response_code(response, 400) def testBadRecordFormat(self): response = self.do_api_call([["foo.testdomain1.test.", 300, "IN"]], [], [], {}, "description") self.assert_response_code(response, 400) def testBadRecordName(self): response = self.do_api_call( [["fo*@#@o.testdomain1.test.", 300, "IN", "A", ["1.1.1.2"]]], [], [], {}, "description" ) self.assert_response_code(response, 400) def testAddComments(self): response = self.do_api_call( [], [], [], {"foo.testdomain1.test.": "foo comment", "bar.testdomain1.test.": "bar comment"}, "description" ) self.assert_response_code(response, 200, "Couldn't add record comments: %s" % response.data) foo = DNSRecord.objects.get(name="foo.testdomain1.test.") self.assertEquals(foo.comment, "foo comment") bar = DNSRecord.objects.get(name="bar.testdomain1.test.") self.assertEquals(bar.comment, "bar comment") def testAddCommentBadZone(self): response = self.do_api_call([], [], [], {"foo.testdomain3.test.": "foo comment"}, "description") self.assert_response_code(response, 400) def testModifyComment(self): DNSRecord(name="foo.testdomain1.test.", comment="old comment").save() response = self.do_api_call([], [], [], {"foo.testdomain1.test.": "new comment"}, "description") self.assert_response_code(response, 200, "Couldn't modify record comment: %s" % response.data) foo = DNSRecord.objects.get(name="foo.testdomain1.test.") self.assertEquals(foo.comment, "new comment") def testDeleteRecordComment(self): DNSRecord(name="foo.testdomain1.test.", comment="existing comment").save() response = self.do_api_call([], [], [], {"foo.testdomain1.test.": None}, "description") self.assert_response_code(response, 200, "Couldn't delete record comment: %s" % response.data) foo = DNSRecord.objects.get(name="foo.testdomain1.test.") self.assertEquals(foo.comment, None) def testAddExistingRecord(self): response = self.do_api_call([["baz.testdomain1.test.", 300, "IN", "A", ["1.2.3.4"]]], [], [], {}, "description") self.assert_response_code(response, 409) def testDeleteRecord(self): response = self.do_api_call([], [["baz.testdomain1.test.", 300, "IN", "A", ["1.1.1.1"]]], [], {}, "description") self.assert_response_code(response, 200) # Now construct a zone with the baz record deleted and make sure it matches the nameserver's zone. self.testdomain1.delete_rdataset("baz.testdomain1.test.", "A") self._increment_serial(self.testdomain1) # Now see if the "foo" A record and only this record was added to the zone on the nameserver. new_zone = self._axfr("testdomain1.test.") self.assertEquals(new_zone, self.testdomain1, "Baz record was not deleted from zone.") self._check_zone_in_hg("testdomain1.test.", new_zone, "deleting a record") def testDeleteRecordBadData(self): response = self.do_api_call([], [["baz.testdomain1.test.", 300, "IN", "A", ["1.1.1.2"]]], [], {}, "description") self.assert_response_code(response, 409, "Should get an error when deleting a record with non-matching data") def testModifyRecord1(self): response = self.do_api_call( [], [], [["baz.testdomain1.test.", [300, "IN", "A", ["1.1.1.1"]], [300, "IN", "A", ["1.1.1.2"]]]], {}, "description", ) self.assert_response_code(response, 200, "Couldn't modify record: %s" % response.data) rd = dns.rdataset.from_text_list("IN", "A", 300, ["1.1.1.2"]) self.testdomain1.replace_rdataset("baz.testdomain1.test.", rd) self._increment_serial(self.testdomain1) new_zone = self._axfr("testdomain1.test.") self.assertEquals(new_zone, self.testdomain1, "Baz record was not modified.") self._check_zone_in_hg("testdomain1.test.", new_zone, "modifying A record value") def testModifyRecord2(self): response = self.do_api_call( [], [], [["baz.testdomain1.test.", [300, "IN", "A", ["1.1.1.1"]], [60, "IN", "A", ["1.1.1.1"]]]], {}, "description", ) self.assert_response_code(response, 200, "Couldn't modify record: %s" % response.data) rd = dns.rdataset.from_text_list("IN", "A", 60, ["1.1.1.1"]) self.testdomain1.replace_rdataset("baz.testdomain1.test.", rd) self._increment_serial(self.testdomain1) new_zone = self._axfr("testdomain1.test.") self.assertEquals(new_zone, self.testdomain1, "Baz record was not modified.") self._check_zone_in_hg("testdomain1.test.", new_zone, "modifying A record TTL") def testModifyRoundRobin(self): # Change an entry in a round-robin response = self.do_api_call( [], [], [ [ "moof.testdomain1.test.", [320, "IN", "A", ["1.1.1.1", "1.1.1.2", "1.1.1.3"]], [320, "IN", "A", ["1.1.1.1", "1.1.1.2", "1.1.1.4"]], ] ], {}, "description", ) self.assert_response_code(response, 200, "Couldn't modify round-robin: %s" % response.data) rd = dns.rdataset.from_text_list("IN", "A", 320, ["1.1.1.1", "1.1.1.2", "1.1.1.4"]) self.testdomain1.replace_rdataset("moof.testdomain1.test.", rd) self._increment_serial(self.testdomain1) new_zone = self._axfr("testdomain1.test.") self.assertEquals(new_zone, self.testdomain1, "round-robin was not modified.") self._check_zone_in_hg("testdomain1.test.", new_zone, "modifying a record in a round-robin") def testModifyRecordBadData(self): response = self.do_api_call( [], [], [["baz.testdomain1.test.", [300, "IN", "A", ["1.1.1.2"]], [300, "IN", "A", ["1.1.1.3"]]]], {}, "description", ) self.assert_response_code(response, 409) def testModifyRecordBadTTL(self): response = self.do_api_call( [], [], [["baz.testdomain1.test.", [500, "IN", "A", ["1.1.1.1"]], [300, "IN", "A", ["1.1.1.3"]]]], {}, "description", ) self.assert_response_code(response, 409) def testAddRecordsToMultipleZones(self): response = self.do_api_call( [ ["foo.testdomain1.test.", 300, "IN", "A", ["1.2.3.4"]], ["bar.testdomain2.test.", 500, "IN", "A", ["1.3.4.5"]], ], [], [], {}, "description", ) self.assert_response_code(response, 200, "Failed to add records to two zones: %s" % response.data) # Check testdomain1.test. rd = dns.rdataset.from_text_list("IN", "A", 300, ["1.2.3.4"]) self.testdomain1.replace_rdataset("foo.testdomain1.test.", rd) self._increment_serial(self.testdomain1) new_zone = self._axfr("testdomain1.test.") self.assertEquals(new_zone, self.testdomain1, "A record was not added to testdomain1") self._check_zone_in_hg("testdomain1.test.", new_zone, "adding A record") # Check testdomain2.test. rd = dns.rdataset.from_text_list("IN", "A", 500, ["1.3.4.5"]) self.testdomain2.replace_rdataset("bar.testdomain2.test.", rd) self._increment_serial(self.testdomain2) new_zone = self._axfr("testdomain2.test.") self.assertEquals(new_zone, self.testdomain2, "A record was not added to testdomain2") self._check_zone_in_hg("testdomain2.test.", new_zone, "adding A record") def testMassiveUpdate(self): # add, modify, and delete records and comments, in multiple zones, all at the same time DNSRecord(name="a.testdomain1.test.", comment="old a comment").save() DNSRecord(name="b.testdomain2.test.", comment="old b comment").save() response = self.do_api_call( [ ["foo.testdomain1.test.", 300, "IN", "A", ["1.1.1.1"]], ["bar.testdomain2.test.", 400, "IN", "A", ["1.1.1.1", "2.2.2.2", "3.3.3.3"]], ], [ ["baz.testdomain1.test.", 300, "IN", "A", ["1.1.1.1"]], ["moof.testdomain2.test.", 320, "IN", "A", ["1.1.1.1", "1.1.1.2", "1.1.1.3"]], ], [ ["baz.testdomain2.test.", [300, "IN", "A", ["1.1.1.1"]], [320, "IN", "A", ["2.2.2.2"]]], [ "moof.testdomain1.test.", [320, "IN", "A", ["1.1.1.1", "1.1.1.2", "1.1.1.3"]], [330, "IN", "A", ["9.9.9.9", "8.8.8.8", "1.1.1.1"]], ], ], { "a.testdomain1.test.": "new a comment", "b.testdomain2.test.": None, "c.testdomain1.test.": "new c comment", }, "description", ) self.assert_response_code(response, 200, "Unable to perform massive update: %s" % response.data) self.assertEquals(response.data, 2) # Check testdomain1.test. # add foo rd = dns.rdataset.from_text_list("IN", "A", 300, ["1.1.1.1"]) self.testdomain1.replace_rdataset("foo.testdomain1.test.", rd) # remove baz self.testdomain1.delete_rdataset("baz.testdomain1.test.", "A") # modify moof rd = dns.rdataset.from_text_list("IN", "A", 330, ["9.9.9.9", "8.8.8.8", "1.1.1.1"]) self.testdomain1.replace_rdataset("moof.testdomain1.test.", rd) self._increment_serial(self.testdomain1) new_zone = self._axfr("testdomain1.test.") self.assertEquals(new_zone, self.testdomain1, "Massive update did not match for testdomain1") self._check_zone_in_hg("testdomain1.test.", new_zone, "performing massive update") # Check testdomain2.test. # add bar rd = dns.rdataset.from_text_list("IN", "A", 400, ["1.1.1.1", "2.2.2.2", "3.3.3.3"]) self.testdomain2.replace_rdataset("bar.testdomain2.test.", rd) # remove moof self.testdomain2.delete_rdataset("moof.testdomain2.test.", "A") # modify baz rd = dns.rdataset.from_text_list("IN", "A", 320, ["2.2.2.2"]) self.testdomain2.replace_rdataset("baz.testdomain2.test.", rd) self._increment_serial(self.testdomain2) new_zone = self._axfr("testdomain2.test.") self.assertEquals(new_zone, self.testdomain2, "Massive update did not match for testdomain2") self._check_zone_in_hg("testdomain2.test.", new_zone, "performing massive update") # Now check comments a = DNSRecord.objects.get(name="a.testdomain1.test.") self.assertEquals(a.comment, "new a comment") b = DNSRecord.objects.get(name="b.testdomain2.test.") self.assertEquals(b.comment, None) c = DNSRecord.objects.get(name="c.testdomain1.test.") self.assertEquals(c.comment, "new c comment") def testHgComment(self): # make a change and ensure that the hg commit comment is what I pass response = self.do_api_call( [["foo.testdomain1.test.", 300, "IN", "A", ["1.2.3.4"]]], [], [], {}, "test description text" ) self.assert_response_code(response, 200, response.data) # Pull in the changes pushed by Jinx. self.hg.run_hg_command("update") history = self.hg.log() # Note: the "unittest: " part comes from the name of the user that made # the update_dns_records call. In this unit test framework, that # username is passed in do_api_call by passing # REMOTE_USER="******" to the API call. self.assertEquals(history[0]["description"], "unittest: test description text")