diff --git a/contrib/archive/backends/impl_ipa/__init__.py b/contrib/archive/backends/impl_ipa/__init__.py index 2e5bc5dd8..20d7b9546 100644 --- a/contrib/archive/backends/impl_ipa/__init__.py +++ b/contrib/archive/backends/impl_ipa/__init__.py @@ -62,7 +62,8 @@ rectype2iparectype = {'A': ('arecord', '%(data)s'), 'NS': ('nsrecord', '%(data)s'), 'PTR': ('ptrrecord', '%(data)s'), 'SPF': ('spfrecord', '%(data)s'), - 'SSHFP': ('sshfprecord', '%(data)s')} + 'SSHFP': ('sshfprecord', '%(data)s'), + 'NAPTR': ('naptrrecord', '%(data)s')} IPA_INVALID_DATA = 3009 IPA_NOT_FOUND = 4001 diff --git a/designate/__init__.py b/designate/__init__.py index 91afebd73..0239fe538 100644 --- a/designate/__init__.py +++ b/designate/__init__.py @@ -69,7 +69,7 @@ designate_opts = [ # Supported record types cfg.ListOpt('supported-record-type', help='Supported record types', default=['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS', - 'PTR', 'SSHFP', 'SOA']), + 'PTR', 'SSHFP', 'SOA', 'NAPTR']), ] # Set some Oslo Log defaults diff --git a/designate/objects/__init__.py b/designate/objects/__init__.py index c1acb072a..5176295ad 100644 --- a/designate/objects/__init__.py +++ b/designate/objects/__init__.py @@ -51,6 +51,7 @@ from designate.objects.rrdata_a import A, AList # noqa from designate.objects.rrdata_aaaa import AAAA, AAAAList # noqa from designate.objects.rrdata_cname import CNAME, CNAMEList # noqa from designate.objects.rrdata_mx import MX, MXList # noqa +from designate.objects.rrdata_naptr import NAPTR, NAPTRList # noqa from designate.objects.rrdata_ns import NS, NSList # noqa from designate.objects.rrdata_ptr import PTR, PTRList # noqa from designate.objects.rrdata_soa import SOA, SOAList # noqa diff --git a/designate/objects/fields.py b/designate/objects/fields.py index 33d5cd38d..397594245 100644 --- a/designate/objects/fields.py +++ b/designate/objects/fields.py @@ -98,6 +98,9 @@ class StringFields(ovoo_fields.StringField): RE_SSHFP_FINGERPRINT = r'^([0-9A-Fa-f]{10,40}|[0-9A-Fa-f]{64})\Z' RE_TLDNAME = r'^(?!.{255,})(?:(?!\-)[A-Za-z0-9_\-]{1,63}(? 255): + raise ValueError("NAPTR record flags field cannot be longer than" + " 255 characters" % value) + if not re.match(self.RE_NAPTR_FLAGS, "%s" % value): + raise ValueError("NAPTR record flags can be S, A, U and P" % value) + return value + + +class NaptrServiceField(StringFields): + def __init__(self, **kwargs): + super(NaptrServiceField, self).__init__(**kwargs) + + def coerce(self, obj, attr, value): + value = super(NaptrServiceField, self).coerce(obj, attr, value) + if (len(value) > 255): + raise ValueError("NAPTR record service field cannot be longer than" + " 255 characters" % value) + if not re.match(self.RE_NAPTR_SERVICE, "%s" % value): + raise ValueError("%s NAPTR record service does not match" % value) + return value + + +class NaptrRegexpField(StringFields): + def __init__(self, **kwargs): + super(NaptrRegexpField, self).__init__(**kwargs) + + def coerce(self, obj, attr, value): + value = super(NaptrRegexpField, self).coerce(obj, attr, value) + if (len(value) > 255): + raise ValueError("NAPTR record regexp field cannot be longer than" + " 255 characters" % value) + if value: + if not re.match(self.RE_NAPTR_REGEXP, "%s" % value): + raise ValueError("%s is not a NAPTR record regexp" % value) + return value + + class Any(ovoo_fields.FieldType): @staticmethod def coerce(obj, attr, value): diff --git a/designate/objects/rrdata_naptr.py b/designate/objects/rrdata_naptr.py new file mode 100644 index 000000000..a126f9718 --- /dev/null +++ b/designate/objects/rrdata_naptr.py @@ -0,0 +1,63 @@ +# Copyright 2018 Canonical Ltd. +# +# Author: Tytus Kurek +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from designate.objects.record import Record +from designate.objects.record import RecordList +from designate.objects import base +from designate.objects import fields + + +@base.DesignateRegistry.register +class NAPTR(Record): + """ + NAPTR Resource Record Type + Defined in: RFC2915 + """ + fields = { + 'order': fields.IntegerFields(minimum=0, maximum=65535), + 'preference': fields.IntegerFields(minimum=0, maximum=65535), + 'flags': fields.NaptrFlagsField(), + 'service': fields.NaptrServiceField(), + 'regexp': fields.NaptrRegexpField(), + 'replacement': fields.DomainField(maxLength=255) + } + + def _to_string(self): + return ("%(order)s %(preference)s %(flags)s %(service)s %(regexp)s " + "%(replacement)s" % self) + + def _from_string(self, v): + order, preference, flags, service, regexp, replacement = v.split(' ') + self.order = int(order) + self.preference = int(preference) + self.flags = flags + self.service = service + self.regexp = regexp + self.replacement = replacement + + # The record type is defined in the RFC. This will be used when the record + # is sent by mini-dns. + RECORD_TYPE = 35 + + +@base.DesignateRegistry.register +class NAPTRList(RecordList): + + LIST_ITEM_TYPE = NAPTR + + fields = { + 'objects': fields.ListOfObjectsField('NAPTR'), + } diff --git a/designate/storage/impl_sqlalchemy/migrate_repo/versions/101_support_naptr_records.py b/designate/storage/impl_sqlalchemy/migrate_repo/versions/101_support_naptr_records.py new file mode 100644 index 000000000..4c03e6e84 --- /dev/null +++ b/designate/storage/impl_sqlalchemy/migrate_repo/versions/101_support_naptr_records.py @@ -0,0 +1,44 @@ +# Copyright 2018 Canonical Ltd. +# +# Author: Tytus Kurek +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import MetaData, Table, Enum + +meta = MetaData() + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS', + 'PTR', 'SSHFP', 'SOA', 'NAPTR'] + + records_table = Table('recordsets', meta, autoload=True) + records_table.columns.type.alter(name='type', type=Enum(*RECORD_TYPES)) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS', + 'PTR', 'SSHFP', 'SOA'] + + records_table = Table('recordsets', meta, autoload=True) + + # Delete all NAPTR records + records_table.filter_by(name='type', type='NAPTR').delete() + + # Remove CAA from the ENUM + records_table.columns.type.alter(type=Enum(*RECORD_TYPES)) diff --git a/designate/storage/impl_sqlalchemy/tables.py b/designate/storage/impl_sqlalchemy/tables.py index feca30f36..22bf5118e 100644 --- a/designate/storage/impl_sqlalchemy/tables.py +++ b/designate/storage/impl_sqlalchemy/tables.py @@ -29,7 +29,7 @@ CONF = cfg.CONF RESOURCE_STATUSES = ['ACTIVE', 'PENDING', 'DELETED', 'ERROR'] RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS', 'PTR', - 'SSHFP', 'SOA'] + 'SSHFP', 'SOA', 'NAPTR'] TASK_STATUSES = ['ACTIVE', 'PENDING', 'DELETED', 'ERROR', 'COMPLETE'] TSIG_ALGORITHMS = ['hmac-md5', 'hmac-sha1', 'hmac-sha224', 'hmac-sha256', 'hmac-sha384', 'hmac-sha512'] diff --git a/designate/tests/test_central/test_service.py b/designate/tests/test_central/test_service.py index 2b0e405cb..5828c1720 100644 --- a/designate/tests/test_central/test_service.py +++ b/designate/tests/test_central/test_service.py @@ -1823,7 +1823,7 @@ class CentralServiceTest(CentralTestCase): def test_update_recordset_immutable_type(self): zone = self.create_zone() # ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS', 'PTR', - # 'SSHFP', 'SOA'] + # 'SSHFP', 'SOA', 'NAPTR'] # Create a recordset recordset = self.create_recordset(zone) cname_recordset = self.create_recordset(zone, type='CNAME') diff --git a/designate/tests/test_objects/test_naptr_object.py b/designate/tests/test_objects/test_naptr_object.py new file mode 100644 index 000000000..6266f93ad --- /dev/null +++ b/designate/tests/test_objects/test_naptr_object.py @@ -0,0 +1,46 @@ +# Copyright 2018 Canonical Ltd. +# +# Author: Tytus Kurek +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_log import log as logging +import oslotest.base + +from designate import objects + +LOG = logging.getLogger(__name__) + + +def debug(*a, **kw): + for v in a: + LOG.debug(repr(v)) + + for k in sorted(kw): + LOG.debug("%s: %s", k, repr(kw[k])) + + +class NAPTRRecordTest(oslotest.base.BaseTestCase): + + def test_parse_naptr(self): + naptr_record = objects.NAPTR() + naptr_record._from_string( + '0 0 S SIP+D2U !^.*$!sip:customer-service@example.com! _sip._udp.example.com.') # noqa + + self.assertEqual(0, naptr_record.order) + self.assertEqual(0, naptr_record.preference) + self.assertEqual('S', naptr_record.flags) + self.assertEqual('SIP+D2U', naptr_record.service) + self.assertEqual('!^.*$!sip:customer-service@example.com!', + naptr_record.regexp) + self.assertEqual('_sip._udp.example.com.', naptr_record.replacement) diff --git a/doc/source/contributor/sourcedoc/objects.rst b/doc/source/contributor/sourcedoc/objects.rst index 884c748ae..d7c00583b 100644 --- a/doc/source/contributor/sourcedoc/objects.rst +++ b/doc/source/contributor/sourcedoc/objects.rst @@ -180,3 +180,9 @@ Objects SSHFP Record :show-inheritance: +Objects NAPTR Record +==================== +.. automodule:: designate.objects.rrdata_naptr + :members: + :undoc-members: + :show-inheritance: