Quellcode durchsuchen

Made handling of fully qualified dns records more consistent

Nuwan Goonasekera vor 6 Jahren
Ursprung
Commit
e1d1f7a84e

+ 11 - 9
cloudbridge/base/resources.py

@@ -915,11 +915,12 @@ class BaseDnsZone(BaseCloudResource, DnsZone):
     @staticmethod
     def assert_valid_resource_name(name):
         if not BaseDnsZone.is_valid_resource_name(name):
-            log.debug("InvalidLabelException raised on %s", name,
+            log.debug("InvalidNameException raised on %s", name,
                       exc_info=True)
-            raise InvalidLabelException(
-                u"Invalid object name: %s. Name must match criteria defined "
-                "in: https://stackoverflow.com/q/10306690/10971151" % name)
+            raise InvalidNameException(
+                u"Invalid object name: %s. Name must be fully qualified "
+                u"(ending with a .) and match criteria defined "
+                u"in: https://stackoverflow.com/q/10306690/10971151" % name)
 
     def delete(self):
         return self._provider.dns.host_zones.delete(self.id)
@@ -929,7 +930,7 @@ class BaseDnsRecord(BaseCloudResource, DnsRecord):
 
     CB_NAME_PATTERN = re.compile(
         r"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9]"
-        r"[a-z0-9-]{0,61}[a-z0-9]$")
+        r"[a-z0-9-]{0,61}[a-z0-9]\.?$")
 
     def __init__(self, provider):
         super(BaseDnsRecord, self).__init__(provider)
@@ -951,8 +952,9 @@ class BaseDnsRecord(BaseCloudResource, DnsRecord):
     @staticmethod
     def assert_valid_resource_name(name):
         if not BaseDnsRecord.is_valid_resource_name(name):
-            log.debug("InvalidLabelException raised on %s", name,
+            log.debug("InvalidNameException raised on %s", name,
                       exc_info=True)
-            raise InvalidLabelException(
-                u"Invalid object name: %s. Name must match criteria defined "
-                "in: https://stackoverflow.com/q/10306690/10971151" % name)
+            raise InvalidNameException(
+                u"Invalid object name: %s. Name must be fully qualified "
+                u"(ending with a .) and match criteria defined "
+                u"in: https://stackoverflow.com/q/10306690/10971151" % name)

+ 9 - 0
cloudbridge/base/services.py

@@ -4,6 +4,7 @@ Base implementation for services available through a provider
 import logging
 
 from cloudbridge.interfaces.exceptions import InvalidParamException
+from cloudbridge.interfaces.resources import DnsRecordType
 from cloudbridge.interfaces.resources import Network
 from cloudbridge.interfaces.services import BucketObjectService
 from cloudbridge.interfaces.services import BucketService
@@ -380,3 +381,11 @@ class BaseDnsRecordService(BasePageableObjectMixin, DnsRecordService,
 
     def __init__(self, provider):
         super(BaseDnsRecordService, self).__init__(provider)
+
+    def _get_fully_qualified_dns(self, name):
+        # Add a trailing dot to fully qualify
+        return name + '.' if not name.endswith('.') else name
+
+    def _standardize_record(self, value, type):
+        return (self._get_fully_qualified_dns(value)
+                if type in (DnsRecordType.CNAME, DnsRecordType.MX) else value)

+ 1 - 1
cloudbridge/interfaces/exceptions.py

@@ -75,7 +75,7 @@ class InvalidLabelException(InvalidNameException):
 class InvalidValueException(CloudBridgeBaseException):
     """
     Marker interface for any attempt to set an invalid value on a CloudBridge
-    resource.An example would be setting an unrecognised value for the
+    resource. An example would be setting an unrecognised value for the
     direction of a firewall rule other than TrafficDirection.INBOUND or
     TrafficDirection.OUTBOUND.
     """

+ 5 - 2
cloudbridge/interfaces/services.py

@@ -1012,10 +1012,13 @@ class DnsRecordService(CloudService):
         :param type: The DnsRecord type. (e.g. A, CNAME, MX etc)
 
         :type data: ``str``
-        :param data: The corresponding value for the record.
+        :param data: The corresponding value for the record. The relevant
+                     values must be fully qualified (e.g. CNAMEs). If the
+                     trailing dot is omitted, it will be automatically
+                     added and thus assumed to be fully qualified.
 
         :type data: ``int``
-        :param data: The ttl (in seconds) for thisrecord.
+        :param data: The ttl (in seconds) for this record.
 
         :rtype: ``object`` of :class:`.DnsRecord`
         :return:  A DnsRecord object

+ 6 - 4
cloudbridge/providers/aws/services.py

@@ -1428,12 +1428,13 @@ class AWSDnsRecordService(BaseDnsRecordService):
         return ClientPagedResultList(self.provider, list(matches),
                                      limit=None, marker=None)
 
-    def _to_resource_records(self, data):
+    def _to_resource_records(self, data, rec_type):
         if isinstance(data, list):
             records = data
         else:
             records = [data]
-        return [{'Value': r} for r in records]
+        return [{'Value': self._standardize_record(r, rec_type)}
+                for r in records]
 
     def create(self, dns_zone, name, type, data, ttl=None):
         AWSDnsRecord.assert_valid_resource_name(name)
@@ -1447,7 +1448,8 @@ class AWSDnsRecordService(BaseDnsRecordService):
                         'Name': name,
                         'Type': type,
                         'TTL': ttl or 300,
-                        'ResourceRecords': self._to_resource_records(data)
+                        'ResourceRecords': self._to_resource_records(
+                            data, type)
                     })
                 }]
             }
