Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions dim-testsuite/t/rr-create-dname-1.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
$ ndcli create zone old.example.com
WARNING - Creating zone old.example.com without profile
WARNING - Primary NS for this Domain is now localhost.
$ ndcli create zone new.example.com
WARNING - Creating zone new.example.com without profile
WARNING - Primary NS for this Domain is now localhost.

# Test creating a basic DNAME record
$ ndcli create rr dept.old.example.com. dname dept.new.example.com. -q

# Test creating DNAME with TTL and comment
$ ndcli create rr sales.old.example.com. ttl 1800 dname sales.new.example.com. --comment "Sales department redirect" -q

# Test DNAME cannot be created at zone apex (RFC 6672 requirement)
$ ndcli create rr old.example.com. dname new.example.com.
ERROR - It is not allowed to create a DNAME for a zone

# Test DNAME conflicts with other records at same name
$ ndcli create rr conflict.old.example.com. a 1.2.3.4 -q
$ ndcli create rr conflict.old.example.com. dname conflict.new.example.com.
ERROR - conflict.old.example.com. DNAME conflict.new.example.com. cannot be created because other RRs with the same name exist

# Test other records cannot be created at same name as DNAME
$ ndcli create rr dept.old.example.com. a 1.2.3.5
ERROR - dept.old.example.com. A 1.2.3.5 cannot be created because a DNAME with the same name exists

# Test records cannot be created under DNAME subtree
$ ndcli create rr hr.dept.old.example.com. a 1.2.3.6
ERROR - hr.dept.old.example.com. A 1.2.3.6 cannot be created under DNAME subtree dept.old.example.com.

# Test DNAME cannot be created if records exist under the subtree
$ ndcli create rr marketing.test.old.example.com. a 1.2.3.7 -q
$ ndcli create rr test.old.example.com. dname test.new.example.com.
ERROR - test.old.example.com. DNAME test.new.example.com. cannot be created because RRs exist under the DNAME subtree

# Clean up the conflicting record and create the DNAME
$ ndcli delete rr marketing.test.old.example.com. a -q
$ ndcli create rr test.old.example.com. dname test.new.example.com. -q

# Test multiple DNAME records can coexist if they don't conflict
$ ndcli create rr finance.old.example.com. dname finance.new.example.com. -q

# Test showing DNAME records
$ ndcli show rr dept.old.example.com. dname
created:2012-11-14 11:03:02
created_by:user
modified:2012-11-14 11:03:02
modified_by:user
rr:dept DNAME dept.new.example.com.
zone:old.example.com

# Test showing DNAME record with comment
$ ndcli show rr sales.old.example.com. dname
comment:Sales department redirect
created:2012-11-14 11:03:02
created_by:user
modified:2012-11-14 11:03:02
modified_by:user
rr:sales 1800 DNAME sales.new.example.com.
ttl:1800
zone:old.example.com

# Test listing zone with DNAME records
$ ndcli list zone old.example.com
record zone ttl type value
@ old.example.com 86400 SOA localhost. hostmaster.old.example.com. 2012111402 14400 3600 605000 86400
conflict old.example.com A 1.2.3.4
dept old.example.com DNAME dept.new.example.com.
finance old.example.com DNAME finance.new.example.com.
sales old.example.com 1800 DNAME sales.new.example.com.
test old.example.com DNAME test.new.example.com.

# Test deleting DNAME records
$ ndcli delete rr sales.old.example.com. dname sales.new.example.com. -q

# Test deleting DNAME by name only
$ ndcli delete rr finance.old.example.com. dname -q

# Verify records were deleted
$ ndcli list zone old.example.com
record zone ttl type value
@ old.example.com 86400 SOA localhost. hostmaster.old.example.com. 2012111403 14400 3600 605000 86400
conflict old.example.com A 1.2.3.4
dept old.example.com DNAME dept.new.example.com.
test old.example.com DNAME test.new.example.com.

# Test that records can be created under DNAME subtree after DNAME is deleted
$ ndcli delete rr dept.old.example.com. dname -q
$ ndcli create rr hr.dept.old.example.com. a 1.2.3.8 -q

# Cleanup
$ ndcli delete zone old.example.com --cleanup
INFO - Deleting RR conflict A 1.2.3.4 from zone old.example.com
INFO - Freeing IP 1.2.3.4 from layer3domain default
INFO - Deleting RR hr.dept A 1.2.3.8 from zone old.example.com
INFO - Freeing IP 1.2.3.8 from layer3domain default
INFO - Deleting RR test DNAME test.new.example.com. from zone old.example.com
$ ndcli delete zone new.example.com --cleanup
90 changes: 90 additions & 0 deletions dim-testsuite/tests/dns_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,96 @@ def test_parse(self):
self.r.rr_create(name=rr_name, type='TXT', strings=original)
assert self.r.rr_list(rr_name)[0]['value'] == canonical[original]

def test_dname_create(self):
"""Test basic DNAME record creation"""
self.r.zone_create('old.example.com')
self.r.zone_create('new.example.com')

# Create basic DNAME record
self.r.rr_create(name='dept.old.example.com.', type='DNAME', target='dept.new.example.com.')
rrs_result = self.r.rr_list('dept.old.example.com.')
assert len(rrs_result) == 1
assert rrs_result[0]['type'] == 'DNAME'
assert rrs_result[0]['value'] == 'dept.new.example.com.'

def test_dname_zone_apex_forbidden(self):
"""Test that DNAME cannot be created at zone apex"""
self.r.zone_create('old.example.com')
self.r.zone_create('new.example.com')

with raises(InvalidParameterError, 'It is not allowed to create a DNAME for a zone'):
self.r.rr_create(name='old.example.com.', type='DNAME', target='new.example.com.')

def test_dname_conflicts_with_other_records(self):
"""Test that DNAME cannot coexist with other records at same name"""
self.r.zone_create('test.com')

# Create A record first
self.r.rr_create(name='conflict.test.com.', type='A', ip='1.2.3.4')

# Try to create DNAME at same name - should fail
with raises(InvalidParameterError, 'cannot be created because other RRs with the same name exist'):
self.r.rr_create(name='conflict.test.com.', type='DNAME', target='target.example.com.')

def test_other_records_conflict_with_dname(self):
"""Test that other records cannot be created at same name as DNAME"""
self.r.zone_create('test.com')

# Create DNAME first
self.r.rr_create(name='dept.test.com.', type='DNAME', target='dept.example.com.')

# Try to create A record at same name - should fail
with raises(InvalidParameterError, 'cannot be created because a DNAME with the same name exists'):
self.r.rr_create(name='dept.test.com.', type='A', ip='1.2.3.4')

def test_dname_subtree_conflict(self):
"""Test that records cannot be created under DNAME subtree"""
self.r.zone_create('test.com')

# Create DNAME first
self.r.rr_create(name='dept.test.com.', type='DNAME', target='dept.example.com.')

# Try to create record under DNAME subtree - should fail
with raises(InvalidParameterError, 'cannot be created under DNAME subtree'):
self.r.rr_create(name='hr.dept.test.com.', type='A', ip='1.2.3.4')

def test_dname_existing_subtree_conflict(self):
"""Test that DNAME cannot be created if records exist under the subtree"""
self.r.zone_create('test.com')

# Create record under subtree first
self.r.rr_create(name='marketing.dept.test.com.', type='A', ip='1.2.3.4')

# Try to create DNAME - should fail
with raises(InvalidParameterError, 'cannot be created because RRs exist under the DNAME subtree'):
self.r.rr_create(name='dept.test.com.', type='DNAME', target='dept.example.com.')

def test_dname_delete(self):
"""Test DNAME record deletion"""
self.r.zone_create('old.example.com')
self.r.zone_create('new.example.com')

# Create DNAME record
self.r.rr_create(name='dept.old.example.com.', type='DNAME', target='dept.new.example.com.')
assert len(self.r.rr_list('dept.old.example.com.')) == 1

# Delete DNAME record
self.r.rr_delete(name='dept.old.example.com.', type='DNAME', target='dept.new.example.com.')
assert len(self.r.rr_list('dept.old.example.com.')) == 0

def test_dname_multiple_non_conflicting(self):
"""Test that multiple DNAME records can coexist if they don't conflict"""
self.r.zone_create('old.example.com')
self.r.zone_create('new.example.com')

# Create multiple DNAME records
self.r.rr_create(name='dept1.old.example.com.', type='DNAME', target='dept1.new.example.com.')
self.r.rr_create(name='dept2.old.example.com.', type='DNAME', target='dept2.new.example.com.')

# Both should exist
assert len(self.r.rr_list('dept1.old.example.com.')) == 1
assert len(self.r.rr_list('dept2.old.example.com.')) == 1


class IpblockRRs(RPCTest):
def setUp(self):
Expand Down
34 changes: 34 additions & 0 deletions dim/dim/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,26 @@ def check_new_rr(new_rr):
.filter(or_(RR.name == new_rr.name,
and_(~RR.type.in_(('CNAME', 'PTR')), RR.target == new_rr.name))).count():
raise InvalidParameterError('%s cannot be created because other RRs with the same name or target exist' % new_rr)
elif new_rr.type == 'DNAME':
if new_rr.name == new_rr.view.zone.name + '.':
raise InvalidParameterError('It is not allowed to create a DNAME for a zone')
# DNAME cannot coexist with other records at the same name (except NS and DS at zone cuts)
if _same_view_or_different_zone(new_rr)\
.filter(RR.name == new_rr.name)\
.filter(~RR.type.in_(('NS', 'DS'))).count():
raise InvalidParameterError('%s cannot be created because other RRs with the same name exist' % new_rr)
# Check for conflicting records under the DNAME subtree
# Records under the DNAME subtree are those that are subdomains of the DNAME
dname_name = new_rr.name if new_rr.name.endswith('.') else new_rr.name + '.'

