]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.api/test_Zones.py
Merge pull request #5883 from pieterlexis/issue-5853-pdnsutil-clobber-metadata
[thirdparty/pdns.git] / regression-tests.api / test_Zones.py
CommitLineData
e2dba705 1import json
d29d5db7 2import time
e2dba705 3import unittest
ccfabd0d 4from copy import deepcopy
6754ef71 5from pprint import pprint
7cbc5255 6from test_helper import ApiTestCase, unique_zone_name, is_auth, is_recursor, get_db_records, pdnsutil_rectify
6754ef71
CH
7
8
9def get_rrset(data, qname, qtype):
10 for rrset in data['rrsets']:
11 if rrset['name'] == qname and rrset['type'] == qtype:
12 return rrset
13 return None
14
15
16def get_first_rec(data, qname, qtype):
17 rrset = get_rrset(data, qname, qtype)
18 if rrset:
19 return rrset['records'][0]
20 return None
21
22
23def eq_zone_rrsets(rrsets, expected):
24 data_got = {}
25 data_expected = {}
26 for type_, expected_records in expected.iteritems():
27 type_ = str(type_)
28 data_got[type_] = set()
29 data_expected[type_] = set()
30 uses_name = any(['name' in expected_record for expected_record in expected_records])
31 # minify + convert received data
32 for rrset in [rrset for rrset in rrsets if rrset['type'] == type_]:
33 print rrset
34 for r in rrset['records']:
35 data_got[type_].add((rrset['name'] if uses_name else '@', rrset['type'], r['content']))
36 # minify expected data
37 for r in expected_records:
38 data_expected[type_].add((r['name'] if uses_name else '@', type_, r['content']))
39
40 print "eq_zone_rrsets: got:"
41 pprint(data_got)
42 print "eq_zone_rrsets: expected:"
43 pprint(data_expected)
44
45 assert data_got == data_expected, "%r != %r" % (data_got, data_expected)
1a152698
CH
46
47
02945d9a 48class Zones(ApiTestCase):
1a152698 49
c1374bdb 50 def test_list_zones(self):
46d06a12 51 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
c1374bdb 52 self.assert_success_json(r)
45de6290 53 domains = r.json()
02945d9a 54 example_com = [domain for domain in domains if domain['name'] in ('example.com', 'example.com.')]
1a152698
CH
55 self.assertEquals(len(example_com), 1)
56 example_com = example_com[0]
a21e8566 57 print(example_com)
02945d9a 58 required_fields = ['id', 'url', 'name', 'kind']
c1374bdb 59 if is_auth():
c04b5870 60 required_fields = required_fields + ['masters', 'last_check', 'notified_serial', 'serial', 'account']
a21e8566 61 self.assertNotEquals(example_com['serial'], 0)
c1374bdb 62 elif is_recursor():
02945d9a
CH
63 required_fields = required_fields + ['recursion_desired', 'servers']
64 for field in required_fields:
65 self.assertIn(field, example_com)
66
67
406497f5 68class AuthZonesHelperMixin(object):
284fdfe9 69 def create_zone(self, name=None, **kwargs):
bee2acae
CH
70 if name is None:
71 name = unique_zone_name()
e2dba705 72 payload = {
bee2acae 73 'name': name,
e2dba705 74 'kind': 'Native',
1d6b70f9 75 'nameservers': ['ns1.example.com.', 'ns2.example.com.']
e2dba705 76 }
284fdfe9 77 for k, v in kwargs.items():
4bdff352
CH
78 if v is None:
79 del payload[k]
80 else:
81 payload[k] = v
1d6b70f9 82 print "sending", payload
e2dba705 83 r = self.session.post(
46d06a12 84 self.url("/api/v1/servers/localhost/zones"),
e2dba705
CH
85 data=json.dumps(payload),
86 headers={'content-type': 'application/json'})
c1374bdb 87 self.assert_success_json(r)
64a36f0d 88 self.assertEquals(r.status_code, 201)
1d6b70f9
CH
89 reply = r.json()
90 print "reply", reply
6754ef71 91 return name, payload, reply
bee2acae 92
406497f5
CH
93
94@unittest.skipIf(not is_auth(), "Not applicable")
95class AuthZones(ApiTestCase, AuthZonesHelperMixin):
96
c1374bdb 97 def test_create_zone(self):
b0af9105 98 # soa_edit_api has a default, override with empty for this test
6754ef71 99 name, payload, data = self.create_zone(serial=22, soa_edit_api='')
8ffb7a9b 100 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'soa_edit_api', 'soa_edit', 'account'):
d29d5db7
CH
101 self.assertIn(k, data)
102 if k in payload:
103 self.assertEquals(data[k], payload[k])
f63168e6 104 # validate generated SOA
6754ef71 105 expected_soa = "a.misconfigured.powerdns.server. hostmaster." + name + " " + \
1d6b70f9 106 str(payload['serial']) + " 10800 3600 604800 3600"
f63168e6 107 self.assertEquals(
6754ef71 108 get_first_rec(data, name, 'SOA')['content'],
1d6b70f9 109 expected_soa
f63168e6 110 )
1d6b70f9 111 # Because we had confusion about dots, check that the DB is without dots.
6754ef71 112 dbrecs = get_db_records(name, 'SOA')
1d6b70f9 113 self.assertEqual(dbrecs[0]['content'], expected_soa.replace('. ', ' '))
d29d5db7 114
c1374bdb 115 def test_create_zone_with_soa_edit_api(self):
f63168e6 116 # soa_edit_api wins over serial
6754ef71 117 name, payload, data = self.create_zone(soa_edit_api='EPOCH', serial=10)
f63168e6 118 for k in ('soa_edit_api', ):
e2dba705
CH
119 self.assertIn(k, data)
120 if k in payload:
121 self.assertEquals(data[k], payload[k])
f63168e6
CH
122 # generated EPOCH serial surely is > fixed serial we passed in
123 print data
124 self.assertGreater(data['serial'], payload['serial'])
6754ef71 125 soa_serial = int(get_first_rec(data, name, 'SOA')['content'].split(' ')[2])
f63168e6
CH
126 self.assertGreater(soa_serial, payload['serial'])
127 self.assertEquals(soa_serial, data['serial'])
6bb25159 128
79532aa7
CH
129 def test_create_zone_with_account(self):
130 # soa_edit_api wins over serial
6754ef71 131 name, payload, data = self.create_zone(account='anaccount', serial=10)
79532aa7
CH
132 print data
133 for k in ('account', ):
134 self.assertIn(k, data)
135 if k in payload:
136 self.assertEquals(data[k], payload[k])
137
9440a9f0
CH
138 def test_create_zone_default_soa_edit_api(self):
139 name, payload, data = self.create_zone()
140 print data
141 self.assertEquals(data['soa_edit_api'], 'DEFAULT')
142
c1374bdb 143 def test_create_zone_with_records(self):
f63168e6 144 name = unique_zone_name()
6754ef71
CH
145 rrset = {
146 "name": name,
147 "type": "A",
148 "ttl": 3600,
149 "records": [{
f63168e6 150 "content": "4.3.2.1",
6754ef71
CH
151 "disabled": False,
152 }],
153 }
154 name, payload, data = self.create_zone(name=name, rrsets=[rrset])
f63168e6 155 # check our record has appeared
6754ef71 156 self.assertEquals(get_rrset(data, name, 'A')['records'], rrset['records'])
f63168e6 157
d0953126
AT
158 def test_create_zone_with_wildcard_records(self):
159 name = unique_zone_name()
6754ef71
CH
160 rrset = {
161 "name": "*."+name,
162 "type": "A",
163 "ttl": 3600,
164 "records": [{
d0953126 165 "content": "4.3.2.1",
6754ef71
CH
166 "disabled": False,
167 }],
168 }
169 name, payload, data = self.create_zone(name=name, rrsets=[rrset])
d0953126 170 # check our record has appeared
6754ef71 171 self.assertEquals(get_rrset(data, rrset['name'], 'A')['records'], rrset['records'])
d0953126 172
c1374bdb 173 def test_create_zone_with_comments(self):
f63168e6 174 name = unique_zone_name()
6754ef71
CH
175 rrset = {
176 "name": name,
177 "type": "soa", # test uppercasing of type, too.
178 "comments": [{
179 "account": "test1",
180 "content": "blah blah",
181 "modified_at": 11112,
182 }],
183 }
184 name, payload, data = self.create_zone(name=name, rrsets=[rrset])
f63168e6 185 # check our comment has appeared
6754ef71 186 self.assertEquals(get_rrset(data, name, 'SOA')['comments'], rrset['comments'])
f63168e6 187
1d6b70f9
CH
188 def test_create_zone_uncanonical_nameservers(self):
189 name = unique_zone_name()
190 payload = {
191 'name': name,
192 'kind': 'Native',
193 'nameservers': ['uncanon.example.com']
194 }
195 print payload
196 r = self.session.post(
197 self.url("/api/v1/servers/localhost/zones"),
198 data=json.dumps(payload),
199 headers={'content-type': 'application/json'})
200 self.assertEquals(r.status_code, 422)
201 self.assertIn('Nameserver is not canonical', r.json()['error'])
202
203 def test_create_auth_zone_no_name(self):
204 name = unique_zone_name()
205 payload = {
206 'name': '',
207 'kind': 'Native',
208 }
209 print payload
210 r = self.session.post(
211 self.url("/api/v1/servers/localhost/zones"),
212 data=json.dumps(payload),
213 headers={'content-type': 'application/json'})
214 self.assertEquals(r.status_code, 422)
215 self.assertIn('is not canonical', r.json()['error'])
216
c1374bdb 217 def test_create_zone_with_custom_soa(self):
f63168e6 218 name = unique_zone_name()
6754ef71
CH
219 content = u"ns1.example.net. testmaster@example.net. 10 10800 3600 604800 3600"
220 rrset = {
221 "name": name,
222 "type": "soa", # test uppercasing of type, too.
223 "ttl": 3600,
224 "records": [{
225 "content": content,
226 "disabled": False,
227 }],
228 }
229 name, payload, data = self.create_zone(name=name, rrsets=[rrset], soa_edit_api='')
230 self.assertEquals(get_rrset(data, name, 'SOA')['records'], rrset['records'])
231 dbrecs = get_db_records(name, 'SOA')
232 self.assertEqual(dbrecs[0]['content'], content.replace('. ', ' '))
1d6b70f9
CH
233
234 def test_create_zone_double_dot(self):
235 name = 'test..' + unique_zone_name()
236 payload = {
237 'name': name,
238 'kind': 'Native',
239 'nameservers': ['ns1.example.com.']
240 }
241 print payload
242 r = self.session.post(
243 self.url("/api/v1/servers/localhost/zones"),
244 data=json.dumps(payload),
245 headers={'content-type': 'application/json'})
246 self.assertEquals(r.status_code, 422)
247 self.assertIn('Unable to parse DNS Name', r.json()['error'])
05776d2f 248
1d6b70f9
CH
249 def test_create_zone_restricted_chars(self):
250 name = 'test:' + unique_zone_name() # : isn't good as a name.
251 payload = {
252 'name': name,
253 'kind': 'Native',
254 'nameservers': ['ns1.example.com']
255 }
256 print payload
257 r = self.session.post(
258 self.url("/api/v1/servers/localhost/zones"),
259 data=json.dumps(payload),
260 headers={'content-type': 'application/json'})
261 self.assertEquals(r.status_code, 422)
262 self.assertIn('contains unsupported characters', r.json()['error'])
4ebf78b1 263
33e6c3e9
CH
264 def test_create_zone_mixed_nameservers_ns_rrset_zonelevel(self):
265 name = unique_zone_name()
266 rrset = {
267 "name": name,
268 "type": "NS",
269 "ttl": 3600,
270 "records": [{
271 "content": "ns2.example.com.",
272 "disabled": False,
273 }],
274 }
275 payload = {
276 'name': name,
277 'kind': 'Native',
278 'nameservers': ['ns1.example.com.'],
279 'rrsets': [rrset],
280 }
281 print payload
282 r = self.session.post(
283 self.url("/api/v1/servers/localhost/zones"),
284 data=json.dumps(payload),
285 headers={'content-type': 'application/json'})
286 self.assertEquals(r.status_code, 422)
287 self.assertIn('Nameservers list MUST NOT be mixed with zone-level NS in rrsets', r.json()['error'])
288
289 def test_create_zone_mixed_nameservers_ns_rrset_below_zonelevel(self):
290 name = unique_zone_name()
291 rrset = {
292 "name": 'subzone.'+name,
293 "type": "NS",
294 "ttl": 3600,
295 "records": [{
296 "content": "ns2.example.com.",
297 "disabled": False,
298 }],
299 }
300 payload = {
301 'name': name,
302 'kind': 'Native',
303 'nameservers': ['ns1.example.com.'],
304 'rrsets': [rrset],
305 }
306 print payload
307 r = self.session.post(
308 self.url("/api/v1/servers/localhost/zones"),
309 data=json.dumps(payload),
310 headers={'content-type': 'application/json'})
311 self.assert_success_json(r)
312
c1374bdb 313 def test_create_zone_with_symbols(self):
6754ef71 314 name, payload, data = self.create_zone(name='foo/bar.'+unique_zone_name())
bee2acae 315 name = payload['name']
1d6b70f9 316 expected_id = name.replace('/', '=2F')
00a9b229
CH
317 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'):
318 self.assertIn(k, data)
319 if k in payload:
320 self.assertEquals(data[k], payload[k])
bee2acae 321 self.assertEquals(data['id'], expected_id)
1d6b70f9
CH
322 dbrecs = get_db_records(name, 'SOA')
323 self.assertEqual(dbrecs[0]['name'], name.rstrip('.'))
00a9b229 324
c1374bdb 325 def test_create_zone_with_nameservers_non_string(self):
e90b4e38
CH
326 # ensure we don't crash
327 name = unique_zone_name()
328 payload = {
329 'name': name,
330 'kind': 'Native',
331 'nameservers': [{'a': 'ns1.example.com'}] # invalid
332 }
333 print payload
334 r = self.session.post(
46d06a12 335 self.url("/api/v1/servers/localhost/zones"),
e90b4e38
CH
336 data=json.dumps(payload),
337 headers={'content-type': 'application/json'})
338 self.assertEquals(r.status_code, 422)
339
986e4858
PL
340 def test_create_zone_with_dnssec(self):
341 """
342 Create a zone with "dnssec" set and see if a key was made.
343 """
344 name = unique_zone_name()
345 name, payload, data = self.create_zone(dnssec=True)
346
347 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
348
349 for k in ('dnssec', ):
350 self.assertIn(k, data)
351 if k in payload:
352 self.assertEquals(data[k], payload[k])
353
354 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name + '/cryptokeys'))
355
356 keys = r.json()
357
358 print keys
359
360 self.assertEquals(r.status_code, 200)
361 self.assertEquals(len(keys), 1)
362 self.assertEquals(keys[0]['type'], 'Cryptokey')
363 self.assertEquals(keys[0]['active'], True)
364 self.assertEquals(keys[0]['keytype'], 'csk')
365
366 def test_create_zone_with_nsec3param(self):
367 """
368 Create a zone with "nsec3param" set and see if the metadata was added.
369 """
370 name = unique_zone_name()
371 nsec3param = '1 0 500 aabbccddeeff'
372 name, payload, data = self.create_zone(dnssec=True, nsec3param=nsec3param)
373
374 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
375
376 for k in ('dnssec', 'nsec3param'):
377 self.assertIn(k, data)
378 if k in payload:
379 self.assertEquals(data[k], payload[k])
380
381 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name + '/metadata/NSEC3PARAM'))
382
383 data = r.json()
384
385 print data
386
387 self.assertEquals(r.status_code, 200)
388 self.assertEquals(len(data['metadata']), 1)
389 self.assertEquals(data['kind'], 'NSEC3PARAM')
390 self.assertEquals(data['metadata'][0], nsec3param)
391
392 def test_create_zone_with_nsec3narrow(self):
393 """
394 Create a zone with "nsec3narrow" set and see if the metadata was added.
395 """
396 name = unique_zone_name()
397 nsec3param = '1 0 500 aabbccddeeff'
398 name, payload, data = self.create_zone(dnssec=True, nsec3param=nsec3param,
399 nsec3narrow=True)
400
401 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
402
403 for k in ('dnssec', 'nsec3param', 'nsec3narrow'):
404 self.assertIn(k, data)
405 if k in payload:
406 self.assertEquals(data[k], payload[k])
407
408 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name + '/metadata/NSEC3NARROW'))
409
410 data = r.json()
411
412 print data
413
414 self.assertEquals(r.status_code, 200)
415 self.assertEquals(len(data['metadata']), 1)
416 self.assertEquals(data['kind'], 'NSEC3NARROW')
417 self.assertEquals(data['metadata'][0], '1')
418
16e25450
CH
419 def test_zone_absolute_url(self):
420 name, payload, data = self.create_zone()
421 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
422 rdata = r.json()
423 print(rdata[0])
424 self.assertTrue(rdata[0]['url'].startswith('/api/v'))
425
24e11043
CJ
426 def test_create_zone_metadata(self):
427 payload_metadata = {"type": "Metadata", "kind": "AXFR-SOURCE", "metadata": ["127.0.0.2"]}
428 r = self.session.post(self.url("/api/v1/servers/localhost/zones/example.com/metadata"),
429 data=json.dumps(payload_metadata))
430 rdata = r.json()
431 self.assertEquals(r.status_code, 201)
432 self.assertEquals(rdata["metadata"], payload_metadata["metadata"])
433
434 def test_create_zone_metadata_kind(self):
435 payload_metadata = {"metadata": ["127.0.0.2"]}
436 r = self.session.put(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE"),
437 data=json.dumps(payload_metadata))
438 rdata = r.json()
439 self.assertEquals(r.status_code, 200)
440 self.assertEquals(rdata["metadata"], payload_metadata["metadata"])
441
442 def test_create_protected_zone_metadata(self):
443 # test whether it prevents modification of certain kinds
444 for k in ("NSEC3NARROW", "NSEC3PARAM", "PRESIGNED", "LUA-AXFR-SCRIPT"):
445 payload = {"metadata": ["FOO", "BAR"]}
446 r = self.session.put(self.url("/api/v1/servers/localhost/zones/example.com/metadata/%s" % k),
447 data=json.dumps(payload))
448 self.assertEquals(r.status_code, 422)
449
450 def test_retrieve_zone_metadata(self):
451 payload_metadata = {"type": "Metadata", "kind": "AXFR-SOURCE", "metadata": ["127.0.0.2"]}
452 self.session.post(self.url("/api/v1/servers/localhost/zones/example.com/metadata"),
453 data=json.dumps(payload_metadata))
454 r = self.session.get(self.url("/api/v1/servers/localhost/zones/example.com/metadata"))
455 rdata = r.json()
456 self.assertEquals(r.status_code, 200)
457 self.assertIn(payload_metadata, rdata)
458
459 def test_delete_zone_metadata(self):
460 r = self.session.delete(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE"))
461 self.assertEquals(r.status_code, 200)
462 r = self.session.get(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE"))
463 rdata = r.json()
464 self.assertEquals(r.status_code, 200)
465 self.assertEquals(rdata["metadata"], [])
466
9ac4e6d5
PL
467 def test_create_external_zone_metadata(self):
468 payload_metadata = {"metadata": ["My very important message"]}
469 r = self.session.put(self.url("/api/v1/servers/localhost/zones/example.com/metadata/X-MYMETA"),
470 data=json.dumps(payload_metadata))
471 self.assertEquals(r.status_code, 200)
472 rdata = r.json()
473 self.assertEquals(rdata["metadata"], payload_metadata["metadata"])
474
4bdff352
CH
475 def test_create_slave_zone(self):
476 # Test that nameservers can be absent for slave zones.
6754ef71 477 name, payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
4bdff352
CH
478 for k in ('name', 'masters', 'kind'):
479 self.assertIn(k, data)
480 self.assertEquals(data[k], payload[k])
4de11a54
CH
481 print "payload:", payload
482 print "data:", data
483 # Because slave zones don't get a SOA, we need to test that they'll show up in the zone list.
46d06a12 484 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
4de11a54
CH
485 zonelist = r.json()
486 print "zonelist:", zonelist
487 self.assertIn(payload['name'], [zone['name'] for zone in zonelist])
488 # Also test that fetching the zone works.
46d06a12 489 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id']))
4de11a54
CH
490 data = r.json()
491 print "zone (fetched):", data
492 for k in ('name', 'masters', 'kind'):
493 self.assertIn(k, data)
494 self.assertEquals(data[k], payload[k])
495 self.assertEqual(data['serial'], 0)
6754ef71 496 self.assertEqual(data['rrsets'], [])
4de11a54
CH
497
498 def test_delete_slave_zone(self):
6754ef71 499 name, payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
46d06a12 500 r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + data['id']))
4de11a54 501 r.raise_for_status()
4bdff352 502
a426cb89 503 def test_retrieve_slave_zone(self):
6754ef71 504 name, payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
a426cb89
CH
505 print "payload:", payload
506 print "data:", data
46d06a12 507 r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/axfr-retrieve"))
a426cb89
CH
508 data = r.json()
509 print "status for axfr-retrieve:", data
510 self.assertEqual(data['result'], u'Added retrieval request for \'' + payload['name'] +
511 '\' from master 127.0.0.2')
512
513 def test_notify_master_zone(self):
6754ef71 514 name, payload, data = self.create_zone(kind='Master')
a426cb89
CH
515 print "payload:", payload
516 print "data:", data
46d06a12 517 r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/notify"))
a426cb89
CH
518 data = r.json()
519 print "status for notify:", data
520 self.assertEqual(data['result'], 'Notification queued')
521
c1374bdb 522 def test_get_zone_with_symbols(self):
6754ef71 523 name, payload, data = self.create_zone(name='foo/bar.'+unique_zone_name())
3c3c006b 524 name = payload['name']
1d6b70f9 525 zone_id = (name.replace('/', '=2F'))
46d06a12 526 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id))
c1374bdb 527 data = r.json()
6bb25159 528 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'dnssec'):
3c3c006b
CH
529 self.assertIn(k, data)
530 if k in payload:
531 self.assertEquals(data[k], payload[k])
532
c1374bdb 533 def test_get_zone(self):
46d06a12 534 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
05776d2f 535 domains = r.json()
1d6b70f9 536 example_com = [domain for domain in domains if domain['name'] == u'example.com.'][0]
46d06a12 537 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + example_com['id']))
c1374bdb 538 self.assert_success_json(r)
05776d2f
CH
539 data = r.json()
540 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'):
541 self.assertIn(k, data)
1d6b70f9 542 self.assertEquals(data['name'], 'example.com.')
7c0ba3d2 543
0f0e73fe
MS
544 def test_import_zone_broken(self):
545 payload = {}
546 payload['zone'] = """
547;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58571
548flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
549;; WARNING: recursion requested but not available
550
551;; OPT PSEUDOSECTION:
552; EDNS: version: 0, flags:; udp: 1680
553;; QUESTION SECTION:
554;powerdns.com. IN SOA
555
556;; ANSWER SECTION:
557powerdns-broken.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
558powerdns-broken.com. 3600 IN NS powerdnssec2.ds9a.nl.
559powerdns-broken.com. 3600 IN AAAA 2001:888:2000:1d::2
560powerdns-broken.com. 86400 IN A 82.94.213.34
561powerdns-broken.com. 3600 IN MX 0 xs.powerdns.com.
562powerdns-broken.com. 3600 IN NS powerdnssec1.ds9a.nl.
563powerdns-broken.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
564"""
1d6b70f9 565 payload['name'] = 'powerdns-broken.com.'
0f0e73fe
MS
566 payload['kind'] = 'Master'
567 payload['nameservers'] = []
568 r = self.session.post(
46d06a12 569 self.url("/api/v1/servers/localhost/zones"),
0f0e73fe
MS
570 data=json.dumps(payload),
571 headers={'content-type': 'application/json'})
572 self.assertEquals(r.status_code, 422)
573
1d6b70f9
CH
574 def test_import_zone_axfr_outofzone(self):
575 # Ensure we don't create out-of-zone records
576 name = unique_zone_name()
577 payload = {}
578 payload['zone'] = """
579NAME 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
580NAME 3600 IN NS powerdnssec2.ds9a.nl.
581example.org. 3600 IN AAAA 2001:888:2000:1d::2
582NAME 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
583""".replace('NAME', name)
584 payload['name'] = name
585 payload['kind'] = 'Master'
586 payload['nameservers'] = []
587 r = self.session.post(
588 self.url("/api/v1/servers/localhost/zones"),
589 data=json.dumps(payload),
590 headers={'content-type': 'application/json'})
591 self.assertEquals(r.status_code, 422)
592 self.assertEqual(r.json()['error'], 'RRset example.org. IN AAAA: Name is out of zone')
593
0f0e73fe
MS
594 def test_import_zone_axfr(self):
595 payload = {}
596 payload['zone'] = """
597;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58571
598;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
599;; WARNING: recursion requested but not available
600
601;; OPT PSEUDOSECTION:
602; EDNS: version: 0, flags:; udp: 1680
603;; QUESTION SECTION:
604;powerdns.com. IN SOA
605
606;; ANSWER SECTION:
607powerdns.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
608powerdns.com. 3600 IN NS powerdnssec2.ds9a.nl.
609powerdns.com. 3600 IN AAAA 2001:888:2000:1d::2
610powerdns.com. 86400 IN A 82.94.213.34
611powerdns.com. 3600 IN MX 0 xs.powerdns.com.
612powerdns.com. 3600 IN NS powerdnssec1.ds9a.nl.
613powerdns.com. 86400 IN SOA powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800
614"""
1d6b70f9 615 payload['name'] = 'powerdns.com.'
0f0e73fe
MS
616 payload['kind'] = 'Master'
617 payload['nameservers'] = []
1d6b70f9 618 payload['soa_edit_api'] = '' # turn off so exact SOA comparison works.
0f0e73fe 619 r = self.session.post(
46d06a12 620 self.url("/api/v1/servers/localhost/zones"),
0f0e73fe
MS
621 data=json.dumps(payload),
622 headers={'content-type': 'application/json'})
623 self.assert_success_json(r)
624 data = r.json()
625 self.assertIn('name', data)
0f0e73fe 626
90568eb2
MS
627 expected = {
628 'NS': [
6754ef71
CH
629 {'content': 'powerdnssec1.ds9a.nl.'},
630 {'content': 'powerdnssec2.ds9a.nl.'},
631 ],
90568eb2 632 'SOA': [
6754ef71
CH
633 {'content': 'powerdnssec1.ds9a.nl. ahu.ds9a.nl. 1343746984 10800 3600 604800 10800'},
634 ],
90568eb2 635 'MX': [
6754ef71
CH
636 {'content': '0 xs.powerdns.com.'},
637 ],
90568eb2 638 'A': [
6754ef71
CH
639 {'content': '82.94.213.34', 'name': 'powerdns.com.'},
640 ],
90568eb2 641 'AAAA': [
6754ef71
CH
642 {'content': '2001:888:2000:1d::2', 'name': 'powerdns.com.'},
643 ],
90568eb2 644 }
0f0e73fe 645
6754ef71 646 eq_zone_rrsets(data['rrsets'], expected)
1d6b70f9 647
e3675a8a 648 # check content in DB is stored WITHOUT trailing dot.
1d6b70f9 649 dbrecs = get_db_records(payload['name'], 'NS')
e3675a8a
CH
650 dbrec = next((dbrec for dbrec in dbrecs if dbrec['content'].startswith('powerdnssec1')))
651 self.assertEqual(dbrec['content'], 'powerdnssec1.ds9a.nl')
0f0e73fe
MS
652
653 def test_import_zone_bind(self):
654 payload = {}
655 payload['zone'] = """
656$TTL 86400 ; 24 hours could have been written as 24h or 1d
657; $TTL used for all RRs without explicit TTL value
658$ORIGIN example.org.
659@ 1D IN SOA ns1.example.org. hostmaster.example.org. (
660 2002022401 ; serial
661 3H ; refresh
662 15 ; retry
663 1w ; expire
664 3h ; minimum
665 )
666 IN NS ns1.example.org. ; in the domain
667 IN NS ns2.smokeyjoe.com. ; external to domain
668 IN MX 10 mail.another.com. ; external mail provider
669; server host definitions
1d6b70f9 670ns1 IN A 192.168.0.1 ;name server definition
0f0e73fe
MS
671www IN A 192.168.0.2 ;web server definition
672ftp IN CNAME www.example.org. ;ftp server definition
673; non server domain hosts
674bill IN A 192.168.0.3
1d6b70f9 675fred IN A 192.168.0.4
0f0e73fe 676"""
1d6b70f9 677 payload['name'] = 'example.org.'
0f0e73fe
MS
678 payload['kind'] = 'Master'
679 payload['nameservers'] = []
1d6b70f9 680 payload['soa_edit_api'] = '' # turn off so exact SOA comparison works.
0f0e73fe 681 r = self.session.post(
46d06a12 682 self.url("/api/v1/servers/localhost/zones"),
0f0e73fe
MS
683 data=json.dumps(payload),
684 headers={'content-type': 'application/json'})
685 self.assert_success_json(r)
686 data = r.json()
687 self.assertIn('name', data)
0f0e73fe 688
90568eb2
MS
689 expected = {
690 'NS': [
6754ef71
CH
691 {'content': 'ns1.example.org.'},
692 {'content': 'ns2.smokeyjoe.com.'},
693 ],
90568eb2 694 'SOA': [
6754ef71
CH
695 {'content': 'ns1.example.org. hostmaster.example.org. 2002022401 10800 15 604800 10800'},
696 ],
90568eb2 697 'MX': [
6754ef71
CH
698 {'content': '10 mail.another.com.'},
699 ],
90568eb2 700 'A': [
6754ef71
CH
701 {'content': '192.168.0.1', 'name': 'ns1.example.org.'},
702 {'content': '192.168.0.2', 'name': 'www.example.org.'},
703 {'content': '192.168.0.3', 'name': 'bill.example.org.'},
704 {'content': '192.168.0.4', 'name': 'fred.example.org.'},
705 ],
90568eb2 706 'CNAME': [
6754ef71
CH
707 {'content': 'www.example.org.', 'name': 'ftp.example.org.'},
708 ],
90568eb2 709 }
0f0e73fe 710
6754ef71 711 eq_zone_rrsets(data['rrsets'], expected)
0f0e73fe 712
c1374bdb 713 def test_export_zone_json(self):
6754ef71 714 name, payload, zone = self.create_zone(nameservers=['ns1.foo.com.', 'ns2.foo.com.'], soa_edit_api='')
a83004d3
CH
715 # export it
716 r = self.session.get(
46d06a12 717 self.url("/api/v1/servers/localhost/zones/" + name + "/export"),
a83004d3
CH
718 headers={'accept': 'application/json;q=0.9,*/*;q=0.8'}
719 )
c1374bdb 720 self.assert_success_json(r)
a83004d3
CH
721 data = r.json()
722 self.assertIn('zone', data)
1d6b70f9
CH
723 expected_data = [name + '\t3600\tNS\tns1.foo.com.',
724 name + '\t3600\tNS\tns2.foo.com.',
725 name + '\t3600\tSOA\ta.misconfigured.powerdns.server. hostmaster.' + name +
726 ' 0 10800 3600 604800 3600']
a83004d3
CH
727 self.assertEquals(data['zone'].strip().split('\n'), expected_data)
728
c1374bdb 729 def test_export_zone_text(self):
6754ef71 730 name, payload, zone = self.create_zone(nameservers=['ns1.foo.com.', 'ns2.foo.com.'], soa_edit_api='')
a83004d3
CH
731 # export it
732 r = self.session.get(
46d06a12 733 self.url("/api/v1/servers/localhost/zones/" + name + "/export"),
a83004d3
CH
734 headers={'accept': '*/*'}
735 )
736 data = r.text.strip().split("\n")
1d6b70f9
CH
737 expected_data = [name + '\t3600\tNS\tns1.foo.com.',
738 name + '\t3600\tNS\tns2.foo.com.',
739 name + '\t3600\tSOA\ta.misconfigured.powerdns.server. hostmaster.' + name +
740 ' 0 10800 3600 604800 3600']
a83004d3
CH
741 self.assertEquals(data, expected_data)
742
c1374bdb 743 def test_update_zone(self):
6754ef71 744 name, payload, zone = self.create_zone()
bee2acae 745 name = payload['name']
d29d5db7 746 # update, set as Master and enable SOA-EDIT-API
7c0ba3d2
CH
747 payload = {
748 'kind': 'Master',
c1374bdb 749 'masters': ['192.0.2.1', '192.0.2.2'],
6bb25159
MS
750 'soa_edit_api': 'EPOCH',
751 'soa_edit': 'EPOCH'
7c0ba3d2
CH
752 }
753 r = self.session.put(
46d06a12 754 self.url("/api/v1/servers/localhost/zones/" + name),
7c0ba3d2
CH
755 data=json.dumps(payload),
756 headers={'content-type': 'application/json'})
f0e76cee
CH
757 self.assert_success(r)
758 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
7c0ba3d2
CH
759 for k in payload.keys():
760 self.assertIn(k, data)
761 self.assertEquals(data[k], payload[k])
d29d5db7 762 # update, back to Native and empty(off)
7c0ba3d2 763 payload = {
d29d5db7 764 'kind': 'Native',
6bb25159
MS
765 'soa_edit_api': '',
766 'soa_edit': ''
7c0ba3d2
CH
767 }
768 r = self.session.put(
46d06a12 769 self.url("/api/v1/servers/localhost/zones/" + name),
7c0ba3d2
CH
770 data=json.dumps(payload),
771 headers={'content-type': 'application/json'})
f0e76cee
CH
772 self.assert_success(r)
773 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
7c0ba3d2
CH
774 for k in payload.keys():
775 self.assertIn(k, data)
776 self.assertEquals(data[k], payload[k])
b3905a3d 777
c1374bdb 778 def test_zone_rr_update(self):
6754ef71 779 name, payload, zone = self.create_zone()
b3905a3d 780 # do a replace (= update)
d708640f 781 rrset = {
b3905a3d
CH
782 'changetype': 'replace',
783 'name': name,
8ce0dc75 784 'type': 'ns',
6754ef71 785 'ttl': 3600,
b3905a3d
CH
786 'records': [
787 {
1d6b70f9 788 "content": "ns1.bar.com.",
cea26350
CH
789 "disabled": False
790 },
791 {
1d6b70f9 792 "content": "ns2-disabled.bar.com.",
cea26350 793 "disabled": True
b3905a3d
CH
794 }
795 ]
796 }
d708640f 797 payload = {'rrsets': [rrset]}
b3905a3d 798 r = self.session.patch(
46d06a12 799 self.url("/api/v1/servers/localhost/zones/" + name),
b3905a3d
CH
800 data=json.dumps(payload),
801 headers={'content-type': 'application/json'})
f0e76cee 802 self.assert_success(r)
b3905a3d 803 # verify that (only) the new record is there
f0e76cee 804 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
6754ef71 805 self.assertEquals(get_rrset(data, name, 'NS')['records'], rrset['records'])
b3905a3d 806
c1374bdb 807 def test_zone_rr_update_mx(self):
05cf6a71 808 # Important to test with MX records, as they have a priority field, which must end up in the content field.
6754ef71 809 name, payload, zone = self.create_zone()
41e3b10e 810 # do a replace (= update)
d708640f 811 rrset = {
41e3b10e
CH
812 'changetype': 'replace',
813 'name': name,
814 'type': 'MX',
6754ef71 815 'ttl': 3600,
41e3b10e
CH
816 'records': [
817 {
1d6b70f9 818 "content": "10 mail.example.org.",
41e3b10e
CH
819 "disabled": False
820 }
821 ]
822 }
d708640f 823 payload = {'rrsets': [rrset]}
41e3b10e 824 r = self.session.patch(
46d06a12 825 self.url("/api/v1/servers/localhost/zones/" + name),
41e3b10e
CH
826 data=json.dumps(payload),
827 headers={'content-type': 'application/json'})
f0e76cee 828 self.assert_success(r)
41e3b10e 829 # verify that (only) the new record is there
f0e76cee 830 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
6754ef71 831 self.assertEquals(get_rrset(data, name, 'MX')['records'], rrset['records'])
d708640f 832
c1374bdb 833 def test_zone_rr_update_multiple_rrsets(self):
6754ef71 834 name, payload, zone = self.create_zone()
d708640f
CH
835 rrset1 = {
836 'changetype': 'replace',
837 'name': name,
838 'type': 'NS',
6754ef71 839 'ttl': 3600,
d708640f
CH
840 'records': [
841 {
6754ef71 842
1d6b70f9 843 "content": "ns9999.example.com.",
d708640f
CH
844 "disabled": False
845 }
846 ]
847 }
848 rrset2 = {
849 'changetype': 'replace',
850 'name': name,
851 'type': 'MX',
6754ef71 852 'ttl': 3600,
d708640f
CH
853 'records': [
854 {
1d6b70f9 855 "content": "10 mx444.example.com.",
d708640f
CH
856 "disabled": False
857 }
858 ]
859 }
860 payload = {'rrsets': [rrset1, rrset2]}
861 r = self.session.patch(
46d06a12 862 self.url("/api/v1/servers/localhost/zones/" + name),
d708640f
CH
863 data=json.dumps(payload),
864 headers={'content-type': 'application/json'})
f0e76cee 865 self.assert_success(r)
d708640f 866 # verify that all rrsets have been updated
f0e76cee 867 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
6754ef71
CH
868 self.assertEquals(get_rrset(data, name, 'NS')['records'], rrset1['records'])
869 self.assertEquals(get_rrset(data, name, 'MX')['records'], rrset2['records'])
41e3b10e 870
e3675a8a
CH
871 def test_zone_rr_update_duplicate_record(self):
872 name, payload, zone = self.create_zone()
873 rrset = {
874 'changetype': 'replace',
875 'name': name,
876 'type': 'NS',
877 'ttl': 3600,
878 'records': [
879 {"content": "ns9999.example.com.", "disabled": False},
880 {"content": "ns9996.example.com.", "disabled": False},
881 {"content": "ns9987.example.com.", "disabled": False},
882 {"content": "ns9988.example.com.", "disabled": False},
883 {"content": "ns9999.example.com.", "disabled": False},
884 ]
885 }
886 payload = {'rrsets': [rrset]}
887 r = self.session.patch(
888 self.url("/api/v1/servers/localhost/zones/" + name),
889 data=json.dumps(payload),
890 headers={'content-type': 'application/json'})
891 self.assertEquals(r.status_code, 422)
892 self.assertIn('Duplicate record in RRset', r.json()['error'])
893
c1374bdb 894 def test_zone_rr_delete(self):
6754ef71 895 name, payload, zone = self.create_zone()
b3905a3d 896 # do a delete of all NS records (these are created with the zone)
d708640f 897 rrset = {
b3905a3d
CH
898 'changetype': 'delete',
899 'name': name,
900 'type': 'NS'
901 }
d708640f 902 payload = {'rrsets': [rrset]}
b3905a3d 903 r = self.session.patch(
46d06a12 904 self.url("/api/v1/servers/localhost/zones/" + name),
b3905a3d
CH
905 data=json.dumps(payload),
906 headers={'content-type': 'application/json'})
f0e76cee 907 self.assert_success(r)
b3905a3d 908 # verify that the records are gone
f0e76cee 909 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
6754ef71 910 self.assertIsNone(get_rrset(data, name, 'NS'))
cea26350 911
c1374bdb 912 def test_zone_disable_reenable(self):
d29d5db7 913 # This also tests that SOA-EDIT-API works.
6754ef71 914 name, payload, zone = self.create_zone(soa_edit_api='EPOCH')
cea26350 915 # disable zone by disabling SOA
d708640f 916 rrset = {
cea26350
CH
917 'changetype': 'replace',
918 'name': name,
919 'type': 'SOA',
6754ef71 920 'ttl': 3600,
cea26350
CH
921 'records': [
922 {
1d6b70f9 923 "content": "ns1.bar.com. hostmaster.foo.org. 1 1 1 1 1",
cea26350
CH
924 "disabled": True
925 }
926 ]
927 }
d708640f 928 payload = {'rrsets': [rrset]}
cea26350 929 r = self.session.patch(
46d06a12 930 self.url("/api/v1/servers/localhost/zones/" + name),
cea26350
CH
931 data=json.dumps(payload),
932 headers={'content-type': 'application/json'})
f0e76cee 933 self.assert_success(r)
d29d5db7 934 # check SOA serial has been edited
f0e76cee
CH
935 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
936 soa_serial1 = get_first_rec(data, name, 'SOA')['content'].split()[2]
d29d5db7
CH
937 self.assertNotEquals(soa_serial1, '1')
938 # make sure domain is still in zone list (disabled SOA!)
46d06a12 939 r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
cea26350
CH
940 domains = r.json()
941 self.assertEquals(len([domain for domain in domains if domain['name'] == name]), 1)
d29d5db7
CH
942 # sleep 1sec to ensure the EPOCH value changes for the next request
943 time.sleep(1)
cea26350 944 # verify that modifying it still works
d708640f
CH
945 rrset['records'][0]['disabled'] = False
946 payload = {'rrsets': [rrset]}
cea26350 947 r = self.session.patch(
46d06a12 948 self.url("/api/v1/servers/localhost/zones/" + name),
cea26350
CH
949 data=json.dumps(payload),
950 headers={'content-type': 'application/json'})
f0e76cee 951 self.assert_success(r)
d29d5db7 952 # check SOA serial has been edited again
f0e76cee
CH
953 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
954 soa_serial2 = get_first_rec(data, name, 'SOA')['content'].split()[2]
d29d5db7
CH
955 self.assertNotEquals(soa_serial2, '1')
956 self.assertNotEquals(soa_serial2, soa_serial1)
02945d9a 957
c1374bdb 958 def test_zone_rr_update_out_of_zone(self):
6754ef71 959 name, payload, zone = self.create_zone()
35f26cc5 960 # replace with qname mismatch
d708640f 961 rrset = {
35f26cc5 962 'changetype': 'replace',
1d6b70f9 963 'name': 'not-in-zone.',
35f26cc5 964 'type': 'NS',
6754ef71 965 'ttl': 3600,
35f26cc5
CH
966 'records': [
967 {
1d6b70f9 968 "content": "ns1.bar.com.",
35f26cc5
CH
969 "disabled": False
970 }
971 ]
972 }
d708640f 973 payload = {'rrsets': [rrset]}
35f26cc5 974 r = self.session.patch(
46d06a12 975 self.url("/api/v1/servers/localhost/zones/" + name),
35f26cc5
CH
976 data=json.dumps(payload),
977 headers={'content-type': 'application/json'})
978 self.assertEquals(r.status_code, 422)
979 self.assertIn('out of zone', r.json()['error'])
980
1d6b70f9 981 def test_zone_rr_update_restricted_chars(self):
6754ef71 982 name, payload, zone = self.create_zone()
1d6b70f9
CH
983 # replace with qname mismatch
984 rrset = {
985 'changetype': 'replace',
986 'name': 'test:' + name,
987 'type': 'NS',
6754ef71 988 'ttl': 3600,
1d6b70f9
CH
989 'records': [
990 {
1d6b70f9
CH
991 "content": "ns1.bar.com.",
992 "disabled": False
993 }
994 ]
995 }
996 payload = {'rrsets': [rrset]}
997 r = self.session.patch(
998 self.url("/api/v1/servers/localhost/zones/" + name),
999 data=json.dumps(payload),
1000 headers={'content-type': 'application/json'})
1001 self.assertEquals(r.status_code, 422)
1002 self.assertIn('contains unsupported characters', r.json()['error'])
1003
24cd86ca 1004 def test_rrset_unknown_type(self):
6754ef71 1005 name, payload, zone = self.create_zone()
24cd86ca
CH
1006 rrset = {
1007 'changetype': 'replace',
1008 'name': name,
1009 'type': 'FAFAFA',
6754ef71 1010 'ttl': 3600,
24cd86ca
CH
1011 'records': [
1012 {
24cd86ca
CH
1013 "content": "4.3.2.1",
1014 "disabled": False
1015 }
1016 ]
1017 }
1018 payload = {'rrsets': [rrset]}
46d06a12 1019 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
24cd86ca
CH
1020 headers={'content-type': 'application/json'})
1021 self.assertEquals(r.status_code, 422)
1022 self.assertIn('unknown type', r.json()['error'])
1023
8560f36a
CH
1024 def test_rrset_cname_and_other(self):
1025 name, payload, zone = self.create_zone()
1026 rrset = {
1027 'changetype': 'replace',
1028 'name': name,
1029 'type': 'CNAME',
1030 'ttl': 3600,
1031 'records': [
1032 {
1033 "content": "example.org.",
1034 "disabled": False
1035 }
1036 ]
1037 }
1038 payload = {'rrsets': [rrset]}
1039 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
1040 headers={'content-type': 'application/json'})
1041 self.assertEquals(r.status_code, 422)
1042 self.assertIn('Conflicts with pre-existing non-CNAME RRset', r.json()['error'])
1043
1044 def test_rrset_other_and_cname(self):
1045 name, payload, zone = self.create_zone()
1046 rrset = {
1047 'changetype': 'replace',
1048 'name': 'sub.'+name,
1049 'type': 'CNAME',
1050 'ttl': 3600,
1051 'records': [
1052 {
1053 "content": "example.org.",
1054 "disabled": False
1055 }
1056 ]
1057 }
1058 payload = {'rrsets': [rrset]}
1059 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
1060 headers={'content-type': 'application/json'})
1061 self.assert_success(r)
1062 rrset = {
1063 'changetype': 'replace',
1064 'name': 'sub.'+name,
1065 'type': 'A',
1066 'ttl': 3600,
1067 'records': [
1068 {
1069 "content": "1.2.3.4",
1070 "disabled": False
1071 }
1072 ]
1073 }
1074 payload = {'rrsets': [rrset]}
1075 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
1076 headers={'content-type': 'application/json'})
1077 self.assertEquals(r.status_code, 422)
1078 self.assertIn('Conflicts with pre-existing CNAME RRset', r.json()['error'])
1079
1e5b9ab9
CH
1080 def test_create_zone_with_leading_space(self):
1081 # Actual regression.
6754ef71 1082 name, payload, zone = self.create_zone()
1e5b9ab9
CH
1083 rrset = {
1084 'changetype': 'replace',
1085 'name': name,
1086 'type': 'A',
6754ef71 1087 'ttl': 3600,
1e5b9ab9
CH
1088 'records': [
1089 {
1e5b9ab9
CH
1090 "content": " 4.3.2.1",
1091 "disabled": False
1092 }
1093 ]
1094 }
1095 payload = {'rrsets': [rrset]}
46d06a12 1096 r = self.session.patch(self.url("/api/v1/servers/localhost/zones/" + name), data=json.dumps(payload),
1e5b9ab9
CH
1097 headers={'content-type': 'application/json'})
1098 self.assertEquals(r.status_code, 422)
1099 self.assertIn('Not in expected format', r.json()['error'])
1100
c1374bdb 1101 def test_zone_rr_delete_out_of_zone(self):
6754ef71 1102 name, payload, zone = self.create_zone()
d708640f 1103 rrset = {
35f26cc5 1104 'changetype': 'delete',
1d6b70f9 1105 'name': 'not-in-zone.',
35f26cc5
CH
1106 'type': 'NS'
1107 }
d708640f 1108 payload = {'rrsets': [rrset]}
35f26cc5 1109 r = self.session.patch(
46d06a12 1110 self.url("/api/v1/servers/localhost/zones/" + name),
35f26cc5
CH
1111 data=json.dumps(payload),
1112 headers={'content-type': 'application/json'})
34df6ecc 1113 print r.content
f0e76cee 1114 self.assert_success(r) # succeed so users can fix their wrong, old data
35f26cc5 1115
37663c3b 1116 def test_zone_delete(self):
6754ef71 1117 name, payload, zone = self.create_zone()
46d06a12 1118 r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + name))
37663c3b
CH
1119 self.assertEquals(r.status_code, 204)
1120 self.assertNotIn('Content-Type', r.headers)
1121
c1374bdb 1122 def test_zone_comment_create(self):
6754ef71 1123 name, payload, zone = self.create_zone()
d708640f 1124 rrset = {
6cc98ddf
CH
1125 'changetype': 'replace',
1126 'name': name,
1127 'type': 'NS',
6754ef71 1128 'ttl': 3600,
6cc98ddf
CH
1129 'comments': [
1130 {
1131 'account': 'test1',
1132 'content': 'blah blah',
1133 },
1134 {
1135 'account': 'test2',
1136 'content': 'blah blah bleh',
1137 }
1138 ]
1139 }
d708640f 1140 payload = {'rrsets': [rrset]}
6cc98ddf 1141 r = self.session.patch(
46d06a12 1142 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
1143 data=json.dumps(payload),
1144 headers={'content-type': 'application/json'})
f0e76cee 1145 self.assert_success(r)
6cc98ddf
CH
1146 # make sure the comments have been set, and that the NS
1147 # records are still present
f0e76cee
CH
1148 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
1149 serverset = get_rrset(data, name, 'NS')
6754ef71
CH
1150 print serverset
1151 self.assertNotEquals(serverset['records'], [])
1152 self.assertNotEquals(serverset['comments'], [])
6cc98ddf 1153 # verify that modified_at has been set by pdns
6754ef71 1154 self.assertNotEquals([c for c in serverset['comments']][0]['modified_at'], 0)
0d7f3c75
CH
1155 # verify that TTL is correct (regression test)
1156 self.assertEquals(serverset['ttl'], 3600)
6cc98ddf 1157
c1374bdb 1158 def test_zone_comment_delete(self):
6cc98ddf 1159 # Test: Delete ONLY comments.
6754ef71 1160 name, payload, zone = self.create_zone()
d708640f 1161 rrset = {
6cc98ddf
CH
1162 'changetype': 'replace',
1163 'name': name,
1164 'type': 'NS',
1165 'comments': []
1166 }
d708640f 1167 payload = {'rrsets': [rrset]}
6cc98ddf 1168 r = self.session.patch(
46d06a12 1169 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
1170 data=json.dumps(payload),
1171 headers={'content-type': 'application/json'})
f0e76cee 1172 self.assert_success(r)
6cc98ddf 1173 # make sure the NS records are still present
f0e76cee
CH
1174 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
1175 serverset = get_rrset(data, name, 'NS')
6754ef71
CH
1176 print serverset
1177 self.assertNotEquals(serverset['records'], [])
1178 self.assertEquals(serverset['comments'], [])
6cc98ddf 1179
c1374bdb 1180 def test_zone_comment_stay_intact(self):
6cc98ddf 1181 # Test if comments on an rrset stay intact if the rrset is replaced
6754ef71 1182 name, payload, zone = self.create_zone()
6cc98ddf 1183 # create a comment
d708640f 1184 rrset = {
6cc98ddf
CH
1185 'changetype': 'replace',
1186 'name': name,
1187 'type': 'NS',
1188 'comments': [
1189 {
1190 'account': 'test1',
1191 'content': 'oh hi there',
2696eea0 1192 'modified_at': 1111
6cc98ddf
CH
1193 }
1194 ]
1195 }
d708640f 1196 payload = {'rrsets': [rrset]}
6cc98ddf 1197 r = self.session.patch(
46d06a12 1198 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
1199 data=json.dumps(payload),
1200 headers={'content-type': 'application/json'})
f0e76cee 1201 self.assert_success(r)
6cc98ddf 1202 # replace rrset records
d708640f 1203 rrset2 = {
6cc98ddf
CH
1204 'changetype': 'replace',
1205 'name': name,
1206 'type': 'NS',
6754ef71 1207 'ttl': 3600,
6cc98ddf
CH
1208 'records': [
1209 {
1d6b70f9 1210 "content": "ns1.bar.com.",
6cc98ddf
CH
1211 "disabled": False
1212 }
1213 ]
1214 }
d708640f 1215 payload2 = {'rrsets': [rrset2]}
6cc98ddf 1216 r = self.session.patch(
46d06a12 1217 self.url("/api/v1/servers/localhost/zones/" + name),
6cc98ddf
CH
1218 data=json.dumps(payload2),
1219 headers={'content-type': 'application/json'})
f0e76cee 1220 self.assert_success(r)
6cc98ddf 1221 # make sure the comments still exist
f0e76cee
CH
1222 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
1223 serverset = get_rrset(data, name, 'NS')
6754ef71
CH
1224 print serverset
1225 self.assertEquals(serverset['records'], rrset2['records'])
1226 self.assertEquals(serverset['comments'], rrset['comments'])
6cc98ddf 1227
3fe7c7d6
CH
1228 def test_zone_auto_ptr_ipv4_create(self):
1229 revzone = '4.2.192.in-addr.arpa.'
1230 _, _, revzonedata = self.create_zone(name=revzone)
1231 name = unique_zone_name()
1232 rrset = {
1233 "name": name,
1234 "type": "A",
1235 "ttl": 3600,
1236 "records": [{
1237 "content": "192.2.4.44",
1238 "disabled": False,
1239 "set-ptr": True,
1240 }],
1241 }
1242 name, payload, data = self.create_zone(name=name, rrsets=[rrset])
1243 del rrset['records'][0]['set-ptr']
1244 self.assertEquals(get_rrset(data, name, 'A')['records'], rrset['records'])
1245 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone)).json()
1246 revsets = [s for s in r['rrsets'] if s['type'] == 'PTR']
1247 print revsets
1248 self.assertEquals(revsets, [{
1249 u'name': u'44.4.2.192.in-addr.arpa.',
1250 u'ttl': 3600,
1251 u'type': u'PTR',
1252 u'comments': [],
1253 u'records': [{
1254 u'content': name,
1255 u'disabled': False,
1256 }],
1257 }])
1258 # with SOA-EDIT-API DEFAULT on the revzone, the serial should now be higher.
1259 self.assertGreater(r['serial'], revzonedata['serial'])
1260
1261 def test_zone_auto_ptr_ipv4_update(self):
1d6b70f9 1262 revzone = '0.2.192.in-addr.arpa.'
a41c038a 1263 _, _, revzonedata = self.create_zone(name=revzone)
6754ef71 1264 name, payload, zone = self.create_zone()
d708640f 1265 rrset = {
d1587ceb
CH
1266 'changetype': 'replace',
1267 'name': name,
1268 'type': 'A',
6754ef71 1269 'ttl': 3600,
d1587ceb
CH
1270 'records': [
1271 {
d1587ceb
CH
1272 "content": '192.2.0.2',
1273 "disabled": False,
1274 "set-ptr": True
1275 }
1276 ]
1277 }
d708640f 1278 payload = {'rrsets': [rrset]}
d1587ceb 1279 r = self.session.patch(
46d06a12 1280 self.url("/api/v1/servers/localhost/zones/" + name),
d1587ceb
CH
1281 data=json.dumps(payload),
1282 headers={'content-type': 'application/json'})
f0e76cee 1283 self.assert_success(r)
a41c038a
CH
1284 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone)).json()
1285 revsets = [s for s in r['rrsets'] if s['type'] == 'PTR']
6754ef71
CH
1286 print revsets
1287 self.assertEquals(revsets, [{
1288 u'name': u'2.0.2.192.in-addr.arpa.',
d1587ceb 1289 u'ttl': 3600,
d1587ceb 1290 u'type': u'PTR',
6754ef71
CH
1291 u'comments': [],
1292 u'records': [{
1293 u'content': name,
1294 u'disabled': False,
1295 }],
d1587ceb 1296 }])
a41c038a
CH
1297 # with SOA-EDIT-API DEFAULT on the revzone, the serial should now be higher.
1298 self.assertGreater(r['serial'], revzonedata['serial'])
d1587ceb 1299
3fe7c7d6 1300 def test_zone_auto_ptr_ipv6_update(self):
d1587ceb 1301 # 2001:DB8::bb:aa
1d6b70f9 1302 revzone = '8.b.d.0.1.0.0.2.ip6.arpa.'
a41c038a 1303 _, _, revzonedata = self.create_zone(name=revzone)
6754ef71 1304 name, payload, zone = self.create_zone()
d708640f 1305 rrset = {
d1587ceb
CH
1306 'changetype': 'replace',
1307 'name': name,
1308 'type': 'AAAA',
6754ef71 1309 'ttl': 3600,
d1587ceb
CH
1310 'records': [
1311 {
d1587ceb
CH
1312 "content": '2001:DB8::bb:aa',
1313 "disabled": False,
1314 "set-ptr": True
1315 }
1316 ]
1317 }
d708640f 1318 payload = {'rrsets': [rrset]}
d1587ceb 1319 r = self.session.patch(
46d06a12 1320 self.url("/api/v1/servers/localhost/zones/" + name),
d1587ceb
CH
1321 data=json.dumps(payload),
1322 headers={'content-type': 'application/json'})
f0e76cee 1323 self.assert_success(r)
a41c038a
CH
1324 r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + revzone)).json()
1325 revsets = [s for s in r['rrsets'] if s['type'] == 'PTR']
6754ef71
CH
1326 print revsets
1327 self.assertEquals(revsets, [{
1328 u'name': u'a.a.0.0.b.b.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.',
d1587ceb 1329 u'ttl': 3600,
d1587ceb 1330 u'type': u'PTR',
6754ef71
CH
1331 u'comments': [],
1332 u'records': [{
1333 u'content': name,
1334 u'disabled': False,
1335 }],
d1587ceb 1336 }])
a41c038a
CH
1337 # with SOA-EDIT-API DEFAULT on the revzone, the serial should now be higher.
1338 self.assertGreater(r['serial'], revzonedata['serial'])
d1587ceb 1339
c1374bdb 1340 def test_search_rr_exact_zone(self):
b1902fab 1341 name = unique_zone_name()
1d6b70f9
CH
1342 self.create_zone(name=name, serial=22, soa_edit_api='')
1343 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name.rstrip('.')))
c1374bdb 1344 self.assert_success_json(r)
b1902fab 1345 print r.json()
1d6b70f9
CH
1346 self.assertEquals(r.json(), [
1347 {u'object_type': u'zone', u'name': name, u'zone_id': name},
1348 {u'content': u'a.misconfigured.powerdns.server. hostmaster.'+name+' 22 10800 3600 604800 3600',
1349 u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
1350 u'ttl': 3600, u'type': u'SOA', u'name': name},
1351 {u'content': u'ns1.example.com.',
1352 u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
1353 u'ttl': 3600, u'type': u'NS', u'name': name},
1354 {u'content': u'ns2.example.com.',
1355 u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
1356 u'ttl': 3600, u'type': u'NS', u'name': name},
1357 ])
b1902fab 1358
c1374bdb 1359 def test_search_rr_substring(self):
1d6b70f9 1360 name = 'search-rr-zone.name.'
b1902fab 1361 self.create_zone(name=name)
46d06a12 1362 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*rr-zone*"))
c1374bdb 1363 self.assert_success_json(r)
b1902fab
CH
1364 print r.json()
1365 # should return zone, SOA, ns1, ns2
60a8e825 1366 self.assertEquals(len(r.json()), 4)
b1902fab 1367
c1374bdb 1368 def test_search_rr_case_insensitive(self):
1d6b70f9 1369 name = 'search-rr-insenszone.name.'
57cb86d8 1370 self.create_zone(name=name)
46d06a12 1371 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*rr-insensZONE*"))
c1374bdb 1372 self.assert_success_json(r)
57cb86d8
CH
1373 print r.json()
1374 # should return zone, SOA, ns1, ns2
60a8e825 1375 self.assertEquals(len(r.json()), 4)
57cb86d8 1376
7cbc5255
CH
1377 def test_search_after_rectify_with_ent(self):
1378 name = 'search-rectified.name.'
1379 rrset = {
1380 "name": 'sub.sub.' + name,
1381 "type": "A",
1382 "ttl": 3600,
1383 "records": [{
1384 "content": "4.3.2.1",
1385 "disabled": False,
1386 }],
1387 }
1388 self.create_zone(name=name, rrsets=[rrset])
1389 pdnsutil_rectify(name)
1390 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=*search-rectified*"))
1391 self.assert_success_json(r)
1392 print r.json()
1393 # should return zone, SOA, ns1, ns2, sub.sub A (but not the ENT)
1394 self.assertEquals(len(r.json()), 5)
1395
986e4858
PL
1396 def test_rrset_parameter_post_false(self):
1397 name = unique_zone_name()
1398 payload = {
1399 'name': name,
1400 'kind': 'Native',
1401 'nameservers': ['ns1.example.com.', 'ns2.example.com.']
1402 }
1403 r = self.session.post(
1404 self.url("/api/v1/servers/localhost/zones?rrsets=false"),
1405 data=json.dumps(payload),
1406 headers={'content-type': 'application/json'})
1407 print r.json()
1408 self.assert_success_json(r)
1409 self.assertEquals(r.status_code, 201)
1410 self.assertEquals(r.json().get('rrsets'), None)
1411
1412 def test_rrset_false_parameter(self):
1413 name = unique_zone_name()
1414 self.create_zone(name=name, kind='Native')
1415 r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=false"))
1416 self.assert_success_json(r)
1417 print r.json()
1418 self.assertEquals(r.json().get('rrsets'), None)
1419
1420 def test_rrset_true_parameter(self):
1421 name = unique_zone_name()
1422 self.create_zone(name=name, kind='Native')
1423 r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=true"))
1424 self.assert_success_json(r)
1425 print r.json()
1426 self.assertEquals(len(r.json().get('rrsets')), 2)
1427
1428 def test_wrong_rrset_parameter(self):
1429 name = unique_zone_name()
1430 self.create_zone(name=name, kind='Native')
1431 r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=foobar"))
1432 self.assertEquals(r.status_code, 422)
1433 self.assertIn("'rrsets' request parameter value 'foobar' is not supported", r.json()['error'])
1434
02945d9a 1435
406497f5
CH
1436@unittest.skipIf(not is_auth(), "Not applicable")
1437class AuthRootZone(ApiTestCase, AuthZonesHelperMixin):
1438
1439 def setUp(self):
1440 super(AuthRootZone, self).setUp()
1441 # zone name is not unique, so delete the zone before each individual test.
46d06a12 1442 self.session.delete(self.url("/api/v1/servers/localhost/zones/=2E"))
406497f5
CH
1443
1444 def test_create_zone(self):
6754ef71 1445 name, payload, data = self.create_zone(name='.', serial=22, soa_edit_api='')
406497f5
CH
1446 for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'soa_edit_api', 'soa_edit', 'account'):
1447 self.assertIn(k, data)
1448 if k in payload:
1449 self.assertEquals(data[k], payload[k])
406497f5 1450 # validate generated SOA
6754ef71 1451 rec = get_first_rec(data, '.', 'SOA')
406497f5 1452 self.assertEquals(
6754ef71 1453 rec['content'],
1d6b70f9 1454 "a.misconfigured.powerdns.server. hostmaster. " + str(payload['serial']) +
406497f5
CH
1455 " 10800 3600 604800 3600"
1456 )
1457 # Regression test: verify zone list works
46d06a12 1458 zonelist = self.session.get(self.url("/api/v1/servers/localhost/zones")).json()
406497f5
CH
1459 print "zonelist:", zonelist
1460 self.assertIn(payload['name'], [zone['name'] for zone in zonelist])
1461 # Also test that fetching the zone works.
1462 print "id:", data['id']
1463 self.assertEquals(data['id'], '=2E')
46d06a12 1464 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id'])).json()
406497f5
CH
1465 print "zone (fetched):", data
1466 for k in ('name', 'kind'):
1467 self.assertIn(k, data)
1468 self.assertEquals(data[k], payload[k])
6754ef71 1469 self.assertEqual(data['rrsets'][0]['name'], '.')
406497f5
CH
1470
1471 def test_update_zone(self):
6754ef71 1472 name, payload, zone = self.create_zone(name='.')
406497f5
CH
1473 zone_id = '=2E'
1474 # update, set as Master and enable SOA-EDIT-API
1475 payload = {
1476 'kind': 'Master',
1477 'masters': ['192.0.2.1', '192.0.2.2'],
1478 'soa_edit_api': 'EPOCH',
1479 'soa_edit': 'EPOCH'
1480 }
1481 r = self.session.put(
46d06a12 1482 self.url("/api/v1/servers/localhost/zones/" + zone_id),
406497f5
CH
1483 data=json.dumps(payload),
1484 headers={'content-type': 'application/json'})
f0e76cee
CH
1485 self.assert_success(r)
1486 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)).json()
406497f5
CH
1487 for k in payload.keys():
1488 self.assertIn(k, data)
1489 self.assertEquals(data[k], payload[k])
1490 # update, back to Native and empty(off)
1491 payload = {
1492 'kind': 'Native',
1493 'soa_edit_api': '',
1494 'soa_edit': ''
1495 }
1496 r = self.session.put(
46d06a12 1497 self.url("/api/v1/servers/localhost/zones/" + zone_id),
406497f5
CH
1498 data=json.dumps(payload),
1499 headers={'content-type': 'application/json'})
f0e76cee
CH
1500 self.assert_success(r)
1501 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)).json()
406497f5
CH
1502 for k in payload.keys():
1503 self.assertIn(k, data)
1504 self.assertEquals(data[k], payload[k])
1505
1506
c1374bdb 1507@unittest.skipIf(not is_recursor(), "Not applicable")
02945d9a
CH
1508class RecursorZones(ApiTestCase):
1509
37bc3d01
CH
1510 def create_zone(self, name=None, kind=None, rd=False, servers=None):
1511 if name is None:
1512 name = unique_zone_name()
1513 if servers is None:
1514 servers = []
02945d9a 1515 payload = {
37bc3d01
CH
1516 'name': name,
1517 'kind': kind,
1518 'servers': servers,
1519 'recursion_desired': rd
02945d9a
CH
1520 }
1521 r = self.session.post(
46d06a12 1522 self.url("/api/v1/servers/localhost/zones"),
02945d9a
CH
1523 data=json.dumps(payload),
1524 headers={'content-type': 'application/json'})
c1374bdb
CH
1525 self.assert_success_json(r)
1526 return payload, r.json()
37bc3d01 1527
c1374bdb 1528 def test_create_auth_zone(self):
37bc3d01 1529 payload, data = self.create_zone(kind='Native')
02945d9a
CH
1530 for k in payload.keys():
1531 self.assertEquals(data[k], payload[k])
1532
1d6b70f9 1533 def test_create_zone_no_name(self):
1d6b70f9
CH
1534 payload = {
1535 'name': '',
1536 'kind': 'Native',
1537 'servers': ['8.8.8.8'],
1538 'recursion_desired': False,
1539 }
1540 print payload
1541 r = self.session.post(
1542 self.url("/api/v1/servers/localhost/zones"),
1543 data=json.dumps(payload),
1544 headers={'content-type': 'application/json'})
1545 self.assertEquals(r.status_code, 422)
1546 self.assertIn('is not canonical', r.json()['error'])
1547
c1374bdb 1548 def test_create_forwarded_zone(self):
37bc3d01 1549 payload, data = self.create_zone(kind='Forwarded', rd=False, servers=['8.8.8.8'])
02945d9a
CH
1550 # return values are normalized
1551 payload['servers'][0] += ':53'
02945d9a
CH
1552 for k in payload.keys():
1553 self.assertEquals(data[k], payload[k])
1554
c1374bdb 1555 def test_create_forwarded_rd_zone(self):
1d6b70f9 1556 payload, data = self.create_zone(name='google.com.', kind='Forwarded', rd=True, servers=['8.8.8.8'])
02945d9a
CH
1557 # return values are normalized
1558 payload['servers'][0] += ':53'
02945d9a
CH
1559 for k in payload.keys():
1560 self.assertEquals(data[k], payload[k])
1561
c1374bdb 1562 def test_create_auth_zone_with_symbols(self):
37bc3d01 1563 payload, data = self.create_zone(name='foo/bar.'+unique_zone_name(), kind='Native')
1dbe38ba 1564 expected_id = (payload['name'].replace('/', '=2F'))
02945d9a
CH
1565 for k in payload.keys():
1566 self.assertEquals(data[k], payload[k])
1567 self.assertEquals(data['id'], expected_id)
e2367534 1568
c1374bdb 1569 def test_rename_auth_zone(self):
37bc3d01 1570 payload, data = self.create_zone(kind='Native')
1d6b70f9 1571 name = payload['name']
e2367534
CH
1572 # now rename it
1573 payload = {
1574 'name': 'renamed-'+name,
1575 'kind': 'Native',
1576 'recursion_desired': False
1577 }
1578 r = self.session.put(
46d06a12 1579 self.url("/api/v1/servers/localhost/zones/" + name),
e2367534
CH
1580 data=json.dumps(payload),
1581 headers={'content-type': 'application/json'})
f0e76cee
CH
1582 self.assert_success(r)
1583 data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + payload['name'])).json()
e2367534
CH
1584 for k in payload.keys():
1585 self.assertEquals(data[k], payload[k])
37bc3d01 1586
37663c3b
CH
1587 def test_zone_delete(self):
1588 payload, zone = self.create_zone(kind='Native')
1589 name = payload['name']
46d06a12 1590 r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + name))
37663c3b
CH
1591 self.assertEquals(r.status_code, 204)
1592 self.assertNotIn('Content-Type', r.headers)
1593
c1374bdb 1594 def test_search_rr_exact_zone(self):
1d6b70f9 1595 name = unique_zone_name()
37bc3d01 1596 self.create_zone(name=name, kind='Native')
46d06a12 1597 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name))
c1374bdb 1598 self.assert_success_json(r)
37bc3d01
CH
1599 print r.json()
1600 self.assertEquals(r.json(), [{u'type': u'zone', u'name': name, u'zone_id': name}])
1601
c1374bdb 1602 def test_search_rr_substring(self):
1d6b70f9 1603 name = 'search-rr-zone.name.'
37bc3d01 1604 self.create_zone(name=name, kind='Native')
46d06a12 1605 r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=rr-zone"))
c1374bdb 1606 self.assert_success_json(r)
37bc3d01
CH
1607 print r.json()
1608 # should return zone, SOA
1609 self.assertEquals(len(r.json()), 2)
ccfabd0d 1610
ccfabd0d
CH
1611@unittest.skipIf(not is_auth(), "Not applicable")
1612class AuthZoneKeys(ApiTestCase, AuthZonesHelperMixin):
1613
1614 def test_get_keys(self):
1615 r = self.session.get(
1616 self.url("/api/v1/servers/localhost/zones/powerdnssec.org./cryptokeys"))
1617 self.assert_success_json(r)
1618 keys = r.json()
1619 self.assertGreater(len(keys), 0)
1620
1621 key0 = deepcopy(keys[0])
1622 del key0['dnskey']
b6bd795c 1623 del key0['ds']
ccfabd0d
CH
1624 expected = {
1625 u'active': True,
1626 u'type': u'Cryptokey',
b6bd795c
PL
1627 u'keytype': u'csk',
1628 u'flags': 257,
ccfabd0d
CH
1629 u'id': 1}
1630 self.assertEquals(key0, expected)
1631
1632 keydata = keys[0]['dnskey'].split()
1633 self.assertEqual(len(keydata), 4)