@@ -1471,7 +1473,7 @@ class AWSDnsRecordService(BaseDnsRecordService):
                         'Type': rec_type,
                         'TTL': record.ttl,
                         'ResourceRecords': self._to_resource_records(
-                            record.data)
+                            record.data, rec_type)
                     }
                 }]
             })

+ 0 - 9
cloudbridge/providers/gcp/services.py

@@ -36,7 +36,6 @@ from cloudbridge.base.services import BaseVMTypeService
 from cloudbridge.base.services import BaseVolumeService
 from cloudbridge.interfaces.exceptions import DuplicateResourceException
 from cloudbridge.interfaces.exceptions import InvalidParamException
-from cloudbridge.interfaces.resources import DnsRecordType
 from cloudbridge.interfaces.resources import TrafficDirection
 from cloudbridge.interfaces.resources import VMFirewall
 from cloudbridge.providers.gcp import helpers
@@ -1714,14 +1713,6 @@ class GCPDnsRecordService(BaseDnsRecordService):
     def __init__(self, provider):
         super(GCPDnsRecordService, self).__init__(provider)
 
-    def _get_fully_qualified_dns(self, name):
-        # Add a trailing dot to fully qualify
-        return name + '.' if not name.endswith('.') else name
-
-    def _standardize_record(self, value, type):
-        return (self._get_fully_qualified_dns(value)
-                if type in (DnsRecordType.CNAME, DnsRecordType.MX) else value)
-
     def _to_resource_records(self, data, rec_type):
         """
         Converts a record to what GCP expects. For example, GCP

+ 28 - 0
tests/test_dns_service.py

@@ -28,6 +28,15 @@ class CloudDnsServiceTestCase(ProviderTestBase):
                        "cb-crudzone", create_dns_zone, cleanup_dns_zone,
                        skip_name_check=True)
 
+    @helpers.skipIfNoService(['dns.host_zones'])
+    def test_create_dns_zones_not_fully_qualified(self):
+        zone_name = "cb-dnszonenfq-{0}.com".format(helpers.get_uuid())
+        test_zone = None
+        with cb_helpers.cleanup_action(lambda: test_zone.delete()):
+            # If zone name is not fully qualified, it should automatically be
+            # handled
+            test_zone = self.provider.dns.host_zones.create(zone_name)
+
     @helpers.skipIfNoService(['dns.host_zones'])
     def test_crud_dns_record(self):
         test_zone = None
@@ -79,3 +88,22 @@ class CloudDnsServiceTestCase(ProviderTestBase):
                 self.assertEqual(test_rec2.type, DnsRecordType.MX)
                 self.assertSetEqual(set(test_rec2.data), set(MX_DATA))
                 self.assertEqual(test_rec2.ttl, 300)
+
+    @helpers.skipIfNoService(['dns.host_zones'])
+    def test_create_dns_rec_not_fully_qualified(self):
+        test_zone = None
+        root_zone_name = "cb-recprop-{0}.com.".format(helpers.get_uuid())
+
+        with cb_helpers.cleanup_action(lambda: test_zone.delete()):
+            test_zone = self.provider.dns.host_zones.create(root_zone_name)
+            test_rec = None
+
+            with cb_helpers.cleanup_action(lambda: test_rec.delete()):
+                zone_name = "subdomain." + root_zone_name
+                test_rec = test_zone.records.create(
+                    zone_name, DnsRecordType.CNAME, data='hello.com', ttl=500)
+
+            with cb_helpers.cleanup_action(lambda: test_rec.delete()):
+                test_rec = test_zone.records.create(
+                    root_zone_name, DnsRecordType.MX,
+                    data=['10 mx1.hello.com', '20 mx2.hello.com'], ttl=500)