# Find all records that could be under this DNAME subtree
all_records = _same_view_or_different_zone(new_rr).filter(RR.name != dname_name).all()

for record in all_records:
record_name = record.name if record.name.endswith('.') else record.name + '.'
# Check if this record is a subdomain of the DNAME
if record_name != dname_name and record_name.endswith('.' + dname_name):
raise InvalidParameterError('%s cannot be created because RRs exist under the DNAME subtree' % new_rr)
elif new_rr.type == 'PTR':
if _same_view_or_different_zone(new_rr)\
.filter(RR.type == 'CNAME').filter(RR.name == new_rr.name).count():
Expand All @@ -194,6 +214,20 @@ def check_new_rr(new_rr):
if _same_view_or_different_zone(new_rr)\
.filter(RR.type == 'CNAME').filter(or_(RR.name == new_rr.name, RR.name == new_rr.target)).count():
raise InvalidParameterError('%s cannot be created because a CNAME with the same name exists' % new_rr)
# Check if new record conflicts with existing DNAME records
if _same_view_or_different_zone(new_rr)\
.filter(RR.type == 'DNAME').filter(RR.name == new_rr.name).count():
raise InvalidParameterError('%s cannot be created because a DNAME with the same name exists' % new_rr)
# Check if new record is under a DNAME subtree
dname_records = _same_view_or_different_zone(new_rr).filter(RR.type == 'DNAME').all()
for dname in dname_records:
# Normalize names by ensuring they end with a dot
dname_name = dname.name if dname.name.endswith('.') else dname.name + '.'
new_rr_name = new_rr.name if new_rr.name.endswith('.') else new_rr.name + '.'

# Check if the new record is a subdomain under the DNAME
if new_rr_name != dname_name and new_rr_name.endswith('.' + dname_name):
raise InvalidParameterError('%s cannot be created under DNAME subtree %s' % (new_rr, dname.name))


def create_single_rr(name, rr_type, zone, view, user, overwrite=False, **kwargs):
Expand Down
5 changes: 5 additions & 0 deletions dim/dim/rrtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ class CNAME(RRType):
validate = {'cname': validate_target}


class DNAME(RRType):
fields = ('target', )
validate = {'target': validate_target}


class MX(RRType):
fields = ('preference', 'exchange')
validate = {'preference': validate_preference,
Expand Down
3 changes: 2 additions & 1 deletion dim/doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ When creating or specifying a RR, the following options are available:

- *name* (string): the fqdn of the RR or the relative name if *zone* was
specified; it can be omitted if *type* is PTR and the *ip* is specified
- *type* (string): one of the supported RR types (A, AAAA, PTR, CNAME, MX, NS,
- *type* (string): one of the supported RR types (A, AAAA, PTR, CNAME, DNAME, MX, NS,
SRV, TXT, SPF, RP, CERT, HINFO, NAPTR)
- :ref:`layer3domain_option`. The layer3domain value is optional when specifying a RR
if there is only one RR with that name, type and value.
Expand All @@ -122,6 +122,7 @@ specified differently for each type:
- A/AAAA: *ip*
- PTR: *ptrdname*
- CNAME: *cname*
- DNAME: *target*
- MX: *preference*, *exchange*
- NS: *nsdname*
- SRV: *priority*, *weight*, *port*, *target*
Expand Down
1 change: 1 addition & 0 deletions ndcli/dimcli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ def complete_rr_value(token, parser):
'caa': {'arguments': [Argument('caa_flags'),
Argument('property_tag'),
Argument('property_value')]},
'dname': {'arguments': [Argument('target')]},
}
RR_FIELDS['aaaa'] = RR_FIELDS['a']
rr_types = list(RR_FIELDS.keys()) + ['soa']
Expand Down
1 change: 1 addition & 0 deletions ndcli/dimcli/zoneimport.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def __init__(self, src, args):
'AAAA': lambda rdata: dict(ip=rdata.address),
'PTR': lambda rdata: dict(ptrdname=str(rdata.target)),
'CNAME': lambda rdata: dict(cname=str(rdata.target)),
'DNAME': lambda rdata: dict(target=str(rdata.target)),
'MX': lambda rdata: dict(preference=int(rdata.preference), exchange=str(rdata.exchange)),
'NS': lambda rdata: dict(nsdname=str(rdata.target)),
'TXT': lambda rdata: dict(strings=rdata.to_text()),
Expand Down
Loading